diff --git a/internal/modules/closings/dto/closingKeuangan.dto.go b/internal/modules/closings/dto/closingKeuangan.dto.go index cf4b5b54..13e7c196 100644 --- a/internal/modules/closings/dto/closingKeuangan.dto.go +++ b/internal/modules/closings/dto/closingKeuangan.dto.go @@ -96,7 +96,7 @@ func getFlagLabel(flagType utils.FlagType) string { return "Pembelian " + string(flagType) } -func buildHppItemsByPurchaseFlags(purchaseItems []entities.PurchaseItem, totalWeightSold, totalPopulation float64) []HppItem { +func buildHppItemsByPurchaseFlags(purchaseItems []entities.PurchaseItem, totalWeightProduced, totalPopulation float64) []HppItem { flags := []utils.FlagType{ utils.FlagDOC, utils.FlagPullet, utils.FlagLayer, utils.FlagPakan, utils.FlagPreStarter, utils.FlagStarter, utils.FlagFinisher, @@ -125,7 +125,7 @@ func buildHppItemsByPurchaseFlags(purchaseItems []entities.PurchaseItem, totalWe if isValid && !seenFlags[flagType] { amount := sumPurchasesByFlag(purchaseItems, flagType) - rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightSold) + rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightProduced) items = append(items, HppItem{ Type: getFlagLabel(flagType), @@ -144,14 +144,14 @@ func buildHppItemsByPurchaseFlags(purchaseItems []entities.PurchaseItem, totalWe // === HPP BAHAN BAKU (from ProjectBudget + ExpenseRealization) === -func ToHppBahanBakuGroup(budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightSold, totalPopulation float64) HppGroup { +func ToHppBahanBakuGroup(budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightProduced, totalPopulation float64) HppGroup { items := []HppItem{} // Overhead: all budgets vs (all expenses EXCEPT ekspedisi) budgetAmount := sumBudgetsByFilter(budgets, func(*entities.ProjectBudget) bool { return true }) realizationAmount := sumRealizationsByFilter(realizations, filterRealizationExceptFlag(utils.FlagEkspedisi)) - budgetRpPerBird, budgetRpPerKg := calculatePerUnitMetrics(budgetAmount, totalPopulation, totalWeightSold) - realizationRpPerBird, realizationRpPerKg := calculatePerUnitMetrics(realizationAmount, totalPopulation, totalWeightSold) + budgetRpPerBird, budgetRpPerKg := calculatePerUnitMetrics(budgetAmount, totalPopulation, totalWeightProduced) + realizationRpPerBird, realizationRpPerKg := calculatePerUnitMetrics(realizationAmount, totalPopulation, totalWeightProduced) if budgetAmount > 0 || realizationAmount > 0 { items = append(items, HppItem{ @@ -165,7 +165,7 @@ func ToHppBahanBakuGroup(budgets []entities.ProjectBudget, realizations []entiti // Ekspedisi: no budgeting, only expenses WITH flag EKSPEDISI ekspedisiAmount := sumRealizationsByFilter(realizations, filterRealizationByNonstockFlag(utils.FlagEkspedisi)) - ekspedisiRpPerBird, ekspedisiRpPerKg := calculatePerUnitMetrics(ekspedisiAmount, totalPopulation, totalWeightSold) + ekspedisiRpPerBird, ekspedisiRpPerKg := calculatePerUnitMetrics(ekspedisiAmount, totalPopulation, totalWeightProduced) if ekspedisiAmount > 0 { items = append(items, HppItem{ @@ -185,7 +185,7 @@ func ToHppBahanBakuGroup(budgets []entities.ProjectBudget, realizations []entiti // === HPP SUMMARY === -func ToSummaryHpp(label string, purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightSold, totalPopulation float64) SummaryHpp { +func ToSummaryHpp(label string, purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightProduced, totalPopulation float64) SummaryHpp { // Budget: purchases + budgets purchaseTotal := sumPurchaseTotal(purchaseItems) budgetTotal := sumBudgetsByFilter(budgets, func(*entities.ProjectBudget) bool { return true }) @@ -194,8 +194,8 @@ func ToSummaryHpp(label string, purchaseItems []entities.PurchaseItem, budgets [ // Realization: all expenses totalRealization := sumRealizationsByFilter(realizations, func(*entities.ExpenseRealization) bool { return true }) - budgetRpPerBird, budgetRpPerKg := calculatePerUnitMetrics(totalBudget, totalPopulation, totalWeightSold) - realizationRpPerBird, realizationRpPerKg := calculatePerUnitMetrics(totalRealization, totalPopulation, totalWeightSold) + budgetRpPerBird, budgetRpPerKg := calculatePerUnitMetrics(totalBudget, totalPopulation, totalWeightProduced) + realizationRpPerBird, realizationRpPerKg := calculatePerUnitMetrics(totalRealization, totalPopulation, totalWeightProduced) return SummaryHpp{ Label: label, @@ -206,16 +206,16 @@ func ToSummaryHpp(label string, purchaseItems []entities.PurchaseItem, budgets [ } } -func ToHppPurchasesSection(purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightSold, totalPopulation float64) HppPurchasesSection { +func ToHppPurchasesSection(purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightProduced, totalPopulation float64) HppPurchasesSection { hppGroups := []HppGroup{ { GroupName: "HPP dan Pengeluaran", - Data: buildHppItemsByPurchaseFlags(purchaseItems, totalWeightSold, totalPopulation), + Data: buildHppItemsByPurchaseFlags(purchaseItems, totalWeightProduced, totalPopulation), }, - ToHppBahanBakuGroup(budgets, realizations, totalWeightSold, totalPopulation), + ToHppBahanBakuGroup(budgets, realizations, totalWeightProduced, totalPopulation), } - summaryHpp := ToSummaryHpp("HPP", purchaseItems, budgets, realizations, totalWeightSold, totalPopulation) + summaryHpp := ToSummaryHpp("HPP", purchaseItems, budgets, realizations, totalWeightProduced, totalPopulation) return HppPurchasesSection{ Hpp: hppGroups, @@ -266,37 +266,33 @@ func ToPenjualanItems(projectFlockCategory string, deliveryProducts []entities.M return items } -func ToPembelianItems(purchases []entities.PurchaseItem, totalPopulation, totalWeightSold float64) []PLItem { - amount := sumPurchasesByFilter(purchases, func(item *entities.PurchaseItem) bool { - if item.Product == nil || len(item.Product.Flags) == 0 { - return false - } - for _, flag := range item.Product.Flags { - flagType := strings.ToUpper(flag.Name) - if flagType == string(utils.FlagDOC) || flagType == string(utils.FlagOVK) || flagType == string(utils.FlagPakan) { - return true - } - } - return false - }) +func ToPembelianItems(purchases []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalPopulation, totalWeightProduced float64) []PLItem { + // Calculate total cost using same logic as report penjualan: + // Total Cost = All Purchase Items + All BOP Expenses + purchaseAmount := sumPurchaseTotal(purchases) - rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightSold) + // Get BOP expenses (all expenses except ekspedisi) + bopAmount := sumRealizationsByFilter(realizations, filterRealizationExceptFlag(utils.FlagEkspedisi)) + + totalCost := purchaseAmount + bopAmount + + rpPerBird, rpPerKg := calculatePerUnitMetrics(totalCost, totalPopulation, totalWeightProduced) return []PLItem{ - ToPLItem("Pembelian Sapronak Supplier", ToFinancialMetrics(rpPerBird, rpPerKg, amount)), + ToPLItem("Harga Pokok Penjualan (HPP)", ToFinancialMetrics(rpPerBird, rpPerKg, totalCost)), } } -func ToOverheadItems(budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalPopulation, totalWeightSold float64) []PLItem { +func ToOverheadItems(budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalPopulation, totalWeightProduced float64) []PLItem { realizationAmount := sumRealizationsByFilter(realizations, filterRealizationExceptFlag(utils.FlagEkspedisi)) - rpPerBird, rpPerKg := calculatePerUnitMetrics(realizationAmount, totalPopulation, totalWeightSold) + rpPerBird, rpPerKg := calculatePerUnitMetrics(realizationAmount, totalPopulation, totalWeightProduced) return []PLItem{ ToPLItem("Pengeluaran Overhead", ToFinancialMetrics(rpPerBird, rpPerKg, realizationAmount)), } } -func ToEkspedisiItems(realizations []entities.ExpenseRealization, totalPopulation, totalWeightSold float64) []PLItem { +func ToEkspedisiItems(realizations []entities.ExpenseRealization, totalPopulation, totalWeightProduced float64) []PLItem { amount := sumRealizationsByFilter(realizations, filterRealizationByNonstockFlag(utils.FlagEkspedisi)) - rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightSold) + rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightProduced) return []PLItem{ ToPLItem("Beban Ekspedisi", ToFinancialMetrics(rpPerBird, rpPerKg, amount)), } @@ -348,7 +344,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) 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 totalWeightSold float64 @@ -360,12 +356,13 @@ func ToClosingKeuanganReport(projectFlockCategory string, purchaseItems []entiti totalWeightSold += delivery.TotalWeight } - hppSection := ToHppPurchasesSection(purchaseItems, budgets, realizations, totalWeightSold, totalPopulation) + // Use totalWeightProduced for HPP calculation (not totalWeightSold) + hppSection := ToHppPurchasesSection(purchaseItems, budgets, realizations, totalWeightProduced, totalPopulation) penjualanItems := ToPenjualanItems(projectFlockCategory, deliveryProducts, totalPopulation, totalWeightSold) - pembelianItems := ToPembelianItems(purchaseItems, totalPopulation, totalWeightSold) - overheadItems := ToOverheadItems(budgets, realizations, totalPopulation, totalWeightSold) - ekspedisiItems := ToEkspedisiItems(realizations, totalPopulation, totalWeightSold) + pembelianItems := ToPembelianItems(purchaseItems, budgets, realizations, totalPopulation, totalWeightProduced) + overheadItems := ToOverheadItems(budgets, realizations, totalPopulation, totalWeightProduced) + ekspedisiItems := ToEkspedisiItems(realizations, totalPopulation, totalWeightProduced) plSection := ToProfitLossSection(penjualanItems, pembelianItems, overheadItems, ekspedisiItems) return ToReportResponse(hppSection, plSection) diff --git a/internal/modules/closings/dto/closingOverhead.dto.go b/internal/modules/closings/dto/closingOverhead.dto.go index 95f3e10b..71975da1 100644 --- a/internal/modules/closings/dto/closingOverhead.dto.go +++ b/internal/modules/closings/dto/closingOverhead.dto.go @@ -69,7 +69,7 @@ func ToOverheadDTO(budget *entity.ProjectBudget, realization *entity.ExpenseReal return dto } -func ToOverheadListDTOs(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalChickinQty float64) OverheadListDTO { +func ToOverheadListDTOs(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalChickinQty, totalActualPopulation float64) OverheadListDTO { overheadsByNonstockID := make(map[uint]*OverheadDTO) latestDateByNonstockID := make(map[uint]string) @@ -119,7 +119,8 @@ func ToOverheadListDTOs(budgets []entity.ProjectBudget, realizations []entity.Ex for nonstockID, overhead := range overheadsByNonstockID { overhead.ActualDate = latestDateByNonstockID[nonstockID] - overhead.CostPerBird = calculateCostPerBird(overhead.ActualTotalAmount, totalChickinQty) + + overhead.CostPerBird = calculateCostPerBird(overhead.ActualTotalAmount, totalActualPopulation) if overhead.ActualQuantity > 0 { overhead.ActualUnitPrice = overhead.ActualTotalAmount / overhead.ActualQuantity @@ -139,7 +140,7 @@ func ToOverheadListDTOs(budgets []entity.ProjectBudget, realizations []entity.Ex BudgetTotalAmount: totalBudgetAmount, ActualQuantity: totalActualQuantity, ActualTotalAmount: totalActualAmount, - CostPerBird: calculateCostPerBird(totalActualAmount, totalChickinQty), + CostPerBird: calculateCostPerBird(totalActualAmount, totalActualPopulation), }, Overheads: overheadItems, } @@ -158,9 +159,9 @@ func calculateTotal(qty, price float64) float64 { return qty * price } -func calculateCostPerBird(totalPrice, totalChickinQty float64) float64 { - if totalChickinQty > 0 { - return totalPrice / totalChickinQty +func calculateCostPerBird(totalPrice, totalActualPopulation float64) float64 { + if totalActualPopulation > 0 { + return totalPrice / totalActualPopulation } return 0 } diff --git a/internal/modules/closings/module.go b/internal/modules/closings/module.go index 494f2736..c89e6125 100644 --- a/internal/modules/closings/module.go +++ b/internal/modules/closings/module.go @@ -13,6 +13,7 @@ import ( rMarketings "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" + rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" rPurchase "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories" rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" @@ -31,11 +32,12 @@ func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * marketingDeliveryProductRepo := rMarketings.NewMarketingDeliveryProductRepository(db) expenseRealizationRepo := rExpenseRealization.NewExpenseRealizationRepository(db) chickinRepo := rChickin.NewChickinRepository(db) + recordingRepo := rRecording.NewRecordingRepository(db) purchaseRepo := rPurchase.NewPurchaseRepository(db) approvalRepo := commonRepo.NewApprovalRepository(db) approvalService := commonSvc.NewApprovalService(approvalRepo) - closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, expenseRealizationRepo, projectBudgetRepo, chickinRepo, purchaseRepo, validate) + closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, expenseRealizationRepo, projectBudgetRepo, chickinRepo, purchaseRepo, recordingRepo, validate) sapronakService := sClosing.NewSapronakService(closingRepo, projectFlockKandangRepo, validate) userService := sUser.NewUserService(userRepo, validate) diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index 29001149..1cb26948 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -15,6 +15,7 @@ import ( marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories" chickinRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" projectflockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" + recordingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" purchaseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" @@ -47,9 +48,10 @@ type closingService struct { ProjectBudgetRepo projectflockRepository.ProjectBudgetRepository ChickinRepo chickinRepository.ProjectChickinRepository PurchaseRepo purchaseRepository.PurchaseRepository + RecordingRepo recordingRepository.RecordingRepository } -func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, expenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository, projectBudgetRepo projectflockRepository.ProjectBudgetRepository, chickinRepo chickinRepository.ProjectChickinRepository, purchaseRepo purchaseRepository.PurchaseRepository, validate *validator.Validate) ClosingService { +func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, expenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository, projectBudgetRepo projectflockRepository.ProjectBudgetRepository, chickinRepo chickinRepository.ProjectChickinRepository, purchaseRepo purchaseRepository.PurchaseRepository, recordingRepo recordingRepository.RecordingRepository, validate *validator.Validate) ClosingService { return &closingService{ Log: utils.Log, Validate: validate, @@ -62,6 +64,7 @@ func NewClosingService(repo repository.ClosingRepository, projectFlockRepo proje ProjectBudgetRepo: projectBudgetRepo, ChickinRepo: chickinRepo, PurchaseRepo: purchaseRepo, + RecordingRepo: recordingRepo, } } @@ -379,7 +382,14 @@ func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint) (*dto.Ove totalChickinQty += chickin.UsageQty } - result := dto.ToOverheadListDTOs(budgets, realizations, totalChickinQty) + totalDepletion, err := s.RecordingRepo.GetTotalDepletionByProjectFlockID(c.Context(), projectFlockID) + if err != nil { + s.Log.Warnf("GetTotalDepletionByProjectFlockID error: %v", err) + } + + totalActualPopulation := totalChickinQty - totalDepletion + + result := dto.ToOverheadListDTOs(budgets, realizations, totalChickinQty, totalActualPopulation) return &result, nil } @@ -435,7 +445,12 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (* return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch chickins") } - report := dto.ToClosingKeuanganReport(projectFlock.Category, purchaseItems, budgets, realizations, deliveryProducts, chickins) + totalWeightProduced, _, err := s.RecordingRepo.GetProductionWeightAndQtyByProjectFlockID(c.Context(), projectFlockID) + if err != nil { + s.Log.Warnf("GetProductionWeightAndQtyByProjectFlockID error: %v", err) + } + + report := dto.ToClosingKeuanganReport(projectFlock.Category, purchaseItems, budgets, realizations, deliveryProducts, chickins, totalWeightProduced) return &report, nil }