mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat(BE):Rekapitulasi hutang supplier
This commit is contained in:
@@ -15,15 +15,9 @@ func UomRoutes(v1 fiber.Router, u user.UserService, s uom.UomService) {
|
||||
route := v1.Group("/uoms")
|
||||
route.Use(m.Auth(u))
|
||||
|
||||
route.Get("/", ctrl.GetAll)
|
||||
route.Post("/", ctrl.CreateOne)
|
||||
route.Get("/:id", ctrl.GetOne)
|
||||
route.Patch("/:id", ctrl.UpdateOne)
|
||||
route.Delete("/:id", ctrl.DeleteOne)
|
||||
|
||||
route.Get("/",m.RequirePermissions(m.P_AreaGetAll), ctrl.GetAll)
|
||||
route.Post("/",m.RequirePermissions(m.P_AreaCreateOne), ctrl.CreateOne)
|
||||
route.Get("/:id",m.RequirePermissions(m.P_AreaGetOne), ctrl.GetOne)
|
||||
route.Patch("/:id",m.RequirePermissions(m.P_AreaUpdateOne), ctrl.UpdateOne)
|
||||
route.Delete("/:id",m.RequirePermissions(m.P_AreaDeleteOne), ctrl.DeleteOne)
|
||||
route.Get("/", m.RequirePermissions(m.P_UomsGetAll), ctrl.GetAll)
|
||||
route.Post("/", m.RequirePermissions(m.P_UomsCreateOne), ctrl.CreateOne)
|
||||
route.Get("/:id", m.RequirePermissions(m.P_UomsGetOne), ctrl.GetOne)
|
||||
route.Patch("/:id", m.RequirePermissions(m.P_UomsUpdateOne), ctrl.UpdateOne)
|
||||
route.Delete("/:id", m.RequirePermissions(m.P_UomsDeleteOne), ctrl.DeleteOne)
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@ func Routes(router fiber.Router, purchaseService service.PurchaseService, userSe
|
||||
route := router.Group("/purchases")
|
||||
route.Use(m.Auth(userService))
|
||||
|
||||
route.Get("/",m.RequirePermissions(m.P_PurchaseGetAll), ctrl.GetAll)
|
||||
route.Get("/:id",m.RequirePermissions(m.P_PurchaseGetOne), ctrl.GetOne)
|
||||
route.Post("/", ctrl.CreateOne)
|
||||
route.Post("/:id/approvals/staff", ctrl.ApproveStaffPurchase)
|
||||
route.Post("/:id/approvals/manager", ctrl.ApproveManagerPurchase)
|
||||
route.Post("/:id/receipts",ctrl.ReceiveProducts)
|
||||
route.Delete("/:id", ctrl.DeletePurchase)
|
||||
route.Delete("/:id/items", ctrl.DeleteItems)
|
||||
route.Get("/", m.RequirePermissions(m.P_PurchaseGetAll), ctrl.GetAll)
|
||||
route.Get("/:id", m.RequirePermissions(m.P_PurchaseGetOne), ctrl.GetOne)
|
||||
route.Post("/", m.RequirePermissions(m.P_PurchaseCreateOne), ctrl.CreateOne)
|
||||
route.Post("/:id/approvals/staff", m.RequirePermissions(m.P_PurchaseApprovalStaff), ctrl.ApproveStaffPurchase)
|
||||
route.Post("/:id/approvals/manager", m.RequirePermissions(m.P_PurchaseApprovalManager), ctrl.ApproveManagerPurchase)
|
||||
route.Post("/:id/receipts", m.RequirePermissions(m.P_PurchaseReceive), ctrl.ReceiveProducts)
|
||||
route.Delete("/:id", m.RequirePermissions(m.P_PurchaseDeleteOne), ctrl.DeletePurchase)
|
||||
route.Delete("/:id/items", m.RequirePermissions(m.P_PurchaseItemDeleteOne), ctrl.DeleteItems)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package controller
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services"
|
||||
@@ -164,6 +165,59 @@ func (c *RepportController) GetPurchaseSupplier(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (c *RepportController) GetDebtSupplier(ctx *fiber.Ctx) error {
|
||||
supplierIDs, err := parseCommaSeparatedInt64s(ctx.Query("supplier_ids", ""))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
query := &validation.DebtSupplierQuery{
|
||||
Page: ctx.QueryInt("page", 1),
|
||||
Limit: ctx.QueryInt("limit", 10),
|
||||
SupplierIDs: supplierIDs,
|
||||
StartDate: ctx.Query("start_date", ""),
|
||||
EndDate: ctx.Query("end_date", ""),
|
||||
FilterBy: ctx.Query("filter_by", ""),
|
||||
SortOrder: ctx.Query("sort_order", ""),
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||
}
|
||||
|
||||
result, totalResults, err := c.RepportService.GetDebtSupplier(ctx, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
supplierIDs = query.SupplierIDs
|
||||
if supplierIDs == nil {
|
||||
supplierIDs = []int64{}
|
||||
}
|
||||
|
||||
filters := map[string]interface{}{
|
||||
"start_date": query.StartDate,
|
||||
"end_date": query.EndDate,
|
||||
"supplier_ids": supplierIDs,
|
||||
"filter_by": query.FilterBy,
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.DebtSupplierDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get supplier debt recap successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
Filters: filters,
|
||||
},
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *RepportController) GetHppPerKandang(ctx *fiber.Ctx) error {
|
||||
data, meta, err := c.RepportService.GetHppPerKandang(ctx)
|
||||
if err != nil {
|
||||
@@ -227,3 +281,27 @@ func (c *RepportController) GetProductionResult(ctx *fiber.Ctx) error {
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
func parseCommaSeparatedInt64s(raw string) ([]int64, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return []int64{}, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(raw, ",")
|
||||
result := make([]int64, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(part, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "supplier_ids must be comma separated integers")
|
||||
}
|
||||
result = append(result, id)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
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"
|
||||
)
|
||||
|
||||
type DebtSupplierRowDTO struct {
|
||||
PrNumber string `json:"pr_number"`
|
||||
PoNumber string `json:"po_number"`
|
||||
PrDate string `json:"pr_date"`
|
||||
PoDate string `json:"po_date"`
|
||||
Aging int `json:"aging"`
|
||||
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
||||
Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
|
||||
DueDate string `json:"due_date"`
|
||||
DueStatus string `json:"due_status"`
|
||||
TotalPrice float64 `json:"total_price"`
|
||||
PaymentPrice float64 `json:"payment_price"`
|
||||
DebtPrice float64 `json:"debt_price"`
|
||||
Status string `json:"status"`
|
||||
TravelNumber string `json:"travel_number"`
|
||||
}
|
||||
|
||||
type DebtSupplierTotalDTO struct {
|
||||
Aging int `json:"aging"`
|
||||
TotalPrice float64 `json:"total_price"`
|
||||
PaymentPrice float64 `json:"payment_price"`
|
||||
DebtPrice float64 `json:"debt_price"`
|
||||
}
|
||||
|
||||
type DebtSupplierDTO struct {
|
||||
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
|
||||
Rows []DebtSupplierRowDTO `json:"rows"`
|
||||
Total DebtSupplierTotalDTO `json:"total"`
|
||||
}
|
||||
@@ -31,12 +31,13 @@ func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
||||
recordingRepository := recordingRepo.NewRecordingRepository(db)
|
||||
approvalRepository := commonRepo.NewApprovalRepository(db)
|
||||
purchaseSupplierRepository := repportRepo.NewPurchaseSupplierRepository(db)
|
||||
debtSupplierRepository := repportRepo.NewDebtSupplierRepository(db)
|
||||
hppPerKandangRepository := repportRepo.NewHppPerKandangRepository(db)
|
||||
productionResultRepository := repportRepo.NewProductionResultRepository(db)
|
||||
userRepository := rUser.NewUserRepository(db)
|
||||
|
||||
approvalSvc := approvalService.NewApprovalService(approvalRepository)
|
||||
repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, marketingDeliveryProductRepository, purchaseRepository, chickinRepository, recordingRepository, approvalSvc, purchaseSupplierRepository, hppPerKandangRepository, productionResultRepository)
|
||||
repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, marketingDeliveryProductRepository, purchaseRepository, chickinRepository, recordingRepository, approvalSvc, purchaseSupplierRepository, debtSupplierRepository, hppPerKandangRepository, productionResultRepository)
|
||||
userService := sUser.NewUserService(userRepository, validate)
|
||||
|
||||
RepportRoutes(router, userService, repportService)
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DebtSupplierRepository interface {
|
||||
GetSuppliersWithPurchases(ctx context.Context, offset, limit int, filters *validation.DebtSupplierQuery) ([]entity.Supplier, int64, error)
|
||||
GetPurchasesBySuppliers(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) ([]entity.Purchase, error)
|
||||
GetPaymentTotalsByReferences(ctx context.Context, supplierIDs []uint, references []string) (map[string]float64, error)
|
||||
}
|
||||
|
||||
type debtSupplierRepositoryImpl struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewDebtSupplierRepository(db *gorm.DB) DebtSupplierRepository {
|
||||
return &debtSupplierRepositoryImpl{db: db}
|
||||
}
|
||||
|
||||
func resolveDebtSupplierDateColumn(filterBy string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(filterBy)) {
|
||||
case "po_date":
|
||||
return "purchases.po_date"
|
||||
case "pr_date":
|
||||
return "purchases.created_at"
|
||||
case "do_date", "received_date", "":
|
||||
return "purchase_items.received_date"
|
||||
default:
|
||||
return "purchase_items.received_date"
|
||||
}
|
||||
}
|
||||
|
||||
func (r *debtSupplierRepositoryImpl) baseSupplierQuery(ctx context.Context, filters *validation.DebtSupplierQuery) *gorm.DB {
|
||||
dateColumn := resolveDebtSupplierDateColumn(filters.FilterBy)
|
||||
|
||||
db := r.db.WithContext(ctx).
|
||||
Model(&entity.Supplier{}).
|
||||
Joins("JOIN purchases ON purchases.supplier_id = suppliers.id").
|
||||
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id")
|
||||
|
||||
if len(filters.SupplierIDs) > 0 {
|
||||
db = db.Where("suppliers.id IN ?", filters.SupplierIDs)
|
||||
}
|
||||
|
||||
if filters.StartDate != "" {
|
||||
if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil {
|
||||
db = db.Where(fmt.Sprintf("DATE(%s) >= ?", dateColumn), dateFrom)
|
||||
}
|
||||
}
|
||||
|
||||
if filters.EndDate != "" {
|
||||
if dateTo, err := utils.ParseDateString(filters.EndDate); err == nil {
|
||||
db = db.Where(fmt.Sprintf("DATE(%s) <= ?", dateColumn), dateTo)
|
||||
}
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func (r *debtSupplierRepositoryImpl) GetSuppliersWithPurchases(ctx context.Context, offset, limit int, filters *validation.DebtSupplierQuery) ([]entity.Supplier, int64, error) {
|
||||
query := r.baseSupplierQuery(ctx, filters)
|
||||
|
||||
var totalSuppliers int64
|
||||
if err := query.
|
||||
Distinct("suppliers.id").
|
||||
Count(&totalSuppliers).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if totalSuppliers == 0 {
|
||||
return []entity.Supplier{}, 0, nil
|
||||
}
|
||||
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
var supplierIDs []uint
|
||||
if err := query.
|
||||
Select("suppliers.id").
|
||||
Order("suppliers.id ASC").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Pluck("suppliers.id", &supplierIDs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if len(supplierIDs) == 0 {
|
||||
return []entity.Supplier{}, totalSuppliers, nil
|
||||
}
|
||||
|
||||
var suppliers []entity.Supplier
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("id IN ?", supplierIDs).
|
||||
Find(&suppliers).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return suppliers, totalSuppliers, nil
|
||||
}
|
||||
|
||||
func (r *debtSupplierRepositoryImpl) GetPurchasesBySuppliers(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) ([]entity.Purchase, error) {
|
||||
if len(supplierIDs) == 0 {
|
||||
return []entity.Purchase{}, nil
|
||||
}
|
||||
|
||||
purchaseIDs, err := r.getPurchaseIDs(ctx, supplierIDs, filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(purchaseIDs) == 0 {
|
||||
return []entity.Purchase{}, nil
|
||||
}
|
||||
|
||||
preloadItems := func(db *gorm.DB) *gorm.DB {
|
||||
db = db.
|
||||
Preload("Warehouse").
|
||||
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 filters.StartDate != "" {
|
||||
if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil {
|
||||
db = db.Where("DATE(purchase_items.received_date) >= ?", dateFrom)
|
||||
}
|
||||
}
|
||||
if filters.EndDate != "" {
|
||||
if dateTo, err := utils.ParseDateString(filters.EndDate); err == nil {
|
||||
db = db.Where("DATE(purchase_items.received_date) <= ?", dateTo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
var purchases []entity.Purchase
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entity.Purchase{}).
|
||||
Preload("Supplier").
|
||||
Preload("Items", preloadItems).
|
||||
Where("purchases.id IN ?", purchaseIDs).
|
||||
Order("purchases.id ASC").
|
||||
Find(&purchases).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return purchases, nil
|
||||
}
|
||||
|
||||
func (r *debtSupplierRepositoryImpl) getPurchaseIDs(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) ([]uint, error) {
|
||||
dateColumn := resolveDebtSupplierDateColumn(filters.FilterBy)
|
||||
|
||||
db := r.db.WithContext(ctx).
|
||||
Table("purchases").
|
||||
Select("DISTINCT purchases.id").
|
||||
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id").
|
||||
Where("purchases.supplier_id IN ?", supplierIDs)
|
||||
|
||||
if filters.StartDate != "" {
|
||||
if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil {
|
||||
db = db.Where(fmt.Sprintf("DATE(%s) >= ?", dateColumn), dateFrom)
|
||||
}
|
||||
}
|
||||
|
||||
if filters.EndDate != "" {
|
||||
if dateTo, err := utils.ParseDateString(filters.EndDate); err == nil {
|
||||
db = db.Where(fmt.Sprintf("DATE(%s) <= ?", dateColumn), dateTo)
|
||||
}
|
||||
}
|
||||
|
||||
var purchaseIDs []uint
|
||||
if err := db.Order("purchases.id ASC").Pluck("purchases.id", &purchaseIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return purchaseIDs, nil
|
||||
}
|
||||
|
||||
func (r *debtSupplierRepositoryImpl) GetPaymentTotalsByReferences(ctx context.Context, supplierIDs []uint, references []string) (map[string]float64, error) {
|
||||
if len(supplierIDs) == 0 || len(references) == 0 {
|
||||
return map[string]float64{}, nil
|
||||
}
|
||||
|
||||
type paymentRow struct {
|
||||
ReferenceNumber *string `gorm:"column:reference_number"`
|
||||
Total float64 `gorm:"column:total"`
|
||||
}
|
||||
|
||||
rows := make([]paymentRow, 0)
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entity.Payment{}).
|
||||
Select("reference_number, SUM(nominal) AS total").
|
||||
Where("party_type = ?", string(utils.PaymentPartySupplier)).
|
||||
Where("direction = ?", "OUT").
|
||||
Where("party_id IN ?", supplierIDs).
|
||||
Where("reference_number IN ?", references).
|
||||
Group("reference_number").
|
||||
Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]float64, len(rows))
|
||||
for _, row := range rows {
|
||||
if row.ReferenceNumber == nil || strings.TrimSpace(*row.ReferenceNumber) == "" {
|
||||
continue
|
||||
}
|
||||
result[*row.ReferenceNumber] = row.Total
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -18,7 +18,8 @@ func RepportRoutes(v1 fiber.Router, u user.UserService, s repport.RepportService
|
||||
route.Get("/expense", m.RequirePermissions(m.P_ReportExpenseGetAll), ctrl.GetExpense)
|
||||
route.Get("/marketing", m.RequirePermissions(m.P_ReportDeliveryGetAll), ctrl.GetMarketing)
|
||||
route.Get("/purchase-supplier", m.RequirePermissions(m.P_ReportPurchaseSupplierGetAll), ctrl.GetPurchaseSupplier)
|
||||
route.Get("/hpp-per-kandang", ctrl.GetHppPerKandang)
|
||||
route.Get("/production-result/:idProjectFlockKandang", ctrl.GetProductionResult)
|
||||
route.Get("/debt-supplier", m.RequirePermissions(m.P_ReportDebtSupplierGetAll), ctrl.GetDebtSupplier)
|
||||
route.Get("/hpp-per-kandang", m.RequirePermissions(m.P_ReportHppPerKandangGetAll), ctrl.GetHppPerKandang)
|
||||
route.Get("/production-result/:idProjectFlockKandang", m.RequirePermissions(m.P_ReportProductionResultGetAll), ctrl.GetProductionResult)
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -43,6 +43,16 @@ type PurchaseSupplierQuery struct {
|
||||
FilterBy string `query:"filter_by" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type DebtSupplierQuery struct {
|
||||
Page int `query:"page" validate:"omitempty,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,min=1,gt=0"`
|
||||
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=do_date po_date pr_date"`
|
||||
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
|
||||
}
|
||||
|
||||
type HppPerKandangQuery struct {
|
||||
Page int `query:"page" validate:"omitempty,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,min=1,max=100,gt=0"`
|
||||
|
||||
Reference in New Issue
Block a user