diff --git a/internal/repository/analytics_repository.go b/internal/repository/analytics_repository.go index 62a9a90..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,29 +143,32 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or outletName = &outlet.Name } } + return r.getPurchaseOrderPurchasingAnalytics(ctx, organizationID, outletID, outletName, dateFrom, dateTo, groupBy) +} +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("inventory_movements im"). + Table("purchase_orders po"). 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, + COALESCE(SUM(poi.amount), 0) as total_purchases, + COUNT(DISTINCT po.id) as total_purchase_orders, + COALESCE(SUM(poi.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) + WHEN COUNT(DISTINCT po.id) > 0 + THEN COALESCE(SUM(poi.amount), 0) / COUNT(DISTINCT po.id) ELSE 0 END as average_purchase_order_value, - COUNT(DISTINCT im.item_id) as total_ingredients, + COUNT(DISTINCT i.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") + 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 @@ -175,36 +177,35 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or var dateFormat string switch groupBy { case "hour": - dateFormat = "DATE_TRUNC('hour', im.created_at)" + dateFormat = "DATE_TRUNC('hour', po.created_at)" case "week": - dateFormat = "DATE_TRUNC('week', im.created_at)" + dateFormat = "DATE_TRUNC('week', po.transaction_date::timestamp)" case "month": - dateFormat = "DATE_TRUNC('month', im.created_at)" + dateFormat = "DATE_TRUNC('month', po.transaction_date::timestamp)" default: - dateFormat = "DATE_TRUNC('day', im.created_at)" + dateFormat = "DATE_TRUNC('day', po.transaction_date::timestamp)" } var data []entities.PurchasingAnalyticsData dataQuery := r.db.WithContext(ctx). - Table("inventory_movements im"). + Table("purchase_orders po"). 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, + COALESCE(SUM(poi.amount), 0) as purchases, + COUNT(DISTINCT po.id) as purchase_orders, + COALESCE(SUM(poi.quantity), 0) as quantity, + COUNT(DISTINCT i.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). + 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.resolveOutletID(dataQuery, outletID, "im.outlet_id") + dataQuery = r.applyPurchaseOrderItemOutletFilter(dataQuery, outletID) if err := dataQuery.Scan(&data).Error; err != nil { return nil, err @@ -212,29 +213,28 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or var ingredientData []entities.PurchasingIngredientData ingredientQuery := r.db.WithContext(ctx). - Table("inventory_movements im"). + Table("purchase_order_items poi"). 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, + COALESCE(SUM(poi.quantity), 0) as quantity, + COALESCE(SUM(poi.amount), 0) as total_cost, CASE - WHEN SUM(im.quantity) > 0 - THEN COALESCE(SUM(im.total_cost), 0) / SUM(im.quantity) + WHEN SUM(poi.quantity) > 0 + THEN COALESCE(SUM(poi.amount), 0) / SUM(poi.quantity) ELSE 0 END as average_unit_cost, - COUNT(DISTINCT im.reference_id) as purchase_order_count + COUNT(DISTINCT po.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). + 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.resolveOutletID(ingredientQuery, outletID, "im.outlet_id") + ingredientQuery = r.applyPurchaseOrderItemOutletFilter(ingredientQuery, outletID) if err := ingredientQuery.Scan(&ingredientData).Error; err != nil { return nil, err @@ -242,26 +242,25 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or var vendorData []entities.PurchasingVendorData vendorQuery := r.db.WithContext(ctx). - Table("inventory_movements im"). + Table("purchase_orders po"). 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 + COALESCE(SUM(poi.amount), 0) as total_cost, + COUNT(DISTINCT po.id) as purchase_order_count, + COUNT(DISTINCT i.id) as ingredient_count, + COALESCE(SUM(poi.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). + 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.resolveOutletID(vendorQuery, outletID, "im.outlet_id") + vendorQuery = r.applyPurchaseOrderItemOutletFilter(vendorQuery, outletID) if err := vendorQuery.Scan(&vendorData).Error; err != nil { return nil, err @@ -276,6 +275,13 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or }, 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 @@ -284,7 +290,6 @@ func (r *AnalyticsRepositoryImpl) GetProductAnalytics(ctx context.Context, organ Select(` p.id as product_id, p.name as product_name, - p.price as product_price, c.id as category_id, c.name as category_name, c.order as category_order, @@ -343,7 +348,7 @@ func (r *AnalyticsRepositoryImpl) GetProductAnalytics(ctx context.Context, organ query = r.resolveOutletID(query, outletID, "o.outlet_id") err := query. - Group("p.id, p.name, p.price, p.cost, c.id, c.name, c.order, mahpp.hpp_per_unit"). + Group("p.id, p.name, p.cost, c.id, c.name, c.order, mahpp.hpp_per_unit"). Order("revenue DESC"). Limit(limit). Scan(&results).Error @@ -638,7 +643,7 @@ func (r *AnalyticsRepositoryImpl) getExpenseByCategory(ctx context.Context, orga query := r.db.WithContext(ctx). Table("expense_items ei"). - Select(`COALESCE(parent_coa.name, 'Lain-lain') as category_name, COALESCE(SUM(ei.amount), 0) as amount`). + Select(`COALESCE(parent_coa.name, coa.name, 'Lain-lain') as category_name, COALESCE(SUM(ei.amount), 0) as amount`). Joins("JOIN expenses e ON ei.expense_id = e.id"). Joins("JOIN chart_of_accounts coa ON ei.chart_of_account_id = coa.id"). Joins("LEFT JOIN chart_of_accounts parent_coa ON coa.parent_id = parent_coa.id"). @@ -651,8 +656,8 @@ func (r *AnalyticsRepositoryImpl) getExpenseByCategory(ctx context.Context, orga } err := query. - Group("parent_coa.name"). - Order("parent_coa.name"). + Group("COALESCE(parent_coa.name, coa.name, 'Lain-lain')"). + Order("COALESCE(parent_coa.name, coa.name, 'Lain-lain')"). Scan(&results).Error return results, err