feat[BE]: Enhance payment allocation logic to support FIFO consumption for sales transactions

This commit is contained in:
aguhh18
2026-01-19 09:27:37 +07:00
parent fb193fc61f
commit 378d633ea4
@@ -464,9 +464,13 @@ func (s *repportService) GetCustomerPayment(ctx *fiber.Ctx, params *validation.C
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
result = append(result, item)
if len(item.Rows) > 0 {
result = append(result, item)
}
} }
totalCustomers = int64(len(result))
return result, totalCustomers, nil return result, totalCustomers, nil
} }
@@ -503,14 +507,8 @@ func (s *repportService) processCustomerPayment(ctx context.Context, customerID
row.Status = status row.Status = status
if status == "LUNAS" { if status == "LUNAS" {
if previousBalance >= tx.TotalPrice { if paymentDate != nil {
days := 0
row.AgingDay = &days
} else if paymentDate != nil {
days := int(paymentDate.Sub(tx.TransDate).Hours() / 24) days := int(paymentDate.Sub(tx.TransDate).Hours() / 24)
if days < 0 {
days = 0
}
row.AgingDay = &days row.AgingDay = &days
} else { } else {
days := 0 days := 0
@@ -518,9 +516,6 @@ func (s *repportService) processCustomerPayment(ctx context.Context, customerID
} }
} else { } else {
days := int(time.Since(tx.TransDate).Hours() / 24) days := int(time.Since(tx.TransDate).Hours() / 24)
if days < 0 {
days = 0
}
row.AgingDay = &days row.AgingDay = &days
} }
} else if tx.TransactionType == "PAYMENT" { } else if tx.TransactionType == "PAYMENT" {
@@ -586,6 +581,67 @@ func (s *repportService) determineSalesStatusAndPaymentDate(transactions []reppo
// 4. BELUM LUNAS: no payment at all // 4. BELUM LUNAS: no payment at all
if previousBalance >= currentSales.TotalPrice { 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
consumed float64
}
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{
date: transactions[i].TransDate,
amount: transactions[i].PaymentAmount,
consumed: 0,
})
runningBalance += transactions[i].PaymentAmount
} else if transactions[i].TransactionType == "SALES" {
salesAmount := transactions[i].TotalPrice
remainingToConsume := salesAmount
// Consume from oldest allocations first (FIFO)
for j := range allocations {
if remainingToConsume <= 0 {
break
}
available := allocations[j].amount - allocations[j].consumed
if available > 0 {
consume := available
if consume > remainingToConsume {
consume = remainingToConsume
}
allocations[j].consumed += consume
remainingToConsume -= consume
}
}
runningBalance -= salesAmount
}
}
// 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
}
return "LUNAS", nil return "LUNAS", nil
} }