Merge branch 'development' into dev/gio

This commit is contained in:
MacBook Air M1
2026-01-13 16:15:15 +07:00
23 changed files with 620 additions and 580 deletions
+13 -12
View File
@@ -22,18 +22,19 @@ const (
)
const (
P_ExpenseGetAll = "lti.expense.list"
P_ExpenseCreateOne = "lti.expense.create"
P_ExpenseUpdateOne = "lti.expense.update"
P_ExpenseGetOne = "lti.expense.detail"
P_ExpenseDeleteOne = "lti.expense.delete"
P_ExpenseApprovalManager = "lti.expense.approve.manager"
P_ExpenseApprovalFinance = "lti.expense.approve.finance"
P_ExpenseCreateRealizations = "lti.expense.create.realization"
P_ExpenseUpdateRealizations = "lti.expense.update.realization"
P_ExpenseCompleteExpense = "lti.expense.complete.expense"
P_ExpenseDocument = "lti.expense.document"
P_ExpenseDocumentRealizations = "lti.expense.document.realization"
P_ExpenseGetAll = "lti.expense.list"
P_ExpenseCreateOne = "lti.expense.create"
P_ExpenseUpdateOne = "lti.expense.update"
P_ExpenseGetOne = "lti.expense.detail"
P_ExpenseDeleteOne = "lti.expense.delete"
P_ExpenseApprovalHeadArea = "lti.expense.approve.head_area"
P_ExpenseApprovalFinance = "lti.expense.approve.finance"
P_ExpenseApprovalUnitVicePresident = "lti.expense.approve.unit_vice_president"
P_ExpenseCreateRealizations = "lti.expense.create.realization"
P_ExpenseUpdateRealizations = "lti.expense.update.realization"
P_ExpenseCompleteExpense = "lti.expense.complete.expense"
P_ExpenseDocument = "lti.expense.document"
P_ExpenseDocumentRealizations = "lti.expense.document.realization"
)
const (
P_AdjustmentGetAll = "lti.inventory.list"
+3 -3
View File
@@ -1,7 +1,7 @@
package dailyChecklists
import (
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/controllers"
dailyChecklist "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/services"
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
@@ -13,7 +13,7 @@ func DailyChecklistRoutes(v1 fiber.Router, u user.UserService, s dailyChecklist.
ctrl := controller.NewDailyChecklistController(s)
route := v1.Group("/daily-checklists")
// route.Use(m.Auth(u))
route.Use(m.Auth(u))
route.Get("/", ctrl.GetAll)
route.Get("/report", ctrl.GetReport)
@@ -22,7 +22,7 @@ func DailyChecklistRoutes(v1 fiber.Router, u user.UserService, s dailyChecklist.
route.Get("/report", ctrl.GetReport)
// create daily checklist
// upsert daily checklist
route.Post("/", ctrl.CreateOne)
// get detail data daily checklist by id
@@ -229,10 +229,12 @@ func (u *ExpenseController) Approval(c *fiber.Ctx) error {
path := c.Path()
approvalType := ""
if strings.Contains(path, "/approvals/manager") {
approvalType = "manager"
if strings.Contains(path, "/approvals/head-area") {
approvalType = "head-area"
} else if strings.Contains(path, "/approvals/finance") {
approvalType = "finance"
} else if strings.Contains(path, "/approvals/unit-vice-president") {
approvalType = "unit-vice-president"
} else {
return fiber.NewError(fiber.StatusBadRequest, "Invalid approval path")
}
+4 -1
View File
@@ -27,8 +27,11 @@ func ExpenseRoutes(v1 fiber.Router, u user.UserService, s expense.ExpenseService
route.Get("/:id", m.RequirePermissions(m.P_ExpenseGetOne), ctrl.GetOne)
route.Patch("/:id", m.RequirePermissions(m.P_ExpenseUpdateOne), ctrl.UpdateOne)
route.Delete("/:id", m.RequirePermissions(m.P_ExpenseDeleteOne), ctrl.DeleteOne)
route.Post("/approvals/manager", m.RequirePermissions(m.P_ExpenseApprovalManager), ctrl.Approval)
route.Post("/approvals/head-area", m.RequirePermissions(m.P_ExpenseApprovalHeadArea), ctrl.Approval)
route.Post("/approvals/finance", m.RequirePermissions(m.P_ExpenseApprovalFinance), ctrl.Approval)
route.Post("/approvals/unit-vice-president", m.RequirePermissions(m.P_ExpenseApprovalUnitVicePresident), ctrl.Approval)
route.Post("/:id/realizations", m.RequirePermissions(m.P_ExpenseCreateRealizations), ctrl.CreateRealization)
route.Patch("/:id/realizations", m.RequirePermissions(m.P_ExpenseUpdateRealizations), ctrl.UpdateRealization)
route.Post("/:id/complete", m.RequirePermissions(m.P_ExpenseCompleteExpense), ctrl.CompleteExpense)
@@ -1049,21 +1049,30 @@ func (s *expenseService) Approval(c *fiber.Ctx, req *validation.ApprovalRequest,
}
var stepNumber approvalutils.ApprovalStep
if approvalType == "manager" {
if approvalType == "head-area" {
stepNumber = utils.ExpenseStepManager
stepNumber = utils.ExpenseStepHeadArea
if latestApproval.StepNumber != uint16(utils.ExpenseStepPengajuan) {
currentStepName := utils.ExpenseApprovalSteps[approvalutils.ApprovalStep(latestApproval.StepNumber)]
return fiber.NewError(fiber.StatusBadRequest,
fmt.Sprintf("Cannot process at Manager step. Latest approval is at %s step. Expected previous step: Pengajuan", currentStepName))
fmt.Sprintf("Cannot process at Head Area step. Latest approval is at %s step. Expected previous step: Pengajuan", currentStepName))
}
} else if approvalType == "unit-vice-president" {
stepNumber = utils.ExpenseStepUnitVicePresident
if latestApproval.StepNumber != uint16(utils.ExpenseStepHeadArea) {
currentStepName := utils.ExpenseApprovalSteps[approvalutils.ApprovalStep(latestApproval.StepNumber)]
return fiber.NewError(fiber.StatusBadRequest,
fmt.Sprintf("Cannot process at Unit Vice President step. Latest approval is at %s step. Expected previous step: Head Area", currentStepName))
}
} else if approvalType == "finance" {
stepNumber = utils.ExpenseStepFinance
if latestApproval.StepNumber != uint16(utils.ExpenseStepManager) {
if latestApproval.StepNumber != uint16(utils.ExpenseStepUnitVicePresident) {
currentStepName := utils.ExpenseApprovalSteps[approvalutils.ApprovalStep(latestApproval.StepNumber)]
return fiber.NewError(fiber.StatusBadRequest,
fmt.Sprintf("Cannot process at Finance step. Latest approval is at %s step. Expected previous step: Manager", currentStepName))
fmt.Sprintf("Cannot process at Finance step. Latest approval is at %s step. Expected previous step: Unit Vice President", currentStepName))
}
} else {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid approval type: %v", approvalType))
@@ -39,12 +39,12 @@ type TransferExpenseReceivingPayload struct {
}
type groupedTransferItem struct {
detail *entity.StockTransferDetail
payload TransferExpenseReceivingPayload
projectFK *uint
kandangID *uint
totalPrice float64
shippingCostTotal float64
detail *entity.StockTransferDetail
payload TransferExpenseReceivingPayload
projectFK *uint
kandangID *uint
totalPrice float64
shippingCostTotal float64
}
func groupingKey(supplierID uint, date time.Time, warehouseID uint) string {
@@ -84,7 +84,6 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it
expenseIDs := make(map[uint64]struct{})
expenseNonstockIDs := make([]uint64, 0)
for _, item := range items {
if item.ExpenseNonstockId != nil && *item.ExpenseNonstockId != 0 {
expenseNonstockIDs = append(expenseNonstockIDs, *item.ExpenseNonstockId)
@@ -92,7 +91,7 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it
}
if len(expenseNonstockIDs) > 0 {
for _, nsID := range expenseNonstockIDs {
var expenseID uint64
if err := tx.Model(&entity.ExpenseNonstock{}).
@@ -106,13 +105,11 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it
}
}
if err := tx.Delete(&entity.ExpenseNonstock{}, expenseNonstockIDs).Error; err != nil {
return err
}
}
approvalRepoTx := commonRepo.NewApprovalRepository(tx)
for expenseID := range expenseIDs {
var count int64
@@ -122,7 +119,6 @@ func (b *transferExpenseBridge) OnItemsDeleted(ctx context.Context, _ uint64, it
return err
}
if count == 0 {
if err := approvalRepoTx.DeleteByTarget(ctx, utils.ApprovalWorkflowExpense.String(), uint(expenseID)); err != nil {
return err
@@ -220,7 +216,6 @@ func (b *transferExpenseBridge) createExpenseViaService(
for _, gi := range items {
note := fmt.Sprintf("stock_transfer_detail:%d", gi.detail.Id)
price := gi.shippingCostTotal
if gi.payload.TransportPerItem != nil {
price = *gi.payload.TransportPerItem * gi.payload.DeliveredQty
@@ -228,7 +223,7 @@ func (b *transferExpenseBridge) createExpenseViaService(
costItems = append(costItems, expenseValidation.CostItem{
NonstockID: expeditionNonstockID,
Quantity: 1,
Quantity: 1,
Price: price,
Notes: note,
})
@@ -251,14 +246,16 @@ func (b *transferExpenseBridge) createExpenseViaService(
return nil, err
}
action := entity.ApprovalActionApproved
actorID := uint(transfer.CreatedBy)
if actorID == 0 {
actorID = 1
}
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(b.db))
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepManager, &action, actorID, nil); err != nil {
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepHeadArea, &action, actorID, nil); err != nil {
return nil, err
}
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepUnitVicePresident, &action, actorID, nil); err != nil {
return nil, err
}
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil {
@@ -328,7 +325,6 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64
ctx := c.Context()
transfer, err := b.transferRepo.GetByID(ctx, uint(transferID), func(db *gorm.DB) *gorm.DB {
return db.
Preload("Details").
@@ -348,11 +344,10 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64
for i := range transfer.Details {
detailMap[transfer.Details[i].Id] = &transfer.Details[i]
for _, deliveryItem := range transfer.Details[i].DeliveryItems {
if deliveryItem.StockTransferDelivery != nil {
shippingCostMap[transfer.Details[i].Id] = deliveryItem.StockTransferDelivery.ShippingCostTotal
break
break
}
}
}
@@ -395,17 +390,14 @@ func (b *transferExpenseBridge) OnItemsDelivered(c *fiber.Ctx, transferID uint64
}
}
shippingCostTotal := shippingCostMap[detail.Id]
totalPrice := shippingCostTotal
if payload.TransportPerItem != nil {
totalPrice = *payload.TransportPerItem * payload.DeliveredQty
}
warehouseID := uint(payload.WarehouseID)
if warehouseID == 0 && transfer.ToWarehouse != nil {
warehouseID = uint(transfer.ToWarehouse.Id)
@@ -20,7 +20,7 @@ type Update struct {
type Query struct {
Page int `query:"page" validate:"omitempty,number,min=1"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=500"`
Search string `query:"search" validate:"omitempty,max=50"`
LocationId int `query:"location_id" validate:"omitempty,number,gt=0"`
PicId int `query:"pic_id" validate:"omitempty,number,gt=0"`
@@ -14,7 +14,7 @@ type Update struct {
type Query struct {
Page int `query:"page" validate:"omitempty,number,min=1"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=500"`
Search string `query:"search" validate:"omitempty,max=50"`
AreaId int `query:"area_id" validate:"omitempty,number,gt=0"`
}
@@ -14,6 +14,7 @@ import (
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
rProduct "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories"
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
sChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/services"
@@ -38,6 +39,7 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
projectflockpopulationrepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
productRepo := rProduct.NewProductRepository(db)
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
userRepo := rUser.NewUserRepository(db)
@@ -88,6 +90,7 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
kandangRepo,
warehouseRepo,
productWarehouseRepo,
productRepo,
projectFlockRepo,
projectflockkandangrepo,
projectflockpopulationrepo,
@@ -12,6 +12,7 @@ import (
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
KandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
rProduct "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories"
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/validations"
@@ -44,6 +45,7 @@ type chickinService struct {
KandangRepo KandangRepo.KandangRepository
WarehouseRepo rWarehouse.WarehouseRepository
ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository
ProductRepo rProduct.ProductRepository
ProjectFlockRepo rProjectFlock.ProjectflockRepository
ProjectflockKandangRepo rProjectFlock.ProjectFlockKandangRepository
ProjectflockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository
@@ -52,7 +54,7 @@ type chickinService struct {
StockLogRepo rStockLogs.StockLogRepository
}
func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, projectflockkandangRepo rProjectFlock.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, projectChickinDetailRepo repository.ProjectChickinDetailRepository, validate *validator.Validate, fifoSvc commonSvc.FifoService) ChickinService {
func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, productRepo rProduct.ProductRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, projectflockkandangRepo rProjectFlock.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, projectChickinDetailRepo repository.ProjectChickinDetailRepository, validate *validator.Validate, fifoSvc commonSvc.FifoService) ChickinService {
return &chickinService{
Log: utils.Log,
Validate: validate,
@@ -60,6 +62,7 @@ func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo Kan
KandangRepo: kandangRepo,
WarehouseRepo: warehouseRepo,
ProductWarehouseRepo: productWarehouseRepo,
ProductRepo: productRepo,
ProjectFlockRepo: projectFlockRepo,
ProjectflockKandangRepo: projectflockkandangRepo,
ProjectflockPopulationRepo: projectflockpopulationRepo,
@@ -99,7 +102,6 @@ func (s chickinService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity
return db.Order("created_at DESC").Order("updated_at DESC")
})
if err != nil {
s.Log.Errorf("Failed to get chickins: %+v", err)
return nil, 0, err
}
return chickins, total, nil
@@ -347,7 +349,6 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "Chickin not found")
}
s.Log.Errorf("Failed to update chickin: %+v", err)
return nil, err
}
@@ -380,7 +381,6 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
warehouseDeltas := make(map[uint]float64)
warehouseDeltas[chickin.ProductWarehouseId] += currentUsageQty
if err := s.adjustProductWarehouseQuantities(c.Context(), s.Repository.DB(), warehouseDeltas); err != nil {
s.Log.Errorf("Failed to adjust product warehouses for deleted chickin %d: %+v", chickin.Id, err)
return err
}
}
@@ -449,6 +449,7 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
chickinRepoTx := repository.NewChickinRepository(dbTransaction)
ProjectFlockPopulationRepotx := s.ProjectflockPopulationRepo.WithTx(dbTransaction)
for _, approvableID := range approvableIDs {
if _, err := approvalSvc.CreateApproval(
@@ -479,39 +480,55 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
category := strings.ToUpper(strings.TrimSpace(kandangForApproval.ProjectFlock.Category))
var targetFlag utils.FlagType
if category == string(utils.ProjectFlockCategoryGrowing) {
warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), kandangForApproval.KandangId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Warehouse for kandang %d not found", kandangForApproval.KandangId))
}
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get warehouse")
}
pfkID := approvableID
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "PULLET", dbTransaction, actorID, &pfkID)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get/create PULLET product warehouse")
}
if err := s.convertChickinsToTarget(c, chickins, targetPW, dbTransaction, actorID); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to convert chickins to target")
}
targetFlag = utils.FlagPullet
} else if category == string(utils.ProjectFlockCategoryLaying) {
warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), kandangForApproval.KandangId)
targetFlag = utils.FlagLayer
} else {
continue
}
for _, chickin := range chickins {
populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(c.Context(), chickin.Id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Warehouse for kandang %d not found", kandangForApproval.KandangId))
}
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get warehouse")
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to check population for chickin %d", chickin.Id))
}
if populationExists {
continue
}
pfkID := approvableID
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "LAYER", dbTransaction, actorID, &pfkID)
sourcePW, err := s.ProductWarehouseRepo.GetByID(c.Context(), chickin.ProductWarehouseId, func(db *gorm.DB) *gorm.DB {
return db.Preload("Product.Flags")
})
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get/create LAYER product warehouse")
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get product warehouse for chickin %d", chickin.Id))
}
if err := s.convertChickinsToTarget(c, chickins, targetPW, dbTransaction, actorID); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to convert chickins to target")
if err := s.autoAddFlagToProduct(c.Context(), dbTransaction, sourcePW.Product.Id, targetFlag); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to auto-add flag to product %d", sourcePW.Product.Id))
}
population := &entity.ProjectFlockPopulation{
ProjectChickinId: chickin.Id,
ProductWarehouseId: sourcePW.Id,
TotalQty: 0,
TotalUsedQty: 0,
Notes: chickin.Notes,
CreatedBy: actorID,
}
if err := ProjectFlockPopulationRepotx.CreateOne(c.Context(), population, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to create population for chickin %d", chickin.Id))
}
if err := chickinRepoTx.PatchOne(c.Context(), chickin.Id, map[string]any{
"pending_usage_qty": 0,
}, nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to reset pending usage qty for chickin %d", chickin.Id))
}
if err := s.ReplenishChickinStocks(c.Context(), dbTransaction, &chickin, sourcePW, population, actorID); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to replenish stock for chickin %d", chickin.Id))
}
}
}
@@ -534,7 +551,6 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
warehouseDeltas := make(map[uint]float64)
warehouseDeltas[chickin.ProductWarehouseId] += chickin.UsageQty
if err := s.adjustProductWarehouseQuantities(c.Context(), dbTransaction, warehouseDeltas); err != nil {
s.Log.Errorf("Failed to adjust product warehouses for rejected chickin %d: %+v", chickin.Id, err)
return err
}
@@ -568,104 +584,35 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
return updated, nil
}
func (s *chickinService) getOrCreateProductWarehouse(ctx *fiber.Ctx, warehouseId uint, categoryCode string, dbTransaction *gorm.DB, actorID uint, projectFlockKandangId *uint) (*entity.ProductWarehouse, error) {
products, err := s.ProductWarehouseRepo.GetByFlagAndWarehouseID(ctx.Context(), categoryCode, warehouseId)
if err == nil && len(products) > 0 {
existingPW := &products[0]
if existingPW.ProjectFlockKandangId == nil && projectFlockKandangId != nil {
existingPW.ProjectFlockKandangId = projectFlockKandangId
if err := s.ProductWarehouseRepo.WithTx(dbTransaction).UpdateOne(ctx.Context(), existingPW.Id, existingPW, nil); err != nil {
return nil, fmt.Errorf("failed to update %s product warehouse with project_flock_kandang_id: %w", categoryCode, err)
}
}
return existingPW, nil
// autoAddFlagToProduct adds target flag to product if not already present (idempotent)
func (s *chickinService) autoAddFlagToProduct(ctx context.Context, tx *gorm.DB, productID uint, targetFlag utils.FlagType) error {
if s.ProductRepo == nil {
return nil
}
product, err := s.ProductWarehouseRepo.GetFirstProductByFlag(ctx.Context(), categoryCode)
currentFlags, err := s.ProductRepo.GetFlags(ctx, productID)
if err != nil {
return nil, fmt.Errorf("failed to get %s product: %w", categoryCode, err)
}
if product == nil {
return nil, fmt.Errorf("no %s product found in system", categoryCode)
return fmt.Errorf("failed to get product flags: %w", err)
}
newPW := &entity.ProductWarehouse{
ProductId: product.Id,
WarehouseId: warehouseId,
ProjectFlockKandangId: projectFlockKandangId,
Quantity: 0,
hasTargetFlag := false
currentFlagNames := make([]string, 0, len(currentFlags))
for _, flag := range currentFlags {
currentFlagNames = append(currentFlagNames, flag.Name)
if flag.Name == string(targetFlag) {
hasTargetFlag = true
}
}
if err := s.ProductWarehouseRepo.WithTx(dbTransaction).CreateOne(ctx.Context(), newPW, nil); err != nil {
return nil, fmt.Errorf("failed to create %s product warehouse: %w", categoryCode, err)
if hasTargetFlag {
return nil
}
return newPW, nil
}
func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []entity.ProjectChickin, targetPW *entity.ProductWarehouse, dbTransaction *gorm.DB, actorID uint) error {
if targetPW == nil || targetPW.Id == 0 {
return fmt.Errorf("invalid target product warehouse")
newFlags := append(currentFlagNames, string(targetFlag))
if err := s.ProductRepo.SyncFlags(ctx, tx, productID, newFlags); err != nil {
return fmt.Errorf("failed to sync flags: %w", err)
}
ProjectFlockPopulationRepotx := s.ProjectflockPopulationRepo.WithTx(dbTransaction)
chickinRepoTx := s.Repository.WithTx(dbTransaction)
var totalQuantityAdded float64
for _, chickin := range chickins {
populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(ctx.Context(), chickin.Id)
if err != nil {
return fmt.Errorf("failed to check population existence for chickin %d: %w", chickin.Id, err)
}
if populationExists {
s.Log.Infof("population already exists for chickin %d, skipping", chickin.Id)
continue
}
quantityToConvert := chickin.UsageQty
population := &entity.ProjectFlockPopulation{
ProjectChickinId: chickin.Id,
ProductWarehouseId: targetPW.Id,
TotalQty: 0, // Will be set by FIFO Replenish
TotalUsedQty: 0,
Notes: chickin.Notes,
CreatedBy: actorID,
}
if err := ProjectFlockPopulationRepotx.CreateOne(ctx.Context(), population, nil); err != nil {
return err
}
// Reset PendingUsageQty to 0 since population has been created
if err := chickinRepoTx.PatchOne(ctx.Context(), chickin.Id, map[string]any{
"pending_usage_qty": 0,
}, nil); err != nil {
return fmt.Errorf("failed to reset pending usage qty for chickin %d: %w", chickin.Id, err)
}
// Replenish stock to target ProductWarehouse based on source flag
// StockableKey is PROJECT_CHICKIN but StockableID refers to Population ID
if err := s.ReplenishChickinStocks(ctx.Context(), dbTransaction, &chickin, targetPW, population, actorID); err != nil {
s.Log.Errorf("Failed to replenish stock for chickin %d: %+v", chickin.Id, err)
return err
}
totalQuantityAdded += quantityToConvert
}
// NOTE: ProductWarehouse target sudah ditambah melalui ReplenishChickinStocks
// yang dipanggil di atas untuk setiap chickin berdasarkan flag source:
// - DOC → replenish ke PULLET
// - PULLET → replenish ke LAYER
// - LAYER → tidak perlu replenish (sudah final)
// - DOC+PULLET+LAYER → replenish ke dirinya sendiri
return nil
}
@@ -674,9 +621,6 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
return nil
}
s.Log.Infof("ConsumeChickinStocks: chickin_id=%d, product_warehouse_id=%d, desired_qty=%.3f",
chickin.Id, chickin.ProductWarehouseId, desiredQty)
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
UsableKey: chickinUsableKey,
UsableID: chickin.Id,
@@ -686,13 +630,9 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
Tx: tx,
})
if err != nil {
s.Log.Errorf("Failed to consume FIFO stock for chickin %d: %+v", chickin.Id, err)
return err
}
s.Log.Infof("ConsumeChickinStocks result: usage_qty=%.3f, pending_qty=%.3f, allocated_allocations=%d",
result.UsageQuantity, result.PendingQuantity, len(result.AddedAllocations))
if err := s.Repository.UpdateUsageFields(ctx, tx, chickin.Id, result.UsageQuantity, result.PendingQuantity); err != nil {
return err
}
@@ -706,10 +646,7 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
CreatedBy: actorID,
Notes: fmt.Sprintf("Chickin #%d", chickin.Id),
}
if err := s.StockLogRepo.CreateOne(ctx, decreaseLog, nil); err != nil {
s.Log.Errorf("Failed to create stock log for chickin %d: %+v", chickin.Id, err)
}
s.StockLogRepo.CreateOne(ctx, decreaseLog, nil)
}
return nil
@@ -720,93 +657,17 @@ func (s *chickinService) ReplenishChickinStocks(ctx context.Context, tx *gorm.DB
return nil
}
sourcePW, err := s.ProductWarehouseRepo.GetByID(ctx, chickin.ProductWarehouseId, func(db *gorm.DB) *gorm.DB {
return db.Preload("Product.Flags")
_, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyProjectFlockPopulation,
StockableID: population.Id,
ProductWarehouseID: targetPW.Id,
Quantity: chickin.UsageQty,
Tx: tx,
})
if err != nil {
return err
}
if sourcePW == nil || sourcePW.Product.Id == 0 {
return fmt.Errorf("source product warehouse or product not found for chickin %d", chickin.Id)
}
sourceFlags := sourcePW.Product.Flags
if len(sourceFlags) == 0 {
s.Log.Warnf("Source product %d has no flags, skipping replenish for chickin %d", sourcePW.Product.Id, chickin.Id)
return nil
}
hasDoc := false
hasPullet := false
hasLayer := false
for _, flag := range sourceFlags {
flagName := utils.FlagType(flag.Name)
if flagName == utils.FlagDOC {
hasDoc = true
} else if flagName == utils.FlagPullet {
hasPullet = true
} else if flagName == utils.FlagLayer {
hasLayer = true
}
}
if hasDoc && hasPullet && hasLayer {
s.Log.Infof("Chickin %d has mixed flags (DOC+PULLET+LAYER), replenishing to source PW %d", chickin.Id, sourcePW.Id)
_, err = s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyProjectFlockPopulation,
StockableID: population.Id,
ProductWarehouseID: sourcePW.Id,
Quantity: chickin.UsageQty,
Tx: tx,
})
if err != nil {
s.Log.Errorf("Failed to replenish stock to source PW for chickin %d: %+v", chickin.Id, err)
return err
}
return nil
}
// LAYER only - no replenish needed
if hasLayer && !hasDoc && !hasPullet {
s.Log.Infof("Chickin %d has LAYER flag only, skipping replenish", chickin.Id)
return nil
}
if hasDoc && !hasPullet && !hasLayer {
s.Log.Infof("Chickin %d has DOC flag, replenishing to PULLET PW %d", chickin.Id, targetPW.Id)
_, err = s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyProjectFlockPopulation,
StockableID: population.Id,
ProductWarehouseID: targetPW.Id,
Quantity: chickin.UsageQty,
Tx: tx,
})
if err != nil {
s.Log.Errorf("Failed to replenish stock to PULLET PW for chickin %d: %+v", chickin.Id, err)
return err
}
return nil
}
if hasPullet && !hasDoc && !hasLayer {
s.Log.Infof("Chickin %d has PULLET flag, replenishing to LAYER PW %d", chickin.Id, targetPW.Id)
_, err = s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyProjectFlockPopulation,
StockableID: population.Id,
ProductWarehouseID: targetPW.Id,
Quantity: chickin.UsageQty,
Tx: tx,
})
if err != nil {
s.Log.Errorf("Failed to replenish stock to LAYER PW for chickin %d: %+v", chickin.Id, err)
return err
}
return nil
}
// Other combinations (e.g., DOC + PULLET without LAYER) - skip for now
s.Log.Warnf("Chickin %d has unsupported flag combination, skipping replenish", chickin.Id)
return nil
}
@@ -825,7 +686,6 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
UsableID: chickin.Id,
Tx: tx,
}); err != nil {
s.Log.Errorf("Failed to release FIFO stock for chickin %d: %+v", chickin.Id, err)
return err
}
@@ -842,9 +702,7 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
CreatedBy: actorID,
Notes: fmt.Sprintf("Chickin #%d - Stock released", chickin.Id),
}
if err := s.StockLogRepo.CreateOne(ctx, increaseLog, nil); err != nil {
s.Log.Errorf("Failed to create stock log for released chickin %d: %+v", chickin.Id, err)
}
s.StockLogRepo.CreateOne(ctx, increaseLog, nil)
}
return nil
@@ -618,7 +618,10 @@ func (b *expenseBridge) createExpenseViaService(
actorID = 1
}
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(b.db))
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepManager, &action, actorID, nil); err != nil {
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepHeadArea, &action, actorID, nil); err != nil {
return nil, err
}
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepUnitVicePresident, &action, actorID, nil); err != nil {
return nil, err
}
if _, err := approvalSvc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(detail.Id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil {
@@ -9,8 +9,8 @@ import (
type DebtSupplierRowDTO struct {
PrNumber string `json:"pr_number"`
PoNumber string `json:"po_number"`
PrDate string `json:"pr_date"`
PoDate string `json:"po_date"`
ReceivedDate string `json:"received_date"`
Aging int `json:"aging"`
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
@@ -21,6 +21,7 @@ type DebtSupplierRowDTO struct {
DebtPrice float64 `json:"debt_price"`
Status string `json:"status"`
TravelNumber string `json:"travel_number"`
Balance float64 `json:"balance"`
}
type DebtSupplierTotalDTO struct {
@@ -31,7 +32,8 @@ type DebtSupplierTotalDTO struct {
}
type DebtSupplierDTO struct {
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
Rows []DebtSupplierRowDTO `json:"rows"`
Total DebtSupplierTotalDTO `json:"total"`
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
InitialBalance float64 `json:"initial_balance"`
Rows []DebtSupplierRowDTO `json:"rows"`
Total DebtSupplierTotalDTO `json:"total"`
}
@@ -15,7 +15,10 @@ import (
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)
GetPaymentsBySuppliers(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) ([]entity.Payment, error)
GetPaymentTotalsByReferences(ctx context.Context, supplierIDs []uint, references []string) (map[string]float64, error)
GetPurchaseTotalsBeforeDate(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) (map[uint]float64, error)
GetPaymentTotalsBeforeDate(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) (map[uint]float64, error)
}
type debtSupplierRepositoryImpl struct {
@@ -28,10 +31,10 @@ 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 "pr_date":
return "purchases.created_at"
case "do_date", "received_date", "":
return "purchase_items.received_date"
default:
@@ -157,6 +160,39 @@ func (r *debtSupplierRepositoryImpl) GetPurchasesBySuppliers(ctx context.Context
return purchases, nil
}
func (r *debtSupplierRepositoryImpl) GetPaymentsBySuppliers(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) ([]entity.Payment, error) {
if len(supplierIDs) == 0 {
return []entity.Payment{}, nil
}
db := r.db.WithContext(ctx).
Model(&entity.Payment{}).
Where("party_type = ?", string(utils.PaymentPartySupplier)).
Where("direction = ?", "OUT").
Where("party_id IN ?", supplierIDs)
if strings.TrimSpace(filters.StartDate) != "" {
if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil {
db = db.Where("DATE(payment_date) >= ?", dateFrom)
}
}
if strings.TrimSpace(filters.EndDate) != "" {
if dateTo, err := utils.ParseDateString(filters.EndDate); err == nil {
db = db.Where("DATE(payment_date) <= ?", dateTo)
}
}
var payments []entity.Payment
if err := db.
Order("payment_date ASC, id ASC").
Find(&payments).Error; err != nil {
return nil, err
}
return payments, nil
}
func (r *debtSupplierRepositoryImpl) getPurchaseIDs(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) ([]uint, error) {
dateColumn := resolveDebtSupplierDateColumn(filters.FilterBy)
@@ -219,3 +255,76 @@ func (r *debtSupplierRepositoryImpl) GetPaymentTotalsByReferences(ctx context.Co
return result, nil
}
func (r *debtSupplierRepositoryImpl) GetPurchaseTotalsBeforeDate(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) (map[uint]float64, error) {
if len(supplierIDs) == 0 || strings.TrimSpace(filters.StartDate) == "" {
return map[uint]float64{}, nil
}
dateFrom, err := utils.ParseDateString(filters.StartDate)
if err != nil {
return map[uint]float64{}, nil
}
dateColumn := resolveDebtSupplierDateColumn(filters.FilterBy)
type purchaseTotalRow struct {
SupplierID uint `gorm:"column:supplier_id"`
Total float64 `gorm:"column:total"`
}
rows := make([]purchaseTotalRow, 0)
if err := r.db.WithContext(ctx).
Table("purchases").
Select("purchases.supplier_id AS supplier_id, SUM(purchase_items.total_price) AS total").
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id").
Where("purchases.supplier_id IN ?", supplierIDs).
Where(fmt.Sprintf("DATE(%s) < ?", dateColumn), dateFrom).
Group("purchases.supplier_id").
Scan(&rows).Error; err != nil {
return nil, err
}
result := make(map[uint]float64, len(rows))
for _, row := range rows {
result[row.SupplierID] = row.Total
}
return result, nil
}
func (r *debtSupplierRepositoryImpl) GetPaymentTotalsBeforeDate(ctx context.Context, supplierIDs []uint, filters *validation.DebtSupplierQuery) (map[uint]float64, error) {
if len(supplierIDs) == 0 || strings.TrimSpace(filters.StartDate) == "" {
return map[uint]float64{}, nil
}
dateFrom, err := utils.ParseDateString(filters.StartDate)
if err != nil {
return map[uint]float64{}, nil
}
type paymentTotalRow struct {
SupplierID uint `gorm:"column:supplier_id"`
Total float64 `gorm:"column:total"`
}
rows := make([]paymentTotalRow, 0)
if err := r.db.WithContext(ctx).
Model(&entity.Payment{}).
Select("party_id AS supplier_id, SUM(nominal) AS total").
Where("party_type = ?", string(utils.PaymentPartySupplier)).
Where("direction = ?", "OUT").
Where("party_id IN ?", supplierIDs).
Where("DATE(payment_date) < ?", dateFrom).
Group("party_id").
Scan(&rows).Error; err != nil {
return nil, err
}
result := make(map[uint]float64, len(rows))
for _, row := range rows {
result[row.SupplierID] = row.Total
}
return result, nil
}
@@ -642,8 +642,8 @@ 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 == "" {
params.FilterBy = "do_date"
if params.FilterBy == "" || strings.EqualFold(strings.TrimSpace(params.FilterBy), "do_date") {
params.FilterBy = "received_date"
}
if err := s.Validate.Struct(params); err != nil {
@@ -675,6 +675,11 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
return nil, 0, err
}
payments, err := s.DebtSupplierRepo.GetPaymentsBySuppliers(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{})
@@ -697,6 +702,21 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
return nil, 0, err
}
paymentsBySupplier := make(map[uint][]entity.Payment, len(supplierIDs))
for _, payment := range payments {
paymentsBySupplier[payment.PartyId] = append(paymentsBySupplier[payment.PartyId], payment)
}
initialPurchaseTotals, err := s.DebtSupplierRepo.GetPurchaseTotalsBeforeDate(c.Context(), supplierIDs, params)
if err != nil {
return nil, 0, err
}
initialPaymentTotals, err := s.DebtSupplierRepo.GetPaymentTotalsBeforeDate(c.Context(), supplierIDs, params)
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")
@@ -710,29 +730,81 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
continue
}
initialBalance := initialPaymentTotals[supplierID] - initialPurchaseTotals[supplierID]
items := purchasesBySupplier[supplierID]
rows := make([]dto.DebtSupplierRowDTO, 0, len(items))
paymentItems := paymentsBySupplier[supplierID]
rows := make([]dto.DebtSupplierRowDTO, 0, len(items)+len(paymentItems))
total := dto.DebtSupplierTotalDTO{}
type debtSupplierRowItem struct {
Row dto.DebtSupplierRowDTO
SortTime time.Time
Order int
DeltaBalance float64
CountTotals bool
}
combinedRows := make([]debtSupplierRowItem, 0, len(items)+len(paymentItems))
for _, purchase := range items {
row := buildDebtSupplierRow(purchase, paymentTotals, now, location)
rows = append(rows, row)
sortTime := resolveDebtSupplierSortTime(purchase, params.FilterBy, location)
combinedRows = append(combinedRows, debtSupplierRowItem{
Row: row,
SortTime: sortTime,
Order: 0,
DeltaBalance: -row.TotalPrice,
CountTotals: true,
})
}
if row.Aging > total.Aging {
total.Aging = row.Aging
for _, payment := range paymentItems {
row := buildDebtSupplierPaymentRow(payment, location)
sortTime := payment.PaymentDate.In(location)
combinedRows = append(combinedRows, debtSupplierRowItem{
Row: row,
SortTime: sortTime,
Order: 1,
DeltaBalance: payment.Nominal,
CountTotals: false,
})
}
sort.SliceStable(combinedRows, func(i, j int) bool {
if combinedRows[i].SortTime.Equal(combinedRows[j].SortTime) {
return combinedRows[i].Order < combinedRows[j].Order
}
return combinedRows[i].SortTime.Before(combinedRows[j].SortTime)
})
balance := initialBalance
for i := range combinedRows {
balance += combinedRows[i].DeltaBalance
combinedRows[i].Row.Balance = balance
if combinedRows[i].CountTotals {
row := combinedRows[i].Row
if row.Aging > total.Aging {
total.Aging = row.Aging
}
total.TotalPrice += row.TotalPrice
total.PaymentPrice += row.PaymentPrice
total.DebtPrice += row.DebtPrice
} else {
combinedRows[i].Row.DebtPrice = balance
}
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
if sortDesc {
for i := len(combinedRows) - 1; i >= 0; i-- {
rows = append(rows, combinedRows[i].Row)
}
return rows[i].PrDate < rows[j].PrDate
})
} else {
for i := range combinedRows {
rows = append(rows, combinedRows[i].Row)
}
}
var supplierDTORef *supplierDTO.SupplierRelationDTO
if supplier.Id != 0 {
@@ -741,9 +813,10 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
}
result = append(result, dto.DebtSupplierDTO{
Supplier: supplierDTORef,
Rows: rows,
Total: total,
Supplier: supplierDTORef,
InitialBalance: initialBalance,
Rows: rows,
Total: total,
})
}
@@ -769,6 +842,7 @@ func buildDebtSupplierRow(purchase entity.Purchase, paymentTotals map[string]flo
totalPrice := 0.0
travelNumber := "-"
receivedDate := ""
var area *areaDTO.AreaRelationDTO
var warehouse *warehouseDTO.WarehouseRelationDTO
@@ -787,8 +861,19 @@ func buildDebtSupplierRow(purchase entity.Purchase, paymentTotals map[string]flo
}
}
earliestReceived := time.Time{}
for _, item := range purchase.Items {
totalPrice += item.TotalPrice
if item.ReceivedDate == nil || item.ReceivedDate.IsZero() {
continue
}
received := item.ReceivedDate.In(loc)
if earliestReceived.IsZero() || received.Before(earliestReceived) {
earliestReceived = received
}
}
if !earliestReceived.IsZero() {
receivedDate = earliestReceived.Format("2006-01-02")
}
}
@@ -820,8 +905,8 @@ func buildDebtSupplierRow(purchase entity.Purchase, paymentTotals map[string]flo
return dto.DebtSupplierRowDTO{
PrNumber: prNumber,
PoNumber: poNumber,
PrDate: prDate.Format("2006-01-02"),
PoDate: poDate,
ReceivedDate: receivedDate,
Aging: aging,
Area: area,
Warehouse: warehouse,
@@ -835,6 +920,62 @@ func buildDebtSupplierRow(purchase entity.Purchase, paymentTotals map[string]flo
}
}
func buildDebtSupplierPaymentRow(payment entity.Payment, loc *time.Location) dto.DebtSupplierRowDTO {
referenceNumber := ""
if payment.ReferenceNumber != nil {
referenceNumber = *payment.ReferenceNumber
}
prNumber := payment.PaymentCode
if strings.TrimSpace(prNumber) == "" {
prNumber = referenceNumber
}
return dto.DebtSupplierRowDTO{
PrNumber: prNumber,
PoNumber: referenceNumber,
PoDate: "-",
ReceivedDate: payment.PaymentDate.In(loc).Format("2006-01-02"),
Aging: 0,
Area: nil,
Warehouse: nil,
DueDate: "-",
DueStatus: "-",
TotalPrice: 0,
PaymentPrice: payment.Nominal,
DebtPrice: 0,
Status: "Pembayaran",
TravelNumber: "-",
}
}
func resolveDebtSupplierSortTime(purchase entity.Purchase, filterBy string, loc *time.Location) time.Time {
switch strings.ToLower(strings.TrimSpace(filterBy)) {
case "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() {
continue
}
received := item.ReceivedDate.In(loc)
if earliest.IsZero() || received.Before(earliest) {
earliest = received
}
}
if !earliest.IsZero() {
return earliest
}
}
return purchase.CreatedAt.In(loc)
}
func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangResponseData, *dto.HppPerKandangMetaDTO, error) {
params, filters, err := s.parseHppPerKandangQuery(ctx)
if err != nil {
@@ -49,7 +49,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=do_date po_date pr_date"`
FilterBy string `query:"filter_by" validate:"omitempty,oneof=received_date po_date pr_date do_date"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
}
+13 -11
View File
@@ -354,20 +354,22 @@ var MarketingApprovalSteps = map[approvalutils.ApprovalStep]string{
// -------------------------------------------------------------------
const (
ApprovalWorkflowExpense approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("EXPENSES")
ExpenseStepPengajuan approvalutils.ApprovalStep = 1
ExpenseStepManager approvalutils.ApprovalStep = 2
ExpenseStepFinance approvalutils.ApprovalStep = 3
ExpenseStepRealisasi approvalutils.ApprovalStep = 4
ExpenseStepSelesai approvalutils.ApprovalStep = 5
ApprovalWorkflowExpense approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("EXPENSES")
ExpenseStepPengajuan approvalutils.ApprovalStep = 1
ExpenseStepHeadArea approvalutils.ApprovalStep = 2
ExpenseStepUnitVicePresident approvalutils.ApprovalStep = 3
ExpenseStepFinance approvalutils.ApprovalStep = 4
ExpenseStepRealisasi approvalutils.ApprovalStep = 5
ExpenseStepSelesai approvalutils.ApprovalStep = 6
)
var ExpenseApprovalSteps = map[approvalutils.ApprovalStep]string{
ExpenseStepPengajuan: "Pengajuan",
ExpenseStepManager: "Approval Manager",
ExpenseStepFinance: "Approval Finance",
ExpenseStepRealisasi: "Realisasi",
ExpenseStepSelesai: "Selesai",
ExpenseStepPengajuan: "Pengajuan",
ExpenseStepHeadArea: "Approval Head Area",
ExpenseStepUnitVicePresident: "Approval Business Unit Vice President",
ExpenseStepFinance: "Approval Finance",
ExpenseStepRealisasi: "Realisasi",
ExpenseStepSelesai: "Selesai",
}
// -------------------------------------------------------------------