mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 15:25:43 +00:00
feat[BE]: membetulkan perhitungan hpp di module penjualan harian
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
// === BASE METRICS ===
|
// === BASE METRICS ===
|
||||||
type FinancialMetrics struct {
|
type FinancialMetrics struct {
|
||||||
RpPerBird float64 `json:"rp_per_bird"`
|
RpPerBird float64 `json:"rp_per_bird"`
|
||||||
@@ -28,9 +35,7 @@ type SummaryHpp struct {
|
|||||||
Comparison
|
Comparison
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ini adalah struct mandiri untuk bagian HPP Purchases
|
|
||||||
type HppPurchasesSection struct {
|
type HppPurchasesSection struct {
|
||||||
Title string `json:"title"`
|
|
||||||
Hpp []HppGroup `json:"hpp"`
|
Hpp []HppGroup `json:"hpp"`
|
||||||
SummaryHpp SummaryHpp `json:"summary_hpp"`
|
SummaryHpp SummaryHpp `json:"summary_hpp"`
|
||||||
}
|
}
|
||||||
@@ -58,14 +63,11 @@ type ProfitLossData struct {
|
|||||||
Summary PLSummaryGroup `json:"summary"`
|
Summary PLSummaryGroup `json:"summary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ini adalah struct mandiri untuk bagian Profit Loss
|
|
||||||
type ProfitLossSection struct {
|
type ProfitLossSection struct {
|
||||||
Title string `json:"title"`
|
Data ProfitLossData `json:"data"`
|
||||||
Data ProfitLossData `json:"data"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === RESPONSE DTO (ROOT) ===
|
// === RESPONSE DTO (ROOT) ===
|
||||||
// Sekarang Root-nya terlihat sangat bersih dan tidak "janggal" lagi
|
|
||||||
type ReportResponse struct {
|
type ReportResponse struct {
|
||||||
HppPurchases HppPurchasesSection `json:"hpp_purchases"`
|
HppPurchases HppPurchasesSection `json:"hpp_purchases"`
|
||||||
ProfitLoss ProfitLossSection `json:"profit_loss"`
|
ProfitLoss ProfitLossSection `json:"profit_loss"`
|
||||||
@@ -73,7 +75,6 @@ type ReportResponse struct {
|
|||||||
|
|
||||||
// === MAPPER FUNCTIONS ===
|
// === MAPPER FUNCTIONS ===
|
||||||
|
|
||||||
// FinancialMetrics Mappers
|
|
||||||
func ToFinancialMetrics(rpPerBird, rpPerKg, amount float64) FinancialMetrics {
|
func ToFinancialMetrics(rpPerBird, rpPerKg, amount float64) FinancialMetrics {
|
||||||
return FinancialMetrics{
|
return FinancialMetrics{
|
||||||
RpPerBird: rpPerBird,
|
RpPerBird: rpPerBird,
|
||||||
@@ -82,7 +83,6 @@ func ToFinancialMetrics(rpPerBird, rpPerKg, amount float64) FinancialMetrics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comparison Mappers
|
|
||||||
func ToComparison(budgeting, realization FinancialMetrics) Comparison {
|
func ToComparison(budgeting, realization FinancialMetrics) Comparison {
|
||||||
return Comparison{
|
return Comparison{
|
||||||
Budgeting: budgeting,
|
Budgeting: budgeting,
|
||||||
@@ -90,40 +90,141 @@ func ToComparison(budgeting, realization FinancialMetrics) Comparison {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HppItem Mappers
|
// === HPP PENGELUARAN (from Purchase Items) ===
|
||||||
func ToHppItem(itemType string, comparison Comparison) HppItem {
|
|
||||||
return HppItem{
|
func getFlagLabel(flagType utils.FlagType) string {
|
||||||
Type: itemType,
|
return "Pembelian " + string(flagType)
|
||||||
Comparison: comparison,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HppGroup Mappers
|
func buildHppItemsByPurchaseFlags(purchaseItems []entities.PurchaseItem, totalWeightSold, totalPopulation float64) []HppItem {
|
||||||
func ToHppGroup(groupName string, items []HppItem) HppGroup {
|
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)
|
||||||
|
|
||||||
|
// Check if valid flag and not processed
|
||||||
|
isValid := false
|
||||||
|
for _, validFlag := range flags {
|
||||||
|
if validFlag == flagType {
|
||||||
|
isValid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid && !seenFlags[flagType] {
|
||||||
|
amount := sumPurchasesByFlag(purchaseItems, flagType)
|
||||||
|
rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightSold)
|
||||||
|
|
||||||
|
items = append(items, HppItem{
|
||||||
|
Type: getFlagLabel(flagType),
|
||||||
|
Comparison: ToComparison(
|
||||||
|
ToFinancialMetrics(rpPerBird, rpPerKg, amount),
|
||||||
|
ToFinancialMetrics(rpPerBird, rpPerKg, amount), // Same for purchase
|
||||||
|
),
|
||||||
|
})
|
||||||
|
seenFlags[flagType] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
// === HPP BAHAN BAKU (from ProjectBudget + ExpenseRealization) ===
|
||||||
|
|
||||||
|
func ToHppBahanBakuGroup(budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightSold, 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)
|
||||||
|
|
||||||
|
if budgetAmount > 0 || realizationAmount > 0 {
|
||||||
|
items = append(items, HppItem{
|
||||||
|
Type: "Pengeluaran Overhead",
|
||||||
|
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, totalWeightSold)
|
||||||
|
|
||||||
|
if ekspedisiAmount > 0 {
|
||||||
|
items = append(items, HppItem{
|
||||||
|
Type: "Beban Ekspedisi",
|
||||||
|
Comparison: ToComparison(
|
||||||
|
ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount),
|
||||||
|
ToFinancialMetrics(ekspedisiRpPerBird, ekspedisiRpPerKg, ekspedisiAmount), // Same as realization
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return HppGroup{
|
return HppGroup{
|
||||||
GroupName: groupName,
|
GroupName: "HPP dan Bahan Baku",
|
||||||
Data: items,
|
Data: items,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SummaryHpp Mappers
|
// === HPP SUMMARY ===
|
||||||
func ToSummaryHpp(label string, comparison Comparison) SummaryHpp {
|
|
||||||
|
func ToSummaryHpp(label string, purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightSold, totalPopulation float64) SummaryHpp {
|
||||||
|
// Budget: purchases + budgets
|
||||||
|
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, totalWeightSold)
|
||||||
|
realizationRpPerBird, realizationRpPerKg := calculatePerUnitMetrics(totalRealization, totalPopulation, totalWeightSold)
|
||||||
|
|
||||||
return SummaryHpp{
|
return SummaryHpp{
|
||||||
Label: label,
|
Label: label,
|
||||||
Comparison: comparison,
|
Comparison: ToComparison(
|
||||||
|
ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, totalBudget),
|
||||||
|
ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, totalRealization),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HppPurchasesSection Mappers
|
func ToHppPurchasesSection(purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightSold, totalPopulation float64) HppPurchasesSection {
|
||||||
func ToHppPurchasesSection(title string, hppGroups []HppGroup, summaryHpp SummaryHpp) HppPurchasesSection {
|
hppGroups := []HppGroup{
|
||||||
|
{
|
||||||
|
GroupName: "HPP dan Pengeluaran",
|
||||||
|
Data: buildHppItemsByPurchaseFlags(purchaseItems, totalWeightSold, totalPopulation),
|
||||||
|
},
|
||||||
|
ToHppBahanBakuGroup(budgets, realizations, totalWeightSold, totalPopulation),
|
||||||
|
}
|
||||||
|
|
||||||
|
summaryHpp := ToSummaryHpp("HPP", purchaseItems, budgets, realizations, totalWeightSold, totalPopulation)
|
||||||
|
|
||||||
return HppPurchasesSection{
|
return HppPurchasesSection{
|
||||||
Title: title,
|
|
||||||
Hpp: hppGroups,
|
Hpp: hppGroups,
|
||||||
SummaryHpp: summaryHpp,
|
SummaryHpp: summaryHpp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PLItem Mappers
|
// === PROFIT & LOSS ===
|
||||||
|
|
||||||
func ToPLItem(itemType string, metrics FinancialMetrics) PLItem {
|
func ToPLItem(itemType string, metrics FinancialMetrics) PLItem {
|
||||||
return PLItem{
|
return PLItem{
|
||||||
Type: itemType,
|
Type: itemType,
|
||||||
@@ -131,7 +232,6 @@ func ToPLItem(itemType string, metrics FinancialMetrics) PLItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PLSummaryItem Mappers
|
|
||||||
func ToPLSummaryItem(label string, metrics FinancialMetrics) PLSummaryItem {
|
func ToPLSummaryItem(label string, metrics FinancialMetrics) PLSummaryItem {
|
||||||
return PLSummaryItem{
|
return PLSummaryItem{
|
||||||
Label: label,
|
Label: label,
|
||||||
@@ -139,33 +239,106 @@ func ToPLSummaryItem(label string, metrics FinancialMetrics) PLSummaryItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PLSummaryGroup Mappers
|
func sumPLItems(items []PLItem) (totalAmount, totalPerBird float64) {
|
||||||
func ToPLSummaryGroup(grossProfit, subTotal, netProfit PLSummaryItem) PLSummaryGroup {
|
for _, item := range items {
|
||||||
return PLSummaryGroup{
|
totalAmount += item.Amount
|
||||||
GrossProfit: grossProfit,
|
totalPerBird += item.RpPerBird
|
||||||
SubTotal: subTotal,
|
}
|
||||||
NetProfit: netProfit,
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToPenjualanItems(projectFlockCategory string, deliveryProducts []entities.MarketingDeliveryProduct, totalPopulation, totalWeightSold float64) []PLItem {
|
||||||
|
// Categorize deliveries by sales type based on Product flags
|
||||||
|
categorized := categorizeDeliveriesBySalesType(deliveryProducts)
|
||||||
|
|
||||||
|
items := []PLItem{}
|
||||||
|
|
||||||
|
// Process each sales category
|
||||||
|
for salesType, deliveries := range categorized {
|
||||||
|
amount := sumDeliveriesByCategory(deliveries)
|
||||||
|
|
||||||
|
// Use totalPopulation and totalWeightSold for per-unit calculations
|
||||||
|
rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightSold)
|
||||||
|
|
||||||
|
items = append(items, ToPLItem(salesType, ToFinancialMetrics(rpPerBird, rpPerKg, amount)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightSold)
|
||||||
|
return []PLItem{
|
||||||
|
ToPLItem("Pembelian Sapronak Supplier", ToFinancialMetrics(rpPerBird, rpPerKg, amount)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProfitLossData Mappers
|
func ToOverheadItems(budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalPopulation, totalWeightSold float64) []PLItem {
|
||||||
func ToProfitLossData(penjualan, pembelian []PLItem, summary PLSummaryGroup) ProfitLossData {
|
realizationAmount := sumRealizationsByFilter(realizations, filterRealizationExceptFlag(utils.FlagEkspedisi))
|
||||||
|
rpPerBird, rpPerKg := calculatePerUnitMetrics(realizationAmount, totalPopulation, totalWeightSold)
|
||||||
|
return []PLItem{
|
||||||
|
ToPLItem("Pengeluaran Overhead", ToFinancialMetrics(rpPerBird, rpPerKg, realizationAmount)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToEkspedisiItems(realizations []entities.ExpenseRealization, totalPopulation, totalWeightSold float64) []PLItem {
|
||||||
|
amount := sumRealizationsByFilter(realizations, filterRealizationByNonstockFlag(utils.FlagEkspedisi))
|
||||||
|
rpPerBird, rpPerKg := calculatePerUnitMetrics(amount, totalPopulation, totalWeightSold)
|
||||||
|
return []PLItem{
|
||||||
|
ToPLItem("Beban Ekspedisi", ToFinancialMetrics(rpPerBird, rpPerKg, amount)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToPLSummaryGroup(penjualanItems, pembelianItems, overheadItems, ekspedisiItems []PLItem) PLSummaryGroup {
|
||||||
|
totalPenjualan, totalPenjualanPerBird := sumPLItems(penjualanItems)
|
||||||
|
totalPembelian, totalPembelianPerBird := sumPLItems(pembelianItems)
|
||||||
|
totalOverhead, _ := sumPLItems(overheadItems)
|
||||||
|
totalEkspedisi, _ := sumPLItems(ekspedisiItems)
|
||||||
|
|
||||||
|
grossProfit := totalPenjualan - totalPembelian
|
||||||
|
grossProfitPerBird := totalPenjualanPerBird - totalPembelianPerBird
|
||||||
|
|
||||||
|
totalOtherExpenses := totalOverhead + totalEkspedisi
|
||||||
|
|
||||||
|
netProfit := grossProfit - totalOtherExpenses
|
||||||
|
netProfitPerBird := grossProfitPerBird - 0.0
|
||||||
|
|
||||||
|
return PLSummaryGroup{
|
||||||
|
GrossProfit: ToPLSummaryItem("LABA RUGI BRUTTO", ToFinancialMetrics(grossProfitPerBird, 0, grossProfit)),
|
||||||
|
SubTotal: ToPLSummaryItem("SUB TOTAL", ToFinancialMetrics(0, 0, totalOtherExpenses)),
|
||||||
|
NetProfit: ToPLSummaryItem("LABA RUGI NETTO", ToFinancialMetrics(netProfitPerBird, 0, netProfit)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProfitLossData(penjualanItems, pembelianItems, overheadItems, ekspedisiItems []PLItem) ProfitLossData {
|
||||||
|
summary := ToPLSummaryGroup(penjualanItems, pembelianItems, overheadItems, ekspedisiItems)
|
||||||
|
|
||||||
return ProfitLossData{
|
return ProfitLossData{
|
||||||
Penjualan: penjualan,
|
Penjualan: penjualanItems,
|
||||||
Pembelian: pembelian,
|
Pembelian: pembelianItems,
|
||||||
Summary: summary,
|
Summary: summary,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProfitLossSection Mappers
|
func ToProfitLossSection(penjualanItems, pembelianItems, overheadItems, ekspedisiItems []PLItem) ProfitLossSection {
|
||||||
func ToProfitLossSection(title string, data ProfitLossData) ProfitLossSection {
|
|
||||||
return ProfitLossSection{
|
return ProfitLossSection{
|
||||||
Title: title,
|
Data: ToProfitLossData(penjualanItems, pembelianItems, overheadItems, ekspedisiItems),
|
||||||
Data: data,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReportResponse Mappers
|
|
||||||
func ToReportResponse(hppPurchases HppPurchasesSection, profitLoss ProfitLossSection) ReportResponse {
|
func ToReportResponse(hppPurchases HppPurchasesSection, profitLoss ProfitLossSection) ReportResponse {
|
||||||
return ReportResponse{
|
return ReportResponse{
|
||||||
HppPurchases: hppPurchases,
|
HppPurchases: hppPurchases,
|
||||||
@@ -173,14 +346,175 @@ func ToReportResponse(hppPurchases HppPurchasesSection, profitLoss ProfitLossSec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to create a complete financial report
|
// === MAIN BUILDER ===
|
||||||
func BuildFinancialReport(
|
|
||||||
hppGroups []HppGroup,
|
func ToClosingKeuanganReport(projectFlockCategory string, purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, deliveryProducts []entities.MarketingDeliveryProduct, chickins []entities.ProjectChickin) ReportResponse {
|
||||||
summaryHpp SummaryHpp,
|
var totalPopulation float64
|
||||||
penjualan, pembelian []PLItem,
|
var totalWeightSold float64
|
||||||
plSummary PLSummaryGroup,
|
|
||||||
) ReportResponse {
|
for _, chickin := range chickins {
|
||||||
hppSection := ToHppPurchasesSection("HPP Pembelian", hppGroups, summaryHpp)
|
totalPopulation += chickin.UsageQty
|
||||||
plSection := ToProfitLossSection("Laporan Laba Rugi", ToProfitLossData(penjualan, pembelian, plSummary))
|
}
|
||||||
|
|
||||||
|
for _, delivery := range deliveryProducts {
|
||||||
|
totalWeightSold += delivery.TotalWeight
|
||||||
|
}
|
||||||
|
|
||||||
|
hppSection := ToHppPurchasesSection(purchaseItems, budgets, realizations, totalWeightSold, totalPopulation)
|
||||||
|
|
||||||
|
penjualanItems := ToPenjualanItems(projectFlockCategory, deliveryProducts, totalPopulation, totalWeightSold)
|
||||||
|
pembelianItems := ToPembelianItems(purchaseItems, totalPopulation, totalWeightSold)
|
||||||
|
overheadItems := ToOverheadItems(budgets, realizations, totalPopulation, totalWeightSold)
|
||||||
|
ekspedisiItems := ToEkspedisiItems(realizations, totalPopulation, totalWeightSold)
|
||||||
|
plSection := ToProfitLossSection(penjualanItems, pembelianItems, overheadItems, ekspedisiItems)
|
||||||
|
|
||||||
return ToReportResponse(hppSection, plSection)
|
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 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
nonstock := realization.ExpenseNonstock.Nonstock
|
||||||
|
for _, flag := range nonstock.Flags {
|
||||||
|
if strings.ToUpper(flag.Name) == string(flagType) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterRealizationExceptFlag(flagType utils.FlagType) func(*entities.ExpenseRealization) bool {
|
||||||
|
hasFlag := filterRealizationByNonstockFlag(flagType)
|
||||||
|
return func(realization *entities.ExpenseRealization) bool {
|
||||||
|
return !hasFlag(realization)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sumPurchasesByFilter(purchases []entities.PurchaseItem, filter func(*entities.PurchaseItem) bool) float64 {
|
||||||
|
amount := 0.0
|
||||||
|
for i := range purchases {
|
||||||
|
if filter(&purchases[i]) {
|
||||||
|
amount += purchases[i].TotalPrice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return amount
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "Penjualan Ayam Besar"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, flag := range product.Flags {
|
||||||
|
flagType := utils.FlagType(strings.ToUpper(flag.Name))
|
||||||
|
|
||||||
|
if isEggProductFlag(flagType) {
|
||||||
|
return "Penjualan Telur"
|
||||||
|
}
|
||||||
|
if isChickenProductFlag(flagType) {
|
||||||
|
return "Penjualan Ayam Besar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Penjualan Ayam Besar"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
rMarketings "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
rMarketings "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||||
rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/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"
|
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
rPurchase "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
|
||||||
|
|
||||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -30,10 +31,11 @@ func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
|||||||
marketingDeliveryProductRepo := rMarketings.NewMarketingDeliveryProductRepository(db)
|
marketingDeliveryProductRepo := rMarketings.NewMarketingDeliveryProductRepository(db)
|
||||||
expenseRealizationRepo := rExpenseRealization.NewExpenseRealizationRepository(db)
|
expenseRealizationRepo := rExpenseRealization.NewExpenseRealizationRepository(db)
|
||||||
chickinRepo := rChickin.NewChickinRepository(db)
|
chickinRepo := rChickin.NewChickinRepository(db)
|
||||||
|
purchaseRepo := rPurchase.NewPurchaseRepository(db)
|
||||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||||
|
|
||||||
closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, expenseRealizationRepo, projectBudgetRepo, chickinRepo, validate)
|
closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, expenseRealizationRepo, projectBudgetRepo, chickinRepo, purchaseRepo, validate)
|
||||||
sapronakService := sClosing.NewSapronakService(closingRepo, projectFlockKandangRepo, validate)
|
sapronakService := sClosing.NewSapronakService(closingRepo, projectFlockKandangRepo, validate)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||||
chickinRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/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"
|
projectflockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
purchaseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||||
|
|
||||||
@@ -45,9 +46,10 @@ type closingService struct {
|
|||||||
ExpenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository
|
ExpenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository
|
||||||
ProjectBudgetRepo projectflockRepository.ProjectBudgetRepository
|
ProjectBudgetRepo projectflockRepository.ProjectBudgetRepository
|
||||||
ChickinRepo chickinRepository.ProjectChickinRepository
|
ChickinRepo chickinRepository.ProjectChickinRepository
|
||||||
|
PurchaseRepo purchaseRepository.PurchaseRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
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, 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, validate *validator.Validate) ClosingService {
|
||||||
return &closingService{
|
return &closingService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
@@ -59,6 +61,7 @@ func NewClosingService(repo repository.ClosingRepository, projectFlockRepo proje
|
|||||||
ExpenseRealizationRepo: expenseRealizationRepo,
|
ExpenseRealizationRepo: expenseRealizationRepo,
|
||||||
ProjectBudgetRepo: projectBudgetRepo,
|
ProjectBudgetRepo: projectBudgetRepo,
|
||||||
ChickinRepo: chickinRepo,
|
ChickinRepo: chickinRepo,
|
||||||
|
PurchaseRepo: purchaseRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,24 +389,35 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.Repository.GetByID(c.Context(), projectFlockID, nil)
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
commonSvc.RelationCheck{Name: "Project Flock", ID: &projectFlockID, Exists: func(ctx context.Context, id uint) (bool, error) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found")
|
_, err := s.ProjectFlockRepo.GetByID(ctx, id, nil)
|
||||||
|
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return err == nil, err
|
||||||
|
}},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
projectFlock, err := s.ProjectFlockRepo.GetByID(c.Context(), projectFlockID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to get project flock %d for closing keuangan: %+v", projectFlockID, err)
|
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
||||||
}
|
}
|
||||||
|
|
||||||
budgets, err := s.ProjectBudgetRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
budgets, err := s.ProjectBudgetRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
s.Log.Errorf("Failed to get budgets for project flock %d: %+v", projectFlockID, err)
|
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch budgets")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch budgets")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
purchaseItems, err := s.PurchaseRepo.GetItemsByProjectFlockID(c.Context(), projectFlockID)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch purchase items")
|
||||||
|
}
|
||||||
|
|
||||||
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to get realizations for project flock %d: %+v", projectFlockID, err)
|
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch realizations")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch realizations")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,204 +427,15 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*
|
|||||||
Preload("MarketingProduct.ProductWarehouse.Product")
|
Preload("MarketingProduct.ProductWarehouse.Product")
|
||||||
})
|
})
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
s.Log.Errorf("Failed to get delivery products for project flock %d: %+v", projectFlockID, err)
|
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch delivery products")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch delivery products")
|
||||||
}
|
}
|
||||||
|
|
||||||
chickins, err := s.ChickinRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
chickins, err := s.ChickinRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to get chickins for project flock %d: %+v", projectFlockID, err)
|
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch chickins")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch chickins")
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalPopulation float64
|
report := dto.ToClosingKeuanganReport(projectFlock.Category, purchaseItems, budgets, realizations, deliveryProducts, chickins)
|
||||||
for _, chickin := range chickins {
|
|
||||||
totalPopulation += chickin.UsageQty
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalWeightSold float64
|
|
||||||
for _, delivery := range deliveryProducts {
|
|
||||||
totalWeightSold += delivery.TotalWeight
|
|
||||||
}
|
|
||||||
|
|
||||||
hppItems := s.buildHppItems(budgets, realizations, totalWeightSold, totalPopulation)
|
|
||||||
hppGroups := []dto.HppGroup{
|
|
||||||
dto.ToHppGroup("Input Produksi", hppItems),
|
|
||||||
}
|
|
||||||
|
|
||||||
summaryHpp := s.calculateHppSummary(budgets, realizations, totalWeightSold, totalPopulation)
|
|
||||||
|
|
||||||
penjualanItems := s.buildPenjualanItems(deliveryProducts, totalPopulation, totalWeightSold)
|
|
||||||
pembelianItems := s.buildPembelianItems(budgets, realizations, totalPopulation, totalWeightSold)
|
|
||||||
plSummary := s.calculatePLSummary(penjualanItems, pembelianItems)
|
|
||||||
|
|
||||||
hppSection := dto.ToHppPurchasesSection("HPP Pembelian", hppGroups, summaryHpp)
|
|
||||||
plSection := dto.ToProfitLossSection("Laporan Laba Rugi", dto.ToProfitLossData(penjualanItems, pembelianItems, plSummary))
|
|
||||||
|
|
||||||
report := dto.ToReportResponse(hppSection, plSection)
|
|
||||||
|
|
||||||
return &report, nil
|
return &report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s closingService) buildHppItems(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalWeightSold, totalPopulation float64) []dto.HppItem {
|
|
||||||
var totalBudgetAmount float64
|
|
||||||
var totalRealizationAmount float64
|
|
||||||
|
|
||||||
for _, budget := range budgets {
|
|
||||||
totalBudgetAmount += budget.Price * budget.Qty
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, realization := range realizations {
|
|
||||||
totalRealizationAmount += realization.Price * realization.Qty
|
|
||||||
}
|
|
||||||
|
|
||||||
budgetRpPerBird := 0.0
|
|
||||||
budgetRpPerKg := 0.0
|
|
||||||
if totalPopulation > 0 {
|
|
||||||
budgetRpPerBird = totalBudgetAmount / totalPopulation
|
|
||||||
}
|
|
||||||
if totalWeightSold > 0 {
|
|
||||||
budgetRpPerKg = totalBudgetAmount / totalWeightSold
|
|
||||||
}
|
|
||||||
|
|
||||||
realizationRpPerBird := 0.0
|
|
||||||
realizationRpPerKg := 0.0
|
|
||||||
if totalPopulation > 0 {
|
|
||||||
realizationRpPerBird = totalRealizationAmount / totalPopulation
|
|
||||||
}
|
|
||||||
if totalWeightSold > 0 {
|
|
||||||
realizationRpPerKg = totalRealizationAmount / totalWeightSold
|
|
||||||
}
|
|
||||||
|
|
||||||
items := []dto.HppItem{
|
|
||||||
dto.ToHppItem("Total HPP Produksi", dto.ToComparison(
|
|
||||||
dto.ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, totalBudgetAmount),
|
|
||||||
dto.ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, totalRealizationAmount),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s closingService) calculateHppSummary(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalWeightSold, totalPopulation float64) dto.SummaryHpp {
|
|
||||||
var totalBudget float64
|
|
||||||
var totalRealization float64
|
|
||||||
|
|
||||||
for _, budget := range budgets {
|
|
||||||
totalBudget += budget.Price * budget.Qty
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, realization := range realizations {
|
|
||||||
totalRealization += realization.Price * realization.Qty
|
|
||||||
}
|
|
||||||
|
|
||||||
budgetRpPerBird := 0.0
|
|
||||||
budgetRpPerKg := 0.0
|
|
||||||
if totalPopulation > 0 {
|
|
||||||
budgetRpPerBird = totalBudget / totalPopulation
|
|
||||||
}
|
|
||||||
if totalWeightSold > 0 {
|
|
||||||
budgetRpPerKg = totalBudget / totalWeightSold
|
|
||||||
}
|
|
||||||
|
|
||||||
realizationRpPerBird := 0.0
|
|
||||||
realizationRpPerKg := 0.0
|
|
||||||
if totalPopulation > 0 {
|
|
||||||
realizationRpPerBird = totalRealization / totalPopulation
|
|
||||||
}
|
|
||||||
if totalWeightSold > 0 {
|
|
||||||
realizationRpPerKg = totalRealization / totalWeightSold
|
|
||||||
}
|
|
||||||
|
|
||||||
return dto.ToSummaryHpp("Total HPP", dto.ToComparison(
|
|
||||||
dto.ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, totalBudget),
|
|
||||||
dto.ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, totalRealization),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s closingService) buildPenjualanItems(deliveryProducts []entity.MarketingDeliveryProduct, totalPopulation, totalWeightSold float64) []dto.PLItem {
|
|
||||||
var totalAmount float64
|
|
||||||
|
|
||||||
for _, delivery := range deliveryProducts {
|
|
||||||
totalAmount += delivery.TotalPrice
|
|
||||||
}
|
|
||||||
|
|
||||||
rpPerBird := 0.0
|
|
||||||
rpPerKg := 0.0
|
|
||||||
if totalPopulation > 0 {
|
|
||||||
rpPerBird = totalAmount / totalPopulation
|
|
||||||
}
|
|
||||||
if totalWeightSold > 0 {
|
|
||||||
rpPerKg = totalAmount / totalWeightSold
|
|
||||||
}
|
|
||||||
|
|
||||||
items := []dto.PLItem{
|
|
||||||
dto.ToPLItem("Penjualan", dto.ToFinancialMetrics(rpPerBird, rpPerKg, totalAmount)),
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s closingService) buildPembelianItems(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalPopulation, totalWeightSold float64) []dto.PLItem {
|
|
||||||
var totalBudget float64
|
|
||||||
var totalRealization float64
|
|
||||||
|
|
||||||
for _, budget := range budgets {
|
|
||||||
totalBudget += budget.Price * budget.Qty
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, realization := range realizations {
|
|
||||||
totalRealization += realization.Price * realization.Qty
|
|
||||||
}
|
|
||||||
|
|
||||||
budgetRpPerBird := 0.0
|
|
||||||
budgetRpPerKg := 0.0
|
|
||||||
if totalPopulation > 0 {
|
|
||||||
budgetRpPerBird = totalBudget / totalPopulation
|
|
||||||
}
|
|
||||||
if totalWeightSold > 0 {
|
|
||||||
budgetRpPerKg = totalBudget / totalWeightSold
|
|
||||||
}
|
|
||||||
|
|
||||||
realizationRpPerBird := 0.0
|
|
||||||
realizationRpPerKg := 0.0
|
|
||||||
if totalPopulation > 0 {
|
|
||||||
realizationRpPerBird = totalRealization / totalPopulation
|
|
||||||
}
|
|
||||||
if totalWeightSold > 0 {
|
|
||||||
realizationRpPerKg = totalRealization / totalWeightSold
|
|
||||||
}
|
|
||||||
|
|
||||||
items := []dto.PLItem{
|
|
||||||
dto.ToPLItem("Beban Pokok Produksi", dto.ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, totalBudget)),
|
|
||||||
dto.ToPLItem("Realisasi Beban Pokok", dto.ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, totalRealization)),
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s closingService) calculatePLSummary(penjualanItems, pembelianItems []dto.PLItem) dto.PLSummaryGroup {
|
|
||||||
var totalPenjualan float64
|
|
||||||
var totalPenjualanPerBird float64
|
|
||||||
var totalPembelian float64
|
|
||||||
var totalPembelianPerBird float64
|
|
||||||
|
|
||||||
for _, item := range penjualanItems {
|
|
||||||
totalPenjualan += item.Amount
|
|
||||||
totalPenjualanPerBird += item.RpPerBird
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range pembelianItems {
|
|
||||||
totalPembelian += item.Amount
|
|
||||||
totalPembelianPerBird += item.RpPerBird
|
|
||||||
}
|
|
||||||
|
|
||||||
grossProfit := totalPenjualan - totalPembelian
|
|
||||||
grossProfitPerBird := totalPenjualanPerBird - totalPembelianPerBird
|
|
||||||
|
|
||||||
return dto.ToPLSummaryGroup(
|
|
||||||
dto.ToPLSummaryItem("Laba Kotor", dto.ToFinancialMetrics(grossProfitPerBird, 0, grossProfit)),
|
|
||||||
dto.ToPLSummaryItem("Sub Total", dto.ToFinancialMetrics(grossProfitPerBird, 0, grossProfit)),
|
|
||||||
dto.ToPLSummaryItem("Laba Bersih", dto.ToFinancialMetrics(grossProfitPerBird, 0, grossProfit)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func (r *ExpenseRealizationRepositoryImpl) GetByProjectFlockID(ctx context.Conte
|
|||||||
Preload("ExpenseNonstock").
|
Preload("ExpenseNonstock").
|
||||||
Preload("ExpenseNonstock.Nonstock").
|
Preload("ExpenseNonstock.Nonstock").
|
||||||
Preload("ExpenseNonstock.Nonstock.Uom").
|
Preload("ExpenseNonstock.Nonstock.Uom").
|
||||||
|
Preload("ExpenseNonstock.Nonstock.Flags").
|
||||||
Preload("ExpenseNonstock.Expense").
|
Preload("ExpenseNonstock.Expense").
|
||||||
Joins("JOIN expense_nonstocks ON expense_nonstocks.id = expense_realizations.expense_nonstock_id").
|
Joins("JOIN expense_nonstocks ON expense_nonstocks.id = expense_realizations.expense_nonstock_id").
|
||||||
Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id").
|
Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id").
|
||||||
@@ -66,7 +67,8 @@ func (r *ExpenseRealizationRepositoryImpl) GetAllWithFilters(ctx context.Context
|
|||||||
Preload("Expense.Supplier").
|
Preload("Expense.Supplier").
|
||||||
Preload("Kandang").
|
Preload("Kandang").
|
||||||
Preload("Kandang.Location").
|
Preload("Kandang.Location").
|
||||||
Preload("Nonstock")
|
Preload("Nonstock").
|
||||||
|
Preload("Nonstock.Flags")
|
||||||
}).
|
}).
|
||||||
Joins("JOIN expense_nonstocks ON expense_nonstocks.id = expense_realizations.expense_nonstock_id").
|
Joins("JOIN expense_nonstocks ON expense_nonstocks.id = expense_realizations.expense_nonstock_id").
|
||||||
Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id").
|
Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id").
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
|
|||||||
Preload("ProductWarehouse").
|
Preload("ProductWarehouse").
|
||||||
Preload("ProductWarehouse.Product").
|
Preload("ProductWarehouse.Product").
|
||||||
Preload("ProductWarehouse.Warehouse").
|
Preload("ProductWarehouse.Warehouse").
|
||||||
Preload("ProductWarehouse.ProjectFlockKandang")
|
Preload("ProductWarehouse.ProjectFlockKandang").
|
||||||
|
Preload("ProductWarehouse.ProjectFlockKandang.ProjectFlock")
|
||||||
}).
|
}).
|
||||||
Joins("JOIN marketing_products ON marketing_products.id = marketing_delivery_products.marketing_product_id").
|
Joins("JOIN marketing_products ON marketing_products.id = marketing_delivery_products.marketing_product_id").
|
||||||
Joins("JOIN marketings ON marketings.id = marketing_products.marketing_id")
|
Joins("JOIN marketings ON marketings.id = marketing_products.marketing_id")
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ type RecordingRepository interface {
|
|||||||
GetFeedUsageInGrams(tx *gorm.DB, recordingID uint) (float64, error)
|
GetFeedUsageInGrams(tx *gorm.DB, recordingID uint) (float64, error)
|
||||||
GetFcrID(tx *gorm.DB, projectFlockKandangId uint) (uint, error)
|
GetFcrID(tx *gorm.DB, projectFlockKandangId uint) (uint, error)
|
||||||
GetFcrStandardWeightKg(tx *gorm.DB, fcrId uint, currentWeightKg float64) (float64, bool, error)
|
GetFcrStandardWeightKg(tx *gorm.DB, fcrId uint, currentWeightKg float64) (float64, bool, error)
|
||||||
|
GetProductionWeightAndQtyByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeight float64, totalQty float64, err error)
|
||||||
|
GetTotalDepletionByProjectFlockID(ctx context.Context, projectFlockID uint) (totalDepletion float64, err error)
|
||||||
|
GetLatestAvgWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (avgWeight float64, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordingRepositoryImpl struct {
|
type RecordingRepositoryImpl struct {
|
||||||
@@ -363,6 +366,74 @@ func (r *RecordingRepositoryImpl) GetFcrStandardWeightKg(tx *gorm.DB, fcrId uint
|
|||||||
return weight, true, nil
|
return weight, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RecordingRepositoryImpl) GetProductionWeightAndQtyByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeight float64, totalQty float64, err error) {
|
||||||
|
if projectFlockID == 0 {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get total chickin quantity for this ProjectFlock
|
||||||
|
totalChickinQty, err := r.getTotalChickinQtyByProjectFlockID(ctx, projectFlockID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get total depletion for this ProjectFlock
|
||||||
|
totalDepletion, err := r.GetTotalDepletionByProjectFlockID(ctx, projectFlockID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate actual quantity produced
|
||||||
|
actualQty := totalChickinQty - totalDepletion
|
||||||
|
|
||||||
|
// Get latest average weight from RecordingBW
|
||||||
|
avgWeight, err := r.GetLatestAvgWeightByProjectFlockID(ctx, projectFlockID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total weight
|
||||||
|
totalWeight = actualQty * avgWeight
|
||||||
|
|
||||||
|
return totalWeight, actualQty, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RecordingRepositoryImpl) getTotalChickinQtyByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) {
|
||||||
|
var result float64
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("project_chickins").
|
||||||
|
Select("COALESCE(SUM(project_chickins.usage_qty), 0)").
|
||||||
|
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = project_chickins.project_flock_kandang_id").
|
||||||
|
Where("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||||
|
Scan(&result).Error
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RecordingRepositoryImpl) GetTotalDepletionByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) {
|
||||||
|
var result float64
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("recording_depletions").
|
||||||
|
Select("COALESCE(SUM(recording_depletions.qty), 0)").
|
||||||
|
Joins("JOIN recordings ON recordings.id = recording_depletions.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).
|
||||||
|
Scan(&result).Error
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RecordingRepositoryImpl) GetLatestAvgWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) {
|
||||||
|
var result float64
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("recording_bws").
|
||||||
|
Select("COALESCE(AVG(recording_bws.avg_weight), 0)").
|
||||||
|
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)").
|
||||||
|
Scan(&result).Error
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
func nextRecordingDay(days []int) int {
|
func nextRecordingDay(days []int) int {
|
||||||
if len(days) == 0 {
|
if len(days) == 0 {
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type PurchaseRepository interface {
|
|||||||
NextPrNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
NextPrNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
||||||
NextPoNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
NextPoNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
||||||
BackfillProjectFlockKandang(ctx context.Context, purchaseID uint) error
|
BackfillProjectFlockKandang(ctx context.Context, purchaseID uint) error
|
||||||
|
GetItemsByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.PurchaseItem, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PurchaseRepositoryImpl struct {
|
type PurchaseRepositoryImpl struct {
|
||||||
@@ -289,6 +290,17 @@ func (r *PurchaseRepositoryImpl) numberExists(ctx context.Context, db *gorm.DB,
|
|||||||
return count > 0, nil
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseRepositoryImpl) GetItemsByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.PurchaseItem, error) {
|
||||||
|
var items []entity.PurchaseItem
|
||||||
|
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).
|
||||||
|
Find(&items).Error
|
||||||
|
return items, err
|
||||||
|
}
|
||||||
|
|
||||||
func parseNumericSuffix(value, prefix string) (int, bool) {
|
func parseNumericSuffix(value, prefix string) (int, bool) {
|
||||||
if !strings.HasPrefix(value, prefix) {
|
if !strings.HasPrefix(value, prefix) {
|
||||||
return 0, false
|
return 0, false
|
||||||
|
|||||||
@@ -96,28 +96,8 @@ func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate total summary from result items
|
|
||||||
var total *dto.Summary
|
total := dto.ToSummaryFromDTOItems(result)
|
||||||
if len(result) > 0 {
|
|
||||||
totalQty := 0
|
|
||||||
totalWeightKg := 0.0
|
|
||||||
totalSalesAmount := int64(0)
|
|
||||||
totalHppAmount := int64(0)
|
|
||||||
|
|
||||||
for _, item := range result {
|
|
||||||
totalQty += int(item.Qty)
|
|
||||||
totalWeightKg += item.TotalWeightKg
|
|
||||||
totalSalesAmount += int64(item.SalesAmount)
|
|
||||||
totalHppAmount += int64(item.HppAmount)
|
|
||||||
}
|
|
||||||
|
|
||||||
total = &dto.Summary{
|
|
||||||
TotalQty: totalQty,
|
|
||||||
TotalWeightKg: totalWeightKg,
|
|
||||||
TotalSalesAmount: totalSalesAmount,
|
|
||||||
TotalHppAmount: totalHppAmount,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx.Status(fiber.StatusOK).
|
return ctx.Status(fiber.StatusOK).
|
||||||
JSON(MarketingReportResponse{
|
JSON(MarketingReportResponse{
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func ToRepportMarketingItemDTO(mdp entity.MarketingDeliveryProduct, hppPricePerK
|
|||||||
agingDays := 0
|
agingDays := 0
|
||||||
if mdp.MarketingProduct.Marketing.SoDate.Year() > 1 {
|
if mdp.MarketingProduct.Marketing.SoDate.Year() > 1 {
|
||||||
soDate = mdp.MarketingProduct.Marketing.SoDate
|
soDate = mdp.MarketingProduct.Marketing.SoDate
|
||||||
agingDays = int(time.Now().Sub(soDate).Hours() / 24)
|
agingDays = int(time.Since(soDate).Hours() / 24)
|
||||||
}
|
}
|
||||||
|
|
||||||
realizationDate := time.Time{}
|
realizationDate := time.Time{}
|
||||||
@@ -113,6 +113,20 @@ func ToRepportMarketingItemDTOs(mdps []entity.MarketingDeliveryProduct, hppPrice
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToRepportMarketingItemDTOsWithHppMap(mdps []entity.MarketingDeliveryProduct, hppMap map[uint]float64) []RepportMarketingItemDTO {
|
||||||
|
items := make([]RepportMarketingItemDTO, 0, len(mdps))
|
||||||
|
for _, mdp := range mdps {
|
||||||
|
hppPerKg := float64(0)
|
||||||
|
if projectFlockKandang := mdp.MarketingProduct.ProductWarehouse.ProjectFlockKandang; projectFlockKandang != nil {
|
||||||
|
if hpp, exists := hppMap[projectFlockKandang.ProjectFlockId]; exists {
|
||||||
|
hppPerKg = hpp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items = append(items, ToRepportMarketingItemDTO(mdp, hppPerKg))
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
func ToSummary(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64) *Summary {
|
func ToSummary(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64) *Summary {
|
||||||
if len(mdps) == 0 {
|
if len(mdps) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -153,6 +167,37 @@ func generateDoNumber(soNumber string, deliveryDate *time.Time, warehouseId uint
|
|||||||
return fmt.Sprintf("%s-%s-%d", soNumber, dateStr, warehouseId)
|
return fmt.Sprintf("%s-%s-%d", soNumber, dateStr, warehouseId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToSummaryFromDTOItems(items []RepportMarketingItemDTO) *Summary {
|
||||||
|
if len(items) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
totalQty := 0
|
||||||
|
totalWeightKg := 0.0
|
||||||
|
totalSalesAmount := int64(0)
|
||||||
|
totalHppAmount := int64(0)
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
totalQty += int(item.Qty)
|
||||||
|
totalWeightKg += item.TotalWeightKg
|
||||||
|
totalSalesAmount += int64(item.SalesAmount)
|
||||||
|
totalHppAmount += int64(item.HppAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalHppPricePerKg := float64(0)
|
||||||
|
if totalWeightKg > 0 {
|
||||||
|
totalHppPricePerKg = float64(totalHppAmount) / totalWeightKg
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Summary{
|
||||||
|
TotalQty: totalQty,
|
||||||
|
TotalWeightKg: totalWeightKg,
|
||||||
|
TotalSalesAmount: totalSalesAmount,
|
||||||
|
TotalHppAmount: totalHppAmount,
|
||||||
|
TotalHppPricePerKg: totalHppPricePerKg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ToRepportMarketingResponseDTO(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64) RepportMarketingResponseDTO {
|
func ToRepportMarketingResponseDTO(mdps []entity.MarketingDeliveryProduct, hppPricePerKg float64) RepportMarketingResponseDTO {
|
||||||
items := ToRepportMarketingItemDTOs(mdps, hppPricePerKg)
|
items := ToRepportMarketingItemDTOs(mdps, hppPricePerKg)
|
||||||
total := ToSummary(mdps, hppPricePerKg)
|
total := ToSummary(mdps, hppPricePerKg)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
|
|
||||||
expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||||
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||||
|
recordingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
|
purchaseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RepportModule struct{}
|
type RepportModule struct{}
|
||||||
@@ -19,10 +21,12 @@ func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
|||||||
|
|
||||||
expenseRealizationRepository := expenseRepo.NewExpenseRealizationRepository(db)
|
expenseRealizationRepository := expenseRepo.NewExpenseRealizationRepository(db)
|
||||||
marketingDeliveryProductRepository := marketingRepo.NewMarketingDeliveryProductRepository(db)
|
marketingDeliveryProductRepository := marketingRepo.NewMarketingDeliveryProductRepository(db)
|
||||||
|
purchaseRepository := purchaseRepo.NewPurchaseRepository(db)
|
||||||
|
recordingRepository := recordingRepo.NewRecordingRepository(db)
|
||||||
approvalRepository := commonRepo.NewApprovalRepository(db)
|
approvalRepository := commonRepo.NewApprovalRepository(db)
|
||||||
|
|
||||||
approvalSvc := approvalService.NewApprovalService(approvalRepository)
|
approvalSvc := approvalService.NewApprovalService(approvalRepository)
|
||||||
repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, marketingDeliveryProductRepository, approvalSvc)
|
repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, marketingDeliveryProductRepository, purchaseRepository, recordingRepository, approvalSvc)
|
||||||
|
|
||||||
RepportRoutes(router, repportService)
|
RepportRoutes(router, repportService)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
||||||
expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||||
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||||
|
recordingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
|
purchaseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -29,15 +31,19 @@ type repportService struct {
|
|||||||
Validate *validator.Validate
|
Validate *validator.Validate
|
||||||
ExpenseRealizationRepo expenseRepo.ExpenseRealizationRepository
|
ExpenseRealizationRepo expenseRepo.ExpenseRealizationRepository
|
||||||
MarketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository
|
MarketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository
|
||||||
|
PurchaseRepo purchaseRepo.PurchaseRepository
|
||||||
|
RecordingRepo recordingRepo.RecordingRepository
|
||||||
ApprovalSvc approvalService.ApprovalService
|
ApprovalSvc approvalService.ApprovalService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRepportService(validate *validator.Validate, expenseRealizationRepo expenseRepo.ExpenseRealizationRepository, marketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository, approvalSvc approvalService.ApprovalService) RepportService {
|
func NewRepportService(validate *validator.Validate, expenseRealizationRepo expenseRepo.ExpenseRealizationRepository, marketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository, purchaseRepo purchaseRepo.PurchaseRepository, recordingRepo recordingRepo.RecordingRepository, approvalSvc approvalService.ApprovalService) RepportService {
|
||||||
return &repportService{
|
return &repportService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
ExpenseRealizationRepo: expenseRealizationRepo,
|
ExpenseRealizationRepo: expenseRealizationRepo,
|
||||||
MarketingDeliveryRepo: marketingDeliveryRepo,
|
MarketingDeliveryRepo: marketingDeliveryRepo,
|
||||||
|
PurchaseRepo: purchaseRepo,
|
||||||
|
RecordingRepo: recordingRepo,
|
||||||
ApprovalSvc: approvalSvc,
|
ApprovalSvc: approvalSvc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +100,7 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing
|
|||||||
|
|
||||||
projectFlockIDs := s.collectProjectFlockIDs(deliveryProducts)
|
projectFlockIDs := s.collectProjectFlockIDs(deliveryProducts)
|
||||||
hppMap := s.buildHppMap(c.Context(), projectFlockIDs, deliveryProducts)
|
hppMap := s.buildHppMap(c.Context(), projectFlockIDs, deliveryProducts)
|
||||||
items := s.mapDeliveryProductsToDTOs(deliveryProducts, hppMap)
|
items := dto.ToRepportMarketingItemDTOsWithHppMap(deliveryProducts, hppMap)
|
||||||
|
|
||||||
return items, total, nil
|
return items, total, nil
|
||||||
}
|
}
|
||||||
@@ -118,24 +124,33 @@ func (s *repportService) collectProjectFlockIDs(deliveryProducts []entity.Market
|
|||||||
func (s *repportService) buildHppMap(ctx context.Context, projectFlockIDs []uint, deliveryProducts []entity.MarketingDeliveryProduct) map[uint]float64 {
|
func (s *repportService) buildHppMap(ctx context.Context, projectFlockIDs []uint, deliveryProducts []entity.MarketingDeliveryProduct) map[uint]float64 {
|
||||||
hppMap := make(map[uint]float64)
|
hppMap := make(map[uint]float64)
|
||||||
for _, projectFlockID := range projectFlockIDs {
|
for _, projectFlockID := range projectFlockIDs {
|
||||||
hppPerKg := s.calculateHppPricePerKg(ctx, projectFlockID, deliveryProducts)
|
category := s.getProjectFlockCategory(deliveryProducts, projectFlockID)
|
||||||
|
hppPerKg := s.calculateHppByCategory(ctx, category, projectFlockID, deliveryProducts)
|
||||||
hppMap[projectFlockID] = hppPerKg
|
hppMap[projectFlockID] = hppPerKg
|
||||||
}
|
}
|
||||||
return hppMap
|
return hppMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *repportService) mapDeliveryProductsToDTOs(deliveryProducts []entity.MarketingDeliveryProduct, hppMap map[uint]float64) []dto.RepportMarketingItemDTO {
|
func (s *repportService) calculateHppByCategory(ctx context.Context, category string, projectFlockID uint, deliveryProducts []entity.MarketingDeliveryProduct) float64 {
|
||||||
items := make([]dto.RepportMarketingItemDTO, 0, len(deliveryProducts))
|
switch utils.ProjectFlockCategory(category) {
|
||||||
|
case utils.ProjectFlockCategoryGrowing:
|
||||||
|
return s.calculateHppPricePerKg(ctx, projectFlockID, deliveryProducts)
|
||||||
|
case utils.ProjectFlockCategoryLaying:
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *repportService) getProjectFlockCategory(deliveryProducts []entity.MarketingDeliveryProduct, projectFlockID uint) string {
|
||||||
for _, dp := range deliveryProducts {
|
for _, dp := range deliveryProducts {
|
||||||
hppPerKg := float64(0)
|
|
||||||
if projectFlockKandang := dp.MarketingProduct.ProductWarehouse.ProjectFlockKandang; projectFlockKandang != nil {
|
if projectFlockKandang := dp.MarketingProduct.ProductWarehouse.ProjectFlockKandang; projectFlockKandang != nil {
|
||||||
if hpp, exists := hppMap[projectFlockKandang.ProjectFlockId]; exists {
|
if projectFlockKandang.ProjectFlockId == projectFlockID {
|
||||||
hppPerKg = hpp
|
return projectFlockKandang.ProjectFlock.Category
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items = append(items, dto.ToRepportMarketingItemDTO(dp, hppPerKg))
|
|
||||||
}
|
}
|
||||||
return items
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *repportService) calculateHppPricePerKg(ctx context.Context, projectFlockID uint, deliveryProducts []entity.MarketingDeliveryProduct) float64 {
|
func (s *repportService) calculateHppPricePerKg(ctx context.Context, projectFlockID uint, deliveryProducts []entity.MarketingDeliveryProduct) float64 {
|
||||||
@@ -143,17 +158,22 @@ func (s *repportService) calculateHppPricePerKg(ctx context.Context, projectFloc
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(ctx, projectFlockID)
|
purchaseItems, err := s.PurchaseRepo.GetItemsByProjectFlockID(ctx, projectFlockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
s.Log.Warnf("GetItemsByProjectFlockID error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(realizations) == 0 {
|
costPurchase := float64(0)
|
||||||
return 0
|
for _, item := range purchaseItems {
|
||||||
|
costPurchase += item.TotalPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(ctx, projectFlockID)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Warnf("GetByProjectFlockID error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
costBop := float64(0)
|
costBop := float64(0)
|
||||||
|
|
||||||
for _, realization := range realizations {
|
for _, realization := range realizations {
|
||||||
cost := realization.Price * realization.Qty
|
cost := realization.Price * realization.Qty
|
||||||
category := ""
|
category := ""
|
||||||
@@ -166,24 +186,21 @@ func (s *repportService) calculateHppPricePerKg(ctx context.Context, projectFloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
totalActualCost := costBop
|
totalActualCost := costPurchase + costBop
|
||||||
|
|
||||||
if totalActualCost == 0 {
|
if totalActualCost == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
totalWeightSold := float64(0)
|
totalWeightProduced, _, err := s.RecordingRepo.GetProductionWeightAndQtyByProjectFlockID(ctx, projectFlockID)
|
||||||
for _, dp := range deliveryProducts {
|
if err != nil {
|
||||||
if dp.MarketingProduct.ProductWarehouse.ProjectFlockKandang != nil &&
|
s.Log.Warnf("GetProductionWeightAndQtyByProjectFlockID error: %v", err)
|
||||||
dp.MarketingProduct.ProductWarehouse.ProjectFlockKandang.ProjectFlockId == projectFlockID {
|
|
||||||
totalWeightSold += dp.TotalWeight
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalWeightSold == 0 {
|
if totalWeightProduced == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
hppPerKg := totalActualCost / totalWeightSold
|
hppPerKg := totalActualCost / totalWeightProduced
|
||||||
return hppPerKg
|
return hppPerKg
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user