feat[BE]: membetulkan perhitungan hpp di module penjualan harian

This commit is contained in:
aguhh18
2025-12-18 09:58:31 +07:00
parent a7069a2e50
commit 1b23861656
11 changed files with 592 additions and 299 deletions
@@ -1,5 +1,12 @@
package dto
import (
"strings"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
)
// === BASE METRICS ===
type FinancialMetrics struct {
RpPerBird float64 `json:"rp_per_bird"`
@@ -28,9 +35,7 @@ type SummaryHpp struct {
Comparison
}
// Ini adalah struct mandiri untuk bagian HPP Purchases
type HppPurchasesSection struct {
Title string `json:"title"`
Hpp []HppGroup `json:"hpp"`
SummaryHpp SummaryHpp `json:"summary_hpp"`
}
@@ -58,14 +63,11 @@ type ProfitLossData struct {
Summary PLSummaryGroup `json:"summary"`
}
// Ini adalah struct mandiri untuk bagian Profit Loss
type ProfitLossSection struct {
Title string `json:"title"`
Data ProfitLossData `json:"data"`
Data ProfitLossData `json:"data"`
}
// === RESPONSE DTO (ROOT) ===
// Sekarang Root-nya terlihat sangat bersih dan tidak "janggal" lagi
type ReportResponse struct {
HppPurchases HppPurchasesSection `json:"hpp_purchases"`
ProfitLoss ProfitLossSection `json:"profit_loss"`
@@ -73,7 +75,6 @@ type ReportResponse struct {
// === MAPPER FUNCTIONS ===
// FinancialMetrics Mappers
func ToFinancialMetrics(rpPerBird, rpPerKg, amount float64) FinancialMetrics {
return FinancialMetrics{
RpPerBird: rpPerBird,
@@ -82,7 +83,6 @@ func ToFinancialMetrics(rpPerBird, rpPerKg, amount float64) FinancialMetrics {
}
}
// Comparison Mappers
func ToComparison(budgeting, realization FinancialMetrics) Comparison {
return Comparison{
Budgeting: budgeting,
@@ -90,40 +90,141 @@ func ToComparison(budgeting, realization FinancialMetrics) Comparison {
}
}
// HppItem Mappers
func ToHppItem(itemType string, comparison Comparison) HppItem {
return HppItem{
Type: itemType,
Comparison: comparison,
}
// === HPP PENGELUARAN (from Purchase Items) ===
func getFlagLabel(flagType utils.FlagType) string {
return "Pembelian " + string(flagType)
}
// HppGroup Mappers
func ToHppGroup(groupName string, items []HppItem) HppGroup {
func buildHppItemsByPurchaseFlags(purchaseItems []entities.PurchaseItem, totalWeightSold, totalPopulation float64) []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)
// 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{
GroupName: groupName,
GroupName: "HPP dan Bahan Baku",
Data: items,
}
}
// SummaryHpp Mappers
func ToSummaryHpp(label string, comparison Comparison) SummaryHpp {
// === HPP SUMMARY ===
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{
Label: label,
Comparison: comparison,
Label: label,
Comparison: ToComparison(
ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, totalBudget),
ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, totalRealization),
),
}
}
// HppPurchasesSection Mappers
func ToHppPurchasesSection(title string, hppGroups []HppGroup, summaryHpp SummaryHpp) HppPurchasesSection {
func ToHppPurchasesSection(purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalWeightSold, totalPopulation float64) 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{
Title: title,
Hpp: hppGroups,
SummaryHpp: summaryHpp,
}
}
// PLItem Mappers
// === PROFIT & LOSS ===
func ToPLItem(itemType string, metrics FinancialMetrics) PLItem {
return PLItem{
Type: itemType,
@@ -131,7 +232,6 @@ func ToPLItem(itemType string, metrics FinancialMetrics) PLItem {
}
}
// PLSummaryItem Mappers
func ToPLSummaryItem(label string, metrics FinancialMetrics) PLSummaryItem {
return PLSummaryItem{
Label: label,
@@ -139,33 +239,106 @@ func ToPLSummaryItem(label string, metrics FinancialMetrics) PLSummaryItem {
}
}
// PLSummaryGroup Mappers
func ToPLSummaryGroup(grossProfit, subTotal, netProfit PLSummaryItem) PLSummaryGroup {
return PLSummaryGroup{
GrossProfit: grossProfit,
SubTotal: subTotal,
NetProfit: netProfit,
func sumPLItems(items []PLItem) (totalAmount, totalPerBird float64) {
for _, item := range items {
totalAmount += item.Amount
totalPerBird += item.RpPerBird
}
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 ToProfitLossData(penjualan, pembelian []PLItem, summary PLSummaryGroup) ProfitLossData {
func ToOverheadItems(budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, totalPopulation, totalWeightSold float64) []PLItem {
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{
Penjualan: penjualan,
Pembelian: pembelian,
Penjualan: penjualanItems,
Pembelian: pembelianItems,
Summary: summary,
}
}
// ProfitLossSection Mappers
func ToProfitLossSection(title string, data ProfitLossData) ProfitLossSection {
func ToProfitLossSection(penjualanItems, pembelianItems, overheadItems, ekspedisiItems []PLItem) ProfitLossSection {
return ProfitLossSection{
Title: title,
Data: data,
Data: ToProfitLossData(penjualanItems, pembelianItems, overheadItems, ekspedisiItems),
}
}
// ReportResponse Mappers
func ToReportResponse(hppPurchases HppPurchasesSection, profitLoss ProfitLossSection) ReportResponse {
return ReportResponse{
HppPurchases: hppPurchases,
@@ -173,14 +346,175 @@ func ToReportResponse(hppPurchases HppPurchasesSection, profitLoss ProfitLossSec
}
}
// Helper function to create a complete financial report
func BuildFinancialReport(
hppGroups []HppGroup,
summaryHpp SummaryHpp,
penjualan, pembelian []PLItem,
plSummary PLSummaryGroup,
) ReportResponse {
hppSection := ToHppPurchasesSection("HPP Pembelian", hppGroups, summaryHpp)
plSection := ToProfitLossSection("Laporan Laba Rugi", ToProfitLossData(penjualan, pembelian, plSummary))
// === MAIN BUILDER ===
func ToClosingKeuanganReport(projectFlockCategory string, purchaseItems []entities.PurchaseItem, budgets []entities.ProjectBudget, realizations []entities.ExpenseRealization, deliveryProducts []entities.MarketingDeliveryProduct, chickins []entities.ProjectChickin) ReportResponse {
var totalPopulation float64
var totalWeightSold float64
for _, chickin := range chickins {
totalPopulation += chickin.UsageQty
}
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)
}
// === 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
}
+3 -1
View File
@@ -13,6 +13,7 @@ import (
rMarketings "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
rPurchase "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
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)
expenseRealizationRepo := rExpenseRealization.NewExpenseRealizationRepository(db)
chickinRepo := rChickin.NewChickinRepository(db)
purchaseRepo := rPurchase.NewPurchaseRepository(db)
approvalRepo := commonRepo.NewApprovalRepository(db)
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)
userService := sUser.NewUserService(userRepo, validate)
@@ -15,6 +15,7 @@ import (
marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
chickinRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
projectflockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
purchaseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
@@ -45,9 +46,10 @@ type closingService struct {
ExpenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository
ProjectBudgetRepo projectflockRepository.ProjectBudgetRepository
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{
Log: utils.Log,
Validate: validate,
@@ -59,6 +61,7 @@ func NewClosingService(repo repository.ClosingRepository, projectFlockRepo proje
ExpenseRealizationRepo: expenseRealizationRepo,
ProjectBudgetRepo: projectBudgetRepo,
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")
}
_, err := s.Repository.GetByID(c.Context(), projectFlockID, nil)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found")
if err := commonSvc.EnsureRelations(c.Context(),
commonSvc.RelationCheck{Name: "Project Flock", ID: &projectFlockID, Exists: func(ctx context.Context, id uint) (bool, error) {
_, 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 {
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")
}
budgets, err := s.ProjectBudgetRepo.GetByProjectFlockID(c.Context(), projectFlockID)
if err != nil {
s.Log.Errorf("Failed to get budgets for project flock %d: %+v", projectFlockID, err)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
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)
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")
}
@@ -413,204 +427,15 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*
Preload("MarketingProduct.ProductWarehouse.Product")
})
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")
}
chickins, err := s.ChickinRepo.GetByProjectFlockID(c.Context(), projectFlockID)
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")
}
var totalPopulation float64
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)
report := dto.ToClosingKeuanganReport(projectFlock.Category, purchaseItems, budgets, realizations, deliveryProducts, chickins)
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.Nonstock").
Preload("ExpenseNonstock.Nonstock.Uom").
Preload("ExpenseNonstock.Nonstock.Flags").
Preload("ExpenseNonstock.Expense").
Joins("JOIN expense_nonstocks ON expense_nonstocks.id = expense_realizations.expense_nonstock_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("Kandang").
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 expenses ON expenses.id = expense_nonstocks.expense_id").
@@ -91,7 +91,8 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
Preload("ProductWarehouse").
Preload("ProductWarehouse.Product").
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 marketings ON marketings.id = marketing_products.marketing_id")
@@ -45,6 +45,9 @@ type RecordingRepository interface {
GetFeedUsageInGrams(tx *gorm.DB, recordingID uint) (float64, error)
GetFcrID(tx *gorm.DB, projectFlockKandangId uint) (uint, 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 {
@@ -363,6 +366,74 @@ func (r *RecordingRepositoryImpl) GetFcrStandardWeightKg(tx *gorm.DB, fcrId uint
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 {
if len(days) == 0 {
return 1
@@ -25,6 +25,7 @@ type PurchaseRepository interface {
NextPrNumber(ctx context.Context, tx *gorm.DB) (string, error)
NextPoNumber(ctx context.Context, tx *gorm.DB) (string, error)
BackfillProjectFlockKandang(ctx context.Context, purchaseID uint) error
GetItemsByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.PurchaseItem, error)
}
type PurchaseRepositoryImpl struct {
@@ -289,6 +290,17 @@ func (r *PurchaseRepositoryImpl) numberExists(ctx context.Context, db *gorm.DB,
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) {
if !strings.HasPrefix(value, prefix) {
return 0, false
@@ -96,28 +96,8 @@ func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error {
return err
}
// Calculate total summary from result items
var total *dto.Summary
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,
}
}
total := dto.ToSummaryFromDTOItems(result)
return ctx.Status(fiber.StatusOK).
JSON(MarketingReportResponse{
@@ -50,7 +50,7 @@ func ToRepportMarketingItemDTO(mdp entity.MarketingDeliveryProduct, hppPricePerK
agingDays := 0
if mdp.MarketingProduct.Marketing.SoDate.Year() > 1 {
soDate = mdp.MarketingProduct.Marketing.SoDate
agingDays = int(time.Now().Sub(soDate).Hours() / 24)
agingDays = int(time.Since(soDate).Hours() / 24)
}
realizationDate := time.Time{}
@@ -113,6 +113,20 @@ func ToRepportMarketingItemDTOs(mdps []entity.MarketingDeliveryProduct, hppPrice
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 {
if len(mdps) == 0 {
return nil
@@ -153,6 +167,37 @@ func generateDoNumber(soNumber string, deliveryDate *time.Time, warehouseId uint
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 {
items := ToRepportMarketingItemDTOs(mdps, hppPricePerKg)
total := ToSummary(mdps, hppPricePerKg)
+5 -1
View File
@@ -11,6 +11,8 @@ import (
expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/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{}
@@ -19,10 +21,12 @@ func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
expenseRealizationRepository := expenseRepo.NewExpenseRealizationRepository(db)
marketingDeliveryProductRepository := marketingRepo.NewMarketingDeliveryProductRepository(db)
purchaseRepository := purchaseRepo.NewPurchaseRepository(db)
recordingRepository := recordingRepo.NewRecordingRepository(db)
approvalRepository := commonRepo.NewApprovalRepository(db)
approvalSvc := approvalService.NewApprovalService(approvalRepository)
repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, marketingDeliveryProductRepository, approvalSvc)
repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, marketingDeliveryProductRepository, purchaseRepository, recordingRepository, approvalSvc)
RepportRoutes(router, repportService)
}
@@ -12,6 +12,8 @@ import (
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/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/gofiber/fiber/v2"
@@ -29,15 +31,19 @@ type repportService struct {
Validate *validator.Validate
ExpenseRealizationRepo expenseRepo.ExpenseRealizationRepository
MarketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository
PurchaseRepo purchaseRepo.PurchaseRepository
RecordingRepo recordingRepo.RecordingRepository
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{
Log: utils.Log,
Validate: validate,
ExpenseRealizationRepo: expenseRealizationRepo,
MarketingDeliveryRepo: marketingDeliveryRepo,
PurchaseRepo: purchaseRepo,
RecordingRepo: recordingRepo,
ApprovalSvc: approvalSvc,
}
}
@@ -94,7 +100,7 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing
projectFlockIDs := s.collectProjectFlockIDs(deliveryProducts)
hppMap := s.buildHppMap(c.Context(), projectFlockIDs, deliveryProducts)
items := s.mapDeliveryProductsToDTOs(deliveryProducts, hppMap)
items := dto.ToRepportMarketingItemDTOsWithHppMap(deliveryProducts, hppMap)
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 {
hppMap := make(map[uint]float64)
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
}
return hppMap
}
func (s *repportService) mapDeliveryProductsToDTOs(deliveryProducts []entity.MarketingDeliveryProduct, hppMap map[uint]float64) []dto.RepportMarketingItemDTO {
items := make([]dto.RepportMarketingItemDTO, 0, len(deliveryProducts))
func (s *repportService) calculateHppByCategory(ctx context.Context, category string, projectFlockID uint, deliveryProducts []entity.MarketingDeliveryProduct) float64 {
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 {
hppPerKg := float64(0)
if projectFlockKandang := dp.MarketingProduct.ProductWarehouse.ProjectFlockKandang; projectFlockKandang != nil {
if hpp, exists := hppMap[projectFlockKandang.ProjectFlockId]; exists {
hppPerKg = hpp
if projectFlockKandang.ProjectFlockId == projectFlockID {
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 {
@@ -143,17 +158,22 @@ func (s *repportService) calculateHppPricePerKg(ctx context.Context, projectFloc
return 0
}
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(ctx, projectFlockID)
purchaseItems, err := s.PurchaseRepo.GetItemsByProjectFlockID(ctx, projectFlockID)
if err != nil {
return 0
s.Log.Warnf("GetItemsByProjectFlockID error: %v", err)
}
if len(realizations) == 0 {
return 0
costPurchase := float64(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)
for _, realization := range realizations {
cost := realization.Price * realization.Qty
category := ""
@@ -166,24 +186,21 @@ func (s *repportService) calculateHppPricePerKg(ctx context.Context, projectFloc
}
}
totalActualCost := costBop
totalActualCost := costPurchase + costBop
if totalActualCost == 0 {
return 0
}
totalWeightSold := float64(0)
for _, dp := range deliveryProducts {
if dp.MarketingProduct.ProductWarehouse.ProjectFlockKandang != nil &&
dp.MarketingProduct.ProductWarehouse.ProjectFlockKandang.ProjectFlockId == projectFlockID {
totalWeightSold += dp.TotalWeight
}
totalWeightProduced, _, err := s.RecordingRepo.GetProductionWeightAndQtyByProjectFlockID(ctx, projectFlockID)
if err != nil {
s.Log.Warnf("GetProductionWeightAndQtyByProjectFlockID error: %v", err)
}
if totalWeightSold == 0 {
if totalWeightProduced == 0 {
return 0
}
hppPerKg := totalActualCost / totalWeightSold
hppPerKg := totalActualCost / totalWeightProduced
return hppPerKg
}