Revert "Revert purchase order"

This reverts commit 657a201fc0.
This commit is contained in:
2026-06-17 18:31:10 +07:00
parent 6c19876a47
commit 2921631ac3
23 changed files with 177 additions and 1527 deletions
+23 -263
View File
@@ -18,7 +18,6 @@ type AnalyticsRepository interface {
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)
GetProfitLossAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) (*entities.ProfitLossAnalytics, error)
GetExclusiveSummaryAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) (*entities.ExclusiveSummaryAnalytics, error)
}
type AnalyticsRepositoryImpl struct {
@@ -153,11 +152,7 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
Table("purchase_orders po").
Select(`
COALESCE(SUM(poi.amount), 0) as total_purchases,
COALESCE(SUM(poi.amount), 0) as raw_material_purchases,
0 as expense_purchases,
COUNT(DISTINCT po.id) as total_purchase_orders,
COUNT(DISTINCT po.id) as raw_material_purchase_orders,
0 as expense_count,
COALESCE(SUM(poi.quantity), 0) as total_quantity,
CASE
WHEN COUNT(DISTINCT po.id) > 0
@@ -167,13 +162,11 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
COUNT(DISTINCT i.id) as total_ingredients,
COUNT(DISTINCT po.vendor_id) as total_vendors
`).
Joins("JOIN purchase_order_items poi ON poi.purchase_order_id = po.id").
Joins("JOIN purchase_categories pc ON poi.purchase_category_id = pc.id").
Joins("JOIN ingredients i ON poi.ingredient_id = i.id").
Joins("JOIN units u ON poi.unit_id = u.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("pc.type = ?", entities.PurchaseCategoryTypeRawMaterial).
Where("po.status = ?", "received").
Where("po.status != ?", "cancelled").
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo)
summaryQuery = r.applyPurchaseOrderItemOutletFilter(summaryQuery, outletID)
@@ -199,22 +192,16 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
Select(`
`+dateFormat+` as date,
COALESCE(SUM(poi.amount), 0) as purchases,
COALESCE(SUM(poi.amount), 0) as raw_material_purchases,
0 as expense_purchases,
COUNT(DISTINCT po.id) as purchase_orders,
COUNT(DISTINCT po.id) as raw_material_purchase_orders,
0 as expense_count,
COALESCE(SUM(poi.quantity), 0) as quantity,
COUNT(DISTINCT i.id) as ingredients,
COUNT(DISTINCT po.vendor_id) as vendors
`).
Joins("JOIN purchase_order_items poi ON poi.purchase_order_id = po.id").
Joins("JOIN purchase_categories pc ON poi.purchase_category_id = pc.id").
Joins("JOIN ingredients i ON poi.ingredient_id = i.id").
Joins("JOIN units u ON poi.unit_id = u.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("pc.type = ?", entities.PurchaseCategoryTypeRawMaterial).
Where("po.status = ?", "received").
Where("po.status != ?", "cancelled").
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo).
Group(dateFormat).
Order(dateFormat)
@@ -240,12 +227,10 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
COUNT(DISTINCT po.id) as purchase_order_count
`).
Joins("JOIN purchase_orders po ON poi.purchase_order_id = po.id").
Joins("JOIN purchase_categories pc ON poi.purchase_category_id = pc.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("pc.type = ?", entities.PurchaseCategoryTypeRawMaterial).
Where("po.status = ?", "received").
Where("po.status != ?", "cancelled").
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo).
Group("i.id, i.name").
Order("total_cost DESC")
@@ -267,13 +252,11 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderPurchasingAnalytics(ctx contex
COALESCE(SUM(poi.quantity), 0) as quantity
`).
Joins("JOIN vendors v ON po.vendor_id = v.id").
Joins("JOIN purchase_order_items poi ON poi.purchase_order_id = po.id").
Joins("JOIN purchase_categories pc ON poi.purchase_category_id = pc.id").
Joins("JOIN ingredients i ON poi.ingredient_id = i.id").
Joins("JOIN units u ON poi.unit_id = u.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("pc.type = ?", entities.PurchaseCategoryTypeRawMaterial).
Where("po.status = ?", "received").
Where("po.status != ?", "cancelled").
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo).
Group("v.id, v.name").
Order("total_cost DESC")
@@ -296,15 +279,7 @@ func (r *AnalyticsRepositoryImpl) applyPurchaseOrderItemOutletFilter(query *gorm
if outletID == nil {
return query
}
return query.Where(`
EXISTS (
SELECT 1
FROM inventory_movements im
WHERE im.purchase_order_item_id = poi.id
AND im.movement_type = ?
AND im.outlet_id = ?
)
`, entities.InventoryMovementTypePurchase, *outletID)
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) {
@@ -315,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,
@@ -374,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
@@ -669,11 +643,11 @@ func (r *AnalyticsRepositoryImpl) getExpenseByCategory(ctx context.Context, orga
query := r.db.WithContext(ctx).
Table("expense_items ei").
Select(`pc.name 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 purchase_categories pc ON ei.purchase_category_id = pc.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").
Where("e.organization_id = ?", organizationID).
Where("pc.type = ?", entities.PurchaseCategoryTypeExpense).
Where("e.status = ?", "approved").
Where("e.transaction_date >= ? AND e.transaction_date <= ?", dateFrom, dateTo)
@@ -682,8 +656,8 @@ func (r *AnalyticsRepositoryImpl) getExpenseByCategory(ctx context.Context, orga
}
err := query.
Group("pc.id, pc.name, pc.sort_order").
Order("pc.sort_order ASC, pc.name ASC").
Group("COALESCE(parent_coa.name, coa.name, 'Lain-lain')").
Order("COALESCE(parent_coa.name, coa.name, 'Lain-lain')").
Scan(&results).Error
return results, err
@@ -694,11 +668,10 @@ func (r *AnalyticsRepositoryImpl) getOperationalExpenseItems(ctx context.Context
query := r.db.WithContext(ctx).
Table("expense_items ei").
Select(`COALESCE(NULLIF(ei.item, ''), ei.description, pc.name) as item, COALESCE(SUM(ei.amount), 0) as amount`).
Select(`COALESCE(NULLIF(ei.item, ''), ei.description, coa.name) as item, COALESCE(SUM(ei.amount), 0) as amount`).
Joins("JOIN expenses e ON ei.expense_id = e.id").
Joins("JOIN purchase_categories pc ON ei.purchase_category_id = pc.id").
Joins("JOIN chart_of_accounts coa ON ei.chart_of_account_id = coa.id").
Where("e.organization_id = ?", organizationID).
Where("pc.type = ?", entities.PurchaseCategoryTypeExpense).
Where("e.status = ?", "approved").
Where("e.transaction_date >= ? AND e.transaction_date <= ?", dateFrom, dateTo)
@@ -707,222 +680,9 @@ func (r *AnalyticsRepositoryImpl) getOperationalExpenseItems(ctx context.Context
}
err := query.
Group("COALESCE(NULLIF(ei.item, ''), ei.description, pc.name)").
Group("COALESCE(NULLIF(ei.item, ''), ei.description, coa.name)").
Order("amount DESC").
Scan(&results).Error
return results, err
}
func (r *AnalyticsRepositoryImpl) GetExclusiveSummaryAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) (*entities.ExclusiveSummaryAnalytics, error) {
type salesResult struct {
SalesTotal float64
SalesCount int64
}
var sales salesResult
salesQuery := r.db.WithContext(ctx).
Table("orders o").
Select(`
COALESCE(SUM(o.total_amount), 0) as sales_total,
COUNT(o.id) as sales_count
`).
Where("o.organization_id = ?", organizationID).
Where("o.status = ?", entities.OrderStatusCompleted).
Where("o.payment_status = ?", entities.PaymentStatusCompleted).
Where("o.is_void = false AND o.is_refund = false").
Where("o.created_at >= ? AND o.created_at <= ?", dateFrom, dateTo)
salesQuery = r.resolveOutletID(salesQuery, outletID, "o.outlet_id")
if err := salesQuery.Scan(&sales).Error; err != nil {
return nil, err
}
hppBreakdown, err := r.getExclusiveSummaryHPPBreakdown(ctx, organizationID, outletID, dateFrom, dateTo)
if err != nil {
return nil, err
}
operationalExpenseBreakdown, err := r.getExclusiveSummaryOperationalExpenseBreakdown(ctx, organizationID, outletID, dateFrom, dateTo)
if err != nil {
return nil, err
}
dailySummary, err := r.getExclusiveSummaryDailySummary(ctx, organizationID, outletID, dateFrom, dateTo)
if err != nil {
return nil, err
}
dailyTransactions, err := r.getExclusiveSummaryDailyTransactions(ctx, organizationID, outletID, dateFrom, dateTo)
if err != nil {
return nil, err
}
return &entities.ExclusiveSummaryAnalytics{
SalesTotal: sales.SalesTotal,
SalesCount: sales.SalesCount,
HPPBreakdown: hppBreakdown,
OperationalExpenseBreakdown: operationalExpenseBreakdown,
DailySummary: dailySummary,
DailyTransactions: dailyTransactions,
}, nil
}
func (r *AnalyticsRepositoryImpl) getExclusiveSummaryHPPBreakdown(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]entities.ExclusiveSummaryCategoryTotal, error) {
var results []entities.ExclusiveSummaryCategoryTotal
query := r.db.WithContext(ctx).
Table("purchase_order_items poi").
Select(`
pc.code as category_code,
pc.name as category_name,
COALESCE(SUM(poi.amount), 0) as amount
`).
Joins("JOIN purchase_orders po ON poi.purchase_order_id = po.id").
Joins("JOIN purchase_categories pc ON poi.purchase_category_id = pc.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("pc.type = ?", entities.PurchaseCategoryTypeRawMaterial).
Where("po.status = ?", "received").
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo)
query = r.applyPurchaseOrderItemOutletFilter(query, outletID)
err := query.
Group("pc.id, pc.code, pc.name, pc.sort_order").
Order("pc.sort_order ASC, pc.name ASC").
Scan(&results).Error
return results, err
}
func (r *AnalyticsRepositoryImpl) getExclusiveSummaryOperationalExpenseBreakdown(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]entities.ExclusiveSummaryCategoryTotal, error) {
var results []entities.ExclusiveSummaryCategoryTotal
query := r.db.WithContext(ctx).
Table("expense_items ei").
Select(`
pc.code as category_code,
pc.name as category_name,
COALESCE(SUM(ei.amount), 0) as amount
`).
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.PurchaseCategoryTypeExpense).
Where("e.status = ?", "approved").
Where("e.transaction_date >= ? AND e.transaction_date <= ?", dateFrom, dateTo)
if outletID != nil {
query = query.Where("e.outlet_id = ?", *outletID)
}
err := query.
Group("pc.id, pc.code, pc.name, pc.sort_order").
Order("pc.sort_order ASC, pc.name ASC").
Scan(&results).Error
return results, err
}
func (r *AnalyticsRepositoryImpl) getExclusiveSummaryDailySummary(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]entities.ExclusiveSummaryDailySummary, error) {
var results []entities.ExclusiveSummaryDailySummary
rawQuery, args := r.exclusiveSummaryTransactionUnionQuery(organizationID, outletID, dateFrom, dateTo)
err := r.db.WithContext(ctx).Raw(`
SELECT date, COUNT(*) as transaction_count, COALESCE(SUM(amount), 0) as total_cost
FROM (`+rawQuery+`) transactions
GROUP BY date
ORDER BY date ASC
`, args...).Scan(&results).Error
return results, err
}
func (r *AnalyticsRepositoryImpl) getExclusiveSummaryDailyTransactions(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) ([]entities.ExclusiveSummaryDailyTransaction, error) {
var results []entities.ExclusiveSummaryDailyTransaction
rawQuery, args := r.exclusiveSummaryTransactionUnionQuery(organizationID, outletID, dateFrom, dateTo)
err := r.db.WithContext(ctx).Raw(`
SELECT date, category_code, category_name, description, amount, source
FROM (`+rawQuery+`) transactions
ORDER BY date ASC, source ASC, category_name ASC, description ASC
`, args...).Scan(&results).Error
return results, err
}
func (r *AnalyticsRepositoryImpl) exclusiveSummaryTransactionUnionQuery(organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) (string, []interface{}) {
poOutletFilter := ""
expenseOutletFilter := ""
args := []interface{}{
organizationID,
entities.PurchaseCategoryTypeRawMaterial,
"received",
dateFrom,
dateTo,
}
if outletID != nil {
poOutletFilter = `AND EXISTS (
SELECT 1
FROM inventory_movements im
WHERE im.purchase_order_item_id = poi.id
AND im.movement_type = 'purchase'
AND im.outlet_id = ?
)`
args = append(args, *outletID)
}
args = append(args,
organizationID,
entities.PurchaseCategoryTypeExpense,
"approved",
dateFrom,
dateTo,
)
if outletID != nil {
expenseOutletFilter = "AND e.outlet_id = ?"
args = append(args, *outletID)
}
query := `
SELECT
DATE(po.transaction_date) as date,
pc.code as category_code,
pc.name as category_name,
COALESCE(NULLIF(poi.description, ''), i.name, pc.name) as description,
poi.amount as amount,
'purchase_order' as source
FROM purchase_order_items poi
JOIN purchase_orders po ON poi.purchase_order_id = po.id
JOIN purchase_categories pc ON poi.purchase_category_id = pc.id
JOIN ingredients i ON poi.ingredient_id = i.id
LEFT JOIN units u ON poi.unit_id = u.id
WHERE po.organization_id = ?
AND pc.type = ?
AND po.status = ?
AND po.transaction_date >= ? AND po.transaction_date <= ?
` + poOutletFilter + `
UNION ALL
SELECT
DATE(e.transaction_date) as date,
pc.code as category_code,
pc.name as category_name,
COALESCE(NULLIF(ei.item, ''), NULLIF(ei.description, ''), pc.name) as description,
ei.amount as amount,
'expense' as source
FROM expense_items ei
JOIN expenses e ON ei.expense_id = e.id
JOIN purchase_categories pc ON ei.purchase_category_id = pc.id
WHERE e.organization_id = ?
AND pc.type = ?
AND e.status = ?
AND e.transaction_date >= ? AND e.transaction_date <= ?
` + expenseOutletFilter + `
`
return query, args
}