mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
FEAT[BE[: enhance marketing report items with aging days calculation
This commit is contained in:
@@ -53,11 +53,10 @@ type ProductRelationDTOFixed struct {
|
||||
SellingPrice *float64 `json:"selling_price,omitempty"`
|
||||
}
|
||||
|
||||
func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppMap map[uint]float64) []RepportMarketingItemDTO {
|
||||
func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppMap map[uint]float64, agingMap map[int]int) []RepportMarketingItemDTO {
|
||||
items := make([]RepportMarketingItemDTO, 0, len(mdps))
|
||||
|
||||
for _, mdp := range mdps {
|
||||
// Get HPP and category from map
|
||||
hppPerKg := float64(0)
|
||||
category := ""
|
||||
if projectFlockKandang := mdp.MarketingProduct.ProductWarehouse.ProjectFlockKandang; projectFlockKandang != nil {
|
||||
@@ -67,13 +66,16 @@ func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppMap map[u
|
||||
category = projectFlockKandang.ProjectFlock.Category
|
||||
}
|
||||
|
||||
// Calculate dates
|
||||
soDate := time.Time{}
|
||||
agingDays := 0
|
||||
if mdp.MarketingProduct.Marketing.SoDate.Year() > 1 {
|
||||
soDate = mdp.MarketingProduct.Marketing.SoDate
|
||||
if ag, exists := agingMap[int(mdp.Id)]; exists {
|
||||
agingDays = ag
|
||||
} else {
|
||||
agingDays = int(time.Since(soDate).Hours() / 24)
|
||||
}
|
||||
}
|
||||
|
||||
realizationDate := time.Time{}
|
||||
if mdp.DeliveryDate != nil {
|
||||
@@ -106,7 +108,6 @@ func ToMarketingReportItems(mdps []entity.MarketingDeliveryProduct, hppMap map[u
|
||||
}
|
||||
}
|
||||
|
||||
// Determine marketing type
|
||||
marketingType := "trading"
|
||||
if hasTrading {
|
||||
marketingType = "trading"
|
||||
@@ -196,13 +197,9 @@ func ToSummaryFromDTOItems(items []RepportMarketingItemDTO) *Summary {
|
||||
totalHppPricePerKg = float64(totalHppAmount) / totalWeightKg
|
||||
}
|
||||
|
||||
if len(items) > 0 {
|
||||
avgSalesAmount = float64(totalSalesAmount) / float64(len(items))
|
||||
}
|
||||
|
||||
if totalQty > 0 {
|
||||
avgWeightKg = totalWeightKg / float64(totalQty)
|
||||
avgSalesAmount = float64(totalSalesAmount) / float64(totalQty) // ← TAMBAHAN INI
|
||||
avgSalesAmount = float64(totalSalesAmount) / float64(totalQty)
|
||||
}
|
||||
|
||||
return &Summary{
|
||||
|
||||
@@ -165,6 +165,47 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
customerGroups := make(map[uint][]entity.MarketingDeliveryProduct)
|
||||
for _, dp := range deliveryProducts {
|
||||
customerID := dp.MarketingProduct.Marketing.CustomerId
|
||||
customerGroups[customerID] = append(customerGroups[customerID], dp)
|
||||
}
|
||||
|
||||
agingMap := make(map[int]int)
|
||||
for customerID := range customerGroups {
|
||||
transactions, err := s.CustomerPaymentRepo.GetCustomerPaymentTransactions(c.Context(), &customerID)
|
||||
if err != nil {
|
||||
s.Log.Warnf("Failed to get transactions for customer %d: %v", customerID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
initialBalance, err := s.CustomerPaymentRepo.GetInitialBalanceByCustomer(c.Context(), customerID)
|
||||
if err != nil {
|
||||
initialBalance = 0
|
||||
}
|
||||
|
||||
runningBalance := initialBalance
|
||||
for i, tx := range transactions {
|
||||
if tx.TransactionType == "SALES" {
|
||||
previousBalance := runningBalance
|
||||
runningBalance -= tx.TotalPrice
|
||||
currentBalance := runningBalance
|
||||
|
||||
_, paymentDate := s.determineSalesStatusAndPaymentDate(transactions, i, previousBalance, currentBalance)
|
||||
|
||||
if paymentDate != nil {
|
||||
agingDays := int(paymentDate.Sub(tx.TransDate).Hours() / 24)
|
||||
agingMap[int(tx.TransactionID)] = agingDays
|
||||
} else {
|
||||
agingDays := int(time.Since(tx.TransDate).Hours() / 24)
|
||||
agingMap[int(tx.TransactionID)] = agingDays
|
||||
}
|
||||
} else if tx.TransactionType == "PAYMENT" {
|
||||
runningBalance += tx.PaymentAmount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
projectFlockIDMap := make(map[uint]bool)
|
||||
hppMap := make(map[uint]float64)
|
||||
|
||||
@@ -181,7 +222,7 @@ func (s *repportService) GetMarketing(c *fiber.Ctx, params *validation.Marketing
|
||||
}
|
||||
}
|
||||
|
||||
items := dto.ToMarketingReportItems(deliveryProducts, hppMap)
|
||||
items := dto.ToMarketingReportItems(deliveryProducts, hppMap, agingMap)
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
@@ -422,12 +463,10 @@ func (s *repportService) GetCustomerPayment(ctx *fiber.Ctx, params *validation.C
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Determine customer IDs to process
|
||||
var customerIDs []uint
|
||||
var totalCustomers int64
|
||||
|
||||
if len(params.CustomerIDs) > 0 {
|
||||
// Specific customer IDs mode (no pagination)
|
||||
customerIDs = params.CustomerIDs
|
||||
totalCustomers = int64(len(customerIDs))
|
||||
|
||||
@@ -435,7 +474,6 @@ func (s *repportService) GetCustomerPayment(ctx *fiber.Ctx, params *validation.C
|
||||
return []dto.CustomerPaymentReportItem{}, 0, nil
|
||||
}
|
||||
} else {
|
||||
// Multiple customers mode with pagination
|
||||
page := params.Page
|
||||
limit := params.Limit
|
||||
if page < 1 {
|
||||
@@ -574,15 +612,7 @@ func (s *repportService) processCustomerPayment(ctx context.Context, customerID
|
||||
func (s *repportService) determineSalesStatusAndPaymentDate(transactions []repportRepo.CustomerPaymentTransaction, currentIndex int, previousBalance, currentBalance float64) (string, *time.Time) {
|
||||
currentSales := transactions[currentIndex]
|
||||
|
||||
// Status Logic:
|
||||
// 1. LUNAS: previousBalance >= salesAmount (paid from deposit)
|
||||
// 2. LUNAS: future payments make AR >= 0 (eventually paid)
|
||||
// 3. DIBAYAR SEBAGIAN: has payment but not enough
|
||||
// 4. BELUM LUNAS: no payment at all
|
||||
|
||||
if previousBalance >= currentSales.TotalPrice {
|
||||
// Cari payment yang digunakan untuk melunasi sales ini dengan FIFO
|
||||
// Track payment allocations that are consumed by previous sales
|
||||
type paymentAllocation struct {
|
||||
date time.Time
|
||||
amount float64
|
||||
@@ -591,7 +621,6 @@ func (s *repportService) determineSalesStatusAndPaymentDate(transactions []reppo
|
||||
allocations := []paymentAllocation{}
|
||||
runningBalance := 0.0
|
||||
|
||||
// Process all transactions before current sales to build allocation map
|
||||
for i := 0; i < currentIndex; i++ {
|
||||
if transactions[i].TransactionType == "PAYMENT" {
|
||||
allocations = append(allocations, paymentAllocation{
|
||||
@@ -604,7 +633,6 @@ func (s *repportService) determineSalesStatusAndPaymentDate(transactions []reppo
|
||||
salesAmount := transactions[i].TotalPrice
|
||||
remainingToConsume := salesAmount
|
||||
|
||||
// Consume from oldest allocations first (FIFO)
|
||||
for j := range allocations {
|
||||
if remainingToConsume <= 0 {
|
||||
break
|
||||
@@ -623,22 +651,18 @@ func (s *repportService) determineSalesStatusAndPaymentDate(transactions []reppo
|
||||
}
|
||||
}
|
||||
|
||||
// Now find which allocation covers the current sales
|
||||
amountNeeded := currentSales.TotalPrice
|
||||
for _, alloc := range allocations {
|
||||
available := alloc.amount - alloc.consumed
|
||||
if available > 0 {
|
||||
if amountNeeded <= available {
|
||||
// This allocation fully covers the sales
|
||||
return "LUNAS", &alloc.date
|
||||
} else {
|
||||
// This allocation partially covers, continue to next
|
||||
amountNeeded -= available
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, use the oldest allocation
|
||||
if len(allocations) > 0 {
|
||||
return "LUNAS", &allocations[0].date
|
||||
}
|
||||
@@ -690,7 +714,6 @@ func mapRecordingToProductionResultDTO(record entity.Recording) dto.ProductionRe
|
||||
if record.Day != nil {
|
||||
result.Woa = float64(*record.Day)
|
||||
}
|
||||
// avgWeight := calculateAverageBodyWeight(record.BodyWeights)
|
||||
avgWeight := 1.0
|
||||
if avgWeight > 0 {
|
||||
result.Bw = avgWeight
|
||||
@@ -1570,12 +1593,9 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
||||
dataRows := make([]dto.HppPerKandangRowDTO, 0, len(repoRows))
|
||||
perRangeMap := make(map[weightRangeKey]*weightRangeAggregate)
|
||||
var totalBirds int64
|
||||
// var totalWeight float64
|
||||
var totalEggPieces int64
|
||||
var totalEggKg float64
|
||||
// var totalRemainingValueRp int64
|
||||
var totalEggValueRp int64
|
||||
// var totalHppSum float64
|
||||
var totalHppCount int
|
||||
var totalDocPriceSum float64
|
||||
var totalDocPriceCount int
|
||||
@@ -1589,14 +1609,6 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
||||
continue
|
||||
}
|
||||
|
||||
// birdsFloat := row.RemainingChickenBirds
|
||||
// if math.IsNaN(birdsFloat) || math.IsInf(birdsFloat, 0) {
|
||||
// birdsFloat = 0
|
||||
// }
|
||||
// weightFloat := row.RemainingChickenWeight
|
||||
// if math.IsNaN(weightFloat) || math.IsInf(weightFloat, 0) {
|
||||
// weightFloat = 0
|
||||
// }
|
||||
eggPiecesFloatRemaining := row.EggProductionPiecesRemaining
|
||||
if math.IsNaN(eggPiecesFloatRemaining) || math.IsInf(eggPiecesFloatRemaining, 0) {
|
||||
eggPiecesFloatRemaining = 0
|
||||
@@ -1632,13 +1644,8 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
||||
weightMax := weightMin + 0.09
|
||||
rangeKey := weightRangeKey{Min: weightMin, Max: weightMax}
|
||||
|
||||
// rowBirds := int64(math.Round(birdsFloat))
|
||||
costEntry := costMap[row.ProjectFlockKandangID]
|
||||
totalCost := costEntry.FeedCost + costEntry.OvkCost + costEntry.DocCost + costEntry.BudgetCost + costEntry.ExpenseCost
|
||||
// hppRp := 0.0
|
||||
// if weightFloat > 0 {
|
||||
// hppRp = totalCost / weightFloat
|
||||
// }
|
||||
eggHpp := 0.0
|
||||
if eggWeightFloat > 0 {
|
||||
eggHpp = (totalCost / eggWeightFloat) / 1000
|
||||
@@ -1646,7 +1653,6 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
||||
|
||||
rowEggPieces := int64(math.Round(eggPiecesFloatRemaining))
|
||||
rowEggValue := int64(eggHpp * eggRemainingWeightFloatRemaining)
|
||||
// rowRemainingValue := int64(hppRp * weightFloat)
|
||||
avgDocPrice := int64(0)
|
||||
if costEntry.DocQty > 0 {
|
||||
avgDocPrice = int64(math.Round(costEntry.DocCost / costEntry.DocQty))
|
||||
@@ -1675,33 +1681,20 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
||||
},
|
||||
AvgWeightKg: avgWeight,
|
||||
NameWithPeriode: nameWithPeriod,
|
||||
// FeedCostRp: costEntry.FeedCost,
|
||||
// OvkCostRp: costEntry.OvkCost,
|
||||
DocSuppliers: docSupplierMap[row.ProjectFlockKandangID],
|
||||
FeedSuppliers: feedSupplierMap[row.ProjectFlockKandangID],
|
||||
EggProductionPieces: int64(math.Round(eggPiecesFloatRemaining)),
|
||||
EggProductionKg: eggRemainingWeightFloatRemaining,
|
||||
// EggProductionTotalWeightKg: eggWeightFloat,
|
||||
// EggProductionTotalPieces: int64(math.Round(eggTotalPiecesFloat)),
|
||||
AverageDocPriceRp: avgDocPrice,
|
||||
// HppRp: hppRp,
|
||||
EggHppRpPerKg: eggHpp,
|
||||
// RemainingValueRp: rowRemainingValue,
|
||||
EggValueRp: rowEggValue,
|
||||
})
|
||||
|
||||
// totalBirds += rowBirds
|
||||
// totalWeight += weightFloat
|
||||
totalEggPieces += rowEggPieces
|
||||
totalEggKg += eggRemainingWeightFloatRemaining
|
||||
// totalRemainingValueRp += rowRemainingValue
|
||||
totalEggValueRp += rowEggValue
|
||||
totalAvgWeightSum += avgWeight
|
||||
totalAvgWeightCount++
|
||||
// if weightFloat > 0 {
|
||||
// totalHppSum += hppRp
|
||||
// totalHppCount++
|
||||
// }
|
||||
if avgDocPrice > 0 {
|
||||
totalDocPriceSum += float64(avgDocPrice)
|
||||
totalDocPriceCount++
|
||||
@@ -1728,8 +1721,6 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
||||
}
|
||||
|
||||
rangeSummary := rangeAgg.Summary
|
||||
// rangeAgg.RemainingBirds += rowBirds
|
||||
// rangeAgg.RemainingWeightKg += row.RemainingChickenWeight
|
||||
rangeAgg.AvgWeightSum += avgWeight
|
||||
rangeAgg.AvgWeightCount++
|
||||
for _, supplier := range feedSupplierMap[row.ProjectFlockKandangID] {
|
||||
@@ -1744,7 +1735,6 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
||||
}
|
||||
rangeSummary.EggProductionPieces += rowEggPieces
|
||||
rangeSummary.EggProductionKg += eggRemainingWeightFloatRemaining
|
||||
// rangeSummary.RemainingValueRp += rowRemainingValue
|
||||
rangeSummary.EggValueRp += rowEggValue
|
||||
if eggWeightFloat > 0 {
|
||||
rangeAgg.EggHppSum += eggHpp
|
||||
|
||||
Reference in New Issue
Block a user