Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a89ff00d94 | ||
|
|
227f11359c | ||
|
|
7a737d7f83 | ||
|
|
312ea94e62 |
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# 1) Build stage
|
# 1) Build stage
|
||||||
FROM golang:1.24-alpine AS build
|
FROM golang:1.21-alpine AS build
|
||||||
RUN apk --no-cache add ca-certificates tzdata git curl
|
RUN apk --no-cache add ca-certificates tzdata git curl
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
|
|||||||
@@ -83,63 +83,6 @@ type SalesAnalyticsData struct {
|
|||||||
NetSales float64 `json:"net_sales"`
|
NetSales float64 `json:"net_sales"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PurchasingAnalyticsRequest struct {
|
|
||||||
OrganizationID uuid.UUID
|
|
||||||
OutletID *string `form:"outlet_id,omitempty"`
|
|
||||||
DateFrom string `form:"date_from" validate:"required"`
|
|
||||||
DateTo string `form:"date_to" validate:"required"`
|
|
||||||
GroupBy string `form:"group_by,default=day" validate:"omitempty,oneof=day hour week month"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PurchasingAnalyticsResponse struct {
|
|
||||||
OrganizationID uuid.UUID `json:"organization_id"`
|
|
||||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
|
||||||
OutletName *string `json:"outlet_name,omitempty"`
|
|
||||||
DateFrom time.Time `json:"date_from"`
|
|
||||||
DateTo time.Time `json:"date_to"`
|
|
||||||
GroupBy string `json:"group_by"`
|
|
||||||
Summary PurchasingSummary `json:"summary"`
|
|
||||||
Data []PurchasingAnalyticsData `json:"data"`
|
|
||||||
IngredientData []PurchasingIngredientData `json:"ingredient_data"`
|
|
||||||
VendorData []PurchasingVendorData `json:"vendor_data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PurchasingSummary struct {
|
|
||||||
TotalPurchases float64 `json:"total_purchases"`
|
|
||||||
TotalPurchaseOrders int64 `json:"total_purchase_orders"`
|
|
||||||
TotalQuantity float64 `json:"total_quantity"`
|
|
||||||
AveragePurchaseOrderValue float64 `json:"average_purchase_order_value"`
|
|
||||||
TotalIngredients int64 `json:"total_ingredients"`
|
|
||||||
TotalVendors int64 `json:"total_vendors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PurchasingAnalyticsData struct {
|
|
||||||
Date time.Time `json:"date"`
|
|
||||||
Purchases float64 `json:"purchases"`
|
|
||||||
PurchaseOrders int64 `json:"purchase_orders"`
|
|
||||||
Quantity float64 `json:"quantity"`
|
|
||||||
Ingredients int64 `json:"ingredients"`
|
|
||||||
Vendors int64 `json:"vendors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PurchasingIngredientData struct {
|
|
||||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
|
||||||
IngredientName string `json:"ingredient_name"`
|
|
||||||
Quantity float64 `json:"quantity"`
|
|
||||||
TotalCost float64 `json:"total_cost"`
|
|
||||||
AverageUnitCost float64 `json:"average_unit_cost"`
|
|
||||||
PurchaseOrderCount int64 `json:"purchase_order_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PurchasingVendorData struct {
|
|
||||||
VendorID uuid.UUID `json:"vendor_id"`
|
|
||||||
VendorName string `json:"vendor_name"`
|
|
||||||
TotalCost float64 `json:"total_cost"`
|
|
||||||
PurchaseOrderCount int64 `json:"purchase_order_count"`
|
|
||||||
IngredientCount int64 `json:"ingredient_count"`
|
|
||||||
Quantity float64 `json:"quantity"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProductAnalyticsRequest represents the request for product analytics
|
// ProductAnalyticsRequest represents the request for product analytics
|
||||||
type ProductAnalyticsRequest struct {
|
type ProductAnalyticsRequest struct {
|
||||||
OrganizationID uuid.UUID
|
OrganizationID uuid.UUID
|
||||||
|
|||||||
@@ -27,51 +27,6 @@ type SalesAnalytics struct {
|
|||||||
NetSales float64 `json:"net_sales"`
|
NetSales float64 `json:"net_sales"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PurchasingAnalytics represents purchasing analytics data
|
|
||||||
type PurchasingAnalytics struct {
|
|
||||||
OutletName *string `json:"outlet_name,omitempty"`
|
|
||||||
Summary PurchasingSummary `json:"summary"`
|
|
||||||
Data []PurchasingAnalyticsData `json:"data"`
|
|
||||||
IngredientData []PurchasingIngredientData `json:"ingredient_data"`
|
|
||||||
VendorData []PurchasingVendorData `json:"vendor_data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PurchasingSummary struct {
|
|
||||||
TotalPurchases float64 `json:"total_purchases"`
|
|
||||||
TotalPurchaseOrders int64 `json:"total_purchase_orders"`
|
|
||||||
TotalQuantity float64 `json:"total_quantity"`
|
|
||||||
AveragePurchaseOrderValue float64 `json:"average_purchase_order_value"`
|
|
||||||
TotalIngredients int64 `json:"total_ingredients"`
|
|
||||||
TotalVendors int64 `json:"total_vendors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PurchasingAnalyticsData struct {
|
|
||||||
Date time.Time `json:"date"`
|
|
||||||
Purchases float64 `json:"purchases"`
|
|
||||||
PurchaseOrders int64 `json:"purchase_orders"`
|
|
||||||
Quantity float64 `json:"quantity"`
|
|
||||||
Ingredients int64 `json:"ingredients"`
|
|
||||||
Vendors int64 `json:"vendors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PurchasingIngredientData struct {
|
|
||||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
|
||||||
IngredientName string `json:"ingredient_name"`
|
|
||||||
Quantity float64 `json:"quantity"`
|
|
||||||
TotalCost float64 `json:"total_cost"`
|
|
||||||
AverageUnitCost float64 `json:"average_unit_cost"`
|
|
||||||
PurchaseOrderCount int64 `json:"purchase_order_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PurchasingVendorData struct {
|
|
||||||
VendorID uuid.UUID `json:"vendor_id"`
|
|
||||||
VendorName string `json:"vendor_name"`
|
|
||||||
TotalCost float64 `json:"total_cost"`
|
|
||||||
PurchaseOrderCount int64 `json:"purchase_order_count"`
|
|
||||||
IngredientCount int64 `json:"ingredient_count"`
|
|
||||||
Quantity float64 `json:"quantity"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProductAnalytics struct {
|
type ProductAnalytics struct {
|
||||||
ProductID uuid.UUID `json:"product_id"`
|
ProductID uuid.UUID `json:"product_id"`
|
||||||
ProductName string `json:"product_name"`
|
ProductName string `json:"product_name"`
|
||||||
|
|||||||
@@ -85,30 +85,6 @@ func (h *AnalyticsHandler) GetSalesAnalytics(c *gin.Context) {
|
|||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "AnalyticsHandler::GetSalesAnalytics")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "AnalyticsHandler::GetSalesAnalytics")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AnalyticsHandler) GetPurchasingAnalytics(c *gin.Context) {
|
|
||||||
ctx := c.Request.Context()
|
|
||||||
contextInfo := appcontext.FromGinContext(ctx)
|
|
||||||
|
|
||||||
var req contract.PurchasingAnalyticsRequest
|
|
||||||
if err := c.ShouldBindQuery(&req); err != nil {
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "AnalyticsHandler::GetPurchasingAnalytics", err.Error())}), "AnalyticsHandler::GetPurchasingAnalytics")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.OrganizationID = contextInfo.OrganizationID
|
|
||||||
req.OutletID = h.resolveOutletID(c, contextInfo.OutletID)
|
|
||||||
modelReq := transformer.PurchasingAnalyticsContractToModel(&req)
|
|
||||||
|
|
||||||
response, err := h.analyticsService.GetPurchasingAnalytics(ctx, modelReq)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "AnalyticsHandler::GetPurchasingAnalytics", err.Error())}), "AnalyticsHandler::GetPurchasingAnalytics")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
contractResp := transformer.PurchasingAnalyticsModelToContract(response)
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "AnalyticsHandler::GetPurchasingAnalytics")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *AnalyticsHandler) GetProductAnalytics(c *gin.Context) {
|
func (h *AnalyticsHandler) GetProductAnalytics(c *gin.Context) {
|
||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
contextInfo := appcontext.FromGinContext(ctx)
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|||||||
@@ -53,6 +53,14 @@ func (h *OrderHandler) CreateOrder(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *OrderHandler) GetOrderByID(c *gin.Context) {
|
func (h *OrderHandler) GetOrderByID(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
if contextInfo.OutletID == uuid.Nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::GetOrderByID", "outlet ID is required")}), "OrderHandler::GetOrderByID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
idStr := c.Param("id")
|
idStr := c.Param("id")
|
||||||
id, err := uuid.Parse(idStr)
|
id, err := uuid.Parse(idStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -60,7 +68,7 @@ func (h *OrderHandler) GetOrderByID(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := h.orderService.GetOrderByID(c.Request.Context(), id)
|
response, err := h.orderService.GetOrderByID(c.Request.Context(), id, contextInfo.OutletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::GetOrderByID", err.Error())}), "OrderHandler::GetOrderByID")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::GetOrderByID", err.Error())}), "OrderHandler::GetOrderByID")
|
||||||
return
|
return
|
||||||
@@ -71,6 +79,14 @@ func (h *OrderHandler) GetOrderByID(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *OrderHandler) UpdateOrder(c *gin.Context) {
|
func (h *OrderHandler) UpdateOrder(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
if contextInfo.OutletID == uuid.Nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::UpdateOrder", "outlet ID is required")}), "OrderHandler::UpdateOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
idStr := c.Param("id")
|
idStr := c.Param("id")
|
||||||
id, err := uuid.Parse(idStr)
|
id, err := uuid.Parse(idStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -85,7 +101,7 @@ func (h *OrderHandler) UpdateOrder(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
modelReq := transformer.UpdateOrderContractToModel(&req)
|
modelReq := transformer.UpdateOrderContractToModel(&req)
|
||||||
response, err := h.orderService.UpdateOrder(c.Request.Context(), id, modelReq)
|
response, err := h.orderService.UpdateOrder(c.Request.Context(), id, modelReq, contextInfo.OutletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::UpdateOrder", err.Error())}), "OrderHandler::UpdateOrder")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::UpdateOrder", err.Error())}), "OrderHandler::UpdateOrder")
|
||||||
return
|
return
|
||||||
@@ -96,6 +112,14 @@ func (h *OrderHandler) UpdateOrder(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *OrderHandler) AddToOrder(c *gin.Context) {
|
func (h *OrderHandler) AddToOrder(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
if contextInfo.OutletID == uuid.Nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::AddToOrder", "outlet ID is required")}), "OrderHandler::AddToOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
idStr := c.Param("id")
|
idStr := c.Param("id")
|
||||||
id, err := uuid.Parse(idStr)
|
id, err := uuid.Parse(idStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -110,7 +134,7 @@ func (h *OrderHandler) AddToOrder(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
modelReq := transformer.AddToOrderContractToModel(&req)
|
modelReq := transformer.AddToOrderContractToModel(&req)
|
||||||
response, err := h.orderService.AddToOrder(c.Request.Context(), id, modelReq)
|
response, err := h.orderService.AddToOrder(c.Request.Context(), id, modelReq, contextInfo.OutletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::AddToOrder", err.Error())}), "OrderHandler::AddToOrder")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::AddToOrder", err.Error())}), "OrderHandler::AddToOrder")
|
||||||
return
|
return
|
||||||
@@ -167,7 +191,7 @@ func (h *OrderHandler) VoidOrder(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
modelReq := transformer.VoidOrderContractToModel(&req)
|
modelReq := transformer.VoidOrderContractToModel(&req)
|
||||||
if err := h.orderService.VoidOrder(ctx, modelReq, userID); err != nil {
|
if err := h.orderService.VoidOrder(ctx, modelReq, userID, contextInfo.OutletID); err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::VoidOrder", err.Error())}), "OrderHandler::VoidOrder")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::VoidOrder", err.Error())}), "OrderHandler::VoidOrder")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -200,7 +224,7 @@ func (h *OrderHandler) RefundOrder(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.orderService.RefundOrder(ctx, id, modelReq, userID); err != nil {
|
if err := h.orderService.RefundOrder(ctx, id, modelReq, userID, contextInfo.OutletID); err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::RefundOrder", err.Error())}), "OrderHandler::RefundOrder")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::RefundOrder", err.Error())}), "OrderHandler::RefundOrder")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -209,6 +233,14 @@ func (h *OrderHandler) RefundOrder(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *OrderHandler) CreatePayment(c *gin.Context) {
|
func (h *OrderHandler) CreatePayment(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
if contextInfo.OutletID == uuid.Nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::CreatePayment", "outlet ID is required")}), "OrderHandler::CreatePayment")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var req contract.CreatePaymentRequest
|
var req contract.CreatePaymentRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::CreatePayment", err.Error())}), "OrderHandler::CreatePayment")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::CreatePayment", err.Error())}), "OrderHandler::CreatePayment")
|
||||||
@@ -222,7 +254,7 @@ func (h *OrderHandler) CreatePayment(c *gin.Context) {
|
|||||||
|
|
||||||
modelReq := transformer.CreatePaymentContractToModel(&req)
|
modelReq := transformer.CreatePaymentContractToModel(&req)
|
||||||
|
|
||||||
response, err := h.orderService.CreatePayment(c.Request.Context(), modelReq)
|
response, err := h.orderService.CreatePayment(c.Request.Context(), modelReq, contextInfo.OutletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::CreatePayment", err.Error())}), "OrderHandler::CreatePayment")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::CreatePayment", err.Error())}), "OrderHandler::CreatePayment")
|
||||||
return
|
return
|
||||||
@@ -260,7 +292,7 @@ func (h *OrderHandler) RefundPayment(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.orderService.RefundPayment(ctx, paymentID, req.RefundAmount, req.Reason, userID); err != nil {
|
if err := h.orderService.RefundPayment(ctx, paymentID, req.RefundAmount, req.Reason, userID, contextInfo.OutletID); err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::RefundPayment", err.Error())}), "OrderHandler::RefundPayment")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::RefundPayment", err.Error())}), "OrderHandler::RefundPayment")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -287,7 +319,7 @@ func (h *OrderHandler) SetOrderCustomer(c *gin.Context) {
|
|||||||
|
|
||||||
modelReq := transformer.SetOrderCustomerContractToModel(&req)
|
modelReq := transformer.SetOrderCustomerContractToModel(&req)
|
||||||
|
|
||||||
response, err := h.orderService.SetOrderCustomer(ctx, orderID, modelReq, contextInfo.OrganizationID)
|
response, err := h.orderService.SetOrderCustomer(ctx, orderID, modelReq, contextInfo.OrganizationID, contextInfo.OutletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::SetOrderCustomer", err.Error())}), "OrderHandler::SetOrderCustomer")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::SetOrderCustomer", err.Error())}), "OrderHandler::SetOrderCustomer")
|
||||||
return
|
return
|
||||||
@@ -314,7 +346,7 @@ func (h *OrderHandler) SplitBill(c *gin.Context) {
|
|||||||
|
|
||||||
req.OrganizationID = contextInfo.OrganizationID
|
req.OrganizationID = contextInfo.OrganizationID
|
||||||
modelReq := transformer.SplitBillContractToModel(&req)
|
modelReq := transformer.SplitBillContractToModel(&req)
|
||||||
response, err := h.orderService.SplitBill(c.Request.Context(), modelReq)
|
response, err := h.orderService.SplitBill(c.Request.Context(), modelReq, contextInfo.OutletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::SplitBill", err.Error())}), "OrderHandler::SplitBill")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::SplitBill", err.Error())}), "OrderHandler::SplitBill")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -87,69 +87,6 @@ type SalesAnalyticsData struct {
|
|||||||
NetSales float64 `json:"net_sales"`
|
NetSales float64 `json:"net_sales"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PurchasingAnalyticsRequest represents the request for purchasing analytics
|
|
||||||
type PurchasingAnalyticsRequest struct {
|
|
||||||
OrganizationID uuid.UUID `validate:"required"`
|
|
||||||
OutletID *uuid.UUID `validate:"omitempty"`
|
|
||||||
DateFrom time.Time `validate:"required"`
|
|
||||||
DateTo time.Time `validate:"required"`
|
|
||||||
GroupBy string `validate:"omitempty,oneof=day hour week month"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PurchasingAnalyticsResponse represents the response for purchasing analytics
|
|
||||||
type PurchasingAnalyticsResponse struct {
|
|
||||||
OrganizationID uuid.UUID `json:"organization_id"`
|
|
||||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
|
||||||
OutletName *string `json:"outlet_name,omitempty"`
|
|
||||||
DateFrom time.Time `json:"date_from"`
|
|
||||||
DateTo time.Time `json:"date_to"`
|
|
||||||
GroupBy string `json:"group_by"`
|
|
||||||
Summary PurchasingSummary `json:"summary"`
|
|
||||||
Data []PurchasingAnalyticsData `json:"data"`
|
|
||||||
IngredientData []PurchasingIngredientData `json:"ingredient_data"`
|
|
||||||
VendorData []PurchasingVendorData `json:"vendor_data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PurchasingSummary represents the summary of purchasing analytics
|
|
||||||
type PurchasingSummary struct {
|
|
||||||
TotalPurchases float64 `json:"total_purchases"`
|
|
||||||
TotalPurchaseOrders int64 `json:"total_purchase_orders"`
|
|
||||||
TotalQuantity float64 `json:"total_quantity"`
|
|
||||||
AveragePurchaseOrderValue float64 `json:"average_purchase_order_value"`
|
|
||||||
TotalIngredients int64 `json:"total_ingredients"`
|
|
||||||
TotalVendors int64 `json:"total_vendors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PurchasingAnalyticsData represents purchasing analytics by time period
|
|
||||||
type PurchasingAnalyticsData struct {
|
|
||||||
Date time.Time `json:"date"`
|
|
||||||
Purchases float64 `json:"purchases"`
|
|
||||||
PurchaseOrders int64 `json:"purchase_orders"`
|
|
||||||
Quantity float64 `json:"quantity"`
|
|
||||||
Ingredients int64 `json:"ingredients"`
|
|
||||||
Vendors int64 `json:"vendors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PurchasingIngredientData represents purchasing analytics for an ingredient
|
|
||||||
type PurchasingIngredientData struct {
|
|
||||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
|
||||||
IngredientName string `json:"ingredient_name"`
|
|
||||||
Quantity float64 `json:"quantity"`
|
|
||||||
TotalCost float64 `json:"total_cost"`
|
|
||||||
AverageUnitCost float64 `json:"average_unit_cost"`
|
|
||||||
PurchaseOrderCount int64 `json:"purchase_order_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PurchasingVendorData represents purchasing analytics for a vendor
|
|
||||||
type PurchasingVendorData struct {
|
|
||||||
VendorID uuid.UUID `json:"vendor_id"`
|
|
||||||
VendorName string `json:"vendor_name"`
|
|
||||||
TotalCost float64 `json:"total_cost"`
|
|
||||||
PurchaseOrderCount int64 `json:"purchase_order_count"`
|
|
||||||
IngredientCount int64 `json:"ingredient_count"`
|
|
||||||
Quantity float64 `json:"quantity"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProductAnalyticsRequest represents the request for product analytics
|
// ProductAnalyticsRequest represents the request for product analytics
|
||||||
type ProductAnalyticsRequest struct {
|
type ProductAnalyticsRequest struct {
|
||||||
OrganizationID uuid.UUID `validate:"required"`
|
OrganizationID uuid.UUID `validate:"required"`
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
type AnalyticsProcessor interface {
|
type AnalyticsProcessor interface {
|
||||||
GetPaymentMethodAnalytics(ctx context.Context, req *models.PaymentMethodAnalyticsRequest) (*models.PaymentMethodAnalyticsResponse, error)
|
GetPaymentMethodAnalytics(ctx context.Context, req *models.PaymentMethodAnalyticsRequest) (*models.PaymentMethodAnalyticsResponse, error)
|
||||||
GetSalesAnalytics(ctx context.Context, req *models.SalesAnalyticsRequest) (*models.SalesAnalyticsResponse, error)
|
GetSalesAnalytics(ctx context.Context, req *models.SalesAnalyticsRequest) (*models.SalesAnalyticsResponse, error)
|
||||||
GetPurchasingAnalytics(ctx context.Context, req *models.PurchasingAnalyticsRequest) (*models.PurchasingAnalyticsResponse, error)
|
|
||||||
GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error)
|
GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error)
|
||||||
GetProductAnalyticsPerCategory(ctx context.Context, req *models.ProductAnalyticsPerCategoryRequest) (*models.ProductAnalyticsPerCategoryResponse, error)
|
GetProductAnalyticsPerCategory(ctx context.Context, req *models.ProductAnalyticsPerCategoryRequest) (*models.ProductAnalyticsPerCategoryResponse, error)
|
||||||
GetDashboardAnalytics(ctx context.Context, req *models.DashboardAnalyticsRequest) (*models.DashboardAnalyticsResponse, error)
|
GetDashboardAnalytics(ctx context.Context, req *models.DashboardAnalyticsRequest) (*models.DashboardAnalyticsResponse, error)
|
||||||
@@ -165,77 +164,6 @@ func (p *AnalyticsProcessorImpl) GetSalesAnalytics(ctx context.Context, req *mod
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *AnalyticsProcessorImpl) GetPurchasingAnalytics(ctx context.Context, req *models.PurchasingAnalyticsRequest) (*models.PurchasingAnalyticsResponse, error) {
|
|
||||||
if req.DateFrom.After(req.DateTo) {
|
|
||||||
return nil, fmt.Errorf("date_from cannot be after date_to")
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.GroupBy == "" {
|
|
||||||
req.GroupBy = "day"
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := p.analyticsRepo.GetPurchasingAnalytics(ctx, req.OrganizationID, req.OutletID, req.DateFrom, req.DateTo, req.GroupBy)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get purchasing analytics: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data := make([]models.PurchasingAnalyticsData, len(result.Data))
|
|
||||||
for i, item := range result.Data {
|
|
||||||
data[i] = models.PurchasingAnalyticsData{
|
|
||||||
Date: item.Date,
|
|
||||||
Purchases: item.Purchases,
|
|
||||||
PurchaseOrders: item.PurchaseOrders,
|
|
||||||
Quantity: item.Quantity,
|
|
||||||
Ingredients: item.Ingredients,
|
|
||||||
Vendors: item.Vendors,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ingredientData := make([]models.PurchasingIngredientData, len(result.IngredientData))
|
|
||||||
for i, item := range result.IngredientData {
|
|
||||||
ingredientData[i] = models.PurchasingIngredientData{
|
|
||||||
IngredientID: item.IngredientID,
|
|
||||||
IngredientName: item.IngredientName,
|
|
||||||
Quantity: item.Quantity,
|
|
||||||
TotalCost: item.TotalCost,
|
|
||||||
AverageUnitCost: item.AverageUnitCost,
|
|
||||||
PurchaseOrderCount: item.PurchaseOrderCount,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vendorData := make([]models.PurchasingVendorData, len(result.VendorData))
|
|
||||||
for i, item := range result.VendorData {
|
|
||||||
vendorData[i] = models.PurchasingVendorData{
|
|
||||||
VendorID: item.VendorID,
|
|
||||||
VendorName: item.VendorName,
|
|
||||||
TotalCost: item.TotalCost,
|
|
||||||
PurchaseOrderCount: item.PurchaseOrderCount,
|
|
||||||
IngredientCount: item.IngredientCount,
|
|
||||||
Quantity: item.Quantity,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &models.PurchasingAnalyticsResponse{
|
|
||||||
OrganizationID: req.OrganizationID,
|
|
||||||
OutletID: req.OutletID,
|
|
||||||
OutletName: result.OutletName,
|
|
||||||
DateFrom: req.DateFrom,
|
|
||||||
DateTo: req.DateTo,
|
|
||||||
GroupBy: req.GroupBy,
|
|
||||||
Summary: models.PurchasingSummary{
|
|
||||||
TotalPurchases: result.Summary.TotalPurchases,
|
|
||||||
TotalPurchaseOrders: result.Summary.TotalPurchaseOrders,
|
|
||||||
TotalQuantity: result.Summary.TotalQuantity,
|
|
||||||
AveragePurchaseOrderValue: result.Summary.AveragePurchaseOrderValue,
|
|
||||||
TotalIngredients: result.Summary.TotalIngredients,
|
|
||||||
TotalVendors: result.Summary.TotalVendors,
|
|
||||||
},
|
|
||||||
Data: data,
|
|
||||||
IngredientData: ingredientData,
|
|
||||||
VendorData: vendorData,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *AnalyticsProcessorImpl) GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error) {
|
func (p *AnalyticsProcessorImpl) GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error) {
|
||||||
// Validate date range
|
// Validate date range
|
||||||
if req.DateFrom.After(req.DateTo) {
|
if req.DateFrom.After(req.DateTo) {
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
package processor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"apskel-pos-be/internal/entities"
|
|
||||||
"apskel-pos-be/internal/models"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type analyticsRepositoryStub struct {
|
|
||||||
purchasingResult *entities.PurchasingAnalytics
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsRepositoryStub) GetPaymentMethodAnalytics(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time) ([]*entities.PaymentMethodAnalytics, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsRepositoryStub) GetSalesAnalytics(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time, string) ([]*entities.SalesAnalytics, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s analyticsRepositoryStub) GetPurchasingAnalytics(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time, string) (*entities.PurchasingAnalytics, error) {
|
|
||||||
return s.purchasingResult, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsRepositoryStub) GetProductAnalytics(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time, int) ([]*entities.ProductAnalytics, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsRepositoryStub) GetProductAnalyticsPerCategory(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time) ([]*entities.ProductAnalyticsPerCategory, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsRepositoryStub) GetDashboardOverview(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time) (*entities.DashboardOverview, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsRepositoryStub) GetProfitLossAnalytics(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time, string) (*entities.ProfitLossAnalytics, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAnalyticsProcessorGetPurchasingAnalyticsPassesOutletName(t *testing.T) {
|
|
||||||
outletID := uuid.New()
|
|
||||||
outletName := "Main Outlet"
|
|
||||||
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
processor := NewAnalyticsProcessorImpl(analyticsRepositoryStub{
|
|
||||||
purchasingResult: &entities.PurchasingAnalytics{
|
|
||||||
OutletName: &outletName,
|
|
||||||
Summary: entities.PurchasingSummary{
|
|
||||||
TotalPurchases: 125,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
result, err := processor.GetPurchasingAnalytics(context.Background(), &models.PurchasingAnalyticsRequest{
|
|
||||||
OrganizationID: uuid.New(),
|
|
||||||
OutletID: &outletID,
|
|
||||||
DateFrom: now,
|
|
||||||
DateTo: now,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, result)
|
|
||||||
require.Equal(t, &outletID, result.OutletID)
|
|
||||||
require.NotNil(t, result.OutletName)
|
|
||||||
require.Equal(t, outletName, *result.OutletName)
|
|
||||||
require.Equal(t, float64(125), result.Summary.TotalPurchases)
|
|
||||||
}
|
|
||||||
@@ -17,33 +17,33 @@ import (
|
|||||||
|
|
||||||
type OrderProcessor interface {
|
type OrderProcessor interface {
|
||||||
CreateOrder(ctx context.Context, req *models.CreateOrderRequest, organizationID uuid.UUID) (*models.OrderResponse, error)
|
CreateOrder(ctx context.Context, req *models.CreateOrderRequest, organizationID uuid.UUID) (*models.OrderResponse, error)
|
||||||
AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error)
|
AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest, outletID uuid.UUID) (*models.AddToOrderResponse, error)
|
||||||
UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest) (*models.OrderResponse, error)
|
UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest, outletID uuid.UUID) (*models.OrderResponse, error)
|
||||||
GetOrderByID(ctx context.Context, id uuid.UUID) (*models.OrderResponse, error)
|
GetOrderByID(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*models.OrderResponse, error)
|
||||||
ListOrders(ctx context.Context, req *models.ListOrdersRequest) (*models.ListOrdersResponse, error)
|
ListOrders(ctx context.Context, req *models.ListOrdersRequest) (*models.ListOrdersResponse, error)
|
||||||
VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID) error
|
VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID) error
|
RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error)
|
CreatePayment(ctx context.Context, req *models.CreatePaymentRequest, outletID uuid.UUID) (*models.PaymentResponse, error)
|
||||||
RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error
|
RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error)
|
SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID, outletID uuid.UUID) (*models.SetOrderCustomerResponse, error)
|
||||||
SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error)
|
SplitBill(ctx context.Context, req *models.SplitBillRequest, outletID uuid.UUID) (*models.SplitBillResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderRepository interface {
|
type OrderRepository interface {
|
||||||
Create(ctx context.Context, order *entities.Order) error
|
Create(ctx context.Context, order *entities.Order) error
|
||||||
GetByID(ctx context.Context, id uuid.UUID) (*entities.Order, error)
|
GetByID(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*entities.Order, error)
|
||||||
GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.Order, error)
|
GetWithRelations(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*entities.Order, error)
|
||||||
Update(ctx context.Context, order *entities.Order) error
|
Update(ctx context.Context, order *entities.Order) error
|
||||||
UpdateStatusSuccess(ctx context.Context, id uuid.UUID, orderStatus entities.OrderStatus, paymentStatus entities.PaymentStatus) error
|
UpdateStatusSuccess(ctx context.Context, id uuid.UUID, orderStatus entities.OrderStatus, paymentStatus entities.PaymentStatus, outletID uuid.UUID) error
|
||||||
Delete(ctx context.Context, id uuid.UUID) error
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Order, int64, error)
|
List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Order, int64, error)
|
||||||
GetByOrderNumber(ctx context.Context, orderNumber string) (*entities.Order, error)
|
GetByOrderNumber(ctx context.Context, orderNumber string) (*entities.Order, error)
|
||||||
ExistsByOrderNumber(ctx context.Context, orderNumber string) (bool, error)
|
ExistsByOrderNumber(ctx context.Context, orderNumber string) (bool, error)
|
||||||
VoidOrder(ctx context.Context, id uuid.UUID, reason string, voidedBy uuid.UUID) error
|
VoidOrder(ctx context.Context, id uuid.UUID, reason string, voidedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID) error
|
VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID) error
|
RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus) error
|
UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus, outletID uuid.UUID) error
|
||||||
UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus) error
|
UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, outletID uuid.UUID) error
|
||||||
GetNextOrderNumber(ctx context.Context, organizationID, outletID uuid.UUID) (string, error)
|
GetNextOrderNumber(ctx context.Context, organizationID, outletID uuid.UUID) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ func (p *OrderProcessorImpl) CreateOrder(ctx context.Context, req *models.Create
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, order.ID)
|
orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, order.ID, uuid.Nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve created order: %w", err)
|
return nil, fmt.Errorf("failed to retrieve created order: %w", err)
|
||||||
}
|
}
|
||||||
@@ -269,8 +269,8 @@ func (p *OrderProcessorImpl) CreateOrder(ctx context.Context, req *models.Create
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderProcessorImpl) AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error) {
|
func (p *OrderProcessorImpl) AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest, outletID uuid.UUID) (*models.AddToOrderResponse, error) {
|
||||||
order, err := p.orderRepo.GetByID(ctx, orderID)
|
order, err := p.orderRepo.GetByID(ctx, orderID, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("order not found: %w", err)
|
return nil, fmt.Errorf("order not found: %w", err)
|
||||||
}
|
}
|
||||||
@@ -415,7 +415,7 @@ func (p *OrderProcessorImpl) AddToOrder(ctx context.Context, orderID uuid.UUID,
|
|||||||
addedItemResponses = append(addedItemResponses, itemResponse)
|
addedItemResponses = append(addedItemResponses, itemResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, orderID)
|
orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, orderID, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve updated order: %w", err)
|
return nil, fmt.Errorf("failed to retrieve updated order: %w", err)
|
||||||
}
|
}
|
||||||
@@ -430,9 +430,8 @@ func (p *OrderProcessorImpl) AddToOrder(ctx context.Context, orderID uuid.UUID,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderProcessorImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest) (*models.OrderResponse, error) {
|
func (p *OrderProcessorImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest, outletID uuid.UUID) (*models.OrderResponse, error) {
|
||||||
// Get existing order
|
order, err := p.orderRepo.GetByID(ctx, id, outletID)
|
||||||
order, err := p.orderRepo.GetByID(ctx, id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("order not found: %w", err)
|
return nil, fmt.Errorf("order not found: %w", err)
|
||||||
}
|
}
|
||||||
@@ -477,11 +476,11 @@ func (p *OrderProcessorImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req
|
|||||||
order.Status = entities.OrderStatusCompleted
|
order.Status = entities.OrderStatusCompleted
|
||||||
order.PaymentStatus = entities.PaymentStatusCompleted
|
order.PaymentStatus = entities.PaymentStatusCompleted
|
||||||
|
|
||||||
if err := p.orderRepo.UpdateStatusSuccess(ctx, order.ID, order.Status, order.PaymentStatus); err != nil {
|
if err := p.orderRepo.UpdateStatusSuccess(ctx, order.ID, order.Status, order.PaymentStatus, outletID); err != nil {
|
||||||
return nil, fmt.Errorf("failed to update order: %w", err)
|
return nil, fmt.Errorf("failed to update order: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, id)
|
orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, id, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve updated order: %w", err)
|
return nil, fmt.Errorf("failed to retrieve updated order: %w", err)
|
||||||
}
|
}
|
||||||
@@ -490,8 +489,8 @@ func (p *OrderProcessorImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderProcessorImpl) GetOrderByID(ctx context.Context, id uuid.UUID) (*models.OrderResponse, error) {
|
func (p *OrderProcessorImpl) GetOrderByID(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*models.OrderResponse, error) {
|
||||||
order, err := p.orderRepo.GetWithRelations(ctx, id)
|
order, err := p.orderRepo.GetWithRelations(ctx, id, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("order not found: %w", err)
|
return nil, fmt.Errorf("order not found: %w", err)
|
||||||
}
|
}
|
||||||
@@ -574,12 +573,12 @@ func (p *OrderProcessorImpl) ListOrders(ctx context.Context, req *models.ListOrd
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderProcessorImpl) VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID) error {
|
func (p *OrderProcessorImpl) VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID, outletID uuid.UUID) error {
|
||||||
if req.OrderID != req.OrderID {
|
if req.OrderID != req.OrderID {
|
||||||
return fmt.Errorf("order ID mismatch: path parameter does not match request body")
|
return fmt.Errorf("order ID mismatch: path parameter does not match request body")
|
||||||
}
|
}
|
||||||
|
|
||||||
order, err := p.orderRepo.GetByID(ctx, req.OrderID)
|
order, err := p.orderRepo.GetByID(ctx, req.OrderID, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("order not found: %w", err)
|
return fmt.Errorf("order not found: %w", err)
|
||||||
}
|
}
|
||||||
@@ -593,7 +592,7 @@ func (p *OrderProcessorImpl) VoidOrder(ctx context.Context, req *models.VoidOrde
|
|||||||
}
|
}
|
||||||
|
|
||||||
if req.Type == "ALL" {
|
if req.Type == "ALL" {
|
||||||
if err := p.orderRepo.VoidOrderWithStatus(ctx, req.OrderID, entities.OrderStatusCancelled, req.Reason, voidedBy); err != nil {
|
if err := p.orderRepo.VoidOrderWithStatus(ctx, req.OrderID, entities.OrderStatusCancelled, req.Reason, voidedBy, outletID); err != nil {
|
||||||
return fmt.Errorf("failed to void order: %w", err)
|
return fmt.Errorf("failed to void order: %w", err)
|
||||||
}
|
}
|
||||||
} else if req.Type == "ITEM" {
|
} else if req.Type == "ITEM" {
|
||||||
@@ -659,7 +658,7 @@ func (p *OrderProcessorImpl) VoidOrder(ctx context.Context, req *models.VoidOrde
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !hasActiveItems {
|
if !hasActiveItems {
|
||||||
if err := p.orderRepo.VoidOrderWithStatus(ctx, req.OrderID, entities.OrderStatusCancelled, req.Reason, voidedBy); err != nil {
|
if err := p.orderRepo.VoidOrderWithStatus(ctx, req.OrderID, entities.OrderStatusCancelled, req.Reason, voidedBy, outletID); err != nil {
|
||||||
return fmt.Errorf("failed to void order after all items voided: %w", err)
|
return fmt.Errorf("failed to void order after all items voided: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -670,8 +669,8 @@ func (p *OrderProcessorImpl) VoidOrder(ctx context.Context, req *models.VoidOrde
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderProcessorImpl) RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID) error {
|
func (p *OrderProcessorImpl) RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID, outletID uuid.UUID) error {
|
||||||
order, err := p.orderRepo.GetWithRelations(ctx, id)
|
order, err := p.orderRepo.GetWithRelations(ctx, id, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("order not found: %w", err)
|
return fmt.Errorf("order not found: %w", err)
|
||||||
}
|
}
|
||||||
@@ -703,7 +702,7 @@ func (p *OrderProcessorImpl) RefundOrder(ctx context.Context, id uuid.UUID, req
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mark order as refunded
|
// Mark order as refunded
|
||||||
if err := p.orderRepo.RefundOrder(ctx, id, reason, refundedBy); err != nil {
|
if err := p.orderRepo.RefundOrder(ctx, id, reason, refundedBy, outletID); err != nil {
|
||||||
return fmt.Errorf("failed to mark order as refunded: %w", err)
|
return fmt.Errorf("failed to mark order as refunded: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -757,7 +756,7 @@ func (p *OrderProcessorImpl) RefundOrder(ctx context.Context, id uuid.UUID, req
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mark order as refunded
|
// Mark order as refunded
|
||||||
if err := p.orderRepo.RefundOrder(ctx, id, reason, refundedBy); err != nil {
|
if err := p.orderRepo.RefundOrder(ctx, id, reason, refundedBy, outletID); err != nil {
|
||||||
return fmt.Errorf("failed to mark order as refunded: %w", err)
|
return fmt.Errorf("failed to mark order as refunded: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -765,8 +764,8 @@ func (p *OrderProcessorImpl) RefundOrder(ctx context.Context, id uuid.UUID, req
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderProcessorImpl) CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error) {
|
func (p *OrderProcessorImpl) CreatePayment(ctx context.Context, req *models.CreatePaymentRequest, outletID uuid.UUID) (*models.PaymentResponse, error) {
|
||||||
order, err := p.orderRepo.GetByID(ctx, req.OrderID)
|
order, err := p.orderRepo.GetByID(ctx, req.OrderID, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("order not found: %w", err)
|
return nil, fmt.Errorf("order not found: %w", err)
|
||||||
}
|
}
|
||||||
@@ -803,7 +802,7 @@ func (p *OrderProcessorImpl) CreatePayment(ctx context.Context, req *models.Crea
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderProcessorImpl) RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error {
|
func (p *OrderProcessorImpl) RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID, outletID uuid.UUID) error {
|
||||||
payment, err := p.paymentRepo.GetByID(ctx, paymentID)
|
payment, err := p.paymentRepo.GetByID(ctx, paymentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("payment not found: %w", err)
|
return fmt.Errorf("payment not found: %w", err)
|
||||||
@@ -875,7 +874,7 @@ func (p *OrderProcessorImpl) updateOrderStatus(ctx context.Context, orderID uuid
|
|||||||
PaymentStatus: entities.PaymentStatusCompleted,
|
PaymentStatus: entities.PaymentStatusCompleted,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.orderRepo.UpdateStatusSuccess(ctx, orderID, orderUpdate.Status, orderUpdate.PaymentStatus); err != nil {
|
if err := p.orderRepo.UpdateStatusSuccess(ctx, orderID, orderUpdate.Status, orderUpdate.PaymentStatus, uuid.Nil); err != nil {
|
||||||
return fmt.Errorf("failed to update order status: %w", err)
|
return fmt.Errorf("failed to update order status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1071,7 +1070,7 @@ func (p *OrderProcessorImpl) processRefund(ctx context.Context, paymentID uuid.U
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderProcessorImpl) updateOrderRefundAmount(ctx context.Context, orderID uuid.UUID, refundAmount float64) error {
|
func (p *OrderProcessorImpl) updateOrderRefundAmount(ctx context.Context, orderID uuid.UUID, refundAmount float64) error {
|
||||||
order, err := p.orderRepo.GetByID(ctx, orderID)
|
order, err := p.orderRepo.GetByID(ctx, orderID, uuid.Nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get order: %w", err)
|
return fmt.Errorf("failed to get order: %w", err)
|
||||||
}
|
}
|
||||||
@@ -1084,8 +1083,8 @@ func (p *OrderProcessorImpl) updateOrderRefundAmount(ctx context.Context, orderI
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderProcessorImpl) SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error) {
|
func (p *OrderProcessorImpl) SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID, outletID uuid.UUID) (*models.SetOrderCustomerResponse, error) {
|
||||||
order, err := p.orderRepo.GetByID(ctx, orderID)
|
order, err := p.orderRepo.GetByID(ctx, orderID, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("order not found: %w", err)
|
return nil, fmt.Errorf("order not found: %w", err)
|
||||||
}
|
}
|
||||||
@@ -1119,8 +1118,8 @@ func (p *OrderProcessorImpl) SetOrderCustomer(ctx context.Context, orderID uuid.
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderProcessorImpl) SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error) {
|
func (p *OrderProcessorImpl) SplitBill(ctx context.Context, req *models.SplitBillRequest, outletID uuid.UUID) (*models.SplitBillResponse, error) {
|
||||||
order, err := p.orderRepo.GetWithRelations(ctx, req.OrderID)
|
order, err := p.orderRepo.GetWithRelations(ctx, req.OrderID, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("order not found: %w", err)
|
return nil, fmt.Errorf("order not found: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ func (p *TableProcessor) OccupyTable(ctx context.Context, tableID uuid.UUID, req
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify order exists
|
// Verify order exists
|
||||||
_, err = p.orderRepo.GetByID(ctx, req.OrderID)
|
_, err = p.orderRepo.GetByID(ctx, req.OrderID, uuid.Nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("order not found")
|
return nil, errors.New("order not found")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
type AnalyticsRepository interface {
|
type AnalyticsRepository interface {
|
||||||
GetPaymentMethodAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.PaymentMethodAnalytics, error)
|
GetPaymentMethodAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.PaymentMethodAnalytics, error)
|
||||||
GetSalesAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) ([]*entities.SalesAnalytics, error)
|
GetSalesAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) ([]*entities.SalesAnalytics, error)
|
||||||
GetPurchasingAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) (*entities.PurchasingAnalytics, error)
|
|
||||||
GetProductAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, limit int) ([]*entities.ProductAnalytics, error)
|
GetProductAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, limit int) ([]*entities.ProductAnalytics, error)
|
||||||
GetProductAnalyticsPerCategory(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.ProductAnalyticsPerCategory, error)
|
GetProductAnalyticsPerCategory(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]*entities.ProductAnalyticsPerCategory, error)
|
||||||
GetDashboardOverview(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) (*entities.DashboardOverview, error)
|
GetDashboardOverview(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) (*entities.DashboardOverview, error)
|
||||||
@@ -123,159 +122,6 @@ func (r *AnalyticsRepositoryImpl) GetSalesAnalytics(ctx context.Context, organiz
|
|||||||
return results, err
|
return results, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) (*entities.PurchasingAnalytics, error) {
|
|
||||||
var summary entities.PurchasingSummary
|
|
||||||
var outletName *string
|
|
||||||
|
|
||||||
if outletID != nil {
|
|
||||||
var outlet struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
result := r.db.WithContext(ctx).
|
|
||||||
Table("outlets").
|
|
||||||
Select("name").
|
|
||||||
Where("id = ? AND organization_id = ?", *outletID, organizationID).
|
|
||||||
Limit(1).
|
|
||||||
Scan(&outlet)
|
|
||||||
if result.Error != nil {
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
if result.RowsAffected > 0 {
|
|
||||||
outletName = &outlet.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
summaryQuery := r.db.WithContext(ctx).
|
|
||||||
Table("inventory_movements im").
|
|
||||||
Select(`
|
|
||||||
COALESCE(SUM(im.total_cost), 0) as total_purchases,
|
|
||||||
COUNT(DISTINCT im.reference_id) as total_purchase_orders,
|
|
||||||
COALESCE(SUM(im.quantity), 0) as total_quantity,
|
|
||||||
CASE
|
|
||||||
WHEN COUNT(DISTINCT im.reference_id) > 0
|
|
||||||
THEN COALESCE(SUM(im.total_cost), 0) / COUNT(DISTINCT im.reference_id)
|
|
||||||
ELSE 0
|
|
||||||
END as average_purchase_order_value,
|
|
||||||
COUNT(DISTINCT im.item_id) as total_ingredients,
|
|
||||||
COUNT(DISTINCT po.vendor_id) as total_vendors
|
|
||||||
`).
|
|
||||||
Joins("LEFT JOIN purchase_orders po ON im.reference_id = po.id").
|
|
||||||
Where("im.organization_id = ?", organizationID).
|
|
||||||
Where("im.movement_type = ?", entities.InventoryMovementTypePurchase).
|
|
||||||
Where("im.item_type = ?", "INGREDIENT").
|
|
||||||
Where("im.reference_type = ?", entities.InventoryMovementReferenceTypePurchaseOrder).
|
|
||||||
Where("im.created_at >= ? AND im.created_at <= ?", dateFrom, dateTo)
|
|
||||||
|
|
||||||
summaryQuery = r.resolveOutletID(summaryQuery, outletID, "im.outlet_id")
|
|
||||||
|
|
||||||
if err := summaryQuery.Scan(&summary).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var dateFormat string
|
|
||||||
switch groupBy {
|
|
||||||
case "hour":
|
|
||||||
dateFormat = "DATE_TRUNC('hour', im.created_at)"
|
|
||||||
case "week":
|
|
||||||
dateFormat = "DATE_TRUNC('week', im.created_at)"
|
|
||||||
case "month":
|
|
||||||
dateFormat = "DATE_TRUNC('month', im.created_at)"
|
|
||||||
default:
|
|
||||||
dateFormat = "DATE_TRUNC('day', im.created_at)"
|
|
||||||
}
|
|
||||||
|
|
||||||
var data []entities.PurchasingAnalyticsData
|
|
||||||
dataQuery := r.db.WithContext(ctx).
|
|
||||||
Table("inventory_movements im").
|
|
||||||
Select(`
|
|
||||||
`+dateFormat+` as date,
|
|
||||||
COALESCE(SUM(im.total_cost), 0) as purchases,
|
|
||||||
COUNT(DISTINCT im.reference_id) as purchase_orders,
|
|
||||||
COALESCE(SUM(im.quantity), 0) as quantity,
|
|
||||||
COUNT(DISTINCT im.item_id) as ingredients,
|
|
||||||
COUNT(DISTINCT po.vendor_id) as vendors
|
|
||||||
`).
|
|
||||||
Joins("LEFT JOIN purchase_orders po ON im.reference_id = po.id").
|
|
||||||
Where("im.organization_id = ?", organizationID).
|
|
||||||
Where("im.movement_type = ?", entities.InventoryMovementTypePurchase).
|
|
||||||
Where("im.item_type = ?", "INGREDIENT").
|
|
||||||
Where("im.reference_type = ?", entities.InventoryMovementReferenceTypePurchaseOrder).
|
|
||||||
Where("im.created_at >= ? AND im.created_at <= ?", dateFrom, dateTo).
|
|
||||||
Group(dateFormat).
|
|
||||||
Order(dateFormat)
|
|
||||||
|
|
||||||
dataQuery = r.resolveOutletID(dataQuery, outletID, "im.outlet_id")
|
|
||||||
|
|
||||||
if err := dataQuery.Scan(&data).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var ingredientData []entities.PurchasingIngredientData
|
|
||||||
ingredientQuery := r.db.WithContext(ctx).
|
|
||||||
Table("inventory_movements im").
|
|
||||||
Select(`
|
|
||||||
i.id as ingredient_id,
|
|
||||||
i.name as ingredient_name,
|
|
||||||
COALESCE(SUM(im.quantity), 0) as quantity,
|
|
||||||
COALESCE(SUM(im.total_cost), 0) as total_cost,
|
|
||||||
CASE
|
|
||||||
WHEN SUM(im.quantity) > 0
|
|
||||||
THEN COALESCE(SUM(im.total_cost), 0) / SUM(im.quantity)
|
|
||||||
ELSE 0
|
|
||||||
END as average_unit_cost,
|
|
||||||
COUNT(DISTINCT im.reference_id) as purchase_order_count
|
|
||||||
`).
|
|
||||||
Joins("JOIN ingredients i ON im.item_id = i.id").
|
|
||||||
Where("im.organization_id = ?", organizationID).
|
|
||||||
Where("im.movement_type = ?", entities.InventoryMovementTypePurchase).
|
|
||||||
Where("im.item_type = ?", "INGREDIENT").
|
|
||||||
Where("im.reference_type = ?", entities.InventoryMovementReferenceTypePurchaseOrder).
|
|
||||||
Where("im.created_at >= ? AND im.created_at <= ?", dateFrom, dateTo).
|
|
||||||
Group("i.id, i.name").
|
|
||||||
Order("total_cost DESC")
|
|
||||||
|
|
||||||
ingredientQuery = r.resolveOutletID(ingredientQuery, outletID, "im.outlet_id")
|
|
||||||
|
|
||||||
if err := ingredientQuery.Scan(&ingredientData).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var vendorData []entities.PurchasingVendorData
|
|
||||||
vendorQuery := r.db.WithContext(ctx).
|
|
||||||
Table("inventory_movements im").
|
|
||||||
Select(`
|
|
||||||
v.id as vendor_id,
|
|
||||||
v.name as vendor_name,
|
|
||||||
COALESCE(SUM(im.total_cost), 0) as total_cost,
|
|
||||||
COUNT(DISTINCT im.reference_id) as purchase_order_count,
|
|
||||||
COUNT(DISTINCT im.item_id) as ingredient_count,
|
|
||||||
COALESCE(SUM(im.quantity), 0) as quantity
|
|
||||||
`).
|
|
||||||
Joins("JOIN purchase_orders po ON im.reference_id = po.id").
|
|
||||||
Joins("JOIN vendors v ON po.vendor_id = v.id").
|
|
||||||
Where("im.organization_id = ?", organizationID).
|
|
||||||
Where("im.movement_type = ?", entities.InventoryMovementTypePurchase).
|
|
||||||
Where("im.item_type = ?", "INGREDIENT").
|
|
||||||
Where("im.reference_type = ?", entities.InventoryMovementReferenceTypePurchaseOrder).
|
|
||||||
Where("im.created_at >= ? AND im.created_at <= ?", dateFrom, dateTo).
|
|
||||||
Group("v.id, v.name").
|
|
||||||
Order("total_cost DESC")
|
|
||||||
|
|
||||||
vendorQuery = r.resolveOutletID(vendorQuery, outletID, "im.outlet_id")
|
|
||||||
|
|
||||||
if err := vendorQuery.Scan(&vendorData).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &entities.PurchasingAnalytics{
|
|
||||||
OutletName: outletName,
|
|
||||||
Summary: summary,
|
|
||||||
Data: data,
|
|
||||||
IngredientData: ingredientData,
|
|
||||||
VendorData: vendorData,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AnalyticsRepositoryImpl) GetProductAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, limit int) ([]*entities.ProductAnalytics, error) {
|
func (r *AnalyticsRepositoryImpl) GetProductAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, limit int) ([]*entities.ProductAnalytics, error) {
|
||||||
var results []*entities.ProductAnalytics
|
var results []*entities.ProductAnalytics
|
||||||
|
|
||||||
|
|||||||
@@ -13,19 +13,20 @@ import (
|
|||||||
|
|
||||||
type OrderRepository interface {
|
type OrderRepository interface {
|
||||||
Create(ctx context.Context, order *entities.Order) error
|
Create(ctx context.Context, order *entities.Order) error
|
||||||
GetByID(ctx context.Context, id uuid.UUID) (*entities.Order, error)
|
GetByID(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*entities.Order, error)
|
||||||
GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.Order, error)
|
GetWithRelations(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*entities.Order, error)
|
||||||
Update(ctx context.Context, order *entities.Order) error
|
Update(ctx context.Context, order *entities.Order) error
|
||||||
Delete(ctx context.Context, id uuid.UUID) error
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Order, int64, error)
|
List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Order, int64, error)
|
||||||
ListBySessionID(ctx context.Context, sessionID string) ([]*entities.Order, error)
|
ListBySessionID(ctx context.Context, sessionID string) ([]*entities.Order, error)
|
||||||
GetByOrderNumber(ctx context.Context, orderNumber string) (*entities.Order, error)
|
GetByOrderNumber(ctx context.Context, orderNumber string) (*entities.Order, error)
|
||||||
ExistsByOrderNumber(ctx context.Context, orderNumber string) (bool, error)
|
ExistsByOrderNumber(ctx context.Context, orderNumber string) (bool, error)
|
||||||
VoidOrder(ctx context.Context, id uuid.UUID, reason string, voidedBy uuid.UUID) error
|
VoidOrder(ctx context.Context, id uuid.UUID, reason string, voidedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID) error
|
VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID) error
|
RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus) error
|
UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus, outletID uuid.UUID) error
|
||||||
UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus) error
|
UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, outletID uuid.UUID) error
|
||||||
|
UpdateStatusSuccess(ctx context.Context, id uuid.UUID, orderStatus entities.OrderStatus, paymentStatus entities.PaymentStatus, outletID uuid.UUID) error
|
||||||
GetNextOrderNumber(ctx context.Context, organizationID, outletID uuid.UUID) (string, error)
|
GetNextOrderNumber(ctx context.Context, organizationID, outletID uuid.UUID) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,18 +44,27 @@ func (r *OrderRepositoryImpl) Create(ctx context.Context, order *entities.Order)
|
|||||||
return r.db.WithContext(ctx).Create(order).Error
|
return r.db.WithContext(ctx).Create(order).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OrderRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.Order, error) {
|
func applyOutletFilter(query *gorm.DB, outletID uuid.UUID) *gorm.DB {
|
||||||
|
if outletID != uuid.Nil {
|
||||||
|
return query.Where("outlet_id = ?", outletID)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OrderRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*entities.Order, error) {
|
||||||
var order entities.Order
|
var order entities.Order
|
||||||
err := r.db.WithContext(ctx).First(&order, "id = ?", id).Error
|
query := r.db.WithContext(ctx)
|
||||||
|
query = applyOutletFilter(query, outletID)
|
||||||
|
err := query.First(&order, "id = ?", id).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &order, nil
|
return &order, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OrderRepositoryImpl) GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.Order, error) {
|
func (r *OrderRepositoryImpl) GetWithRelations(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*entities.Order, error) {
|
||||||
var order entities.Order
|
var order entities.Order
|
||||||
err := r.db.WithContext(ctx).
|
query := r.db.WithContext(ctx).
|
||||||
Preload("Organization").
|
Preload("Organization").
|
||||||
Preload("Outlet").
|
Preload("Outlet").
|
||||||
Preload("User").
|
Preload("User").
|
||||||
@@ -63,8 +73,9 @@ func (r *OrderRepositoryImpl) GetWithRelations(ctx context.Context, id uuid.UUID
|
|||||||
Preload("OrderItems.ProductVariant").
|
Preload("OrderItems.ProductVariant").
|
||||||
Preload("Payments").
|
Preload("Payments").
|
||||||
Preload("Payments.PaymentMethod").
|
Preload("Payments.PaymentMethod").
|
||||||
Preload("Payments.PaymentOrderItems").
|
Preload("Payments.PaymentOrderItems")
|
||||||
First(&order, "id = ?", id).Error
|
query = applyOutletFilter(query, outletID)
|
||||||
|
err := query.First(&order, "id = ?", id).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -80,10 +91,12 @@ func (r *OrderRepositoryImpl) UpdateStatusSuccess(
|
|||||||
id uuid.UUID,
|
id uuid.UUID,
|
||||||
orderStatus entities.OrderStatus,
|
orderStatus entities.OrderStatus,
|
||||||
paymentStatus entities.PaymentStatus,
|
paymentStatus entities.PaymentStatus,
|
||||||
|
outletID uuid.UUID,
|
||||||
) error {
|
) error {
|
||||||
return r.db.WithContext(ctx).
|
query := r.db.WithContext(ctx).
|
||||||
Model(&entities.Order{}).
|
Model(&entities.Order{})
|
||||||
Where("id = ?", id.String()).
|
query = applyOutletFilter(query, outletID)
|
||||||
|
return query.Where("id = ?", id.String()).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"status": orderStatus,
|
"status": orderStatus,
|
||||||
"payment_status": paymentStatus,
|
"payment_status": paymentStatus,
|
||||||
@@ -164,10 +177,11 @@ func (r *OrderRepositoryImpl) ExistsByOrderNumber(ctx context.Context, orderNumb
|
|||||||
return count > 0, err
|
return count > 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OrderRepositoryImpl) VoidOrder(ctx context.Context, id uuid.UUID, reason string, voidedBy uuid.UUID) error {
|
func (r *OrderRepositoryImpl) VoidOrder(ctx context.Context, id uuid.UUID, reason string, voidedBy uuid.UUID, outletID uuid.UUID) error {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
return r.db.WithContext(ctx).Model(&entities.Order{}).
|
query := r.db.WithContext(ctx).Model(&entities.Order{})
|
||||||
Where("id = ?", id).
|
query = applyOutletFilter(query, outletID)
|
||||||
|
return query.Where("id = ?", id).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"is_void": true,
|
"is_void": true,
|
||||||
"void_reason": reason,
|
"void_reason": reason,
|
||||||
@@ -176,10 +190,11 @@ func (r *OrderRepositoryImpl) VoidOrder(ctx context.Context, id uuid.UUID, reaso
|
|||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OrderRepositoryImpl) VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID) error {
|
func (r *OrderRepositoryImpl) VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID, outletID uuid.UUID) error {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
return r.db.WithContext(ctx).Model(&entities.Order{}).
|
query := r.db.WithContext(ctx).Model(&entities.Order{})
|
||||||
Where("id = ?", id).
|
query = applyOutletFilter(query, outletID)
|
||||||
|
return query.Where("id = ?", id).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"status": status,
|
"status": status,
|
||||||
"is_void": true,
|
"is_void": true,
|
||||||
@@ -189,10 +204,11 @@ func (r *OrderRepositoryImpl) VoidOrderWithStatus(ctx context.Context, id uuid.U
|
|||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OrderRepositoryImpl) RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID) error {
|
func (r *OrderRepositoryImpl) RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID, outletID uuid.UUID) error {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
return r.db.WithContext(ctx).Model(&entities.Order{}).
|
query := r.db.WithContext(ctx).Model(&entities.Order{})
|
||||||
Where("id = ?", id).
|
query = applyOutletFilter(query, outletID)
|
||||||
|
return query.Where("id = ?", id).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"is_refund": true,
|
"is_refund": true,
|
||||||
"refund_reason": reason,
|
"refund_reason": reason,
|
||||||
@@ -201,15 +217,17 @@ func (r *OrderRepositoryImpl) RefundOrder(ctx context.Context, id uuid.UUID, rea
|
|||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OrderRepositoryImpl) UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus) error {
|
func (r *OrderRepositoryImpl) UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus, outletID uuid.UUID) error {
|
||||||
return r.db.WithContext(ctx).Model(&entities.Order{}).
|
query := r.db.WithContext(ctx).Model(&entities.Order{})
|
||||||
Where("id = ?", id).
|
query = applyOutletFilter(query, outletID)
|
||||||
|
return query.Where("id = ?", id).
|
||||||
Update("payment_status", status).Error
|
Update("payment_status", status).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OrderRepositoryImpl) UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus) error {
|
func (r *OrderRepositoryImpl) UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, outletID uuid.UUID) error {
|
||||||
return r.db.WithContext(ctx).Model(&entities.Order{}).
|
query := r.db.WithContext(ctx).Model(&entities.Order{})
|
||||||
Where("id = ?", id).
|
query = applyOutletFilter(query, outletID)
|
||||||
|
return query.Where("id = ?", id).
|
||||||
Update("status", status).Error
|
Update("status", status).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func (r *OrganizationRepositoryImpl) GetTotalOmset(ctx context.Context, organiza
|
|||||||
var total float64
|
var total float64
|
||||||
err := r.db.WithContext(ctx).
|
err := r.db.WithContext(ctx).
|
||||||
Table("orders").
|
Table("orders").
|
||||||
Where("organization_id = ? AND payment_status = ?", organizationID, "completed").
|
Where("organization_id = ? AND payment_status = ? AND is_void = ? AND is_refund = ?", organizationID, "completed", false, false).
|
||||||
Select("COALESCE(SUM(total_amount), 0)").
|
Select("COALESCE(SUM(total_amount), 0)").
|
||||||
Scan(&total).Error
|
Scan(&total).Error
|
||||||
return total, err
|
return total, err
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ func NewTxManager(db *gorm.DB) *TxManager { return &TxManager{db: db} }
|
|||||||
|
|
||||||
// WithTransaction runs fn inside a DB transaction, injecting the *gorm.DB tx into ctx.
|
// WithTransaction runs fn inside a DB transaction, injecting the *gorm.DB tx into ctx.
|
||||||
func (m *TxManager) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
|
func (m *TxManager) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
|
||||||
if m == nil || m.db == nil {
|
|
||||||
return fn(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
return m.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
ctxTx := context.WithValue(ctx, txKey, tx)
|
ctxTx := context.WithValue(ctx, txKey, tx)
|
||||||
return fn(ctxTx)
|
return fn(ctxTx)
|
||||||
|
|||||||
@@ -325,7 +325,6 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
{
|
{
|
||||||
analytics.GET("/payment-methods", r.analyticsHandler.GetPaymentMethodAnalytics)
|
analytics.GET("/payment-methods", r.analyticsHandler.GetPaymentMethodAnalytics)
|
||||||
analytics.GET("/sales", r.analyticsHandler.GetSalesAnalytics)
|
analytics.GET("/sales", r.analyticsHandler.GetSalesAnalytics)
|
||||||
analytics.GET("/purchasing", r.analyticsHandler.GetPurchasingAnalytics)
|
|
||||||
analytics.GET("/products", r.analyticsHandler.GetProductAnalytics)
|
analytics.GET("/products", r.analyticsHandler.GetProductAnalytics)
|
||||||
analytics.GET("/categories", r.analyticsHandler.GetProductAnalyticsPerCategory)
|
analytics.GET("/categories", r.analyticsHandler.GetProductAnalyticsPerCategory)
|
||||||
analytics.GET("/dashboard", r.analyticsHandler.GetDashboardAnalytics)
|
analytics.GET("/dashboard", r.analyticsHandler.GetDashboardAnalytics)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
type AnalyticsService interface {
|
type AnalyticsService interface {
|
||||||
GetPaymentMethodAnalytics(ctx context.Context, req *models.PaymentMethodAnalyticsRequest) (*models.PaymentMethodAnalyticsResponse, error)
|
GetPaymentMethodAnalytics(ctx context.Context, req *models.PaymentMethodAnalyticsRequest) (*models.PaymentMethodAnalyticsResponse, error)
|
||||||
GetSalesAnalytics(ctx context.Context, req *models.SalesAnalyticsRequest) (*models.SalesAnalyticsResponse, error)
|
GetSalesAnalytics(ctx context.Context, req *models.SalesAnalyticsRequest) (*models.SalesAnalyticsResponse, error)
|
||||||
GetPurchasingAnalytics(ctx context.Context, req *models.PurchasingAnalyticsRequest) (*models.PurchasingAnalyticsResponse, error)
|
|
||||||
GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error)
|
GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error)
|
||||||
GetProductAnalyticsPerCategory(ctx context.Context, req *models.ProductAnalyticsPerCategoryRequest) (*models.ProductAnalyticsPerCategoryResponse, error)
|
GetProductAnalyticsPerCategory(ctx context.Context, req *models.ProductAnalyticsPerCategoryRequest) (*models.ProductAnalyticsPerCategoryResponse, error)
|
||||||
GetDashboardAnalytics(ctx context.Context, req *models.DashboardAnalyticsRequest) (*models.DashboardAnalyticsResponse, error)
|
GetDashboardAnalytics(ctx context.Context, req *models.DashboardAnalyticsRequest) (*models.DashboardAnalyticsResponse, error)
|
||||||
@@ -58,19 +57,6 @@ func (s *AnalyticsServiceImpl) GetSalesAnalytics(ctx context.Context, req *model
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AnalyticsServiceImpl) GetPurchasingAnalytics(ctx context.Context, req *models.PurchasingAnalyticsRequest) (*models.PurchasingAnalyticsResponse, error) {
|
|
||||||
if err := s.validatePurchasingAnalyticsRequest(req); err != nil {
|
|
||||||
return nil, fmt.Errorf("validation error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := s.analyticsProcessor.GetPurchasingAnalytics(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get purchasing analytics: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *AnalyticsServiceImpl) GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error) {
|
func (s *AnalyticsServiceImpl) GetProductAnalytics(ctx context.Context, req *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error) {
|
||||||
// Validate request
|
// Validate request
|
||||||
if err := s.validateProductAnalyticsRequest(req); err != nil {
|
if err := s.validateProductAnalyticsRequest(req); err != nil {
|
||||||
@@ -182,42 +168,6 @@ func (s *AnalyticsServiceImpl) validateSalesAnalyticsRequest(req *models.SalesAn
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AnalyticsServiceImpl) validatePurchasingAnalyticsRequest(req *models.PurchasingAnalyticsRequest) error {
|
|
||||||
if req == nil {
|
|
||||||
return fmt.Errorf("request cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.OrganizationID == uuid.Nil {
|
|
||||||
return fmt.Errorf("organization ID is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.DateFrom.IsZero() {
|
|
||||||
return fmt.Errorf("date_from is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.DateTo.IsZero() {
|
|
||||||
return fmt.Errorf("date_to is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.DateFrom.After(req.DateTo) {
|
|
||||||
return fmt.Errorf("date_from cannot be after date_to")
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.GroupBy != "" {
|
|
||||||
validGroupBy := map[string]bool{
|
|
||||||
"day": true,
|
|
||||||
"hour": true,
|
|
||||||
"week": true,
|
|
||||||
"month": true,
|
|
||||||
}
|
|
||||||
if !validGroupBy[req.GroupBy] {
|
|
||||||
return fmt.Errorf("invalid group_by value: %s", req.GroupBy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *AnalyticsServiceImpl) validateProductAnalyticsRequest(req *models.ProductAnalyticsRequest) error {
|
func (s *AnalyticsServiceImpl) validateProductAnalyticsRequest(req *models.ProductAnalyticsRequest) error {
|
||||||
if req.OrganizationID == uuid.Nil {
|
if req.OrganizationID == uuid.Nil {
|
||||||
return fmt.Errorf("organization ID is required")
|
return fmt.Errorf("organization ID is required")
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"apskel-pos-be/internal/models"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type analyticsProcessorStub struct{}
|
|
||||||
|
|
||||||
func (analyticsProcessorStub) GetPaymentMethodAnalytics(context.Context, *models.PaymentMethodAnalyticsRequest) (*models.PaymentMethodAnalyticsResponse, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsProcessorStub) GetSalesAnalytics(context.Context, *models.SalesAnalyticsRequest) (*models.SalesAnalyticsResponse, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsProcessorStub) GetPurchasingAnalytics(context.Context, *models.PurchasingAnalyticsRequest) (*models.PurchasingAnalyticsResponse, error) {
|
|
||||||
return &models.PurchasingAnalyticsResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsProcessorStub) GetProductAnalytics(context.Context, *models.ProductAnalyticsRequest) (*models.ProductAnalyticsResponse, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsProcessorStub) GetProductAnalyticsPerCategory(context.Context, *models.ProductAnalyticsPerCategoryRequest) (*models.ProductAnalyticsPerCategoryResponse, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsProcessorStub) GetDashboardAnalytics(context.Context, *models.DashboardAnalyticsRequest) (*models.DashboardAnalyticsResponse, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (analyticsProcessorStub) GetProfitLossAnalytics(context.Context, *models.ProfitLossAnalyticsRequest) (*models.ProfitLossAnalyticsResponse, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAnalyticsServiceGetPurchasingAnalyticsValidation(t *testing.T) {
|
|
||||||
service := NewAnalyticsServiceImpl(analyticsProcessorStub{})
|
|
||||||
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
req *models.PurchasingAnalyticsRequest
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "missing organization",
|
|
||||||
req: &models.PurchasingAnalyticsRequest{
|
|
||||||
DateFrom: now,
|
|
||||||
DateTo: now,
|
|
||||||
},
|
|
||||||
wantErr: "organization ID is required",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing date_from",
|
|
||||||
req: &models.PurchasingAnalyticsRequest{
|
|
||||||
OrganizationID: uuid.New(),
|
|
||||||
DateTo: now,
|
|
||||||
},
|
|
||||||
wantErr: "date_from is required",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing date_to",
|
|
||||||
req: &models.PurchasingAnalyticsRequest{
|
|
||||||
OrganizationID: uuid.New(),
|
|
||||||
DateFrom: now,
|
|
||||||
},
|
|
||||||
wantErr: "date_to is required",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "reversed dates",
|
|
||||||
req: &models.PurchasingAnalyticsRequest{
|
|
||||||
OrganizationID: uuid.New(),
|
|
||||||
DateFrom: now.AddDate(0, 0, 1),
|
|
||||||
DateTo: now,
|
|
||||||
},
|
|
||||||
wantErr: "date_from cannot be after date_to",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid group_by",
|
|
||||||
req: &models.PurchasingAnalyticsRequest{
|
|
||||||
OrganizationID: uuid.New(),
|
|
||||||
DateFrom: now,
|
|
||||||
DateTo: now,
|
|
||||||
GroupBy: "quarter",
|
|
||||||
},
|
|
||||||
wantErr: "invalid group_by value: quarter",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
resp, err := service.GetPurchasingAnalytics(context.Background(), tt.req)
|
|
||||||
|
|
||||||
require.Nil(t, resp)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Contains(t, err.Error(), tt.wantErr)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAnalyticsServiceGetPurchasingAnalyticsAllowsEmptyGroupBy(t *testing.T) {
|
|
||||||
service := NewAnalyticsServiceImpl(analyticsProcessorStub{})
|
|
||||||
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
|
|
||||||
resp, err := service.GetPurchasingAnalytics(context.Background(), &models.PurchasingAnalyticsRequest{
|
|
||||||
OrganizationID: uuid.New(),
|
|
||||||
DateFrom: now,
|
|
||||||
DateTo: now,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
}
|
|
||||||
@@ -23,16 +23,16 @@ type orderUserRepository interface {
|
|||||||
|
|
||||||
type OrderService interface {
|
type OrderService interface {
|
||||||
CreateOrder(ctx context.Context, req *models.CreateOrderRequest, organizationID uuid.UUID) (*models.OrderResponse, error)
|
CreateOrder(ctx context.Context, req *models.CreateOrderRequest, organizationID uuid.UUID) (*models.OrderResponse, error)
|
||||||
AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error)
|
AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest, outletID uuid.UUID) (*models.AddToOrderResponse, error)
|
||||||
UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest) (*models.OrderResponse, error)
|
UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest, outletID uuid.UUID) (*models.OrderResponse, error)
|
||||||
GetOrderByID(ctx context.Context, id uuid.UUID) (*models.OrderResponse, error)
|
GetOrderByID(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*models.OrderResponse, error)
|
||||||
ListOrders(ctx context.Context, req *models.ListOrdersRequest) (*models.ListOrdersResponse, error)
|
ListOrders(ctx context.Context, req *models.ListOrdersRequest) (*models.ListOrdersResponse, error)
|
||||||
VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID) error
|
VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID) error
|
RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error)
|
CreatePayment(ctx context.Context, req *models.CreatePaymentRequest, outletID uuid.UUID) (*models.PaymentResponse, error)
|
||||||
RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error
|
RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID, outletID uuid.UUID) error
|
||||||
SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error)
|
SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID, outletID uuid.UUID) (*models.SetOrderCustomerResponse, error)
|
||||||
SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error)
|
SplitBill(ctx context.Context, req *models.SplitBillRequest, outletID uuid.UUID) (*models.SplitBillResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderServiceImpl struct {
|
type OrderServiceImpl struct {
|
||||||
@@ -199,7 +199,7 @@ func (s *OrderServiceImpl) createIngredientTransactions(ctx context.Context, ord
|
|||||||
// Calculate waste quantities
|
// Calculate waste quantities
|
||||||
transactions, err := s.calculateWasteQuantities(productRecipes, float64(orderItem.Quantity))
|
transactions, err := s.calculateWasteQuantities(productRecipes, float64(orderItem.Quantity))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to calculate waste quantities for product %s: %w", orderItem.ProductID, err)
|
return nil, fmt.Errorf("failed to calculate waste quantities for product %s: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set common fields for all transactions
|
// Set common fields for all transactions
|
||||||
@@ -216,7 +216,7 @@ func (s *OrderServiceImpl) createIngredientTransactions(ctx context.Context, ord
|
|||||||
return allTransactions, nil
|
return allTransactions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error) {
|
func (s *OrderServiceImpl) AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest, outletID uuid.UUID) (*models.AddToOrderResponse, error) {
|
||||||
if orderID == uuid.Nil {
|
if orderID == uuid.Nil {
|
||||||
return nil, fmt.Errorf("invalid order ID")
|
return nil, fmt.Errorf("invalid order ID")
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,7 @@ func (s *OrderServiceImpl) AddToOrder(ctx context.Context, orderID uuid.UUID, re
|
|||||||
var ingredientTransactions []*contract.CreateOrderIngredientTransactionRequest
|
var ingredientTransactions []*contract.CreateOrderIngredientTransactionRequest
|
||||||
|
|
||||||
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||||
addResp, err := s.orderProcessor.AddToOrder(txCtx, orderID, req)
|
addResp, err := s.orderProcessor.AddToOrder(txCtx, orderID, req, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to add items to order: %w", err)
|
return fmt.Errorf("failed to add items to order: %w", err)
|
||||||
}
|
}
|
||||||
@@ -257,12 +257,12 @@ func (s *OrderServiceImpl) AddToOrder(ctx context.Context, orderID uuid.UUID, re
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest) (*models.OrderResponse, error) {
|
func (s *OrderServiceImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest, outletID uuid.UUID) (*models.OrderResponse, error) {
|
||||||
if err := s.validateUpdateOrderRequest(req); err != nil {
|
if err := s.validateUpdateOrderRequest(req); err != nil {
|
||||||
return nil, fmt.Errorf("validation error: %w", err)
|
return nil, fmt.Errorf("validation error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := s.orderProcessor.UpdateOrder(ctx, id, req)
|
response, err := s.orderProcessor.UpdateOrder(ctx, id, req, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to update order: %w", err)
|
return nil, fmt.Errorf("failed to update order: %w", err)
|
||||||
}
|
}
|
||||||
@@ -270,12 +270,12 @@ func (s *OrderServiceImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req *m
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) GetOrderByID(ctx context.Context, id uuid.UUID) (*models.OrderResponse, error) {
|
func (s *OrderServiceImpl) GetOrderByID(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*models.OrderResponse, error) {
|
||||||
if id == uuid.Nil {
|
if id == uuid.Nil {
|
||||||
return nil, fmt.Errorf("invalid order ID")
|
return nil, fmt.Errorf("invalid order ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := s.orderProcessor.GetOrderByID(ctx, id)
|
response, err := s.orderProcessor.GetOrderByID(ctx, id, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get order: %w", err)
|
return nil, fmt.Errorf("failed to get order: %w", err)
|
||||||
}
|
}
|
||||||
@@ -296,7 +296,7 @@ func (s *OrderServiceImpl) ListOrders(ctx context.Context, req *models.ListOrder
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID) error {
|
func (s *OrderServiceImpl) VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID, outletID uuid.UUID) error {
|
||||||
if req.OrderID == uuid.Nil {
|
if req.OrderID == uuid.Nil {
|
||||||
return fmt.Errorf("invalid order ID")
|
return fmt.Errorf("invalid order ID")
|
||||||
}
|
}
|
||||||
@@ -305,7 +305,7 @@ func (s *OrderServiceImpl) VoidOrder(ctx context.Context, req *models.VoidOrderR
|
|||||||
return fmt.Errorf("invalid user ID")
|
return fmt.Errorf("invalid user ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.orderProcessor.VoidOrder(ctx, req, voidedBy); err != nil {
|
if err := s.orderProcessor.VoidOrder(ctx, req, voidedBy, outletID); err != nil {
|
||||||
return fmt.Errorf("failed to void order: %w", err)
|
return fmt.Errorf("failed to void order: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ func (s *OrderServiceImpl) VoidOrder(ctx context.Context, req *models.VoidOrderR
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID) error {
|
func (s *OrderServiceImpl) RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID, outletID uuid.UUID) error {
|
||||||
// Validate inputs
|
// Validate inputs
|
||||||
if id == uuid.Nil {
|
if id == uuid.Nil {
|
||||||
return fmt.Errorf("invalid order ID")
|
return fmt.Errorf("invalid order ID")
|
||||||
@@ -331,19 +331,19 @@ func (s *OrderServiceImpl) RefundOrder(ctx context.Context, id uuid.UUID, req *m
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process order refund
|
// Process order refund
|
||||||
if err := s.orderProcessor.RefundOrder(ctx, id, req, refundedBy); err != nil {
|
if err := s.orderProcessor.RefundOrder(ctx, id, req, refundedBy, outletID); err != nil {
|
||||||
return fmt.Errorf("failed to refund order: %w", err)
|
return fmt.Errorf("failed to refund order: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error) {
|
func (s *OrderServiceImpl) CreatePayment(ctx context.Context, req *models.CreatePaymentRequest, outletID uuid.UUID) (*models.PaymentResponse, error) {
|
||||||
if err := s.validateCreatePaymentRequest(req); err != nil {
|
if err := s.validateCreatePaymentRequest(req); err != nil {
|
||||||
return nil, fmt.Errorf("validation error: %w", err)
|
return nil, fmt.Errorf("validation error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := s.orderProcessor.CreatePayment(ctx, req)
|
response, err := s.orderProcessor.CreatePayment(ctx, req, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create payment: %w", err)
|
return nil, fmt.Errorf("failed to create payment: %w", err)
|
||||||
}
|
}
|
||||||
@@ -355,7 +355,7 @@ func (s *OrderServiceImpl) CreatePayment(ctx context.Context, req *models.Create
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error {
|
func (s *OrderServiceImpl) RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID, outletID uuid.UUID) error {
|
||||||
// Validate inputs
|
// Validate inputs
|
||||||
if paymentID == uuid.Nil {
|
if paymentID == uuid.Nil {
|
||||||
return fmt.Errorf("invalid payment ID")
|
return fmt.Errorf("invalid payment ID")
|
||||||
@@ -368,14 +368,14 @@ func (s *OrderServiceImpl) RefundPayment(ctx context.Context, paymentID uuid.UUI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process payment refund
|
// Process payment refund
|
||||||
if err := s.orderProcessor.RefundPayment(ctx, paymentID, refundAmount, reason, refundedBy); err != nil {
|
if err := s.orderProcessor.RefundPayment(ctx, paymentID, refundAmount, reason, refundedBy, outletID); err != nil {
|
||||||
return fmt.Errorf("failed to refund payment: %w", err)
|
return fmt.Errorf("failed to refund payment: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error) {
|
func (s *OrderServiceImpl) SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID, outletID uuid.UUID) (*models.SetOrderCustomerResponse, error) {
|
||||||
// Validate inputs
|
// Validate inputs
|
||||||
if orderID == uuid.Nil {
|
if orderID == uuid.Nil {
|
||||||
return nil, fmt.Errorf("invalid order ID")
|
return nil, fmt.Errorf("invalid order ID")
|
||||||
@@ -391,7 +391,7 @@ func (s *OrderServiceImpl) SetOrderCustomer(ctx context.Context, orderID uuid.UU
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process setting customer for order
|
// Process setting customer for order
|
||||||
response, err := s.orderProcessor.SetOrderCustomer(ctx, orderID, req, organizationID)
|
response, err := s.orderProcessor.SetOrderCustomer(ctx, orderID, req, organizationID, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to set customer for order: %w", err)
|
return nil, fmt.Errorf("failed to set customer for order: %w", err)
|
||||||
}
|
}
|
||||||
@@ -584,12 +584,12 @@ func (s *OrderServiceImpl) validateSetOrderCustomerRequest(req *models.SetOrderC
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error) {
|
func (s *OrderServiceImpl) SplitBill(ctx context.Context, req *models.SplitBillRequest, outletID uuid.UUID) (*models.SplitBillResponse, error) {
|
||||||
if err := s.validateSplitBillRequest(req); err != nil {
|
if err := s.validateSplitBillRequest(req); err != nil {
|
||||||
return nil, fmt.Errorf("validation error: %w", err)
|
return nil, fmt.Errorf("validation error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := s.orderProcessor.SplitBill(ctx, req)
|
response, err := s.orderProcessor.SplitBill(ctx, req, outletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to split bill: %w", err)
|
return nil, fmt.Errorf("failed to split bill: %w", err)
|
||||||
}
|
}
|
||||||
@@ -680,7 +680,7 @@ func (s *OrderServiceImpl) occupyTableWithOrder(ctx context.Context, tableID, or
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) handleTableReleaseOnPayment(ctx context.Context, orderID uuid.UUID) error {
|
func (s *OrderServiceImpl) handleTableReleaseOnPayment(ctx context.Context, orderID uuid.UUID) error {
|
||||||
order, err := s.orderProcessor.GetOrderByID(ctx, orderID)
|
order, err := s.orderProcessor.GetOrderByID(ctx, orderID, uuid.Nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get order: %w", err)
|
return fmt.Errorf("failed to get order: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,14 +114,6 @@ func (m *MockTableRepository) GetByID(ctx context.Context, id uuid.UUID) (*entit
|
|||||||
return args.Get(0).(*entities.Table), args.Error(1)
|
return args.Get(0).(*entities.Table), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockTableRepository) GetByToken(ctx context.Context, token string) (*entities.Table, error) {
|
|
||||||
args := m.Called(ctx, token)
|
|
||||||
if args.Get(0) == nil {
|
|
||||||
return nil, args.Error(1)
|
|
||||||
}
|
|
||||||
return args.Get(0).(*entities.Table), args.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockTableRepository) GetByOutletID(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error) {
|
func (m *MockTableRepository) GetByOutletID(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error) {
|
||||||
args := m.Called(ctx, outletID)
|
args := m.Called(ctx, outletID)
|
||||||
if args.Get(0) == nil {
|
if args.Get(0) == nil {
|
||||||
@@ -190,11 +182,6 @@ func (m *MockTableRepository) GetByOrderID(ctx context.Context, orderID uuid.UUI
|
|||||||
return args.Get(0).(*entities.Table), args.Error(1)
|
return args.Get(0).(*entities.Table), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockTableRepository) UpdateToken(ctx context.Context, tableID uuid.UUID, token string) error {
|
|
||||||
args := m.Called(ctx, tableID, token)
|
|
||||||
return args.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateOrderWithTableOccupation(t *testing.T) {
|
func TestCreateOrderWithTableOccupation(t *testing.T) {
|
||||||
// Setup
|
// Setup
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|||||||
@@ -138,91 +138,6 @@ func SalesAnalyticsModelToContract(resp *models.SalesAnalyticsResponse) *contrac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PurchasingAnalyticsContractToModel converts contract request to model
|
|
||||||
func PurchasingAnalyticsContractToModel(req *contract.PurchasingAnalyticsRequest) *models.PurchasingAnalyticsRequest {
|
|
||||||
var dateFrom, dateTo time.Time
|
|
||||||
|
|
||||||
if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil {
|
|
||||||
if fromTime != nil {
|
|
||||||
dateFrom = *fromTime
|
|
||||||
}
|
|
||||||
if toTime != nil {
|
|
||||||
dateTo = *toTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &models.PurchasingAnalyticsRequest{
|
|
||||||
OrganizationID: req.OrganizationID,
|
|
||||||
OutletID: parseOutletID(req.OutletID),
|
|
||||||
DateFrom: dateFrom,
|
|
||||||
DateTo: dateTo,
|
|
||||||
GroupBy: req.GroupBy,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PurchasingAnalyticsModelToContract converts model response to contract
|
|
||||||
func PurchasingAnalyticsModelToContract(resp *models.PurchasingAnalyticsResponse) *contract.PurchasingAnalyticsResponse {
|
|
||||||
if resp == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
data := make([]contract.PurchasingAnalyticsData, len(resp.Data))
|
|
||||||
for i, item := range resp.Data {
|
|
||||||
data[i] = contract.PurchasingAnalyticsData{
|
|
||||||
Date: item.Date,
|
|
||||||
Purchases: item.Purchases,
|
|
||||||
PurchaseOrders: item.PurchaseOrders,
|
|
||||||
Quantity: item.Quantity,
|
|
||||||
Ingredients: item.Ingredients,
|
|
||||||
Vendors: item.Vendors,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ingredientData := make([]contract.PurchasingIngredientData, len(resp.IngredientData))
|
|
||||||
for i, item := range resp.IngredientData {
|
|
||||||
ingredientData[i] = contract.PurchasingIngredientData{
|
|
||||||
IngredientID: item.IngredientID,
|
|
||||||
IngredientName: item.IngredientName,
|
|
||||||
Quantity: item.Quantity,
|
|
||||||
TotalCost: item.TotalCost,
|
|
||||||
AverageUnitCost: item.AverageUnitCost,
|
|
||||||
PurchaseOrderCount: item.PurchaseOrderCount,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vendorData := make([]contract.PurchasingVendorData, len(resp.VendorData))
|
|
||||||
for i, item := range resp.VendorData {
|
|
||||||
vendorData[i] = contract.PurchasingVendorData{
|
|
||||||
VendorID: item.VendorID,
|
|
||||||
VendorName: item.VendorName,
|
|
||||||
TotalCost: item.TotalCost,
|
|
||||||
PurchaseOrderCount: item.PurchaseOrderCount,
|
|
||||||
IngredientCount: item.IngredientCount,
|
|
||||||
Quantity: item.Quantity,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &contract.PurchasingAnalyticsResponse{
|
|
||||||
OrganizationID: resp.OrganizationID,
|
|
||||||
OutletID: resp.OutletID,
|
|
||||||
OutletName: resp.OutletName,
|
|
||||||
DateFrom: resp.DateFrom,
|
|
||||||
DateTo: resp.DateTo,
|
|
||||||
GroupBy: resp.GroupBy,
|
|
||||||
Summary: contract.PurchasingSummary{
|
|
||||||
TotalPurchases: resp.Summary.TotalPurchases,
|
|
||||||
TotalPurchaseOrders: resp.Summary.TotalPurchaseOrders,
|
|
||||||
TotalQuantity: resp.Summary.TotalQuantity,
|
|
||||||
AveragePurchaseOrderValue: resp.Summary.AveragePurchaseOrderValue,
|
|
||||||
TotalIngredients: resp.Summary.TotalIngredients,
|
|
||||||
TotalVendors: resp.Summary.TotalVendors,
|
|
||||||
},
|
|
||||||
Data: data,
|
|
||||||
IngredientData: ingredientData,
|
|
||||||
VendorData: vendorData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProductAnalyticsContractToModel converts contract request to model
|
// ProductAnalyticsContractToModel converts contract request to model
|
||||||
func ProductAnalyticsContractToModel(req *contract.ProductAnalyticsRequest) *models.ProductAnalyticsRequest {
|
func ProductAnalyticsContractToModel(req *contract.ProductAnalyticsRequest) *models.ProductAnalyticsRequest {
|
||||||
var dateFrom, dateTo time.Time
|
var dateFrom, dateTo time.Time
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
package transformer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"apskel-pos-be/internal/contract"
|
|
||||||
"apskel-pos-be/internal/models"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPurchasingAnalyticsContractToModelParsesDateRangeAndOutlet(t *testing.T) {
|
|
||||||
orgID := uuid.New()
|
|
||||||
outletID := uuid.New().String()
|
|
||||||
|
|
||||||
req := &contract.PurchasingAnalyticsRequest{
|
|
||||||
OrganizationID: orgID,
|
|
||||||
OutletID: &outletID,
|
|
||||||
DateFrom: "01-05-2026",
|
|
||||||
DateTo: "02-05-2026",
|
|
||||||
GroupBy: "week",
|
|
||||||
}
|
|
||||||
|
|
||||||
result := PurchasingAnalyticsContractToModel(req)
|
|
||||||
|
|
||||||
require.Equal(t, orgID, result.OrganizationID)
|
|
||||||
require.NotNil(t, result.OutletID)
|
|
||||||
require.Equal(t, outletID, result.OutletID.String())
|
|
||||||
require.Equal(t, "week", result.GroupBy)
|
|
||||||
|
|
||||||
location, err := time.LoadLocation("Asia/Jakarta")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, time.Date(2026, 5, 1, 0, 0, 0, 0, location), result.DateFrom)
|
|
||||||
require.Equal(t, time.Date(2026, 5, 2, 23, 59, 59, int(time.Second-time.Nanosecond), location), result.DateTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPurchasingAnalyticsContractToModelIgnoresInvalidOutlet(t *testing.T) {
|
|
||||||
outletID := "not-a-uuid"
|
|
||||||
|
|
||||||
result := PurchasingAnalyticsContractToModel(&contract.PurchasingAnalyticsRequest{
|
|
||||||
OutletID: &outletID,
|
|
||||||
DateFrom: "01-05-2026",
|
|
||||||
DateTo: "02-05-2026",
|
|
||||||
})
|
|
||||||
|
|
||||||
require.Nil(t, result.OutletID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPurchasingAnalyticsModelToContractCopiesOutletName(t *testing.T) {
|
|
||||||
outletID := uuid.New()
|
|
||||||
outletName := "Main Outlet"
|
|
||||||
|
|
||||||
result := PurchasingAnalyticsModelToContract(&models.PurchasingAnalyticsResponse{
|
|
||||||
OrganizationID: uuid.New(),
|
|
||||||
OutletID: &outletID,
|
|
||||||
OutletName: &outletName,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NotNil(t, result)
|
|
||||||
require.Equal(t, &outletID, result.OutletID)
|
|
||||||
require.NotNil(t, result.OutletName)
|
|
||||||
require.Equal(t, outletName, *result.OutletName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPurchasingAnalyticsModelToContractOmitsNilOutletName(t *testing.T) {
|
|
||||||
result := PurchasingAnalyticsModelToContract(&models.PurchasingAnalyticsResponse{
|
|
||||||
OrganizationID: uuid.New(),
|
|
||||||
})
|
|
||||||
|
|
||||||
payload, err := json.Marshal(result)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotContains(t, string(payload), "outlet_name")
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user