From f4172fcea71003b577365e2c6c02ea8ec42bb72b Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 11 Jun 2026 16:27:50 +0700 Subject: [PATCH] Fix purchase analytic by outlet --- internal/repository/analytics_repository.go | 162 +++----------------- 1 file changed, 24 insertions(+), 138 deletions(-) diff --git a/internal/repository/analytics_repository.go b/internal/repository/analytics_repository.go index c03ee0f..9667788 100644 --- a/internal/repository/analytics_repository.go +++ b/internal/repository/analytics_repository.go @@ -124,7 +124,6 @@ func (r *AnalyticsRepositoryImpl) GetSalesAnalytics(ctx context.Context, organiz } 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 { @@ -144,142 +143,10 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or outletName = &outlet.Name } } - if outletID == nil { - return r.getPurchaseOrderPurchasingAnalytics(ctx, organizationID, dateFrom, dateTo, groupBy) - } - - 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 + return r.getPurchaseOrderPurchasingAnalytics(ctx, organizationID, outletID, outletName, dateFrom, dateTo, groupBy) } -func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx context.Context, organizationID uuid.UUID, dateFrom, dateTo time.Time, groupBy string) (*entities.PurchasingAnalytics, error) { +func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, outletName *string, dateFrom, dateTo time.Time, groupBy string) (*entities.PurchasingAnalytics, error) { var summary entities.PurchasingSummary summaryQuery := r.db.WithContext(ctx). Table("purchase_orders po"). @@ -292,13 +159,16 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex THEN COALESCE(SUM(poi.amount), 0) / COUNT(DISTINCT po.id) ELSE 0 END as average_purchase_order_value, - COUNT(DISTINCT poi.ingredient_id) as total_ingredients, + COUNT(DISTINCT i.id) as total_ingredients, COUNT(DISTINCT po.vendor_id) as total_vendors `). Joins("LEFT JOIN purchase_order_items poi ON poi.purchase_order_id = po.id"). + Joins("LEFT JOIN ingredients i ON poi.ingredient_id = i.id"). + Joins("LEFT JOIN units u ON poi.unit_id = u.id"). Where("po.organization_id = ?", organizationID). Where("po.status != ?", "cancelled"). Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo) + summaryQuery = r.applyPurchaseOrderItemOutletFilter(summaryQuery, outletID) if err := summaryQuery.Scan(&summary).Error; err != nil { return nil, err @@ -324,15 +194,18 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex COALESCE(SUM(poi.amount), 0) as purchases, COUNT(DISTINCT po.id) as purchase_orders, COALESCE(SUM(poi.quantity), 0) as quantity, - COUNT(DISTINCT poi.ingredient_id) as ingredients, + COUNT(DISTINCT i.id) as ingredients, COUNT(DISTINCT po.vendor_id) as vendors `). Joins("LEFT JOIN purchase_order_items poi ON poi.purchase_order_id = po.id"). + Joins("LEFT JOIN ingredients i ON poi.ingredient_id = i.id"). + Joins("LEFT JOIN units u ON poi.unit_id = u.id"). Where("po.organization_id = ?", organizationID). Where("po.status != ?", "cancelled"). Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo). Group(dateFormat). Order(dateFormat) + dataQuery = r.applyPurchaseOrderItemOutletFilter(dataQuery, outletID) if err := dataQuery.Scan(&data).Error; err != nil { return nil, err @@ -355,11 +228,13 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex `). Joins("JOIN purchase_orders po ON poi.purchase_order_id = po.id"). Joins("JOIN ingredients i ON poi.ingredient_id = i.id"). + Joins("LEFT JOIN units u ON poi.unit_id = u.id"). Where("po.organization_id = ?", organizationID). Where("po.status != ?", "cancelled"). Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo). Group("i.id, i.name"). Order("total_cost DESC") + ingredientQuery = r.applyPurchaseOrderItemOutletFilter(ingredientQuery, outletID) if err := ingredientQuery.Scan(&ingredientData).Error; err != nil { return nil, err @@ -373,22 +248,26 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex v.name as vendor_name, COALESCE(SUM(poi.amount), 0) as total_cost, COUNT(DISTINCT po.id) as purchase_order_count, - COUNT(DISTINCT poi.ingredient_id) as ingredient_count, + COUNT(DISTINCT i.id) as ingredient_count, COALESCE(SUM(poi.quantity), 0) as quantity `). Joins("JOIN vendors v ON po.vendor_id = v.id"). Joins("LEFT JOIN purchase_order_items poi ON poi.purchase_order_id = po.id"). + Joins("LEFT JOIN ingredients i ON poi.ingredient_id = i.id"). + Joins("LEFT JOIN units u ON poi.unit_id = u.id"). Where("po.organization_id = ?", organizationID). Where("po.status != ?", "cancelled"). Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo). Group("v.id, v.name"). Order("total_cost DESC") + vendorQuery = r.applyPurchaseOrderItemOutletFilter(vendorQuery, outletID) if err := vendorQuery.Scan(&vendorData).Error; err != nil { return nil, err } return &entities.PurchasingAnalytics{ + OutletName: outletName, Summary: summary, Data: data, IngredientData: ingredientData, @@ -396,6 +275,13 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex }, nil } +func (r *AnalyticsRepositoryImpl) applyPurchaseOrderItemOutletFilter(query *gorm.DB, outletID *uuid.UUID) *gorm.DB { + if outletID == nil { + return query + } + return query.Where("(i.outlet_id = ? OR u.outlet_id = ?)", *outletID, *outletID) +} + 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