feat: updat analytic profit loss add purchasing

This commit is contained in:
Efril 2026-06-24 00:11:04 +07:00
parent 793919cf10
commit 9b0fc9a63b
7 changed files with 201 additions and 26 deletions

View File

@ -272,10 +272,28 @@ type ProfitLossAnalyticsResponse struct {
Data []ProfitLossData `json:"data"` Data []ProfitLossData `json:"data"`
ProductData []ProductProfitData `json:"product_data"` ProductData []ProductProfitData `json:"product_data"`
MainSummary []ProfitLossSummaryRow `json:"main_summary"` MainSummary []ProfitLossSummaryRow `json:"main_summary"`
Purchasing ProfitLossPurchasing `json:"purchasing"`
OperationalExpenses []OperationalExpenseItem `json:"operational_expenses"` OperationalExpenses []OperationalExpenseItem `json:"operational_expenses"`
OperationalExpensesTotal float64 `json:"operational_expenses_total"` OperationalExpensesTotal float64 `json:"operational_expenses_total"`
} }
type ProfitLossPurchasing struct {
TodayTotal float64 `json:"today_total"`
MtdTotal float64 `json:"mtd_total"`
TodayRawMaterial float64 `json:"today_raw_material"`
MtdRawMaterial float64 `json:"mtd_raw_material"`
TodayExpense float64 `json:"today_expense"`
MtdExpense float64 `json:"mtd_expense"`
Items []ProfitLossPurchasingItem `json:"items"`
}
type ProfitLossPurchasingItem struct {
Date time.Time `json:"date"`
Item string `json:"item"`
Quantity float64 `json:"quantity"`
Nominal float64 `json:"nominal"`
}
type ProfitLossSummary struct { type ProfitLossSummary struct {
TotalRevenue float64 `json:"total_revenue"` TotalRevenue float64 `json:"total_revenue"`
TotalCost float64 `json:"total_cost"` TotalCost float64 `json:"total_cost"`

View File

@ -126,16 +126,30 @@ type DashboardOverview struct {
} }
type ProfitLossAnalytics struct { type ProfitLossAnalytics struct {
Summary ProfitLossSummary Summary ProfitLossSummary
Data []ProfitLossData Data []ProfitLossData
ProductData []ProductProfitData ProductData []ProductProfitData
TodayRevenue float64 TodayRevenue float64
TodayCost float64 TodayCost float64
MtdRevenue float64 MtdRevenue float64
MtdCost float64 MtdCost float64
TodayExpenseByCategory []ExpenseCategoryTotal TodayPurchasing float64
MtdExpenseByCategory []ExpenseCategoryTotal MtdPurchasing float64
OperationalExpenseItems []OperationalExpenseItem TodayPurchasingRawMaterial float64
MtdPurchasingRawMaterial float64
TodayPurchasingExpense float64
MtdPurchasingExpense float64
PurchasingItems []PurchasingItemDetail
TodayExpenseByCategory []ExpenseCategoryTotal
MtdExpenseByCategory []ExpenseCategoryTotal
OperationalExpenseItems []OperationalExpenseItem
}
type PurchasingItemDetail struct {
Date time.Time
Item string
Quantity float64
Amount float64
} }
type ProfitLossSummary struct { type ProfitLossSummary struct {

View File

@ -282,10 +282,28 @@ type ProfitLossAnalyticsResponse struct {
Data []ProfitLossData `json:"data"` Data []ProfitLossData `json:"data"`
ProductData []ProductProfitData `json:"product_data"` ProductData []ProductProfitData `json:"product_data"`
MainSummary []ProfitLossSummaryRow `json:"main_summary"` MainSummary []ProfitLossSummaryRow `json:"main_summary"`
Purchasing ProfitLossPurchasing `json:"purchasing"`
OperationalExpenses []OperationalExpenseItem `json:"operational_expenses"` OperationalExpenses []OperationalExpenseItem `json:"operational_expenses"`
OperationalExpensesTotal float64 `json:"operational_expenses_total"` OperationalExpensesTotal float64 `json:"operational_expenses_total"`
} }
type ProfitLossPurchasing struct {
TodayTotal float64 `json:"today_total"`
MtdTotal float64 `json:"mtd_total"`
TodayRawMaterial float64 `json:"today_raw_material"`
MtdRawMaterial float64 `json:"mtd_raw_material"`
TodayExpense float64 `json:"today_expense"`
MtdExpense float64 `json:"mtd_expense"`
Items []ProfitLossPurchasingItem `json:"items"`
}
type ProfitLossPurchasingItem struct {
Date time.Time `json:"date"`
Item string `json:"item"`
Quantity float64 `json:"quantity"`
Nominal float64 `json:"nominal"`
}
type ProfitLossSummary struct { type ProfitLossSummary struct {
TotalRevenue float64 `json:"total_revenue"` TotalRevenue float64 `json:"total_revenue"`
TotalCost float64 `json:"total_cost"` TotalCost float64 `json:"total_cost"`

View File

@ -626,6 +626,16 @@ func (p *AnalyticsProcessorImpl) GetProfitLossAnalytics(ctx context.Context, req
opsTotal += item.Amount opsTotal += item.Amount
} }
purchasingItems := make([]models.ProfitLossPurchasingItem, len(result.PurchasingItems))
for i, item := range result.PurchasingItems {
purchasingItems[i] = models.ProfitLossPurchasingItem{
Date: item.Date,
Item: item.Item,
Quantity: item.Quantity,
Nominal: item.Amount,
}
}
return &models.ProfitLossAnalyticsResponse{ return &models.ProfitLossAnalyticsResponse{
OrganizationID: req.OrganizationID, OrganizationID: req.OrganizationID,
OutletID: req.OutletID, OutletID: req.OutletID,
@ -646,9 +656,18 @@ func (p *AnalyticsProcessorImpl) GetProfitLossAnalytics(ctx context.Context, req
AverageProfit: result.Summary.AverageProfit, AverageProfit: result.Summary.AverageProfit,
ProfitabilityRatio: result.Summary.ProfitabilityRatio, ProfitabilityRatio: result.Summary.ProfitabilityRatio,
}, },
Data: data, Data: data,
ProductData: productData, ProductData: productData,
MainSummary: mainSummary, MainSummary: mainSummary,
Purchasing: models.ProfitLossPurchasing{
TodayTotal: result.TodayPurchasing,
MtdTotal: result.MtdPurchasing,
TodayRawMaterial: result.TodayPurchasingRawMaterial,
MtdRawMaterial: result.MtdPurchasingRawMaterial,
TodayExpense: result.TodayPurchasingExpense,
MtdExpense: result.MtdPurchasingExpense,
Items: purchasingItems,
},
OperationalExpenses: opsItems, OperationalExpenses: opsItems,
OperationalExpensesTotal: opsTotal, OperationalExpensesTotal: opsTotal,
}, nil }, nil

View File

@ -68,6 +68,10 @@ func (s *analyticsRepositoryStub) GetExclusiveSummaryBankBalances(context.Contex
return s.bankBalances, nil return s.bankBalances, nil
} }
func (analyticsRepositoryStub) GetOutletName(context.Context, uuid.UUID, uuid.UUID) (string, error) {
return "", nil
}
type expenseRepositoryStub struct{} type expenseRepositoryStub struct{}
func (expenseRepositoryStub) Create(context.Context, *entities.Expense) error { return nil } func (expenseRepositoryStub) Create(context.Context, *entities.Expense) error { return nil }

View File

@ -747,17 +747,38 @@ func (r *AnalyticsRepositoryImpl) GetProfitLossAnalytics(ctx context.Context, or
} }
opsItems = mergeOperationalExpenseItems(opsItems, poOpsItems) opsItems = mergeOperationalExpenseItems(opsItems, poOpsItems)
todayPurchasing, err := r.getPurchaseOrderTotals(ctx, organizationID, todayStart, todayEnd)
if err != nil {
return nil, err
}
mtdPurchasing, err := r.getPurchaseOrderTotals(ctx, organizationID, mtdStart, todayEnd)
if err != nil {
return nil, err
}
purchasingItems, err := r.getPurchasingItemDetails(ctx, organizationID, dateFrom, dateTo)
if err != nil {
return nil, err
}
return &entities.ProfitLossAnalytics{ return &entities.ProfitLossAnalytics{
Summary: summary, Summary: summary,
Data: data, Data: data,
ProductData: productData, ProductData: productData,
TodayRevenue: todayRC.Revenue, TodayRevenue: todayRC.Revenue,
TodayCost: todayRC.Cost, TodayCost: todayRC.Cost,
MtdRevenue: mtdRC.Revenue, MtdRevenue: mtdRC.Revenue,
MtdCost: mtdRC.Cost, MtdCost: mtdRC.Cost,
TodayExpenseByCategory: todayExpenseByCategory, TodayPurchasing: todayPurchasing.Total,
MtdExpenseByCategory: mtdExpenseByCategory, MtdPurchasing: mtdPurchasing.Total,
OperationalExpenseItems: opsItems, TodayPurchasingRawMaterial: todayPurchasing.RawMaterial,
MtdPurchasingRawMaterial: mtdPurchasing.RawMaterial,
TodayPurchasingExpense: todayPurchasing.Expense,
MtdPurchasingExpense: mtdPurchasing.Expense,
PurchasingItems: purchasingItems,
TodayExpenseByCategory: todayExpenseByCategory,
MtdExpenseByCategory: mtdExpenseByCategory,
OperationalExpenseItems: opsItems,
}, nil }, nil
} }
@ -784,6 +805,68 @@ func (r *AnalyticsRepositoryImpl) getPurchaseOrderRawMaterialTotal(ctx context.C
return result.Total, nil return result.Total, nil
} }
type purchasingTotals struct {
Total float64
RawMaterial float64
Expense float64
}
func (r *AnalyticsRepositoryImpl) getPurchaseOrderTotals(ctx context.Context, organizationID uuid.UUID, dateFrom, dateTo time.Time) (purchasingTotals, error) {
type result struct {
Total float64
RawMaterial float64
Expense float64
}
var res result
query := r.db.WithContext(ctx).
Table("purchase_order_items poi").
Select(`
COALESCE(SUM(`+purchaseOrderItemTotalAmountSQL()+`), 0) as total,
COALESCE(SUM(`+purchaseOrderRawMaterialAmountSQL()+`), 0) as raw_material,
COALESCE(SUM(`+purchaseOrderExpenseAmountSQL()+`), 0) as expense
`).
Joins("JOIN purchase_orders po ON poi.purchase_order_id = po.id").
Joins("JOIN purchase_categories pc ON poi.purchase_category_id = pc.id").
Where("po.organization_id = ?", organizationID).
Where("po.status = ?", "received").
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo)
if err := query.Scan(&res).Error; err != nil {
return purchasingTotals{}, err
}
return purchasingTotals{
Total: res.Total,
RawMaterial: res.RawMaterial,
Expense: res.Expense,
}, nil
}
func (r *AnalyticsRepositoryImpl) getPurchasingItemDetails(ctx context.Context, organizationID uuid.UUID, dateFrom, dateTo time.Time) ([]entities.PurchasingItemDetail, error) {
var results []entities.PurchasingItemDetail
query := r.db.WithContext(ctx).
Table("purchase_order_items poi").
Select(`
po.transaction_date as date,
COALESCE(NULLIF(poi.description, ''), i.name, pc.name) as item,
COALESCE(poi.quantity, 0) as quantity,
CASE WHEN pc.type = '`+string(entities.PurchaseCategoryTypeRawMaterial)+`' THEN COALESCE(poi.quantity, 0) * poi.amount ELSE poi.amount END as amount
`).
Joins("JOIN purchase_orders po ON poi.purchase_order_id = po.id").
Joins("LEFT JOIN purchase_categories pc ON poi.purchase_category_id = pc.id").
Joins("LEFT JOIN ingredients i ON poi.ingredient_id = i.id").
Where("po.organization_id = ?", organizationID).
Where("po.status = ?", "received").
Where("po.transaction_date >= ? AND po.transaction_date <= ?", dateFrom, dateTo).
Order("po.transaction_date DESC, poi.created_at DESC")
if err := query.Scan(&results).Error; err != nil {
return nil, err
}
return results, nil
}
func (r *AnalyticsRepositoryImpl) getPurchaseOrderRawMaterialCostByPeriod(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) ([]entities.ProfitLossData, error) { func (r *AnalyticsRepositoryImpl) getPurchaseOrderRawMaterialCostByPeriod(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) ([]entities.ProfitLossData, error) {
var dateFormat string var dateFormat string
switch groupBy { switch groupBy {

View File

@ -524,6 +524,16 @@ func ProfitLossAnalyticsModelToContract(resp *models.ProfitLossAnalyticsResponse
} }
} }
purchasingItems := make([]contract.ProfitLossPurchasingItem, len(resp.Purchasing.Items))
for i, item := range resp.Purchasing.Items {
purchasingItems[i] = contract.ProfitLossPurchasingItem{
Date: item.Date,
Item: item.Item,
Quantity: item.Quantity,
Nominal: item.Nominal,
}
}
return &contract.ProfitLossAnalyticsResponse{ return &contract.ProfitLossAnalyticsResponse{
OrganizationID: resp.OrganizationID, OrganizationID: resp.OrganizationID,
OutletID: resp.OutletID, OutletID: resp.OutletID,
@ -544,9 +554,18 @@ func ProfitLossAnalyticsModelToContract(resp *models.ProfitLossAnalyticsResponse
AverageProfit: resp.Summary.AverageProfit, AverageProfit: resp.Summary.AverageProfit,
ProfitabilityRatio: resp.Summary.ProfitabilityRatio, ProfitabilityRatio: resp.Summary.ProfitabilityRatio,
}, },
Data: data, Data: data,
ProductData: productData, ProductData: productData,
MainSummary: mainSummary, MainSummary: mainSummary,
Purchasing: contract.ProfitLossPurchasing{
TodayTotal: resp.Purchasing.TodayTotal,
MtdTotal: resp.Purchasing.MtdTotal,
TodayRawMaterial: resp.Purchasing.TodayRawMaterial,
MtdRawMaterial: resp.Purchasing.MtdRawMaterial,
TodayExpense: resp.Purchasing.TodayExpense,
MtdExpense: resp.Purchasing.MtdExpense,
Items: purchasingItems,
},
OperationalExpenses: opsItems, OperationalExpenses: opsItems,
OperationalExpensesTotal: resp.OperationalExpensesTotal, OperationalExpensesTotal: resp.OperationalExpensesTotal,
} }