mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 23:35:43 +00:00
feat: Refactor closing services and add ClosingKeuanganService
- Updated ClosingRoutes to include ClosingKeuanganService. - Removed GetClosingKeuangan method from ClosingService interface and its implementation. - Introduced new ClosingKeuanganService with GetClosingKeuangan method to handle financial logic. - Implemented detailed logging and error handling in the new service. - Added GetTotalWeightProducedFromUniformityByProjectFlockID method in RecordingRepository to support weight calculations. - Enhanced the logic for fetching and classifying product usage data by flags. - Built comprehensive DTO responses for HPP and Profit Loss sections.
This commit is contained in:
@@ -1,588 +0,0 @@
|
||||
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
|
||||
TotalEggWeightKg 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
|
||||
TotalEggWeightKg float64
|
||||
TotalDepletion float64
|
||||
}
|
||||
|
||||
// === BASE METRICS ===
|
||||
|
||||
type FinancialMetrics struct {
|
||||
RpPerBird float64 `json:"rp_per_bird"`
|
||||
RpPerKg float64 `json:"rp_per_kg"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
type Comparison struct {
|
||||
Budgeting FinancialMetrics `json:"budgeting"`
|
||||
Realization FinancialMetrics `json:"realization"`
|
||||
}
|
||||
|
||||
// === HPP PURCHASES PACKAGE ===
|
||||
|
||||
type HppItem struct {
|
||||
Type string `json:"type"`
|
||||
Comparison
|
||||
}
|
||||
|
||||
type HppGroup struct {
|
||||
GroupName string `json:"group_name"`
|
||||
Data []HppItem `json:"data"`
|
||||
}
|
||||
|
||||
type SummaryHpp struct {
|
||||
Label string `json:"label"`
|
||||
Budgeting FinancialMetrics `json:"budgeting"`
|
||||
Realization FinancialMetrics `json:"realization"`
|
||||
EggBudgeting *FinancialMetrics `json:"egg_budgeting,omitempty"`
|
||||
EggRealization *FinancialMetrics `json:"egg_realization,omitempty"`
|
||||
}
|
||||
|
||||
type HppPurchasesSection struct {
|
||||
Hpp []HppGroup `json:"hpp"`
|
||||
SummaryHpp SummaryHpp `json:"summary_hpp"`
|
||||
}
|
||||
|
||||
// === PROFIT LOSS PACKAGE ===
|
||||
|
||||
type PLItem struct {
|
||||
Type string `json:"type"`
|
||||
FinancialMetrics
|
||||
}
|
||||
|
||||
type PLSummaryItem struct {
|
||||
Label string `json:"label"`
|
||||
FinancialMetrics
|
||||
}
|
||||
|
||||
type PLSummaryGroup struct {
|
||||
GrossProfit PLSummaryItem `json:"gross_profit"`
|
||||
SubTotal PLSummaryItem `json:"sub_total"`
|
||||
NetProfit PLSummaryItem `json:"net_profit"`
|
||||
}
|
||||
|
||||
type ProfitLossData struct {
|
||||
Penjualan []PLItem `json:"penjualan"`
|
||||
Pembelian []PLItem `json:"pembelian"`
|
||||
Overhead PLItem `json:"overhead"`
|
||||
Ekspedisi PLItem `json:"ekspedisi"`
|
||||
Summary PLSummaryGroup `json:"summary"`
|
||||
}
|
||||
|
||||
type ProfitLossSection struct {
|
||||
Data ProfitLossData `json:"data"`
|
||||
}
|
||||
|
||||
// === RESPONSE DTO (ROOT) ===
|
||||
|
||||
type ReportResponse struct {
|
||||
HppPurchases HppPurchasesSection `json:"hpp_purchases"`
|
||||
ProfitLoss ProfitLossSection `json:"profit_loss"`
|
||||
}
|
||||
|
||||
// === MAPPER FUNCTIONS ===
|
||||
|
||||
func ToFinancialMetrics(rpPerBird, rpPerKg, amount float64) FinancialMetrics {
|
||||
return FinancialMetrics{
|
||||
RpPerBird: rpPerBird,
|
||||
RpPerKg: rpPerKg,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
func ToComparison(budgeting, realization FinancialMetrics) Comparison {
|
||||
return Comparison{
|
||||
Budgeting: budgeting,
|
||||
Realization: realization,
|
||||
}
|
||||
}
|
||||
|
||||
// === HPP PENGELUARAN (from Purchase Items) ===
|
||||
|
||||
func getFlagLabel(flagType utils.FlagType) string {
|
||||
return PurchaseLabelPrefix + string(flagType)
|
||||
}
|
||||
|
||||
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,
|
||||
utils.FlagOVK, utils.FlagObat, utils.FlagVitamin, utils.FlagKimia,
|
||||
}
|
||||
|
||||
items := []HppItem{}
|
||||
seenFlags := make(map[utils.FlagType]bool)
|
||||
|
||||
for _, item := range purchaseItems {
|
||||
if item.Product == nil || len(item.Product.Flags) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, flag := range item.Product.Flags {
|
||||
flagType := utils.FlagType(flag.Name)
|
||||
|
||||
if slices.Contains(flags, flagType) && !seenFlags[flagType] {
|
||||
amount := sumPurchasesByFlag(purchaseItems, flagType)
|
||||
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),
|
||||
),
|
||||
})
|
||||
seenFlags[flagType] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
// === HPP BAHAN BAKU (from ProjectBudget + ExpenseRealization) ===
|
||||
|
||||
func createHppOverheadItem(budgetAmount, realizationAmount float64, ctx CalculationContext) HppItem {
|
||||
budgetRpPerBird, budgetRpPerKg := calculatePerUnitMetrics(budgetAmount, ctx.TotalPopulation, ctx.TotalWeightProduced)
|
||||
realizationRpPerBird, realizationRpPerKg := calculatePerUnitMetrics(realizationAmount, ctx.TotalPopulation, ctx.TotalWeightProduced)
|
||||
|
||||
return HppItem{
|
||||
Type: HPPLabelOverhead,
|
||||
Comparison: ToComparison(
|
||||
ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, budgetAmount),
|
||||
ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, realizationAmount),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func createHppEkspedisiItem(ekspedisiAmount float64, ctx CalculationContext) HppItem {
|
||||
ekspedisiRpPerBird, ekspedisiRpPerKg := calculatePerUnitMetrics(ekspedisiAmount, ctx.TotalPopulation, ctx.TotalWeightProduced)
|
||||
|
||||
return HppItem{
|
||||
Type: HPPLabelEkspedisi,
|
||||
Comparison: ToComparison(
|
||||
ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount),
|
||||
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: HPPGroupBahanBaku,
|
||||
Data: items,
|
||||
}
|
||||
}
|
||||
|
||||
// === HPP SUMMARY ===
|
||||
|
||||
func ToSummaryHpp(label string, purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, projectFlockCategory string, ctx CalculationContext) SummaryHpp {
|
||||
purchaseTotal := sumPurchaseTotal(purchaseItems)
|
||||
budgetTotal := sumBudgetsByFilter(budgets, func(*entities.ProjectBudget) bool { return true })
|
||||
totalBudget := purchaseTotal + budgetTotal
|
||||
|
||||
totalRealization := sumRealizationsByFilter(realizations, func(*entities.ExpenseRealization) bool { return true })
|
||||
|
||||
budgetRpPerBird, budgetRpPerKg := calculatePerUnitMetrics(totalBudget, ctx.TotalPopulation, ctx.TotalWeightProduced)
|
||||
realizationRpPerBird, realizationRpPerKg := calculatePerUnitMetrics(totalRealization, ctx.TotalPopulation, ctx.TotalWeightProduced)
|
||||
|
||||
summary := SummaryHpp{
|
||||
Label: label,
|
||||
Budgeting: ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, totalBudget),
|
||||
Realization: ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, totalRealization),
|
||||
}
|
||||
|
||||
if projectFlockCategory == string(utils.ProjectFlockCategoryLaying) && ctx.TotalEggWeightKg > 0 {
|
||||
budgetEggRpPerKg, _ := calculatePerUnitMetrics(totalBudget, 0, ctx.TotalEggWeightKg)
|
||||
realizationEggRpPerKg, _ := calculatePerUnitMetrics(totalRealization, 0, ctx.TotalEggWeightKg)
|
||||
|
||||
summary.EggBudgeting = &FinancialMetrics{
|
||||
RpPerBird: 0,
|
||||
RpPerKg: budgetEggRpPerKg,
|
||||
Amount: totalBudget,
|
||||
}
|
||||
summary.EggRealization = &FinancialMetrics{
|
||||
RpPerBird: 0,
|
||||
RpPerKg: realizationEggRpPerKg,
|
||||
Amount: totalRealization,
|
||||
}
|
||||
}
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
func ToHppPurchasesSection(purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, projectFlockCategory string, ctx CalculationContext) HppPurchasesSection {
|
||||
hppGroups := []HppGroup{
|
||||
{
|
||||
GroupName: HPPGroupPengeluaran,
|
||||
Data: buildHppItemsByPurchaseFlags(purchaseItems, ctx),
|
||||
},
|
||||
ToHppBahanBakuGroup(budgets, realizations, ctx),
|
||||
}
|
||||
|
||||
summaryHpp := ToSummaryHpp(HPPSummaryLabel, purchaseItems, budgets, realizations, projectFlockCategory, ctx)
|
||||
|
||||
return HppPurchasesSection{
|
||||
Hpp: hppGroups,
|
||||
SummaryHpp: summaryHpp,
|
||||
}
|
||||
}
|
||||
|
||||
// === PROFIT & LOSS ===
|
||||
|
||||
func ToPLItem(itemType string, metrics FinancialMetrics) PLItem {
|
||||
return PLItem{
|
||||
Type: itemType,
|
||||
FinancialMetrics: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
func ToPLSummaryItem(label string, metrics FinancialMetrics) PLSummaryItem {
|
||||
return PLSummaryItem{
|
||||
Label: label,
|
||||
FinancialMetrics: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
totalPerBird += item.RpPerBird
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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{}
|
||||
|
||||
categorized := categorizeDeliveriesBySalesType(deliveryProducts)
|
||||
|
||||
if projectFlockCategory == string(utils.ProjectFlockCategoryLaying) {
|
||||
ayamAmount := sumDeliveriesByCategory(categorized[PLSalesTypeChicken])
|
||||
telurAmount := sumDeliveriesByCategory(categorized[PLSalesTypeEgg])
|
||||
|
||||
items = append(items, createPenjualanItem(PLSalesTypeChicken, ayamAmount, ctx))
|
||||
items = append(items, createPenjualanItem(PLSalesTypeEgg, telurAmount, ctx))
|
||||
} else {
|
||||
ayamAmount := sumDeliveriesByCategory(categorized[PLSalesTypeChicken])
|
||||
items = append(items, createPenjualanItem(PLSalesTypeChicken, ayamAmount, ctx))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func ToPembelianItems(purchases []entities.PurchaseItem, realizations []entities.ExpenseRealization, ctx CalculationContext) []PLItem {
|
||||
purchaseAmount := sumPurchaseTotal(purchases)
|
||||
|
||||
return []PLItem{
|
||||
createPLItemWithMetrics(PLItemTypeSapronak, purchaseAmount, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func ToOverheadItems(realizations []entities.ExpenseRealization, ctx CalculationContext) []PLItem {
|
||||
realizationAmount := getOperationalExpenses(realizations)
|
||||
return []PLItem{
|
||||
createPLItemWithMetrics(PLItemTypeOverhead, realizationAmount, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func ToEkspedisiItems(realizations []entities.ExpenseRealization, ctx CalculationContext) []PLItem {
|
||||
amount := sumRealizationsByFilter(realizations, filterRealizationByNonstockFlag(utils.FlagEkspedisi))
|
||||
return []PLItem{
|
||||
createPLItemWithMetrics(PLItemTypeEkspedisi, amount, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func ToPLSummaryGroup(penjualanItems, pembelianItems, overheadItems, ekspedisiItems []PLItem) PLSummaryGroup {
|
||||
totalPenjualan, totalPenjualanPerBird := sumPLItems(penjualanItems)
|
||||
totalPembelian, totalPembelianPerBird := sumPLItems(pembelianItems)
|
||||
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 - totalOtherExpensesPerBird
|
||||
|
||||
return PLSummaryGroup{
|
||||
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)
|
||||
|
||||
totalOverhead := aggregatePLItems(overheadItems, PLItemTypeOverhead)
|
||||
totalEkspedisi := aggregatePLItems(ekspedisiItems, PLItemTypeEkspedisi)
|
||||
|
||||
return ProfitLossData{
|
||||
Penjualan: penjualanItems,
|
||||
Pembelian: pembelianItems,
|
||||
Overhead: totalOverhead,
|
||||
Ekspedisi: totalEkspedisi,
|
||||
Summary: summary,
|
||||
}
|
||||
}
|
||||
|
||||
func ToProfitLossSection(penjualanItems, pembelianItems, overheadItems, ekspedisiItems []PLItem) ProfitLossSection {
|
||||
return ProfitLossSection{
|
||||
Data: ToProfitLossData(penjualanItems, pembelianItems, overheadItems, ekspedisiItems),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
ProfitLoss: profitLoss,
|
||||
}
|
||||
}
|
||||
|
||||
func ToClosingKeuanganReport(input ClosingKeuanganInput) ReportResponse {
|
||||
var totalPopulation float64
|
||||
var totalWeightSold float64
|
||||
|
||||
for _, chickin := range input.Chickins {
|
||||
totalPopulation += chickin.UsageQty
|
||||
}
|
||||
|
||||
for _, delivery := range input.DeliveryProducts {
|
||||
totalWeightSold += delivery.TotalWeight
|
||||
}
|
||||
|
||||
ctx := CalculationContext{
|
||||
TotalPopulation: totalPopulation,
|
||||
TotalWeightProduced: input.TotalWeightProduced,
|
||||
TotalEggWeightKg: input.TotalEggWeightKg,
|
||||
TotalDepletion: input.TotalDepletion,
|
||||
TotalWeightSold: totalWeightSold,
|
||||
ActualPopulation: totalPopulation - input.TotalDepletion,
|
||||
}
|
||||
|
||||
hppSection := ToHppPurchasesSection(input.PurchaseItems, input.Budgets, input.Realizations, input.ProjectFlockCategory, 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)
|
||||
}
|
||||
|
||||
// === HELPER FUNCTIONS ===
|
||||
|
||||
func calculatePerUnitMetrics(amount, totalPopulation, totalWeightSold float64) (rpPerBird, rpPerKg float64) {
|
||||
if totalPopulation > 0 {
|
||||
rpPerBird = amount / totalPopulation
|
||||
}
|
||||
if totalWeightSold > 0 {
|
||||
rpPerKg = amount / totalWeightSold
|
||||
}
|
||||
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
|
||||
}
|
||||
return hasProductFlag(item.Product.Flags, flagType)
|
||||
}
|
||||
}
|
||||
|
||||
func filterRealizationByNonstockFlag(flagType utils.FlagType) func(*entities.ExpenseRealization) bool {
|
||||
return func(realization *entities.ExpenseRealization) bool {
|
||||
if realization.ExpenseNonstock == nil || realization.ExpenseNonstock.Nonstock == nil {
|
||||
return false
|
||||
}
|
||||
return hasProductFlag(realization.ExpenseNonstock.Nonstock.Flags, flagType)
|
||||
}
|
||||
}
|
||||
|
||||
func filterRealizationExceptFlag(flagType utils.FlagType) func(*entities.ExpenseRealization) bool {
|
||||
hasFlag := filterRealizationByNonstockFlag(flagType)
|
||||
return func(realization *entities.ExpenseRealization) bool {
|
||||
return !hasFlag(realization)
|
||||
}
|
||||
}
|
||||
|
||||
func sumByFilter[T any](items []T, extractor func(*T) float64, filter func(*T) bool) float64 {
|
||||
amount := 0.0
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
switch flagType {
|
||||
case utils.FlagDOC, utils.FlagPullet, utils.FlagLayer,
|
||||
utils.FlagAyamAfkir, utils.FlagAyamCulling, utils.FlagAyamMati:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEggProductFlag(flagType utils.FlagType) bool {
|
||||
switch flagType {
|
||||
case utils.FlagTelur, utils.FlagTelurUtuh, utils.FlagTelurPecah,
|
||||
utils.FlagTelurPutih, utils.FlagTelurRetak:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getSalesTypeFromProductFlags(product *entities.Product) string {
|
||||
if product == nil || len(product.Flags) == 0 {
|
||||
return PLSalesTypeChicken
|
||||
}
|
||||
|
||||
for _, flag := range product.Flags {
|
||||
flagType := utils.FlagType(strings.ToUpper(flag.Name))
|
||||
|
||||
if isEggProductFlag(flagType) {
|
||||
return PLSalesTypeEgg
|
||||
}
|
||||
if isChickenProductFlag(flagType) {
|
||||
return PLSalesTypeChicken
|
||||
}
|
||||
}
|
||||
|
||||
return PLSalesTypeChicken
|
||||
}
|
||||
|
||||
func categorizeDeliveriesBySalesType(deliveries []entities.MarketingDeliveryProduct) map[string][]entities.MarketingDeliveryProduct {
|
||||
categorized := make(map[string][]entities.MarketingDeliveryProduct)
|
||||
|
||||
for _, delivery := range deliveries {
|
||||
product := delivery.MarketingProduct.ProductWarehouse.Product
|
||||
salesType := getSalesTypeFromProductFlags(&product)
|
||||
|
||||
categorized[salesType] = append(categorized[salesType], delivery)
|
||||
}
|
||||
|
||||
return categorized
|
||||
}
|
||||
|
||||
func sumDeliveriesByCategory(deliveries []entities.MarketingDeliveryProduct) float64 {
|
||||
amount := 0.0
|
||||
for _, delivery := range deliveries {
|
||||
amount += delivery.TotalPrice
|
||||
}
|
||||
return amount
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package dto
|
||||
|
||||
// === NEW CLOSING KEUANGAN DTO ===
|
||||
|
||||
// FinancialMetrics represents financial metrics with per unit and total amounts
|
||||
type FinancialMetrics struct {
|
||||
RpPerBird float64 `json:"rp_per_bird"`
|
||||
RpPerKg float64 `json:"rp_per_kg"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
// HPPItem represents an item in HPP section
|
||||
type HPPItem struct {
|
||||
ID uint `json:"id"`
|
||||
Category string `json:"category"` // "purchase" or "overhead"
|
||||
Code string `json:"code"` // "PAKAN", "OVK", "DOC", "EKSPEDISI"
|
||||
Label string `json:"label"`
|
||||
Budgeting FinancialMetrics `json:"budgeting"`
|
||||
Realization FinancialMetrics `json:"realization"`
|
||||
}
|
||||
|
||||
// HPPSummary represents summary for HPP section
|
||||
type HPPSummary struct {
|
||||
Label string `json:"label"`
|
||||
Budgeting FinancialMetrics `json:"budgeting"`
|
||||
Realization FinancialMetrics `json:"realization"`
|
||||
EggBudgeting *FinancialMetrics `json:"egg_budgeting,omitempty"`
|
||||
EggRealization *FinancialMetrics `json:"egg_realization,omitempty"`
|
||||
}
|
||||
|
||||
// HPPSection represents HPP data section
|
||||
type HPPSection struct {
|
||||
Items []HPPItem `json:"items"`
|
||||
Summary HPPSummary `json:"summary"`
|
||||
}
|
||||
|
||||
// ProfitLossItem represents an item in Profit & Loss section
|
||||
type ProfitLossItem struct {
|
||||
Code string `json:"code"` // "SALES", "PURCHASE_DOC", "OVERHEAD", "EKSPEDISI"
|
||||
Label string `json:"label"`
|
||||
Type string `json:"type"` // "income", "purchase", "overhead"
|
||||
RpPerBird float64 `json:"rp_per_bird"`
|
||||
RpPerKg float64 `json:"rp_per_kg"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
// ProfitLossSummary represents summary for Profit & Loss section
|
||||
type ProfitLossSummary struct {
|
||||
GrossProfit FinancialMetrics `json:"gross_profit"`
|
||||
SubTotal FinancialMetrics `json:"sub_total"`
|
||||
NetProfit FinancialMetrics `json:"net_profit"`
|
||||
}
|
||||
|
||||
// ProfitLossSection represents Profit & Loss data section
|
||||
type ProfitLossSection struct {
|
||||
Items []ProfitLossItem `json:"items"`
|
||||
Summary ProfitLossSummary `json:"summary"`
|
||||
}
|
||||
|
||||
// ClosingKeuanganData represents the main data structure
|
||||
type ClosingKeuanganData struct {
|
||||
HPP HPPSection `json:"hpp"`
|
||||
ProfitLoss ProfitLossSection `json:"profit_loss"`
|
||||
}
|
||||
|
||||
// ClosingKeuanganResponse represents the full API response
|
||||
type ClosingKeuanganResponse struct {
|
||||
Code int `json:"code"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data ClosingKeuanganData `json:"data"`
|
||||
}
|
||||
|
||||
// === MAPPER FUNCTIONS ===
|
||||
|
||||
// ToFinancialMetrics creates FinancialMetrics from values
|
||||
func ToFinancialMetrics(rpPerBird, rpPerKg, amount float64) FinancialMetrics {
|
||||
return FinancialMetrics{
|
||||
RpPerBird: rpPerBird,
|
||||
RpPerKg: rpPerKg,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
// ToHPPItem creates HPP item
|
||||
func ToHPPItem(id uint, category, code, label string, budgeting, realization FinancialMetrics) HPPItem {
|
||||
return HPPItem{
|
||||
ID: id,
|
||||
Category: category,
|
||||
Code: code,
|
||||
Label: label,
|
||||
Budgeting: budgeting,
|
||||
Realization: realization,
|
||||
}
|
||||
}
|
||||
|
||||
// ToHPPSummary creates HPP summary
|
||||
func ToHPPSummary(label string, budgeting, realization FinancialMetrics, eggBudgeting, eggRealization *FinancialMetrics) HPPSummary {
|
||||
return HPPSummary{
|
||||
Label: label,
|
||||
Budgeting: budgeting,
|
||||
Realization: realization,
|
||||
EggBudgeting: eggBudgeting,
|
||||
EggRealization: eggRealization,
|
||||
}
|
||||
}
|
||||
|
||||
// ToHPPSection creates HPP section
|
||||
func ToHPPSection(items []HPPItem, summary HPPSummary) HPPSection {
|
||||
return HPPSection{
|
||||
Items: items,
|
||||
Summary: summary,
|
||||
}
|
||||
}
|
||||
|
||||
// ToProfitLossItem creates Profit & Loss item
|
||||
func ToProfitLossItem(code, label, itemType string, rpPerBird, rpPerKg, amount float64) ProfitLossItem {
|
||||
return ProfitLossItem{
|
||||
Code: code,
|
||||
Label: label,
|
||||
Type: itemType,
|
||||
RpPerBird: rpPerBird,
|
||||
RpPerKg: rpPerKg,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
// ToProfitLossSummary creates Profit & Loss summary
|
||||
func ToProfitLossSummary(grossProfit, subTotal, netProfit FinancialMetrics) ProfitLossSummary {
|
||||
return ProfitLossSummary{
|
||||
GrossProfit: grossProfit,
|
||||
SubTotal: subTotal,
|
||||
NetProfit: netProfit,
|
||||
}
|
||||
}
|
||||
|
||||
// ToProfitLossSection creates Profit & Loss section
|
||||
func ToProfitLossSection(items []ProfitLossItem, summary ProfitLossSummary) ProfitLossSection {
|
||||
return ProfitLossSection{
|
||||
Items: items,
|
||||
Summary: summary,
|
||||
}
|
||||
}
|
||||
|
||||
// ToClosingKeuanganData creates complete closing keuangan data
|
||||
func ToClosingKeuanganData(hpp HPPSection, profitLoss ProfitLossSection) ClosingKeuanganData {
|
||||
return ClosingKeuanganData{
|
||||
HPP: hpp,
|
||||
ProfitLoss: profitLoss,
|
||||
}
|
||||
}
|
||||
|
||||
// ToSuccessClosingKeuanganResponse creates success response
|
||||
func ToSuccessClosingKeuanganResponse(data ClosingKeuanganData) ClosingKeuanganResponse {
|
||||
return ClosingKeuanganResponse{
|
||||
Code: 200,
|
||||
Status: "success",
|
||||
Message: "Get closing keuangan successfully",
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user