mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
implement fifo-v2 to transfer stock pakan
This commit is contained in:
@@ -52,7 +52,6 @@ func (TransferModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||
approvalSvc := commonSvc.NewApprovalService(approvalRepo)
|
||||
if err := approvalSvc.RegisterWorkflowSteps(utils.ApprovalWorkflowExpense, utils.ExpenseApprovalSteps); err != nil {
|
||||
@@ -71,6 +70,7 @@ func (TransferModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
||||
)
|
||||
|
||||
fifoService := commonSvc.NewFifoService(db, stockAllocRepo, productWarehouseRepo, utils.Log)
|
||||
fifoStockV2Service := commonSvc.NewFifoStockV2Service(db, utils.Log)
|
||||
expenseBridge := sTransfer.NewTransferExpenseBridge(
|
||||
db,
|
||||
stockTransferRepo,
|
||||
@@ -111,7 +111,7 @@ func (TransferModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
||||
panic(err)
|
||||
}
|
||||
|
||||
transferService := sTransfer.NewTransferService(validate, stockTransferRepo, stockTransferDetailRepo, stockTransferDeliveryRepo, StockTransferDeliveryItemRepo, stockLogsRepo, productWarehouseRepo, supplierRepo, warehouseRepo, projectFlockKandangRepo, documentSvc, fifoService, expenseBridge)
|
||||
transferService := sTransfer.NewTransferService(validate, stockTransferRepo, stockTransferDetailRepo, stockTransferDeliveryRepo, StockTransferDeliveryItemRepo, stockLogsRepo, productWarehouseRepo, supplierRepo, warehouseRepo, projectFlockKandangRepo, documentSvc, fifoService, fifoStockV2Service, expenseBridge)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
TransferRoutes(router, userService, transferService)
|
||||
|
||||
@@ -46,10 +46,11 @@ type transferService struct {
|
||||
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
||||
DocumentSvc commonSvc.DocumentService
|
||||
FifoSvc commonSvc.FifoService
|
||||
FifoStockV2Svc commonSvc.FifoStockV2Service
|
||||
ExpenseBridge TransferExpenseBridge
|
||||
}
|
||||
|
||||
func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTransfer.StockTransferRepository, stockTransferDetailRepo rStockTransfer.StockTransferDetailRepository, stockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository, stockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository, stockLogsRepo rStockLogs.StockLogRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, supplierRepo rSupplier.SupplierRepository, warehouseRepo warehouseRepo.WarehouseRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, documentSvc commonSvc.DocumentService, fifoSvc commonSvc.FifoService, expenseBridge TransferExpenseBridge) TransferService {
|
||||
func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTransfer.StockTransferRepository, stockTransferDetailRepo rStockTransfer.StockTransferDetailRepository, stockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository, stockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository, stockLogsRepo rStockLogs.StockLogRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, supplierRepo rSupplier.SupplierRepository, warehouseRepo warehouseRepo.WarehouseRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, documentSvc commonSvc.DocumentService, fifoSvc commonSvc.FifoService, fifoStockV2Svc commonSvc.FifoStockV2Service, expenseBridge TransferExpenseBridge) TransferService {
|
||||
return &transferService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
@@ -64,6 +65,7 @@ func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTr
|
||||
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
||||
DocumentSvc: documentSvc,
|
||||
FifoSvc: fifoSvc,
|
||||
FifoStockV2Svc: fifoStockV2Svc,
|
||||
ExpenseBridge: expenseBridge,
|
||||
}
|
||||
}
|
||||
@@ -442,26 +444,73 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
}
|
||||
}
|
||||
|
||||
pakanProducts := map[uint]bool{}
|
||||
if s.FifoStockV2Svc != nil && len(req.Products) > 0 {
|
||||
pakanProducts, err = s.resolvePakanProducts(c.Context(), tx, req.Products)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, product := range req.Products {
|
||||
detail := detailMap[uint64(product.ProductID)]
|
||||
|
||||
consumeResult, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
||||
UsableKey: fifo.UsableKeyStockTransferOut,
|
||||
UsableID: uint(detail.Id),
|
||||
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
|
||||
Quantity: product.ProductQty,
|
||||
AllowPending: false,
|
||||
Tx: tx,
|
||||
})
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak mencukupi untuk produk %d di gudang asal. Error: %v", product.ProductID, err))
|
||||
outUsageQty := 0.0
|
||||
outPendingQty := 0.0
|
||||
useFifoV2 := s.FifoStockV2Svc != nil && pakanProducts[uint(product.ProductID)]
|
||||
if useFifoV2 {
|
||||
s.Log.Infof(
|
||||
"[fifo-v2][transfer] use reflow movement=%s detail_id=%d product_id=%d source_pw=%d qty=%.3f",
|
||||
entityTransfer.MovementNumber,
|
||||
detail.Id,
|
||||
product.ProductID,
|
||||
*detail.SourceProductWarehouseID,
|
||||
product.ProductQty,
|
||||
)
|
||||
reflowResult, err := s.FifoStockV2Svc.Reflow(c.Context(), commonSvc.FifoStockV2ReflowRequest{
|
||||
FlagGroupCode: "PAKAN",
|
||||
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
|
||||
Usable: commonSvc.FifoStockV2Ref{
|
||||
ID: uint(detail.Id),
|
||||
LegacyTypeKey: fifo.UsableKeyStockTransferOut.String(),
|
||||
FunctionCode: "STOCK_TRANSFER_OUT",
|
||||
},
|
||||
DesiredQty: product.ProductQty,
|
||||
Tx: tx,
|
||||
})
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak mencukupi untuk produk %d di gudang asal. Error: %v", product.ProductID, err))
|
||||
}
|
||||
outUsageQty = reflowResult.Allocate.AllocatedQty
|
||||
outPendingQty = reflowResult.Allocate.PendingQty
|
||||
s.Log.Infof(
|
||||
"[fifo-v2][transfer] reflow result movement=%s detail_id=%d usage=%.3f pending=%.3f",
|
||||
entityTransfer.MovementNumber,
|
||||
detail.Id,
|
||||
outUsageQty,
|
||||
outPendingQty,
|
||||
)
|
||||
} else {
|
||||
consumeResult, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
||||
UsableKey: fifo.UsableKeyStockTransferOut,
|
||||
UsableID: uint(detail.Id),
|
||||
ProductWarehouseID: uint(*detail.SourceProductWarehouseID),
|
||||
Quantity: product.ProductQty,
|
||||
AllowPending: false,
|
||||
Tx: tx,
|
||||
})
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak mencukupi untuk produk %d di gudang asal. Error: %v", product.ProductID, err))
|
||||
}
|
||||
outUsageQty = consumeResult.UsageQuantity
|
||||
outPendingQty = consumeResult.PendingQuantity
|
||||
}
|
||||
|
||||
if err := tx.Model(&entity.StockTransferDetail{}).
|
||||
Where("id = ?", detail.Id).
|
||||
Updates(map[string]interface{}{
|
||||
"usage_qty": consumeResult.UsageQuantity,
|
||||
"pending_qty": consumeResult.PendingQuantity,
|
||||
"usage_qty": outUsageQty,
|
||||
"pending_qty": outPendingQty,
|
||||
}).Error; err != nil {
|
||||
s.Log.Errorf("Failed to update tracking usage for detail_id=%d, product_id=%d: %+v", detail.Id, product.ProductID, err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memperbarui data tracking")
|
||||
@@ -493,6 +542,17 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
}
|
||||
|
||||
note := fmt.Sprintf("Transfer #%s", entityTransfer.MovementNumber)
|
||||
inAddedQty := 0.0
|
||||
if useFifoV2 {
|
||||
s.Log.Infof(
|
||||
"[fifo-v2][transfer] stock-in uses replenish path movement=%s detail_id=%d product_id=%d dest_pw=%d qty=%.3f",
|
||||
entityTransfer.MovementNumber,
|
||||
detail.Id,
|
||||
product.ProductID,
|
||||
*detail.DestProductWarehouseID,
|
||||
product.ProductQty,
|
||||
)
|
||||
}
|
||||
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
|
||||
StockableKey: fifo.StockableKeyStockTransferIn,
|
||||
StockableID: uint(detail.Id),
|
||||
@@ -505,11 +565,12 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
s.Log.Errorf("Failed to replenish stock for product_id=%d, pw_id=%d, qty=%.2f: %+v", product.ProductID, *detail.DestProductWarehouseID, product.ProductQty, err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal menambah stok gudang tujuan")
|
||||
}
|
||||
inAddedQty = replenishResult.AddedQuantity
|
||||
|
||||
if err := tx.Model(&entity.StockTransferDetail{}).
|
||||
Where("id = ?", detail.Id).
|
||||
Updates(map[string]interface{}{
|
||||
"total_qty": replenishResult.AddedQuantity,
|
||||
"total_qty": inAddedQty,
|
||||
}).Error; err != nil {
|
||||
s.Log.Errorf("Failed to update tracking total for detail_id=%d, product_id=%d: %+v", detail.Id, product.ProductID, err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memperbarui data tracking")
|
||||
@@ -596,6 +657,53 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *transferService) resolvePakanProducts(
|
||||
ctx context.Context,
|
||||
tx *gorm.DB,
|
||||
products []validation.TransferProduct,
|
||||
) (map[uint]bool, error) {
|
||||
out := make(map[uint]bool, len(products))
|
||||
if len(products) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
productIDs := make([]uint, 0, len(products))
|
||||
seen := make(map[uint]struct{}, len(products))
|
||||
for _, product := range products {
|
||||
if product.ProductID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[product.ProductID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[product.ProductID] = struct{}{}
|
||||
productIDs = append(productIDs, product.ProductID)
|
||||
}
|
||||
if len(productIDs) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type row struct {
|
||||
ProductID uint `gorm:"column:product_id"`
|
||||
}
|
||||
var rows []row
|
||||
err := tx.WithContext(ctx).
|
||||
Table("flags f").
|
||||
Select("DISTINCT f.flagable_id AS product_id").
|
||||
Where("f.flagable_type = ?", entity.FlagableTypeProduct).
|
||||
Where("f.name IN ?", []string{"PAKAN", "PRE-STARTER", "STARTER", "FINISHER"}).
|
||||
Where("f.flagable_id IN ?", productIDs).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
out[row.ProductID] = true
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *transferService) notifyExpenseItemsDelivered(c *fiber.Ctx, transferID uint64, payloads []TransferExpenseReceivingPayload) error {
|
||||
if s.ExpenseBridge == nil || transferID == 0 || len(payloads) == 0 {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user