feat(BE):Rekapitulasi hutang supplier

This commit is contained in:
ragilap
2026-01-11 19:41:21 +07:00
parent d3c7d65bf5
commit 09f1b29359
12 changed files with 588 additions and 47 deletions
@@ -18,6 +18,9 @@ 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"
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
chickinRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
recordingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
purchaseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
@@ -34,6 +37,7 @@ type RepportService interface {
GetExpense(ctx *fiber.Ctx, params *validation.ExpenseQuery) ([]dto.RepportExpenseListDTO, int64, error)
GetMarketing(ctx *fiber.Ctx, params *validation.MarketingQuery) ([]dto.RepportMarketingItemDTO, int64, error)
GetPurchaseSupplier(ctx *fiber.Ctx, params *validation.PurchaseSupplierQuery) ([]dto.PurchaseSupplierDTO, int64, error)
GetDebtSupplier(ctx *fiber.Ctx, params *validation.DebtSupplierQuery) ([]dto.DebtSupplierDTO, int64, error)
GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangResponseData, *dto.HppPerKandangMetaDTO, error)
GetProductionResult(ctx *fiber.Ctx, params *validation.ProductionResultQuery) ([]dto.ProductionResultDTO, int64, error)
}
@@ -48,6 +52,7 @@ type repportService struct {
RecordingRepo recordingRepo.RecordingRepository
ApprovalSvc approvalService.ApprovalService
PurchaseSupplierRepo repportRepo.PurchaseSupplierRepository
DebtSupplierRepo repportRepo.DebtSupplierRepository
HppPerKandangRepo repportRepo.HppPerKandangRepository
ProductionResultRepo repportRepo.ProductionResultRepository
}
@@ -70,6 +75,7 @@ func NewRepportService(
recordingRepo recordingRepo.RecordingRepository,
approvalSvc approvalService.ApprovalService,
purchaseSupplierRepo repportRepo.PurchaseSupplierRepository,
debtSupplierRepo repportRepo.DebtSupplierRepository,
hppPerKandangRepo repportRepo.HppPerKandangRepository,
productionResultRepo repportRepo.ProductionResultRepository,
) RepportService {
@@ -83,6 +89,7 @@ func NewRepportService(
RecordingRepo: recordingRepo,
ApprovalSvc: approvalSvc,
PurchaseSupplierRepo: purchaseSupplierRepo,
DebtSupplierRepo: debtSupplierRepo,
HppPerKandangRepo: hppPerKandangRepo,
ProductionResultRepo: productionResultRepo,
}
@@ -634,6 +641,200 @@ func (s *repportService) GetPurchaseSupplier(c *fiber.Ctx, params *validation.Pu
return result, totalSuppliers, nil
}
func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSupplierQuery) ([]dto.DebtSupplierDTO, int64, error) {
if params.FilterBy == "" {
params.FilterBy = "do_date"
}
if err := s.Validate.Struct(params); err != nil {
return nil, 0, err
}
offset := (params.Page - 1) * params.Limit
if offset < 0 {
offset = 0
}
suppliers, totalSuppliers, err := s.DebtSupplierRepo.GetSuppliersWithPurchases(c.Context(), offset, params.Limit, params)
if err != nil {
return nil, 0, err
}
if totalSuppliers == 0 || len(suppliers) == 0 {
return []dto.DebtSupplierDTO{}, totalSuppliers, nil
}
supplierMap := make(map[uint]entity.Supplier, len(suppliers))
supplierIDs := make([]uint, 0, len(suppliers))
for _, supplier := range suppliers {
supplierMap[supplier.Id] = supplier
supplierIDs = append(supplierIDs, supplier.Id)
}
purchases, err := s.DebtSupplierRepo.GetPurchasesBySuppliers(c.Context(), supplierIDs, params)
if err != nil {
return nil, 0, err
}
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
}
location, err := time.LoadLocation("Asia/Jakarta")
if err != nil {
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "failed to load timezone configuration")
}
now := time.Now().In(location)
result := make([]dto.DebtSupplierDTO, 0, len(supplierIDs))
for _, supplierID := range supplierIDs {
supplier, exists := supplierMap[supplierID]
if !exists {
continue
}
items := purchasesBySupplier[supplierID]
rows := make([]dto.DebtSupplierRowDTO, 0, len(items))
total := dto.DebtSupplierTotalDTO{}
for _, purchase := range items {
row := buildDebtSupplierRow(purchase, paymentTotals, now, location)
rows = append(rows, row)
if row.Aging > total.Aging {
total.Aging = row.Aging
}
total.TotalPrice += row.TotalPrice
total.PaymentPrice += row.PaymentPrice
total.DebtPrice += row.DebtPrice
}
sortDesc := strings.EqualFold(params.SortOrder, "desc")
sort.SliceStable(rows, func(i, j int) bool {
if sortDesc {
return rows[i].PrDate > rows[j].PrDate
}
return rows[i].PrDate < rows[j].PrDate
})
var supplierDTORef *supplierDTO.SupplierRelationDTO
if supplier.Id != 0 {
mapped := supplierDTO.ToSupplierRelationDTO(supplier)
supplierDTORef = &mapped
}
result = append(result, dto.DebtSupplierDTO{
Supplier: supplierDTORef,
Rows: rows,
Total: total,
})
}
return result, totalSuppliers, nil
}
func buildDebtSupplierRow(purchase entity.Purchase, paymentTotals map[string]float64, 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)
aging := int(endDate.Sub(startDate).Hours() / 24)
totalPrice := 0.0
travelNumber := "-"
var area *areaDTO.AreaRelationDTO
var warehouse *warehouseDTO.WarehouseRelationDTO
if len(purchase.Items) > 0 {
firstItem := purchase.Items[0]
if firstItem.TravelNumber != nil && strings.TrimSpace(*firstItem.TravelNumber) != "" {
travelNumber = *firstItem.TravelNumber
}
if firstItem.Warehouse != nil && firstItem.Warehouse.Id != 0 {
mappedWarehouse := warehouseDTO.ToWarehouseRelationDTO(*firstItem.Warehouse)
warehouse = &mappedWarehouse
if firstItem.Warehouse.Area.Id != 0 {
mappedArea := areaDTO.ToAreaRelationDTO(firstItem.Warehouse.Area)
area = &mappedArea
}
}
for _, item := range purchase.Items {
totalPrice += item.TotalPrice
}
}
paymentPrice := paymentTotals[reference]
debtPrice := paymentPrice - totalPrice
dueDate := ""
dueStatus := "-"
if purchase.DueDate != nil && !purchase.DueDate.IsZero() {
due := purchase.DueDate.In(loc)
dueDate = due.Format("2006-01-02")
if now.After(due) {
dueStatus = "Sudah Jatuh Tempo"
} else {
dueStatus = "Mendekati Jatuh Tempo"
}
}
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")
}
return dto.DebtSupplierRowDTO{
PrNumber: prNumber,
PoNumber: poNumber,
PrDate: prDate.Format("2006-01-02"),
PoDate: poDate,
Aging: aging,
Area: area,
Warehouse: warehouse,
DueDate: dueDate,
DueStatus: dueStatus,
TotalPrice: totalPrice,
PaymentPrice: paymentPrice,
DebtPrice: debtPrice,
Status: status,
TravelNumber: travelNumber,
}
}
func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangResponseData, *dto.HppPerKandangMetaDTO, error) {
params, filters, err := s.parseHppPerKandangQuery(ctx)
if err != nil {