feat[BE]: add overhead and ekspedisi items to profit loss report; include total depletion in closing report calculation

This commit is contained in:
aguhh18
2025-12-18 14:49:48 +07:00
parent 096a446450
commit f2df7f4847
2 changed files with 57 additions and 29 deletions
@@ -60,6 +60,8 @@ type PLSummaryGroup struct {
type ProfitLossData struct { type ProfitLossData struct {
Penjualan []PLItem `json:"penjualan"` Penjualan []PLItem `json:"penjualan"`
Pembelian []PLItem `json:"pembelian"` Pembelian []PLItem `json:"pembelian"`
Overhead PLItem `json:"overhead"`
Ekspedisi PLItem `json:"ekspedisi"`
Summary PLSummaryGroup `json:"summary"` Summary PLSummaryGroup `json:"summary"`
} }
@@ -167,7 +169,6 @@ func ToHppBahanBakuGroup(budgets []entities.ProjectBudget, realizations []entiti
ekspedisiAmount := sumRealizationsByFilter(realizations, filterRealizationByNonstockFlag(utils.FlagEkspedisi)) ekspedisiAmount := sumRealizationsByFilter(realizations, filterRealizationByNonstockFlag(utils.FlagEkspedisi))
ekspedisiRpPerBird, ekspedisiRpPerKg := calculatePerUnitMetrics(ekspedisiAmount, totalPopulation, totalWeightProduced) ekspedisiRpPerBird, ekspedisiRpPerKg := calculatePerUnitMetrics(ekspedisiAmount, totalPopulation, totalWeightProduced)
if ekspedisiAmount > 0 {
items = append(items, HppItem{ items = append(items, HppItem{
Type: "Beban Ekspedisi", Type: "Beban Ekspedisi",
Comparison: ToComparison( Comparison: ToComparison(
@@ -175,7 +176,6 @@ func ToHppBahanBakuGroup(budgets []entities.ProjectBudget, realizations []entiti
ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount), // Same as realization ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount), // Same as realization
), ),
}) })
}
return HppGroup{ return HppGroup{
GroupName: "HPP dan Bahan Baku", GroupName: "HPP dan Bahan Baku",
@@ -248,19 +248,28 @@ func sumPLItems(items []PLItem) (totalAmount, totalPerBird float64) {
} }
func ToPenjualanItems(projectFlockCategory string, deliveryProducts []entities.MarketingDeliveryProduct, totalPopulation, totalWeightSold float64) []PLItem { func ToPenjualanItems(projectFlockCategory string, deliveryProducts []entities.MarketingDeliveryProduct, totalPopulation, totalWeightSold float64) []PLItem {
items := []PLItem{}
// Categorize deliveries by sales type based on Product flags // Categorize deliveries by sales type based on Product flags
categorized := categorizeDeliveriesBySalesType(deliveryProducts) categorized := categorizeDeliveriesBySalesType(deliveryProducts)
items := []PLItem{} if projectFlockCategory == string(utils.ProjectFlockCategoryLaying) {
// For LAYING: show both Penjualan Ayam Besar and Penjualan Telur (even if 0)
ayamAmount := sumDeliveriesByCategory(categorized["Penjualan Ayam Besar"])
telurAmount := sumDeliveriesByCategory(categorized["Penjualan Telur"])
// Process each sales category // Penjualan Ayam Besar
for salesType, deliveries := range categorized { rpPerBird, rpPerKg := calculatePerUnitMetrics(ayamAmount, totalPopulation, totalWeightSold)
amount := sumDeliveriesByCategory(deliveries) items = append(items, ToPLItem("Penjualan Ayam Besar", ToFinancialMetrics(rpPerBird, rpPerKg, ayamAmount)))
// Use totalPopulation and totalWeightSold for per-unit calculations // Penjualan Telur
rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightSold) rpPerBird, rpPerKg = calculatePerUnitMetrics(telurAmount, totalPopulation, totalWeightSold)
items = append(items, ToPLItem("Penjualan Telur", ToFinancialMetrics(rpPerBird, rpPerKg, telurAmount)))
items = append(items, ToPLItem(salesType, ToFinancialMetrics(rpPerBird, rpPerKg, amount))) } else {
// For GROWING: show only Penjualan Ayam Besar
ayamAmount := sumDeliveriesByCategory(categorized["Penjualan Ayam Besar"])
rpPerBird, rpPerKg := calculatePerUnitMetrics(ayamAmount, totalPopulation, totalWeightSold)
items = append(items, ToPLItem("Penjualan Ayam Besar", ToFinancialMetrics(rpPerBird, rpPerKg, ayamAmount)))
} }
return items return items
@@ -278,7 +287,7 @@ func ToPembelianItems(purchases []entities.PurchaseItem, budgets []entities.Proj
rpPerBird, rpPerKg := calculatePerUnitMetrics(totalCost, totalPopulation, totalWeightProduced) rpPerBird, rpPerKg := calculatePerUnitMetrics(totalCost, totalPopulation, totalWeightProduced)
return []PLItem{ return []PLItem{
ToPLItem("Harga Pokok Penjualan (HPP)", ToFinancialMetrics(rpPerBird, rpPerKg, totalCost)), ToPLItem("Pembelian Sapronak", ToFinancialMetrics(rpPerBird, rpPerKg, totalCost)),
} }
} }
@@ -301,20 +310,21 @@ func ToEkspedisiItems(realizations []entities.ExpenseRealization, totalPopulatio
func ToPLSummaryGroup(penjualanItems, pembelianItems, overheadItems, ekspedisiItems []PLItem) PLSummaryGroup { func ToPLSummaryGroup(penjualanItems, pembelianItems, overheadItems, ekspedisiItems []PLItem) PLSummaryGroup {
totalPenjualan, totalPenjualanPerBird := sumPLItems(penjualanItems) totalPenjualan, totalPenjualanPerBird := sumPLItems(penjualanItems)
totalPembelian, totalPembelianPerBird := sumPLItems(pembelianItems) totalPembelian, totalPembelianPerBird := sumPLItems(pembelianItems)
totalOverhead, _ := sumPLItems(overheadItems) totalOverhead, totalOverheadPerBird := sumPLItems(overheadItems)
totalEkspedisi, _ := sumPLItems(ekspedisiItems) totalEkspedisi, totalEkspedisiPerBird := sumPLItems(ekspedisiItems)
grossProfit := totalPenjualan - totalPembelian grossProfit := totalPenjualan - totalPembelian
grossProfitPerBird := totalPenjualanPerBird - totalPembelianPerBird grossProfitPerBird := totalPenjualanPerBird - totalPembelianPerBird
totalOtherExpenses := totalOverhead + totalEkspedisi totalOtherExpenses := totalOverhead + totalEkspedisi
totalOtherExpensesPerBird := totalOverheadPerBird + totalEkspedisiPerBird
netProfit := grossProfit - totalOtherExpenses netProfit := grossProfit - totalOtherExpenses
netProfitPerBird := grossProfitPerBird - 0.0 netProfitPerBird := grossProfitPerBird - totalOtherExpensesPerBird
return PLSummaryGroup{ return PLSummaryGroup{
GrossProfit: ToPLSummaryItem("LABA RUGI BRUTTO", ToFinancialMetrics(grossProfitPerBird, 0, grossProfit)), GrossProfit: ToPLSummaryItem("LABA RUGI BRUTTO", ToFinancialMetrics(grossProfitPerBird, 0, grossProfit)),
SubTotal: ToPLSummaryItem("SUB TOTAL", ToFinancialMetrics(0, 0, totalOtherExpenses)), SubTotal: ToPLSummaryItem("SUB TOTAL", ToFinancialMetrics(totalOtherExpensesPerBird, 0, totalOtherExpenses)),
NetProfit: ToPLSummaryItem("LABA RUGI NETTO", ToFinancialMetrics(netProfitPerBird, 0, netProfit)), NetProfit: ToPLSummaryItem("LABA RUGI NETTO", ToFinancialMetrics(netProfitPerBird, 0, netProfit)),
} }
} }
@@ -322,9 +332,15 @@ func ToPLSummaryGroup(penjualanItems, pembelianItems, overheadItems, ekspedisiIt
func ToProfitLossData(penjualanItems, pembelianItems, overheadItems, ekspedisiItems []PLItem) ProfitLossData { func ToProfitLossData(penjualanItems, pembelianItems, overheadItems, ekspedisiItems []PLItem) ProfitLossData {
summary := ToPLSummaryGroup(penjualanItems, pembelianItems, overheadItems, ekspedisiItems) summary := ToPLSummaryGroup(penjualanItems, pembelianItems, overheadItems, ekspedisiItems)
// Get total overhead and ekspedisi as single items
totalOverhead := aggregatePLItems(overheadItems, "Pengeluaran Overhead")
totalEkspedisi := aggregatePLItems(ekspedisiItems, "Beban Ekspedisi")
return ProfitLossData{ return ProfitLossData{
Penjualan: penjualanItems, Penjualan: penjualanItems,
Pembelian: pembelianItems, Pembelian: pembelianItems,
Overhead: totalOverhead,
Ekspedisi: totalEkspedisi,
Summary: summary, Summary: summary,
} }
} }
@@ -335,6 +351,11 @@ func ToProfitLossSection(penjualanItems, pembelianItems, overheadItems, ekspedis
} }
} }
func aggregatePLItems(items []PLItem, label string) PLItem {
totalAmount, totalPerBird := sumPLItems(items)
return ToPLItem(label, ToFinancialMetrics(totalPerBird, 0, totalAmount))
}
func ToReportResponse(hppPurchases HppPurchasesSection, profitLoss ProfitLossSection) ReportResponse { func ToReportResponse(hppPurchases HppPurchasesSection, profitLoss ProfitLossSection) ReportResponse {
return ReportResponse{ return ReportResponse{
HppPurchases: hppPurchases, HppPurchases: hppPurchases,
@@ -342,9 +363,7 @@ func ToReportResponse(hppPurchases HppPurchasesSection, profitLoss ProfitLossSec
} }
} }
// === MAIN BUILDER === func ToClosingKeuanganReport(projectFlockCategory string, purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, deliveryProducts []entities.MarketingDeliveryProduct, chickins []entities.ProjectChickin, totalWeightProduced, totalDepletion float64) ReportResponse {
func ToClosingKeuanganReport(projectFlockCategory string, purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, deliveryProducts []entities.MarketingDeliveryProduct, chickins []entities.ProjectChickin, totalWeightProduced float64) ReportResponse {
var totalPopulation float64 var totalPopulation float64
var totalWeightSold float64 var totalWeightSold float64
@@ -356,13 +375,16 @@ func ToClosingKeuanganReport(projectFlockCategory string, purchaseItems []entiti
totalWeightSold += delivery.TotalWeight totalWeightSold += delivery.TotalWeight
} }
// Calculate actual population (chickin - depletion) for cost allocation
actualPopulation := totalPopulation - totalDepletion
// Use totalWeightProduced for HPP calculation (not totalWeightSold) // Use totalWeightProduced for HPP calculation (not totalWeightSold)
hppSection := ToHppPurchasesSection(purchaseItems, budgets, realizations, totalWeightProduced, totalPopulation) hppSection := ToHppPurchasesSection(purchaseItems, budgets, realizations, totalWeightProduced, totalPopulation)
penjualanItems := ToPenjualanItems(projectFlockCategory, deliveryProducts, totalPopulation, totalWeightSold) penjualanItems := ToPenjualanItems(projectFlockCategory, deliveryProducts, totalPopulation, totalWeightSold)
pembelianItems := ToPembelianItems(purchaseItems, budgets, realizations, totalPopulation, totalWeightProduced) pembelianItems := ToPembelianItems(purchaseItems, budgets, realizations, actualPopulation, totalWeightProduced)
overheadItems := ToOverheadItems(budgets, realizations, totalPopulation, totalWeightProduced) overheadItems := ToOverheadItems(budgets, realizations, actualPopulation, totalWeightProduced)
ekspedisiItems := ToEkspedisiItems(realizations, totalPopulation, totalWeightProduced) ekspedisiItems := ToEkspedisiItems(realizations, actualPopulation, totalWeightProduced)
plSection := ToProfitLossSection(penjualanItems, pembelianItems, overheadItems, ekspedisiItems) plSection := ToProfitLossSection(penjualanItems, pembelianItems, overheadItems, ekspedisiItems)
return ToReportResponse(hppSection, plSection) return ToReportResponse(hppSection, plSection)
@@ -450,7 +450,13 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*
s.Log.Warnf("GetProductionWeightAndQtyByProjectFlockID error: %v", err) s.Log.Warnf("GetProductionWeightAndQtyByProjectFlockID error: %v", err)
} }
report := dto.ToClosingKeuanganReport(projectFlock.Category, purchaseItems, budgets, realizations, deliveryProducts, chickins, totalWeightProduced) // Fetch depletion data to calculate actual population for cost allocation
totalDepletion, err := s.RecordingRepo.GetTotalDepletionByProjectFlockID(c.Context(), projectFlockID)
if err != nil {
s.Log.Warnf("GetTotalDepletionByProjectFlockID error: %v", err)
}
report := dto.ToClosingKeuanganReport(projectFlock.Category, purchaseItems, budgets, realizations, deliveryProducts, chickins, totalWeightProduced, totalDepletion)
return &report, nil return &report, nil
} }