diff --git a/internal/modules/closings/dto/closingKeuangan.dto.go b/internal/modules/closings/dto/closingKeuangan.dto.go index 13e7c196..978c0b60 100644 --- a/internal/modules/closings/dto/closingKeuangan.dto.go +++ b/internal/modules/closings/dto/closingKeuangan.dto.go @@ -60,6 +60,8 @@ type PLSummaryGroup struct { type ProfitLossData struct { Penjualan []PLItem `json:"penjualan"` Pembelian []PLItem `json:"pembelian"` + Overhead PLItem `json:"overhead"` + Ekspedisi PLItem `json:"ekspedisi"` Summary PLSummaryGroup `json:"summary"` } @@ -167,15 +169,13 @@ func ToHppBahanBakuGroup(budgets []entities.ProjectBudget, realizations []entiti ekspedisiAmount := sumRealizationsByFilter(realizations, filterRealizationByNonstockFlag(utils.FlagEkspedisi)) ekspedisiRpPerBird, ekspedisiRpPerKg := calculatePerUnitMetrics(ekspedisiAmount, totalPopulation, totalWeightProduced) - if ekspedisiAmount > 0 { - items = append(items, HppItem{ - Type: "Beban Ekspedisi", - Comparison: ToComparison( - ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount), - ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount), // Same as realization - ), - }) - } + items = append(items, HppItem{ + Type: "Beban Ekspedisi", + Comparison: ToComparison( + ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount), + ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount), // Same as realization + ), + }) return HppGroup{ 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 { + items := []PLItem{} + // Categorize deliveries by sales type based on Product flags 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 - for salesType, deliveries := range categorized { - amount := sumDeliveriesByCategory(deliveries) + // Penjualan Ayam Besar + rpPerBird, rpPerKg := calculatePerUnitMetrics(ayamAmount, totalPopulation, totalWeightSold) + items = append(items, ToPLItem("Penjualan Ayam Besar", ToFinancialMetrics(rpPerBird, rpPerKg, ayamAmount))) - // Use totalPopulation and totalWeightSold for per-unit calculations - rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightSold) - - items = append(items, ToPLItem(salesType, ToFinancialMetrics(rpPerBird, rpPerKg, amount))) + // Penjualan Telur + rpPerBird, rpPerKg = calculatePerUnitMetrics(telurAmount, totalPopulation, totalWeightSold) + items = append(items, ToPLItem("Penjualan Telur", ToFinancialMetrics(rpPerBird, rpPerKg, telurAmount))) + } 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 @@ -278,7 +287,7 @@ func ToPembelianItems(purchases []entities.PurchaseItem, budgets []entities.Proj rpPerBird, rpPerKg := calculatePerUnitMetrics(totalCost, totalPopulation, totalWeightProduced) 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 { totalPenjualan, totalPenjualanPerBird := sumPLItems(penjualanItems) totalPembelian, totalPembelianPerBird := sumPLItems(pembelianItems) - totalOverhead, _ := sumPLItems(overheadItems) - totalEkspedisi, _ := sumPLItems(ekspedisiItems) + totalOverhead, totalOverheadPerBird := sumPLItems(overheadItems) + totalEkspedisi, totalEkspedisiPerBird := sumPLItems(ekspedisiItems) grossProfit := totalPenjualan - totalPembelian grossProfitPerBird := totalPenjualanPerBird - totalPembelianPerBird totalOtherExpenses := totalOverhead + totalEkspedisi + totalOtherExpensesPerBird := totalOverheadPerBird + totalEkspedisiPerBird netProfit := grossProfit - totalOtherExpenses - netProfitPerBird := grossProfitPerBird - 0.0 + netProfitPerBird := grossProfitPerBird - totalOtherExpensesPerBird return PLSummaryGroup{ 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)), } } @@ -322,9 +332,15 @@ func ToPLSummaryGroup(penjualanItems, pembelianItems, overheadItems, ekspedisiIt func ToProfitLossData(penjualanItems, pembelianItems, overheadItems, ekspedisiItems []PLItem) ProfitLossData { 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{ Penjualan: penjualanItems, Pembelian: pembelianItems, + Overhead: totalOverhead, + Ekspedisi: totalEkspedisi, 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 { return ReportResponse{ 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 float64) ReportResponse { +func ToClosingKeuanganReport(projectFlockCategory string, purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, deliveryProducts []entities.MarketingDeliveryProduct, chickins []entities.ProjectChickin, totalWeightProduced, totalDepletion float64) ReportResponse { var totalPopulation float64 var totalWeightSold float64 @@ -356,13 +375,16 @@ func ToClosingKeuanganReport(projectFlockCategory string, purchaseItems []entiti totalWeightSold += delivery.TotalWeight } + // Calculate actual population (chickin - depletion) for cost allocation + actualPopulation := totalPopulation - totalDepletion + // Use totalWeightProduced for HPP calculation (not totalWeightSold) hppSection := ToHppPurchasesSection(purchaseItems, budgets, realizations, totalWeightProduced, totalPopulation) penjualanItems := ToPenjualanItems(projectFlockCategory, deliveryProducts, totalPopulation, totalWeightSold) - pembelianItems := ToPembelianItems(purchaseItems, budgets, realizations, totalPopulation, totalWeightProduced) - overheadItems := ToOverheadItems(budgets, realizations, totalPopulation, totalWeightProduced) - ekspedisiItems := ToEkspedisiItems(realizations, totalPopulation, totalWeightProduced) + pembelianItems := ToPembelianItems(purchaseItems, budgets, realizations, actualPopulation, totalWeightProduced) + overheadItems := ToOverheadItems(budgets, realizations, actualPopulation, totalWeightProduced) + ekspedisiItems := ToEkspedisiItems(realizations, actualPopulation, totalWeightProduced) plSection := ToProfitLossSection(penjualanItems, pembelianItems, overheadItems, ekspedisiItems) return ToReportResponse(hppSection, plSection) diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index 1cb26948..84b14ace 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -450,7 +450,13 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (* 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 }