Fix expense to be nullable without raw material.

This commit is contained in:
2026-06-10 14:25:23 +07:00
parent d0c090a657
commit 1718c5adab
24 changed files with 342 additions and 233 deletions
+36 -36
View File
@@ -146,7 +146,7 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or
}
rawMaterialOutletFilter := ""
nonInventoryOutletFilter := ""
expenseOutletFilter := ""
rawMaterialSummaryArgs := []interface{}{
organizationID,
entities.InventoryMovementTypePurchase,
@@ -155,20 +155,20 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or
dateFrom,
dateTo,
}
nonInventorySummaryArgs := []interface{}{
expenseSummaryArgs := []interface{}{
organizationID,
entities.PurchaseCategoryTypeNonInventory,
entities.PurchaseCategoryTypeExpense,
"approved",
dateFrom,
dateTo,
}
if outletID != nil {
rawMaterialOutletFilter = "AND im.outlet_id = ?"
nonInventoryOutletFilter = "AND e.outlet_id = ?"
expenseOutletFilter = "AND e.outlet_id = ?"
rawMaterialSummaryArgs = append(rawMaterialSummaryArgs, *outletID)
nonInventorySummaryArgs = append(nonInventorySummaryArgs, *outletID)
expenseSummaryArgs = append(expenseSummaryArgs, *outletID)
}
summaryArgs := append(rawMaterialSummaryArgs, nonInventorySummaryArgs...)
summaryArgs := append(rawMaterialSummaryArgs, expenseSummaryArgs...)
summaryQuery := `
WITH raw_material AS (
@@ -187,10 +187,10 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or
AND im.created_at >= ? AND im.created_at <= ?
` + rawMaterialOutletFilter + `
),
non_inventory AS (
expense AS (
SELECT
COALESCE(SUM(ei.amount), 0) as non_inventory_purchases,
COUNT(DISTINCT e.id) as non_inventory_expense_count
COALESCE(SUM(ei.amount), 0) as expense_purchases,
COUNT(DISTINCT e.id) as expense_count
FROM expense_items ei
JOIN expenses e ON ei.expense_id = e.id
JOIN purchase_categories pc ON ei.purchase_category_id = pc.id
@@ -198,25 +198,25 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or
AND pc.type = ?
AND e.status = ?
AND e.transaction_date >= ? AND e.transaction_date <= ?
` + nonInventoryOutletFilter + `
` + expenseOutletFilter + `
)
SELECT
rm.raw_material_purchases + ni.non_inventory_purchases as total_purchases,
rm.raw_material_purchases + ex.expense_purchases as total_purchases,
rm.raw_material_purchases,
ni.non_inventory_purchases,
rm.raw_material_purchase_orders + ni.non_inventory_expense_count as total_purchase_orders,
ex.expense_purchases,
rm.raw_material_purchase_orders + ex.expense_count as total_purchase_orders,
rm.raw_material_purchase_orders,
ni.non_inventory_expense_count,
ex.expense_count,
rm.total_quantity,
CASE
WHEN rm.raw_material_purchase_orders + ni.non_inventory_expense_count > 0
THEN (rm.raw_material_purchases + ni.non_inventory_purchases) / (rm.raw_material_purchase_orders + ni.non_inventory_expense_count)
WHEN rm.raw_material_purchase_orders + ex.expense_count > 0
THEN (rm.raw_material_purchases + ex.expense_purchases) / (rm.raw_material_purchase_orders + ex.expense_count)
ELSE 0
END as average_purchase_order_value,
rm.total_ingredients,
rm.total_vendors
FROM raw_material rm
CROSS JOIN non_inventory ni
CROSS JOIN expense ex
`
if err := r.db.WithContext(ctx).Raw(summaryQuery, summaryArgs...).Scan(&summary).Error; err != nil {
@@ -235,14 +235,14 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or
dateFormat = "DATE_TRUNC('day', im.created_at)::timestamp"
}
nonInventoryDateFormat := "DATE_TRUNC('day', e.transaction_date)::timestamp"
expenseDateFormat := "DATE_TRUNC('day', e.transaction_date)::timestamp"
switch groupBy {
case "hour":
nonInventoryDateFormat = "DATE_TRUNC('hour', e.transaction_date)::timestamp"
expenseDateFormat = "DATE_TRUNC('hour', e.transaction_date)::timestamp"
case "week":
nonInventoryDateFormat = "DATE_TRUNC('week', e.transaction_date)::timestamp"
expenseDateFormat = "DATE_TRUNC('week', e.transaction_date)::timestamp"
case "month":
nonInventoryDateFormat = "DATE_TRUNC('month', e.transaction_date)::timestamp"
expenseDateFormat = "DATE_TRUNC('month', e.transaction_date)::timestamp"
}
rawMaterialDataArgs := []interface{}{
@@ -253,18 +253,18 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or
dateFrom,
dateTo,
}
nonInventoryDataArgs := []interface{}{
expenseDataArgs := []interface{}{
organizationID,
entities.PurchaseCategoryTypeNonInventory,
entities.PurchaseCategoryTypeExpense,
"approved",
dateFrom,
dateTo,
}
if outletID != nil {
rawMaterialDataArgs = append(rawMaterialDataArgs, *outletID)
nonInventoryDataArgs = append(nonInventoryDataArgs, *outletID)
expenseDataArgs = append(expenseDataArgs, *outletID)
}
dataArgs := append(rawMaterialDataArgs, nonInventoryDataArgs...)
dataArgs := append(rawMaterialDataArgs, expenseDataArgs...)
var data []entities.PurchasingAnalyticsData
dataQuery := `
@@ -286,11 +286,11 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or
` + rawMaterialOutletFilter + `
GROUP BY 1
),
non_inventory AS (
expense AS (
SELECT
` + nonInventoryDateFormat + ` as date,
COALESCE(SUM(ei.amount), 0) as non_inventory_purchases,
COUNT(DISTINCT e.id) as non_inventory_expense_count
` + expenseDateFormat + ` as date,
COALESCE(SUM(ei.amount), 0) as expense_purchases,
COUNT(DISTINCT e.id) as expense_count
FROM expense_items ei
JOIN expenses e ON ei.expense_id = e.id
JOIN purchase_categories pc ON ei.purchase_category_id = pc.id
@@ -298,22 +298,22 @@ func (r *AnalyticsRepositoryImpl) GetPurchasingAnalytics(ctx context.Context, or
AND pc.type = ?
AND e.status = ?
AND e.transaction_date >= ? AND e.transaction_date <= ?
` + nonInventoryOutletFilter + `
` + expenseOutletFilter + `
GROUP BY 1
)
SELECT
COALESCE(rm.date, ni.date) as date,
COALESCE(rm.raw_material_purchases, 0) + COALESCE(ni.non_inventory_purchases, 0) as purchases,
COALESCE(rm.date, ex.date) as date,
COALESCE(rm.raw_material_purchases, 0) + COALESCE(ex.expense_purchases, 0) as purchases,
COALESCE(rm.raw_material_purchases, 0) as raw_material_purchases,
COALESCE(ni.non_inventory_purchases, 0) as non_inventory_purchases,
COALESCE(rm.raw_material_purchase_orders, 0) + COALESCE(ni.non_inventory_expense_count, 0) as purchase_orders,
COALESCE(ex.expense_purchases, 0) as expense_purchases,
COALESCE(rm.raw_material_purchase_orders, 0) + COALESCE(ex.expense_count, 0) as purchase_orders,
COALESCE(rm.raw_material_purchase_orders, 0) as raw_material_purchase_orders,
COALESCE(ni.non_inventory_expense_count, 0) as non_inventory_expense_count,
COALESCE(ex.expense_count, 0) as expense_count,
COALESCE(rm.quantity, 0) as quantity,
COALESCE(rm.ingredients, 0) as ingredients,
COALESCE(rm.vendors, 0) as vendors
FROM raw_material rm
FULL OUTER JOIN non_inventory ni ON rm.date = ni.date
FULL OUTER JOIN expense ex ON rm.date = ex.date
ORDER BY date
`
+1 -1
View File
@@ -207,7 +207,7 @@ func (r *ExpenseRepositoryImpl) GetAnalytics(ctx context.Context, organizationID
Joins("JOIN expenses e ON ei.expense_id = e.id").
Joins("JOIN purchase_categories pc ON ei.purchase_category_id = pc.id").
Where("e.organization_id = ?", organizationID).
Where("pc.type = ?", entities.PurchaseCategoryTypeNonInventory).
Where("pc.type = ?", entities.PurchaseCategoryTypeExpense).
Where("e.status = ?", "approved").
Where("e.transaction_date >= ? AND e.transaction_date <= ?", dateFrom, dateTo).
Group("pc.id, pc.name, pc.type").