mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat[BE-384]: enhance closing reports by introducing calculation context and improving data handling; refactor related functions for better clarity and maintainability
This commit is contained in:
@@ -1,13 +1,58 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
// === CONSTANTS ===
|
||||
const (
|
||||
HPPGroupPengeluaran = "HPP dan Pengeluaran"
|
||||
HPPGroupBahanBaku = "HPP dan Bahan Baku"
|
||||
HPPLabelOverhead = "Pengeluaran Overhead"
|
||||
HPPLabelEkspedisi = "Beban Ekspedisi"
|
||||
HPPSummaryLabel = "HPP"
|
||||
|
||||
PLSalesTypeChicken = "Penjualan Ayam Besar"
|
||||
PLSalesTypeEgg = "Penjualan Telur"
|
||||
|
||||
PLItemTypeSapronak = "Pembelian Sapronak"
|
||||
PLItemTypeOverhead = "Pengeluaran Overhead"
|
||||
PLItemTypeEkspedisi = "Beban Ekspedisi"
|
||||
|
||||
PLSummaryLabelGrossProfit = "LABA RUGI BRUTTO"
|
||||
PLSummaryLabelSubTotal = "SUB TOTAL"
|
||||
PLSummaryLabelNetProfit = "LABA RUGI NETTO"
|
||||
|
||||
PurchaseLabelPrefix = "Pembelian "
|
||||
)
|
||||
|
||||
// === CONTEXT STRUCTS ===
|
||||
|
||||
type CalculationContext struct {
|
||||
TotalPopulation float64
|
||||
TotalWeightProduced float64
|
||||
TotalDepletion float64
|
||||
TotalWeightSold float64
|
||||
ActualPopulation float64
|
||||
}
|
||||
|
||||
type ClosingKeuanganInput struct {
|
||||
ProjectFlockCategory string
|
||||
PurchaseItems []entities.PurchaseItem
|
||||
Budgets []entities.ProjectBudget
|
||||
Realizations []entities.ExpenseRealization
|
||||
DeliveryProducts []entities.MarketingDeliveryProduct
|
||||
Chickins []entities.ProjectChickin
|
||||
TotalWeightProduced float64
|
||||
TotalDepletion float64
|
||||
}
|
||||
|
||||
// === BASE METRICS ===
|
||||
|
||||
type FinancialMetrics struct {
|
||||
RpPerBird float64 `json:"rp_per_bird"`
|
||||
RpPerKg float64 `json:"rp_per_kg"`
|
||||
@@ -20,6 +65,7 @@ type Comparison struct {
|
||||
}
|
||||
|
||||
// === HPP PURCHASES PACKAGE ===
|
||||
|
||||
type HppItem struct {
|
||||
Type string `json:"type"`
|
||||
Comparison
|
||||
@@ -41,6 +87,7 @@ type HppPurchasesSection struct {
|
||||
}
|
||||
|
||||
// === PROFIT LOSS PACKAGE ===
|
||||
|
||||
type PLItem struct {
|
||||
Type string `json:"type"`
|
||||
FinancialMetrics
|
||||
@@ -70,6 +117,7 @@ type ProfitLossSection struct {
|
||||
}
|
||||
|
||||
// === RESPONSE DTO (ROOT) ===
|
||||
|
||||
type ReportResponse struct {
|
||||
HppPurchases HppPurchasesSection `json:"hpp_purchases"`
|
||||
ProfitLoss ProfitLossSection `json:"profit_loss"`
|
||||
@@ -95,10 +143,10 @@ func ToComparison(budgeting, realization FinancialMetrics) Comparison {
|
||||
// === HPP PENGELUARAN (from Purchase Items) ===
|
||||
|
||||
func getFlagLabel(flagType utils.FlagType) string {
|
||||
return "Pembelian " + string(flagType)
|
||||
return PurchaseLabelPrefix + string(flagType)
|
||||
}
|
||||
|
||||
func buildHppItemsByPurchaseFlags(purchaseItems []entities.PurchaseItem, totalWeightProduced, totalPopulation float64) []HppItem {
|
||||
func buildHppItemsByPurchaseFlags(purchaseItems []entities.PurchaseItem, ctx CalculationContext) []HppItem {
|
||||
flags := []utils.FlagType{
|
||||
utils.FlagDOC, utils.FlagPullet, utils.FlagLayer, utils.FlagPakan,
|
||||
utils.FlagPreStarter, utils.FlagStarter, utils.FlagFinisher,
|
||||
@@ -116,24 +164,15 @@ func buildHppItemsByPurchaseFlags(purchaseItems []entities.PurchaseItem, totalWe
|
||||
for _, flag := range item.Product.Flags {
|
||||
flagType := utils.FlagType(flag.Name)
|
||||
|
||||
// Check if valid flag and not processed
|
||||
isValid := false
|
||||
for _, validFlag := range flags {
|
||||
if validFlag == flagType {
|
||||
isValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isValid && !seenFlags[flagType] {
|
||||
if slices.Contains(flags, flagType) && !seenFlags[flagType] {
|
||||
amount := sumPurchasesByFlag(purchaseItems, flagType)
|
||||
rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightProduced)
|
||||
rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, ctx.TotalPopulation, ctx.TotalWeightProduced)
|
||||
|
||||
items = append(items, HppItem{
|
||||
Type: getFlagLabel(flagType),
|
||||
Comparison: ToComparison(
|
||||
ToFinancialMetrics(rpPerBird, rpPerKg, amount),
|
||||
ToFinancialMetrics(rpPerBird, rpPerKg, amount), // Same for purchase
|
||||
ToFinancialMetrics(rpPerBird, rpPerKg, amount),
|
||||
),
|
||||
})
|
||||
seenFlags[flagType] = true
|
||||
@@ -146,56 +185,61 @@ func buildHppItemsByPurchaseFlags(purchaseItems []entities.PurchaseItem, totalWe
|
||||
|
||||
// === HPP BAHAN BAKU (from ProjectBudget + ExpenseRealization) ===
|
||||
|
||||
func ToHppBahanBakuGroup(budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightProduced, totalPopulation float64) HppGroup {
|
||||
items := []HppItem{}
|
||||
func createHppOverheadItem(budgetAmount, realizationAmount float64, ctx CalculationContext) HppItem {
|
||||
budgetRpPerBird, budgetRpPerKg := calculatePerUnitMetrics(budgetAmount, ctx.TotalPopulation, ctx.TotalWeightProduced)
|
||||
realizationRpPerBird, realizationRpPerKg := calculatePerUnitMetrics(realizationAmount, ctx.TotalPopulation, ctx.TotalWeightProduced)
|
||||
|
||||
// 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, totalWeightProduced)
|
||||
realizationRpPerBird, realizationRpPerKg := calculatePerUnitMetrics(realizationAmount, totalPopulation, totalWeightProduced)
|
||||
|
||||
if budgetAmount > 0 || realizationAmount > 0 {
|
||||
items = append(items, HppItem{
|
||||
Type: "Pengeluaran Overhead",
|
||||
Comparison: ToComparison(
|
||||
ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, budgetAmount),
|
||||
ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, realizationAmount),
|
||||
),
|
||||
})
|
||||
return HppItem{
|
||||
Type: HPPLabelOverhead,
|
||||
Comparison: ToComparison(
|
||||
ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, budgetAmount),
|
||||
ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, realizationAmount),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Ekspedisi: no budgeting, only expenses WITH flag EKSPEDISI
|
||||
ekspedisiAmount := sumRealizationsByFilter(realizations, filterRealizationByNonstockFlag(utils.FlagEkspedisi))
|
||||
ekspedisiRpPerBird, ekspedisiRpPerKg := calculatePerUnitMetrics(ekspedisiAmount, totalPopulation, totalWeightProduced)
|
||||
func createHppEkspedisiItem(ekspedisiAmount float64, ctx CalculationContext) HppItem {
|
||||
ekspedisiRpPerBird, ekspedisiRpPerKg := calculatePerUnitMetrics(ekspedisiAmount, ctx.TotalPopulation, ctx.TotalWeightProduced)
|
||||
|
||||
items = append(items, HppItem{
|
||||
Type: "Beban Ekspedisi",
|
||||
return HppItem{
|
||||
Type: HPPLabelEkspedisi,
|
||||
Comparison: ToComparison(
|
||||
ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount),
|
||||
ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount), // Same as realization
|
||||
ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ToHppBahanBakuGroup(budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, ctx CalculationContext) HppGroup {
|
||||
items := []HppItem{}
|
||||
|
||||
budgetAmount := sumBudgetsByFilter(budgets, func(*entities.ProjectBudget) bool { return true })
|
||||
realizationAmount := getOperationalExpenses(realizations)
|
||||
|
||||
if budgetAmount > 0 || realizationAmount > 0 {
|
||||
items = append(items, createHppOverheadItem(budgetAmount, realizationAmount, ctx))
|
||||
}
|
||||
|
||||
ekspedisiAmount := sumRealizationsByFilter(realizations, filterRealizationByNonstockFlag(utils.FlagEkspedisi))
|
||||
items = append(items, createHppEkspedisiItem(ekspedisiAmount, ctx))
|
||||
|
||||
return HppGroup{
|
||||
GroupName: "HPP dan Bahan Baku",
|
||||
GroupName: HPPGroupBahanBaku,
|
||||
Data: items,
|
||||
}
|
||||
}
|
||||
|
||||
// === HPP SUMMARY ===
|
||||
|
||||
func ToSummaryHpp(label string, purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightProduced, totalPopulation float64) SummaryHpp {
|
||||
// Budget: purchases + budgets
|
||||
func ToSummaryHpp(label string, purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, ctx CalculationContext) SummaryHpp {
|
||||
purchaseTotal := sumPurchaseTotal(purchaseItems)
|
||||
budgetTotal := sumBudgetsByFilter(budgets, func(*entities.ProjectBudget) bool { return true })
|
||||
totalBudget := purchaseTotal + budgetTotal
|
||||
|
||||
// Realization: all expenses
|
||||
totalRealization := sumRealizationsByFilter(realizations, func(*entities.ExpenseRealization) bool { return true })
|
||||
|
||||
budgetRpPerBird, budgetRpPerKg := calculatePerUnitMetrics(totalBudget, totalPopulation, totalWeightProduced)
|
||||
realizationRpPerBird, realizationRpPerKg := calculatePerUnitMetrics(totalRealization, totalPopulation, totalWeightProduced)
|
||||
budgetRpPerBird, budgetRpPerKg := calculatePerUnitMetrics(totalBudget, ctx.TotalPopulation, ctx.TotalWeightProduced)
|
||||
realizationRpPerBird, realizationRpPerKg := calculatePerUnitMetrics(totalRealization, ctx.TotalPopulation, ctx.TotalWeightProduced)
|
||||
|
||||
return SummaryHpp{
|
||||
Label: label,
|
||||
@@ -206,16 +250,16 @@ func ToSummaryHpp(label string, purchaseItems []entities.PurchaseItem, budgets [
|
||||
}
|
||||
}
|
||||
|
||||
func ToHppPurchasesSection(purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightProduced, totalPopulation float64) HppPurchasesSection {
|
||||
func ToHppPurchasesSection(purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, ctx CalculationContext) HppPurchasesSection {
|
||||
hppGroups := []HppGroup{
|
||||
{
|
||||
GroupName: "HPP dan Pengeluaran",
|
||||
Data: buildHppItemsByPurchaseFlags(purchaseItems, totalWeightProduced, totalPopulation),
|
||||
GroupName: HPPGroupPengeluaran,
|
||||
Data: buildHppItemsByPurchaseFlags(purchaseItems, ctx),
|
||||
},
|
||||
ToHppBahanBakuGroup(budgets, realizations, totalWeightProduced, totalPopulation),
|
||||
ToHppBahanBakuGroup(budgets, realizations, ctx),
|
||||
}
|
||||
|
||||
summaryHpp := ToSummaryHpp("HPP", purchaseItems, budgets, realizations, totalWeightProduced, totalPopulation)
|
||||
summaryHpp := ToSummaryHpp(HPPSummaryLabel, purchaseItems, budgets, realizations, ctx)
|
||||
|
||||
return HppPurchasesSection{
|
||||
Hpp: hppGroups,
|
||||
@@ -239,6 +283,11 @@ func ToPLSummaryItem(label string, metrics FinancialMetrics) PLSummaryItem {
|
||||
}
|
||||
}
|
||||
|
||||
func createPLItemWithMetrics(itemType string, amount float64, ctx CalculationContext) PLItem {
|
||||
rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, ctx.ActualPopulation, ctx.TotalWeightProduced)
|
||||
return ToPLItem(itemType, ToFinancialMetrics(rpPerBird, rpPerKg, amount))
|
||||
}
|
||||
|
||||
func sumPLItems(items []PLItem) (totalAmount, totalPerBird float64) {
|
||||
for _, item := range items {
|
||||
totalAmount += item.Amount
|
||||
@@ -247,63 +296,51 @@ func sumPLItems(items []PLItem) (totalAmount, totalPerBird float64) {
|
||||
return
|
||||
}
|
||||
|
||||
func ToPenjualanItems(projectFlockCategory string, deliveryProducts []entities.MarketingDeliveryProduct, totalPopulation, totalWeightSold float64) []PLItem {
|
||||
func createPenjualanItem(salesType string, amount float64, ctx CalculationContext) PLItem {
|
||||
rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, ctx.ActualPopulation, ctx.TotalWeightSold)
|
||||
return ToPLItem(salesType, ToFinancialMetrics(rpPerBird, rpPerKg, amount))
|
||||
}
|
||||
|
||||
func ToPenjualanItems(projectFlockCategory string, deliveryProducts []entities.MarketingDeliveryProduct, ctx CalculationContext) []PLItem {
|
||||
items := []PLItem{}
|
||||
|
||||
// Categorize deliveries by sales type based on Product flags
|
||||
categorized := categorizeDeliveriesBySalesType(deliveryProducts)
|
||||
|
||||
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"])
|
||||
ayamAmount := sumDeliveriesByCategory(categorized[PLSalesTypeChicken])
|
||||
telurAmount := sumDeliveriesByCategory(categorized[PLSalesTypeEgg])
|
||||
|
||||
// Penjualan Ayam Besar
|
||||
rpPerBird, rpPerKg := calculatePerUnitMetrics(ayamAmount, totalPopulation, totalWeightSold)
|
||||
items = append(items, ToPLItem("Penjualan Ayam Besar", ToFinancialMetrics(rpPerBird, rpPerKg, ayamAmount)))
|
||||
|
||||
// Penjualan Telur
|
||||
rpPerBird, rpPerKg = calculatePerUnitMetrics(telurAmount, totalPopulation, totalWeightSold)
|
||||
items = append(items, ToPLItem("Penjualan Telur", ToFinancialMetrics(rpPerBird, rpPerKg, telurAmount)))
|
||||
items = append(items, createPenjualanItem(PLSalesTypeChicken, ayamAmount, ctx))
|
||||
items = append(items, createPenjualanItem(PLSalesTypeEgg, telurAmount, ctx))
|
||||
} 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)))
|
||||
ayamAmount := sumDeliveriesByCategory(categorized[PLSalesTypeChicken])
|
||||
items = append(items, createPenjualanItem(PLSalesTypeChicken, ayamAmount, ctx))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
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
|
||||
func ToPembelianItems(purchases []entities.PurchaseItem, realizations []entities.ExpenseRealization, ctx CalculationContext) []PLItem {
|
||||
purchaseAmount := sumPurchaseTotal(purchases)
|
||||
|
||||
// Get BOP expenses (all expenses except ekspedisi)
|
||||
bopAmount := sumRealizationsByFilter(realizations, filterRealizationExceptFlag(utils.FlagEkspedisi))
|
||||
|
||||
bopAmount := getOperationalExpenses(realizations)
|
||||
totalCost := purchaseAmount + bopAmount
|
||||
|
||||
rpPerBird, rpPerKg := calculatePerUnitMetrics(totalCost, totalPopulation, totalWeightProduced)
|
||||
return []PLItem{
|
||||
ToPLItem("Pembelian Sapronak", ToFinancialMetrics(rpPerBird, rpPerKg, totalCost)),
|
||||
createPLItemWithMetrics(PLItemTypeSapronak, totalCost, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func ToOverheadItems(budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalPopulation, totalWeightProduced float64) []PLItem {
|
||||
realizationAmount := sumRealizationsByFilter(realizations, filterRealizationExceptFlag(utils.FlagEkspedisi))
|
||||
rpPerBird, rpPerKg := calculatePerUnitMetrics(realizationAmount, totalPopulation, totalWeightProduced)
|
||||
func ToOverheadItems(realizations []entities.ExpenseRealization, ctx CalculationContext) []PLItem {
|
||||
realizationAmount := getOperationalExpenses(realizations)
|
||||
return []PLItem{
|
||||
ToPLItem("Pengeluaran Overhead", ToFinancialMetrics(rpPerBird, rpPerKg, realizationAmount)),
|
||||
createPLItemWithMetrics(PLItemTypeOverhead, realizationAmount, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func ToEkspedisiItems(realizations []entities.ExpenseRealization, totalPopulation, totalWeightProduced float64) []PLItem {
|
||||
func ToEkspedisiItems(realizations []entities.ExpenseRealization, ctx CalculationContext) []PLItem {
|
||||
amount := sumRealizationsByFilter(realizations, filterRealizationByNonstockFlag(utils.FlagEkspedisi))
|
||||
rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightProduced)
|
||||
return []PLItem{
|
||||
ToPLItem("Beban Ekspedisi", ToFinancialMetrics(rpPerBird, rpPerKg, amount)),
|
||||
createPLItemWithMetrics(PLItemTypeEkspedisi, amount, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,18 +360,17 @@ func ToPLSummaryGroup(penjualanItems, pembelianItems, overheadItems, ekspedisiIt
|
||||
netProfitPerBird := grossProfitPerBird - totalOtherExpensesPerBird
|
||||
|
||||
return PLSummaryGroup{
|
||||
GrossProfit: ToPLSummaryItem("LABA RUGI BRUTTO", ToFinancialMetrics(grossProfitPerBird, 0, grossProfit)),
|
||||
SubTotal: ToPLSummaryItem("SUB TOTAL", ToFinancialMetrics(totalOtherExpensesPerBird, 0, totalOtherExpenses)),
|
||||
NetProfit: ToPLSummaryItem("LABA RUGI NETTO", ToFinancialMetrics(netProfitPerBird, 0, netProfit)),
|
||||
GrossProfit: ToPLSummaryItem(PLSummaryLabelGrossProfit, ToFinancialMetrics(grossProfitPerBird, 0, grossProfit)),
|
||||
SubTotal: ToPLSummaryItem(PLSummaryLabelSubTotal, ToFinancialMetrics(totalOtherExpensesPerBird, 0, totalOtherExpenses)),
|
||||
NetProfit: ToPLSummaryItem(PLSummaryLabelNetProfit, ToFinancialMetrics(netProfitPerBird, 0, netProfit)),
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
totalOverhead := aggregatePLItems(overheadItems, PLItemTypeOverhead)
|
||||
totalEkspedisi := aggregatePLItems(ekspedisiItems, PLItemTypeEkspedisi)
|
||||
|
||||
return ProfitLossData{
|
||||
Penjualan: penjualanItems,
|
||||
@@ -363,28 +399,31 @@ func ToReportResponse(hppPurchases HppPurchasesSection, profitLoss ProfitLossSec
|
||||
}
|
||||
}
|
||||
|
||||
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(input ClosingKeuanganInput) ReportResponse {
|
||||
var totalPopulation float64
|
||||
var totalWeightSold float64
|
||||
|
||||
for _, chickin := range chickins {
|
||||
for _, chickin := range input.Chickins {
|
||||
totalPopulation += chickin.UsageQty
|
||||
}
|
||||
|
||||
for _, delivery := range deliveryProducts {
|
||||
for _, delivery := range input.DeliveryProducts {
|
||||
totalWeightSold += delivery.TotalWeight
|
||||
}
|
||||
|
||||
// Calculate actual population (chickin - depletion) for cost allocation
|
||||
actualPopulation := totalPopulation - totalDepletion
|
||||
ctx := CalculationContext{
|
||||
TotalPopulation: totalPopulation,
|
||||
TotalWeightProduced: input.TotalWeightProduced,
|
||||
TotalDepletion: input.TotalDepletion,
|
||||
TotalWeightSold: totalWeightSold,
|
||||
ActualPopulation: totalPopulation - input.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, actualPopulation, totalWeightProduced)
|
||||
overheadItems := ToOverheadItems(budgets, realizations, actualPopulation, totalWeightProduced)
|
||||
ekspedisiItems := ToEkspedisiItems(realizations, actualPopulation, totalWeightProduced)
|
||||
hppSection := ToHppPurchasesSection(input.PurchaseItems, input.Budgets, input.Realizations, ctx)
|
||||
penjualanItems := ToPenjualanItems(input.ProjectFlockCategory, input.DeliveryProducts, ctx)
|
||||
pembelianItems := ToPembelianItems(input.PurchaseItems, input.Realizations, ctx)
|
||||
overheadItems := ToOverheadItems(input.Realizations, ctx)
|
||||
ekspedisiItems := ToEkspedisiItems(input.Realizations, ctx)
|
||||
plSection := ToProfitLossSection(penjualanItems, pembelianItems, overheadItems, ekspedisiItems)
|
||||
|
||||
return ToReportResponse(hppSection, plSection)
|
||||
@@ -402,17 +441,21 @@ func calculatePerUnitMetrics(amount, totalPopulation, totalWeightSold float64) (
|
||||
return rpPerBird, rpPerKg
|
||||
}
|
||||
|
||||
func hasProductFlag(flags []entities.Flag, flagType utils.FlagType) bool {
|
||||
for _, flag := range flags {
|
||||
if strings.ToUpper(flag.Name) == string(flagType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func filterByPurchaseFlag(flagType utils.FlagType) func(*entities.PurchaseItem) bool {
|
||||
return func(item *entities.PurchaseItem) bool {
|
||||
if item.Product == nil || len(item.Product.Flags) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, flag := range item.Product.Flags {
|
||||
if strings.ToUpper(flag.Name) == string(flagType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return hasProductFlag(item.Product.Flags, flagType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,13 +464,7 @@ func filterRealizationByNonstockFlag(flagType utils.FlagType) func(*entities.Exp
|
||||
if realization.ExpenseNonstock == nil || realization.ExpenseNonstock.Nonstock == nil {
|
||||
return false
|
||||
}
|
||||
nonstock := realization.ExpenseNonstock.Nonstock
|
||||
for _, flag := range nonstock.Flags {
|
||||
if strings.ToUpper(flag.Name) == string(flagType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return hasProductFlag(realization.ExpenseNonstock.Nonstock.Flags, flagType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,46 +475,38 @@ func filterRealizationExceptFlag(flagType utils.FlagType) func(*entities.Expense
|
||||
}
|
||||
}
|
||||
|
||||
func sumPurchasesByFilter(purchases []entities.PurchaseItem, filter func(*entities.PurchaseItem) bool) float64 {
|
||||
func sumByFilter[T any](items []T, extractor func(*T) float64, filter func(*T) bool) float64 {
|
||||
amount := 0.0
|
||||
for i := range purchases {
|
||||
if filter(&purchases[i]) {
|
||||
amount += purchases[i].TotalPrice
|
||||
for i := range items {
|
||||
if filter(&items[i]) {
|
||||
amount += extractor(&items[i])
|
||||
}
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
func sumPurchasesByFilter(purchases []entities.PurchaseItem, filter func(*entities.PurchaseItem) bool) float64 {
|
||||
return sumByFilter(purchases, func(p *entities.PurchaseItem) float64 { return p.TotalPrice }, filter)
|
||||
}
|
||||
|
||||
func sumPurchasesByFlag(purchases []entities.PurchaseItem, flagType utils.FlagType) float64 {
|
||||
return sumPurchasesByFilter(purchases, filterByPurchaseFlag(flagType))
|
||||
}
|
||||
|
||||
func sumPurchaseTotal(purchases []entities.PurchaseItem) float64 {
|
||||
amount := 0.0
|
||||
for i := range purchases {
|
||||
amount += purchases[i].TotalPrice
|
||||
}
|
||||
return amount
|
||||
return sumByFilter(purchases, func(p *entities.PurchaseItem) float64 { return p.TotalPrice }, func(*entities.PurchaseItem) bool { return true })
|
||||
}
|
||||
|
||||
func sumBudgetsByFilter(budgets []entities.ProjectBudget, filter func(*entities.ProjectBudget) bool) float64 {
|
||||
amount := 0.0
|
||||
for i := range budgets {
|
||||
if filter(&budgets[i]) {
|
||||
amount += budgets[i].Price * budgets[i].Qty
|
||||
}
|
||||
}
|
||||
return amount
|
||||
return sumByFilter(budgets, func(b *entities.ProjectBudget) float64 { return b.Price * b.Qty }, filter)
|
||||
}
|
||||
|
||||
func sumRealizationsByFilter(realizations []entities.ExpenseRealization, filter func(*entities.ExpenseRealization) bool) float64 {
|
||||
amount := 0.0
|
||||
for i := range realizations {
|
||||
if filter(&realizations[i]) {
|
||||
amount += realizations[i].Price * realizations[i].Qty
|
||||
}
|
||||
}
|
||||
return amount
|
||||
return sumByFilter(realizations, func(r *entities.ExpenseRealization) float64 { return r.Price * r.Qty }, filter)
|
||||
}
|
||||
|
||||
func getOperationalExpenses(realizations []entities.ExpenseRealization) float64 {
|
||||
return sumRealizationsByFilter(realizations, filterRealizationExceptFlag(utils.FlagEkspedisi))
|
||||
}
|
||||
|
||||
func isChickenProductFlag(flagType utils.FlagType) bool {
|
||||
@@ -500,21 +529,21 @@ func isEggProductFlag(flagType utils.FlagType) bool {
|
||||
|
||||
func getSalesTypeFromProductFlags(product *entities.Product) string {
|
||||
if product == nil || len(product.Flags) == 0 {
|
||||
return "Penjualan Ayam Besar"
|
||||
return PLSalesTypeChicken
|
||||
}
|
||||
|
||||
for _, flag := range product.Flags {
|
||||
flagType := utils.FlagType(strings.ToUpper(flag.Name))
|
||||
|
||||
if isEggProductFlag(flagType) {
|
||||
return "Penjualan Telur"
|
||||
return PLSalesTypeEgg
|
||||
}
|
||||
if isChickenProductFlag(flagType) {
|
||||
return "Penjualan Ayam Besar"
|
||||
return PLSalesTypeChicken
|
||||
}
|
||||
}
|
||||
|
||||
return "Penjualan Ayam Besar"
|
||||
return PLSalesTypeChicken
|
||||
}
|
||||
|
||||
func categorizeDeliveriesBySalesType(deliveries []entities.MarketingDeliveryProduct) map[string][]entities.MarketingDeliveryProduct {
|
||||
|
||||
@@ -35,8 +35,7 @@ type PenjualanRealisasiResponseDTO struct {
|
||||
|
||||
func ToSalesDTO(e entity.MarketingDeliveryProduct) SalesDTO {
|
||||
|
||||
// todo: usia ayam masih dummy
|
||||
age := 0
|
||||
age := calculateAgeFromChickin(e.MarketingProduct.ProductWarehouse.ProjectFlockKandang, e.DeliveryDate)
|
||||
|
||||
var product *productDTO.ProductRelationDTO
|
||||
if e.MarketingProduct.ProductWarehouse.Product.Id != 0 {
|
||||
@@ -101,3 +100,20 @@ func extractPeriodFromRealisasi(realisasi []entity.MarketingDeliveryProduct) int
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func calculateAgeFromChickin(projectFlockKandang *entity.ProjectFlockKandang, deliveryDate *time.Time) int {
|
||||
if projectFlockKandang == nil || deliveryDate == nil || len(projectFlockKandang.Chickins) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
earliestChickinDate := projectFlockKandang.Chickins[0].ChickInDate
|
||||
for _, chickin := range projectFlockKandang.Chickins {
|
||||
if chickin.ChickInDate.Before(earliestChickinDate) {
|
||||
earliestChickinDate = chickin.ChickInDate
|
||||
}
|
||||
}
|
||||
|
||||
ageInDays := int(deliveryDate.Sub(earliestChickinDate).Hours() / 24)
|
||||
ageInWeeks := ageInDays / 7
|
||||
return ageInWeeks
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ func (s closingService) GetPenjualan(c *fiber.Ctx, projectFlockID uint) ([]entit
|
||||
Preload("MarketingProduct.ProductWarehouse.Warehouse").
|
||||
Preload("MarketingProduct.ProductWarehouse.ProjectFlockKandang").
|
||||
Preload("MarketingProduct.ProductWarehouse.ProjectFlockKandang.Kandang").
|
||||
Preload("MarketingProduct.ProductWarehouse.ProjectFlockKandang.Chickins").
|
||||
Preload("MarketingProduct.Marketing").
|
||||
Preload("MarketingProduct.Marketing.Customer").
|
||||
Order("marketing_delivery_products.delivery_date DESC")
|
||||
@@ -450,13 +451,23 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*
|
||||
s.Log.Warnf("GetProductionWeightAndQtyByProjectFlockID error: %v", err)
|
||||
}
|
||||
|
||||
// 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)
|
||||
input := dto.ClosingKeuanganInput{
|
||||
ProjectFlockCategory: projectFlock.Category,
|
||||
PurchaseItems: purchaseItems,
|
||||
Budgets: budgets,
|
||||
Realizations: realizations,
|
||||
DeliveryProducts: deliveryProducts,
|
||||
Chickins: chickins,
|
||||
TotalWeightProduced: totalWeightProduced,
|
||||
TotalDepletion: totalDepletion,
|
||||
}
|
||||
|
||||
report := dto.ToClosingKeuanganReport(input)
|
||||
|
||||
return &report, nil
|
||||
}
|
||||
|
||||
@@ -143,6 +143,10 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product warehouse %d is not bound to kandang's warehouse", chickinReq.ProductWarehouseId))
|
||||
}
|
||||
|
||||
if productWarehouse.ProjectFlockKandangId == nil || *productWarehouse.ProjectFlockKandangId != req.ProjectFlockKandangId {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product warehouse %d is not attached to project_flock_kandang %d. Only product warehouses with matching project_flock_kandang_id can be chickin-ed", chickinReq.ProductWarehouseId, req.ProjectFlockKandangId))
|
||||
}
|
||||
|
||||
chickinDate, err := utils.ParseDateString(chickinReq.ChickInDate)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid ChickInDate format for product warehouse %d", chickinReq.ProductWarehouseId))
|
||||
@@ -450,7 +454,8 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get warehouse")
|
||||
}
|
||||
|
||||
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "PULLET", dbTransaction, actorID)
|
||||
pfkID := approvableID
|
||||
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "PULLET", dbTransaction, actorID, &pfkID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get/create PULLET product warehouse")
|
||||
}
|
||||
@@ -466,7 +471,8 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get warehouse")
|
||||
}
|
||||
|
||||
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "PULLET", dbTransaction, actorID)
|
||||
pfkID := approvableID
|
||||
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "PULLET", dbTransaction, actorID, &pfkID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get/create PULLET product warehouse")
|
||||
}
|
||||
@@ -538,11 +544,19 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (s *chickinService) getOrCreateProductWarehouse(ctx *fiber.Ctx, warehouseId uint, categoryCode string, dbTransaction *gorm.DB, actorID uint) (*entity.ProductWarehouse, error) {
|
||||
func (s *chickinService) getOrCreateProductWarehouse(ctx *fiber.Ctx, warehouseId uint, categoryCode string, dbTransaction *gorm.DB, actorID uint, projectFlockKandangId *uint) (*entity.ProductWarehouse, error) {
|
||||
|
||||
products, err := s.ProductWarehouseRepo.GetByFlagAndWarehouseID(ctx.Context(), categoryCode, warehouseId)
|
||||
if err == nil && len(products) > 0 {
|
||||
return &products[0], nil
|
||||
existingPW := &products[0]
|
||||
// Update project_flock_kandang_id if not already set
|
||||
if existingPW.ProjectFlockKandangId == nil && projectFlockKandangId != nil {
|
||||
existingPW.ProjectFlockKandangId = projectFlockKandangId
|
||||
if err := s.ProductWarehouseRepo.WithTx(dbTransaction).UpdateOne(ctx.Context(), existingPW.Id, existingPW, nil); err != nil {
|
||||
return nil, fmt.Errorf("failed to update %s product warehouse with project_flock_kandang_id: %w", categoryCode, err)
|
||||
}
|
||||
}
|
||||
return existingPW, nil
|
||||
}
|
||||
|
||||
product, err := s.ProductWarehouseRepo.GetFirstProductByFlag(ctx.Context(), categoryCode)
|
||||
@@ -554,9 +568,10 @@ func (s *chickinService) getOrCreateProductWarehouse(ctx *fiber.Ctx, warehouseId
|
||||
}
|
||||
|
||||
newPW := &entity.ProductWarehouse{
|
||||
ProductId: product.Id,
|
||||
WarehouseId: warehouseId,
|
||||
Quantity: 0,
|
||||
ProductId: product.Id,
|
||||
WarehouseId: warehouseId,
|
||||
ProjectFlockKandangId: projectFlockKandangId,
|
||||
Quantity: 0,
|
||||
// CreatedBy: actorID,
|
||||
}
|
||||
|
||||
|
||||
+9
-6
@@ -190,13 +190,16 @@ func (s projectFlockKandangService) getAvailableQuantities(c *fiber.Ctx, project
|
||||
|
||||
result := make(map[uint]float64)
|
||||
for _, pw := range products {
|
||||
availableQty, err := s.calculateAvailableQuantityForProductWarehouse(c, projectFlockKandang, &pw)
|
||||
if err != nil {
|
||||
s.Log.Warnf("Failed to calculate available quantity for product warehouse %d: %v", pw.Id, err)
|
||||
}
|
||||
|
||||
if availableQty > 0 {
|
||||
result[pw.Id] = availableQty
|
||||
if pw.ProjectFlockKandangId != nil && *pw.ProjectFlockKandangId == projectFlockKandang.Id {
|
||||
availableQty, err := s.calculateAvailableQuantityForProductWarehouse(c, projectFlockKandang, &pw)
|
||||
if err != nil {
|
||||
s.Log.Warnf("Failed to calculate available quantity for product warehouse %d: %v", pw.Id, err)
|
||||
}
|
||||
|
||||
if availableQty > 0 {
|
||||
result[pw.Id] = availableQty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -425,7 +425,7 @@ func (r *RecordingRepositoryImpl) GetLatestAvgWeightByProjectFlockID(ctx context
|
||||
Joins("JOIN recordings ON recordings.id = recording_bws.recording_id").
|
||||
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = recordings.project_flock_kandangs_id").
|
||||
Where("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||
Where("recordings.record_datetime = (SELECT MAX(record_datetime) FROM recordings WHERE project_flock_kandangs_id = project_flock_kandangs.id)").
|
||||
Where("recordings.record_datetime = (SELECT MAX(record_datetime) FROM recordings r2 WHERE r2.project_flock_kandangs_id IN (SELECT id FROM project_flock_kandangs WHERE project_flock_id = ?))", projectFlockID).
|
||||
Scan(&result).Error
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ type PurchaseRepository interface {
|
||||
NextPoNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
||||
BackfillProjectFlockKandang(ctx context.Context, purchaseID uint) error
|
||||
GetItemsByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.PurchaseItem, error)
|
||||
GetItemsByWarehouseKandang(ctx context.Context, projectFlockID uint) ([]entity.PurchaseItem, error)
|
||||
}
|
||||
|
||||
type PurchaseRepositoryImpl struct {
|
||||
@@ -291,13 +292,34 @@ func (r *PurchaseRepositoryImpl) numberExists(ctx context.Context, db *gorm.DB,
|
||||
}
|
||||
|
||||
func (r *PurchaseRepositoryImpl) GetItemsByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.PurchaseItem, error) {
|
||||
|
||||
return r.GetItemsByWarehouseKandang(ctx, projectFlockID)
|
||||
}
|
||||
|
||||
func (r *PurchaseRepositoryImpl) GetItemsByWarehouseKandang(ctx context.Context, projectFlockID uint) ([]entity.PurchaseItem, error) {
|
||||
var items []entity.PurchaseItem
|
||||
|
||||
var kandangIDs []uint
|
||||
err := r.DB().WithContext(ctx).
|
||||
Table("project_flock_kandangs").
|
||||
Where("project_flock_id = ?", projectFlockID).
|
||||
Pluck("kandang_id", &kandangIDs).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(kandangIDs) == 0 {
|
||||
return []entity.PurchaseItem{}, nil
|
||||
}
|
||||
|
||||
err = r.DB().WithContext(ctx).
|
||||
Preload("Product").
|
||||
Preload("Product.Flags").
|
||||
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = purchase_items.project_flock_kandang_id").
|
||||
Where("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||
Joins("JOIN warehouses ON warehouses.id = purchase_items.warehouse_id").
|
||||
Where("warehouses.kandang_id IN ?", kandangIDs).
|
||||
Find(&items).Error
|
||||
|
||||
return items, err
|
||||
}
|
||||
|
||||
|
||||
@@ -123,25 +123,42 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing
|
||||
func (s *repportService) calculateHppPricePerKg(ctx context.Context, projectFlockID uint, category string) float64 {
|
||||
totalCost := s.getTotalProjectCost(ctx, projectFlockID)
|
||||
if totalCost == 0 {
|
||||
s.Log.Warnf("HPP calculation: No cost found for project flock ID %d. Check if purchase items are linked to project_flock_kandang_id", projectFlockID)
|
||||
return 0
|
||||
}
|
||||
|
||||
chickinQty, _ := s.ChickinRepo.GetTotalChickinQtyByProjectFlockID(ctx, projectFlockID)
|
||||
depletion, _ := s.RecordingRepo.GetTotalDepletionByProjectFlockID(ctx, projectFlockID)
|
||||
avgWeight, _ := s.RecordingRepo.GetLatestAvgWeightByProjectFlockID(ctx, projectFlockID)
|
||||
chickinQty, err := s.ChickinRepo.GetTotalChickinQtyByProjectFlockID(ctx, projectFlockID)
|
||||
if err != nil {
|
||||
s.Log.Warnf("HPP calculation: Failed to get chickin qty for project flock ID %d: %v", projectFlockID, err)
|
||||
}
|
||||
|
||||
depletion, err := s.RecordingRepo.GetTotalDepletionByProjectFlockID(ctx, projectFlockID)
|
||||
if err != nil {
|
||||
s.Log.Warnf("HPP calculation: Failed to get depletion for project flock ID %d: %v", projectFlockID, err)
|
||||
}
|
||||
|
||||
avgWeight, err := s.RecordingRepo.GetLatestAvgWeightByProjectFlockID(ctx, projectFlockID)
|
||||
if err != nil {
|
||||
s.Log.Warnf("HPP calculation: Failed to get avg weight for project flock ID %d: %v", projectFlockID, err)
|
||||
}
|
||||
|
||||
var totalWeight float64
|
||||
if utils.ProjectFlockCategory(category) == utils.ProjectFlockCategoryGrowing {
|
||||
totalWeight = (chickinQty - depletion) * avgWeight
|
||||
} else {
|
||||
eggWeight, _ := s.RecordingRepo.GetTotalEggProductionWeightByProjectFlockID(ctx, projectFlockID)
|
||||
eggWeight, err := s.RecordingRepo.GetTotalEggProductionWeightByProjectFlockID(ctx, projectFlockID)
|
||||
if err != nil {
|
||||
s.Log.Warnf("HPP calculation: Failed to get egg weight for project flock ID %d: %v", projectFlockID, err)
|
||||
}
|
||||
totalWeight = (chickinQty-depletion)*avgWeight + eggWeight
|
||||
}
|
||||
|
||||
if totalWeight == 0 {
|
||||
return 0
|
||||
}
|
||||
return totalCost / totalWeight
|
||||
|
||||
hppPricePerKg := totalCost / totalWeight
|
||||
return hppPricePerKg
|
||||
}
|
||||
|
||||
func (s *repportService) getTotalProjectCost(ctx context.Context, projectFlockID uint) float64 {
|
||||
@@ -151,24 +168,30 @@ func (s *repportService) getTotalProjectCost(ctx context.Context, projectFlockID
|
||||
|
||||
purchases, err := s.PurchaseRepo.GetItemsByProjectFlockID(ctx, projectFlockID)
|
||||
if err != nil {
|
||||
s.Log.Warnf("GetItemsByProjectFlockID error: %v", err)
|
||||
s.Log.Errorf("getTotalProjectCost: GetItemsByProjectFlockID error for project flock ID %d: %v", projectFlockID, err)
|
||||
return 0
|
||||
}
|
||||
|
||||
cost := float64(0)
|
||||
purchaseCost := float64(0)
|
||||
for _, p := range purchases {
|
||||
cost += p.TotalPrice
|
||||
purchaseCost += p.TotalPrice
|
||||
}
|
||||
cost += purchaseCost
|
||||
|
||||
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(ctx, projectFlockID)
|
||||
if err != nil {
|
||||
s.Log.Warnf("GetByProjectFlockID error: %v", err)
|
||||
s.Log.Warnf("getTotalProjectCost: GetByProjectFlockID error for project flock ID %d: %v", projectFlockID, err)
|
||||
}
|
||||
|
||||
bopCost := float64(0)
|
||||
for _, r := range realizations {
|
||||
if r.ExpenseNonstock != nil && r.ExpenseNonstock.Expense != nil &&
|
||||
r.ExpenseNonstock.Expense.Category == string(utils.ExpenseCategoryBOP) {
|
||||
cost += r.Price * r.Qty
|
||||
bopCost += r.Price * r.Qty
|
||||
}
|
||||
}
|
||||
cost += bopCost
|
||||
|
||||
return cost
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user