package dto import ( "encoding/json" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" ) // === DTO Structs === type OverheadDTO struct { ItemName string `json:"item_name"` UOMName string `json:"uom_name"` BudgetQuantity float64 `json:"budget_quantity"` BudgetUnitPrice float64 `json:"budget_unit_price"` BudgetTotalAmount float64 `json:"budget_total_amount"` ActualDate string `json:"actual_date"` ActualQuantity float64 `json:"actual_quantity"` ActualUnitPrice float64 `json:"actual_unit_price"` ActualTotalAmount float64 `json:"actual_total_amount"` CostPerBird float64 `json:"cost_per_bird"` } type TotalDTO struct { BudgetQuantity float64 `json:"budget_quantity"` BudgetTotalAmount float64 `json:"budget_total_amount"` ActualQuantity float64 `json:"actual_quantity"` ActualTotalAmount float64 `json:"actual_total_amount"` CostPerBird float64 `json:"cost_per_bird"` } type OverheadListDTO struct { Total TotalDTO `json:"total"` Overheads []OverheadDTO `json:"overheads"` } // === Mapper Functions === func ToOverheadDTO(budget *entity.ProjectBudget, realization *entity.ExpenseRealization) OverheadDTO { if budget == nil && realization == nil { return OverheadDTO{} } var itemName, itemUOM string if budget != nil { itemName, itemUOM = getItemInfo(budget.Nonstock) } if itemName == "" && realization != nil && realization.ExpenseNonstock != nil { itemName, itemUOM = getItemInfo(realization.ExpenseNonstock.Nonstock) } dto := OverheadDTO{ ItemName: itemName, UOMName: itemUOM, } if budget != nil { dto.BudgetQuantity = budget.Qty dto.BudgetUnitPrice = budget.Price dto.BudgetTotalAmount = calculateTotal(budget.Qty, budget.Price) } if realization != nil { dto.ActualQuantity = realization.Qty dto.ActualUnitPrice = realization.Price dto.ActualTotalAmount = calculateTotal(realization.Qty, realization.Price) dto.ActualDate = formatRealizationDate(realization) } return dto } func ToOverheadListDTOs(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalChickinQty, totalActualPopulation float64, isPerKandang bool, totalKandangCount int, projectFlockKandangCountMap map[uint]int) OverheadListDTO { overheadsByNonstockID := make(map[uint]*OverheadDTO) latestDateByNonstockID := make(map[uint]string) for i := range budgets { nonstockID := budgets[i].NonstockId if overheadsByNonstockID[nonstockID] == nil { overheadsByNonstockID[nonstockID] = &OverheadDTO{} } itemName, itemUOM := getItemInfo(budgets[i].Nonstock) overheadsByNonstockID[nonstockID].ItemName = itemName overheadsByNonstockID[nonstockID].UOMName = itemUOM budgetQty := budgets[i].Qty budgetPrice := budgets[i].Price budgetTotal := calculateTotal(budgets[i].Qty, budgets[i].Price) // Budget division: per kandang view only if isPerKandang && totalKandangCount > 0 { budgetQty = budgetQty / float64(totalKandangCount) budgetTotal = budgetTotal / float64(totalKandangCount) } overheadsByNonstockID[nonstockID].BudgetQuantity = budgetQty overheadsByNonstockID[nonstockID].BudgetUnitPrice = budgetPrice overheadsByNonstockID[nonstockID].BudgetTotalAmount = budgetTotal } for i := range realizations { if realizations[i].ExpenseNonstock == nil || realizations[i].ExpenseNonstock.NonstockId == nil { continue } nonstockID := uint(*realizations[i].ExpenseNonstock.NonstockId) if overheadsByNonstockID[nonstockID] == nil { overheadsByNonstockID[nonstockID] = &OverheadDTO{} } qty := realizations[i].Qty totalAmount := calculateTotal(realizations[i].Qty, realizations[i].Price) // Farm-level expense division if realizations[i].ExpenseNonstock.Expense != nil && realizations[i].ExpenseNonstock.Expense.ProjectFlockId != nil { projectFlockIDs := parseProjectFlockIDsFromJSON(*realizations[i].ExpenseNonstock.Expense.ProjectFlockId) if len(projectFlockIDs) > 0 { totalKandangInAllProjects := 0 for _, pfID := range projectFlockIDs { if count, exists := projectFlockKandangCountMap[pfID]; exists { totalKandangInAllProjects += count } } if totalKandangInAllProjects > 0 { if isPerKandang { qty = qty / float64(totalKandangInAllProjects) totalAmount = totalAmount / float64(totalKandangInAllProjects) } else { // Overhead ALL: divide by total kandang then multiply by this project's kandang count perKandangAmount := totalAmount / float64(totalKandangInAllProjects) perKandangQty := qty / float64(totalKandangInAllProjects) qty = perKandangQty * float64(totalKandangCount) totalAmount = perKandangAmount * float64(totalKandangCount) } } } } overheadsByNonstockID[nonstockID].ActualQuantity += qty overheadsByNonstockID[nonstockID].ActualTotalAmount += totalAmount if overheadsByNonstockID[nonstockID].ItemName == "" { itemName, itemUOM := getItemInfo(realizations[i].ExpenseNonstock.Nonstock) overheadsByNonstockID[nonstockID].ItemName = itemName overheadsByNonstockID[nonstockID].UOMName = itemUOM } realizationDateStr := formatRealizationDate(&realizations[i]) if realizationDateStr != "" { if latestDateByNonstockID[nonstockID] == "" || realizationDateStr > latestDateByNonstockID[nonstockID] { latestDateByNonstockID[nonstockID] = realizationDateStr } } } var totalBudgetQuantity, totalBudgetAmount, totalActualQuantity, totalActualAmount float64 overheadItems := make([]OverheadDTO, 0, len(overheadsByNonstockID)) for nonstockID, overhead := range overheadsByNonstockID { overhead.ActualDate = latestDateByNonstockID[nonstockID] overhead.CostPerBird = calculateCostPerBird(overhead.ActualTotalAmount, totalActualPopulation) if overhead.ActualQuantity > 0 { overhead.ActualUnitPrice = overhead.ActualTotalAmount / overhead.ActualQuantity } totalBudgetQuantity += overhead.BudgetQuantity totalBudgetAmount += overhead.BudgetTotalAmount totalActualQuantity += overhead.ActualQuantity totalActualAmount += overhead.ActualTotalAmount overheadItems = append(overheadItems, *overhead) } return OverheadListDTO{ Total: TotalDTO{ BudgetQuantity: totalBudgetQuantity, BudgetTotalAmount: totalBudgetAmount, ActualQuantity: totalActualQuantity, ActualTotalAmount: totalActualAmount, CostPerBird: calculateCostPerBird(totalActualAmount, totalActualPopulation), }, Overheads: overheadItems, } } func parseProjectFlockIDsFromJSON(projectFlockJSON string) []uint { if projectFlockJSON == "" { return []uint{} } var projectFlocks []uint if err := json.Unmarshal([]byte(projectFlockJSON), &projectFlocks); err != nil { return []uint{} } return projectFlocks } func countProjectFlocksInJSON(projectFlockJSON string) int { projectFlocks := parseProjectFlockIDsFromJSON(projectFlockJSON) if len(projectFlocks) == 0 { return 1 } return len(projectFlocks) } func getItemInfo(nonstock *entity.Nonstock) (string, string) { if nonstock != nil && nonstock.Id != 0 { return nonstock.Name, nonstock.Uom.Name } return "", "" } func calculateTotal(qty, price float64) float64 { return qty * price } func calculateCostPerBird(totalPrice, totalActualPopulation float64) float64 { if totalActualPopulation > 0 { return totalPrice / totalActualPopulation } return 0 } func formatRealizationDate(realization *entity.ExpenseRealization) string { if realization != nil && realization.ExpenseNonstock != nil && realization.ExpenseNonstock.Expense != nil { if !realization.ExpenseNonstock.Expense.RealizationDate.IsZero() { return realization.ExpenseNonstock.Expense.RealizationDate.Format("2006-01-02T15:04:05Z07:00") } } return "" }