From 9a328ae1e44d914ad0774f8c91278f80fa39dded Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 3 Feb 2026 08:06:52 +0700 Subject: [PATCH] FEAT[BE] :implement proportional distribution with rounding for stock allocation in transfer laying approval process --- .../services/deliveryorder.service.go | 1 + .../services/transfer_laying.service.go | 85 ++++++++++++------- 2 files changed, 55 insertions(+), 31 deletions(-) diff --git a/internal/modules/marketing/services/deliveryorder.service.go b/internal/modules/marketing/services/deliveryorder.service.go index 1d0a9481..6d9392a6 100644 --- a/internal/modules/marketing/services/deliveryorder.service.go +++ b/internal/modules/marketing/services/deliveryorder.service.go @@ -520,6 +520,7 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor CreatedBy: actorID, Notes: fmt.Sprintf("FIFO consume (%.2f)", result.UsageQuantity), } + stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, marketingProduct.ProductWarehouseId, 1) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs") diff --git a/internal/modules/production/transfer_layings/services/transfer_laying.service.go b/internal/modules/production/transfer_layings/services/transfer_laying.service.go index e6e9a862..15351e56 100644 --- a/internal/modules/production/transfer_layings/services/transfer_laying.service.go +++ b/internal/modules/production/transfer_layings/services/transfer_laying.service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "strings" "time" @@ -743,7 +744,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) ( err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { repoTx := s.Repository.WithTx(dbTransaction) approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction)) - + stockAllocationRepo := commonRepo.NewStockAllocationRepository(dbTransaction) sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction) targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction) stockLogRepoTx := rStockLogs.NewStockLogRepository(dbTransaction) @@ -817,6 +818,27 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) ( return fiber.NewError(fiber.StatusInternalServerError, "Gagal update source usage qty") } + targetShares := distributeProportionalWithRounding(targets, totalTargetQty, sourceShare) + + for i, target := range targets { + roundedQty := math.Round(targetShares[i]) + if roundedQty <= 0 { + continue + } + mappingAllocation := &entity.StockAllocation{ + StockableType: fifo.UsableKeyTransferToLayingOut.String(), + StockableId: source.Id, + UsableType: fifo.StockableKeyTransferToLayingIn.String(), + UsableId: target.Id, + ProductWarehouseId: *source.ProductWarehouseId, + Qty: roundedQty, + Status: entity.StockAllocationStatusActive, + } + if err := stockAllocationRepo.CreateOne(c.Context(), mappingAllocation, nil); err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Gagal create mapping allocation source→target") + } + } + stockLogDecrease := &entity.StockLog{ ProductWarehouseId: *source.ProductWarehouseId, CreatedBy: actorID, @@ -937,36 +959,6 @@ func createApprovalTransferLaying(ctx context.Context, tx *gorm.DB, transferLayi return err } -func (s *transferLayingService) getOrCreateProductWarehouse(ctx context.Context, tx *gorm.DB, productID uint, warehouseID uint, quantity float64, actorID uint, projectFlockKandangId *uint) (*entity.ProductWarehouse, error) { - - productWarehouseRepoTx := rInventory.NewProductWarehouseRepository(tx) - - existing, err := productWarehouseRepoTx.GetProductWarehouseByProductAndWarehouseID(ctx, productID, warehouseID) - if err == nil && existing != nil { - - if err := productWarehouseRepoTx.PatchOne(ctx, existing.Id, map[string]any{"qty": gorm.Expr("qty + ?", quantity)}, nil); err != nil { - return nil, err - } - return existing, nil - } - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return nil, err - } - - newWarehouse := &entity.ProductWarehouse{ - ProductId: productID, - WarehouseId: warehouseID, - ProjectFlockKandangId: projectFlockKandangId, - Quantity: quantity, - } - - if err := productWarehouseRepoTx.CreateOne(ctx, newWarehouse, nil); err != nil { - return nil, err - } - - return newWarehouse, nil -} - func (s transferLayingService) GetAvailableQtyPerKandang(ctx *fiber.Ctx, projectFlockID uint) (*entity.ProjectFlock, map[uint]float64, error) { pf, err := s.ProjectFlockRepo.GetByID(ctx.Context(), projectFlockID, func(db *gorm.DB) *gorm.DB { @@ -1060,3 +1052,34 @@ func (s transferLayingService) GetMaxTargetQtyPerKandang(c *fiber.Ctx, projectFl return kandangMaxTargetQty, nil } + +func distributeProportionalWithRounding(targets []entity.LayingTransferTarget, totalTargetQty, sourceShare float64) []float64 { + if len(targets) == 0 { + return []float64{} + } + + targetShares := make([]float64, len(targets)) + totalRounded := 0.0 + + for i, target := range targets { + targetShares[i] = (target.TotalQty / totalTargetQty) * sourceShare + totalRounded += math.Round(targetShares[i]) + } + + diff := sourceShare - totalRounded + + if diff != 0 { + maxIdx := 0 + maxDecimal := 0.0 + for i, share := range targetShares { + decimal := share - math.Round(share) + if decimal > maxDecimal { + maxDecimal = decimal + maxIdx = i + } + } + targetShares[maxIdx] += diff + } + + return targetShares +}