Merge branch 'feat/BE/Rekapitulasi-hutang-supplier' into 'development'

[Feat/be] Adjustment Counting debt supplier

See merge request mbugroup/lti-api!173
This commit is contained in:
Hafizh A. Y.
2026-01-13 17:40:18 +00:00
4 changed files with 37 additions and 69 deletions
+1
View File
@@ -14,6 +14,7 @@ Makefile
docker-compose.local.yml
docker-compose.yaml
Dockerfile
Dockerfile.local
.gitlab-ci.yml
# Go build cache
.gocache/
@@ -31,11 +31,9 @@ func NewDebtSupplierRepository(db *gorm.DB) DebtSupplierRepository {
func resolveDebtSupplierDateColumn(filterBy string) string {
switch strings.ToLower(strings.TrimSpace(filterBy)) {
case "receive_date":
return "purchases.receive_date"
case "po_date":
return "purchases.po_date"
case "do_date", "received_date", "":
case "received_date", "":
return "purchase_items.received_date"
default:
return "purchase_items.received_date"
@@ -130,7 +128,7 @@ func (r *debtSupplierRepositoryImpl) GetPurchasesBySuppliers(ctx context.Context
Preload("Warehouse.Area").
Order("purchase_items.id ASC")
if strings.EqualFold(strings.TrimSpace(filters.FilterBy), "do_date") || strings.EqualFold(strings.TrimSpace(filters.FilterBy), "received_date") || strings.TrimSpace(filters.FilterBy) == "" {
if strings.EqualFold(strings.TrimSpace(filters.FilterBy), "received_date") || strings.TrimSpace(filters.FilterBy) == "" {
if filters.StartDate != "" {
if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil {
db = db.Where("DATE(purchase_items.received_date) >= ?", dateFrom)
@@ -642,7 +642,7 @@ func (s *repportService) GetPurchaseSupplier(c *fiber.Ctx, params *validation.Pu
}
func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSupplierQuery) ([]dto.DebtSupplierDTO, int64, error) {
if params.FilterBy == "" || strings.EqualFold(strings.TrimSpace(params.FilterBy), "do_date") {
if params.FilterBy == "" {
params.FilterBy = "received_date"
}
@@ -681,25 +681,8 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
}
purchasesBySupplier := make(map[uint][]entity.Purchase, len(supplierIDs))
references := make([]string, 0)
seenRefs := make(map[string]struct{})
for _, purchase := range purchases {
supplierID := purchase.SupplierId
purchasesBySupplier[supplierID] = append(purchasesBySupplier[supplierID], purchase)
reference := purchase.PrNumber
if purchase.PoNumber != nil && strings.TrimSpace(*purchase.PoNumber) != "" {
reference = *purchase.PoNumber
}
if _, exists := seenRefs[reference]; !exists {
seenRefs[reference] = struct{}{}
references = append(references, reference)
}
}
paymentTotals, err := s.DebtSupplierRepo.GetPaymentTotalsByReferences(c.Context(), supplierIDs, references)
if err != nil {
return nil, 0, err
purchasesBySupplier[purchase.SupplierId] = append(purchasesBySupplier[purchase.SupplierId], purchase)
}
paymentsBySupplier := make(map[uint][]entity.Payment, len(supplierIDs))
@@ -724,19 +707,6 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
now := time.Now().In(location)
result := make([]dto.DebtSupplierDTO, 0, len(supplierIDs))
for _, supplierID := range supplierIDs {
supplier, exists := supplierMap[supplierID]
if !exists {
continue
}
initialBalance := initialPaymentTotals[supplierID] - initialPurchaseTotals[supplierID]
items := purchasesBySupplier[supplierID]
paymentItems := paymentsBySupplier[supplierID]
rows := make([]dto.DebtSupplierRowDTO, 0, len(items)+len(paymentItems))
total := dto.DebtSupplierTotalDTO{}
type debtSupplierRowItem struct {
Row dto.DebtSupplierRowDTO
SortTime time.Time
@@ -745,9 +715,20 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
CountTotals bool
}
for _, supplierID := range supplierIDs {
supplier, exists := supplierMap[supplierID]
if !exists {
continue
}
initialBalance := initialPaymentTotals[supplierID] - initialPurchaseTotals[supplierID]
items := purchasesBySupplier[supplierID]
paymentItems := paymentsBySupplier[supplierID]
total := dto.DebtSupplierTotalDTO{}
combinedRows := make([]debtSupplierRowItem, 0, len(items)+len(paymentItems))
for _, purchase := range items {
row := buildDebtSupplierRow(purchase, paymentTotals, now, location)
row := buildDebtSupplierRow(purchase, now, location)
sortTime := resolveDebtSupplierSortTime(purchase, params.FilterBy, location)
combinedRows = append(combinedRows, debtSupplierRowItem{
Row: row,
@@ -780,6 +761,7 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
balance := initialBalance
for i := range combinedRows {
balance += combinedRows[i].DeltaBalance
combinedRows[i].Row.DebtPrice = balance
combinedRows[i].Row.Balance = balance
if combinedRows[i].CountTotals {
@@ -788,13 +770,13 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
total.Aging = row.Aging
}
total.TotalPrice += row.TotalPrice
total.PaymentPrice += row.PaymentPrice
total.DebtPrice += row.DebtPrice
} else {
combinedRows[i].Row.DebtPrice = balance
total.PaymentPrice += combinedRows[i].Row.PaymentPrice
}
}
total.DebtPrice = balance
rows := make([]dto.DebtSupplierRowDTO, 0, len(combinedRows))
sortDesc := strings.EqualFold(params.SortOrder, "desc")
if sortDesc {
for i := len(combinedRows) - 1; i >= 0; i-- {
@@ -823,18 +805,13 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
return result, totalSuppliers, nil
}
func buildDebtSupplierRow(purchase entity.Purchase, paymentTotals map[string]float64, now time.Time, loc *time.Location) dto.DebtSupplierRowDTO {
func buildDebtSupplierRow(purchase entity.Purchase, now time.Time, loc *time.Location) dto.DebtSupplierRowDTO {
prNumber := purchase.PrNumber
poNumber := ""
if purchase.PoNumber != nil {
poNumber = *purchase.PoNumber
}
reference := prNumber
if strings.TrimSpace(poNumber) != "" {
reference = poNumber
}
prDate := purchase.CreatedAt.In(loc)
startDate := time.Date(prDate.Year(), prDate.Month(), prDate.Day(), 0, 0, 0, 0, loc)
endDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
@@ -877,9 +854,6 @@ func buildDebtSupplierRow(purchase entity.Purchase, paymentTotals map[string]flo
}
}
paymentPrice := paymentTotals[reference]
debtPrice := paymentPrice - totalPrice
dueDate := ""
dueStatus := "-"
if purchase.DueDate != nil && !purchase.DueDate.IsZero() {
@@ -893,10 +867,6 @@ func buildDebtSupplierRow(purchase entity.Purchase, paymentTotals map[string]flo
}
status := "Belum Lunas"
if debtPrice >= 0 {
status = "Lunas"
}
poDate := ""
if purchase.PoDate != nil && !purchase.PoDate.IsZero() {
poDate = purchase.PoDate.In(loc).Format("2006-01-02")
@@ -913,10 +883,11 @@ func buildDebtSupplierRow(purchase entity.Purchase, paymentTotals map[string]flo
DueDate: dueDate,
DueStatus: dueStatus,
TotalPrice: totalPrice,
PaymentPrice: paymentPrice,
DebtPrice: debtPrice,
PaymentPrice: 0,
DebtPrice: 0,
Status: status,
TravelNumber: travelNumber,
Balance: 0,
}
}
@@ -946,18 +917,17 @@ func buildDebtSupplierPaymentRow(payment entity.Payment, loc *time.Location) dto
DebtPrice: 0,
Status: "Pembayaran",
TravelNumber: "-",
Balance: 0,
}
}
func resolveDebtSupplierSortTime(purchase entity.Purchase, filterBy string, loc *time.Location) time.Time {
switch strings.ToLower(strings.TrimSpace(filterBy)) {
case "po_date":
if strings.EqualFold(strings.TrimSpace(filterBy), "po_date") {
if purchase.PoDate != nil && !purchase.PoDate.IsZero() {
return purchase.PoDate.In(loc)
}
case "pr_date":
return purchase.CreatedAt.In(loc)
default:
}
earliest := time.Time{}
for _, item := range purchase.Items {
if item.ReceivedDate == nil || item.ReceivedDate.IsZero() {
@@ -971,7 +941,6 @@ func resolveDebtSupplierSortTime(purchase entity.Purchase, filterBy string, loc
if !earliest.IsZero() {
return earliest
}
}
return purchase.CreatedAt.In(loc)
}
@@ -50,7 +50,7 @@ type DebtSupplierQuery struct {
SupplierIDs []int64 `query:"-" validate:"omitempty,dive,gt=0"`
StartDate string `query:"start_date" validate:"omitempty,datetime=2006-01-02"`
EndDate string `query:"end_date" validate:"omitempty,datetime=2006-01-02"`
FilterBy string `query:"filter_by" validate:"omitempty,oneof=received_date po_date pr_date do_date"`
FilterBy string `query:"filter_by" validate:"omitempty,oneof=received_date po_date"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
}