mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-25 15:55:44 +00:00
FEAT[BE] :implement proportional distribution with rounding for stock allocation in transfer laying approval process
This commit is contained in:
@@ -520,6 +520,7 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
|
|||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
Notes: fmt.Sprintf("FIFO consume (%.2f)", result.UsageQuantity),
|
Notes: fmt.Sprintf("FIFO consume (%.2f)", result.UsageQuantity),
|
||||||
}
|
}
|
||||||
|
|
||||||
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, marketingProduct.ProductWarehouseId, 1)
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, marketingProduct.ProductWarehouseId, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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 {
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
repoTx := s.Repository.WithTx(dbTransaction)
|
repoTx := s.Repository.WithTx(dbTransaction)
|
||||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
|
stockAllocationRepo := commonRepo.NewStockAllocationRepository(dbTransaction)
|
||||||
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
||||||
targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
|
targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
|
||||||
stockLogRepoTx := rStockLogs.NewStockLogRepository(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")
|
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{
|
stockLogDecrease := &entity.StockLog{
|
||||||
ProductWarehouseId: *source.ProductWarehouseId,
|
ProductWarehouseId: *source.ProductWarehouseId,
|
||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
@@ -937,36 +959,6 @@ func createApprovalTransferLaying(ctx context.Context, tx *gorm.DB, transferLayi
|
|||||||
return err
|
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) {
|
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 {
|
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
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user