feat(BE): integrate FIFO service for stock adjustments and transfers

- Added FIFO service integration in the adjustments module to manage stockable and usable items for adjustments.
- Created a new repository for adjustment stocks to handle database operations.
- Enhanced the adjustment service to track stock adjustments using FIFO logic for both increase and decrease operations.
- Updated product warehouse DTOs and repositories to include project flock information.
- Implemented FIFO logic in the transfer module to manage stock transfers between warehouses.
- Added integration tests for FIFO operations in stock transfers, ensuring correct stock consumption and replenishment.
This commit is contained in:
aguhh18
2025-12-28 19:15:41 +07:00
parent a0d2c1c7dd
commit 812db3f79e
17 changed files with 950 additions and 104 deletions
@@ -12,6 +12,7 @@ import (
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
adjustmentStockRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/validations"
ProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
productRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories"
@@ -29,24 +30,37 @@ type AdjustmentService interface {
}
type adjustmentService struct {
Log *logrus.Logger
Validate *validator.Validate
StockLogsRepository stockLogsRepo.StockLogRepository
WarehouseRepo warehouseRepo.WarehouseRepository
ProductWarehouseRepo ProductWarehouse.ProductWarehouseRepository
ProductRepo productRepo.ProductRepository
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
Log *logrus.Logger
Validate *validator.Validate
StockLogsRepository stockLogsRepo.StockLogRepository
WarehouseRepo warehouseRepo.WarehouseRepository
ProductWarehouseRepo ProductWarehouse.ProductWarehouseRepository
ProductRepo productRepo.ProductRepository
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
AdjustmentStockRepository adjustmentStockRepo.AdjustmentStockRepository
FifoSvc common.FifoService
}
func NewAdjustmentService(productRepo productRepo.ProductRepository, stockLogsRepo stockLogsRepo.StockLogRepository, warehouseRepo warehouseRepo.WarehouseRepository, productWarehouseRepo ProductWarehouse.ProductWarehouseRepository, validate *validator.Validate, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository) AdjustmentService {
func NewAdjustmentService(
productRepo productRepo.ProductRepository,
stockLogsRepo stockLogsRepo.StockLogRepository,
warehouseRepo warehouseRepo.WarehouseRepository,
productWarehouseRepo ProductWarehouse.ProductWarehouseRepository,
adjustmentStockRepo adjustmentStockRepo.AdjustmentStockRepository,
fifoSvc common.FifoService,
validate *validator.Validate,
projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository,
) AdjustmentService {
return &adjustmentService{
Log: utils.Log,
Validate: validate,
StockLogsRepository: stockLogsRepo,
WarehouseRepo: warehouseRepo,
ProductWarehouseRepo: productWarehouseRepo,
ProductRepo: productRepo,
ProjectFlockKandangRepo: projectFlockKandangRepo,
Log: utils.Log,
Validate: validate,
StockLogsRepository: stockLogsRepo,
WarehouseRepo: warehouseRepo,
ProductWarehouseRepo: productWarehouseRepo,
ProductRepo: productRepo,
ProjectFlockKandangRepo: projectFlockKandangRepo,
AdjustmentStockRepository: adjustmentStockRepo,
FifoSvc: fifoSvc,
}
}
@@ -152,15 +166,16 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get product warehouse")
}
// Create StockLog for history tracking
afterQuantity := productWarehouse.Quantity
newLog := &entity.StockLog{
LoggableType: string(utils.StockLogTypeAdjustment),
LoggableId: 0,
Notes: req.Note,
ProductWarehouseId: productWarehouse.Id,
CreatedBy: actorID, // TODO: should Get from auth middleware
CreatedBy: actorID,
}
if transactionType == string(utils.StockLogTransactionTypeIncrease) {
afterQuantity += req.Quantity
newLog.Increase = afterQuantity
@@ -177,6 +192,57 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
return err
}
// Create AdjustmentStock record for FIFO tracking
adjustmentStock := &entity.AdjustmentStock{
StockLogId: newLog.Id,
ProductWarehouseId: productWarehouse.Id,
}
if transactionType == string(utils.StockLogTransactionTypeIncrease) {
// Adjustment INCREASE → Replenish stock (Stockable)
note := fmt.Sprintf("Stock Adjustment IN #%d", newLog.Id)
replenishResult, err := s.FifoSvc.Replenish(ctx, common.StockReplenishRequest{
StockableKey: "ADJUSTMENT_IN",
StockableID: newLog.Id,
ProductWarehouseID: uint(productWarehouse.Id),
Quantity: req.Quantity,
Note: &note,
Tx: tx,
})
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to replenish stock via FIFO: %v", err))
}
// Update stockable tracking fields
adjustmentStock.TotalQty = replenishResult.AddedQuantity
adjustmentStock.TotalUsed = 0
} else {
// Adjustment DECREASE → Consume stock (Usable)
consumeResult, err := s.FifoSvc.Consume(ctx, common.StockConsumeRequest{
UsableKey: "ADJUSTMENT_OUT",
UsableID: newLog.Id,
ProductWarehouseID: uint(productWarehouse.Id),
Quantity: req.Quantity,
AllowPending: false, // Don't allow pending for adjustment
Tx: tx,
})
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Failed to consume stock via FIFO: %v", err))
}
// Update usable tracking fields
adjustmentStock.UsageQty = consumeResult.UsageQuantity
adjustmentStock.PendingQty = consumeResult.PendingQuantity
}
// Save AdjustmentStock record
if err := s.AdjustmentStockRepository.WithTx(tx).CreateOne(ctx, adjustmentStock, nil); err != nil {
s.Log.Errorf("Failed to create adjustment stock: %+v", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create adjustment stock record")
}
// Update ProductWarehouse quantity (for backward compatibility/reporting)
productWarehouse.Quantity = afterQuantity
if err := s.ProductWarehouseRepo.WithTx(tx).UpdateOne(ctx, productWarehouse.Id, productWarehouse, nil); err != nil {
s.Log.Errorf("Failed to update product warehouse quantity: %+v", err)