fix: first push need support testing, and implemented fifo v2 to all modules

This commit is contained in:
Hafizh A. Y
2026-02-27 19:09:01 +07:00
parent a2de21e351
commit 944604adad
21 changed files with 1105 additions and 810 deletions
+2 -40
View File
@@ -2,7 +2,6 @@ package chickins
import (
"fmt"
"strings"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
@@ -10,7 +9,6 @@ import (
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
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"
@@ -40,45 +38,9 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
productRepo := rProduct.NewProductRepository(db)
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
fifoStockV2Service := commonSvc.NewFifoStockV2Service(db, utils.Log)
userRepo := rUser.NewUserRepository(db)
if err := fifoService.RegisterUsable(fifo.UsableConfig{
Key: fifo.UsableKeyProjectChickin,
Table: "project_chickins",
Columns: fifo.UsableColumns{
ID: "id",
ProductWarehouseID: "product_warehouse_id",
UsageQuantity: "usage_qty",
PendingQuantity: "pending_usage_qty",
CreatedAt: "created_at",
},
ExcludedStockables: []fifo.StockableKey{fifo.StockableKeyProjectFlockPopulation},
}); err != nil {
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
panic(fmt.Sprintf("failed to register chickin usable workflow: %v", err))
}
}
if err := fifoService.RegisterStockable(fifo.StockableConfig{
Key: fifo.StockableKeyProjectFlockPopulation,
Table: "project_flock_populations",
Columns: fifo.StockableColumns{
ID: "id",
ProductWarehouseID: "product_warehouse_id",
TotalQuantity: "total_qty",
TotalUsedQuantity: "total_used_qty",
CreatedAt: "created_at",
},
OrderBy: []string{"created_at ASC", "id ASC"},
}); err != nil {
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
panic(fmt.Sprintf("failed to register project flock population stockable workflow: %v", err))
}
}
approvalRepo := commonRepo.NewApprovalRepository(db)
approvalService := commonSvc.NewApprovalService(approvalRepo)
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowChickin, utils.ChickinApprovalSteps); err != nil {
@@ -96,7 +58,7 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
projectflockpopulationrepo,
chickinDetailRepo,
validate,
fifoService)
fifoStockV2Service)
userService := sUser.NewUserService(userRepo, validate)
ChickinRoutes(router, userService, chickinService)
@@ -19,7 +19,6 @@ import (
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
@@ -27,8 +26,6 @@ import (
"gorm.io/gorm"
)
var chickinUsableKey = fifo.UsableKeyProjectChickin
type ChickinService interface {
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error)
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectChickin, error)
@@ -51,11 +48,11 @@ type chickinService struct {
ProjectflockKandangRepo rProjectFlock.ProjectFlockKandangRepository
ProjectflockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository
ProjectChickinDetailRepo repository.ProjectChickinDetailRepository
FifoSvc commonSvc.FifoService
FifoStockV2Svc commonSvc.FifoStockV2Service
StockLogRepo rStockLogs.StockLogRepository
}
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 {
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, fifoStockV2Svc commonSvc.FifoStockV2Service) ChickinService {
return &chickinService{
Log: utils.Log,
Validate: validate,
@@ -68,7 +65,7 @@ func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo Kan
ProjectflockKandangRepo: projectflockkandangRepo,
ProjectflockPopulationRepo: projectflockpopulationRepo,
ProjectChickinDetailRepo: projectChickinDetailRepo,
FifoSvc: fifoSvc,
FifoStockV2Svc: fifoStockV2Svc,
StockLogRepo: rStockLogs.NewStockLogRepository(repo.DB()),
}
}
@@ -372,18 +369,9 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
}
if chickin.UsageQty > 0 {
currentUsageQty := chickin.UsageQty
if err := s.ReleaseChickinStocks(c.Context(), s.Repository.DB(), chickin, actorID); err != nil {
return err
}
warehouseDeltas := make(map[uint]float64)
warehouseDeltas[chickin.ProductWarehouseId] += currentUsageQty
if err := s.adjustProductWarehouseQuantities(c.Context(), s.Repository.DB(), warehouseDeltas); err != nil {
return err
}
}
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
@@ -549,12 +537,6 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to release stock for rejected chickin %d: %v", chickin.Id, err))
}
warehouseDeltas := make(map[uint]float64)
warehouseDeltas[chickin.ProductWarehouseId] += chickin.UsageQty
if err := s.adjustProductWarehouseQuantities(c.Context(), dbTransaction, warehouseDeltas); err != nil {
return err
}
if err := chickinRepoTx.DeleteOne(c.Context(), chickin.Id); err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to delete rejected chickin %d", chickin.Id))
@@ -617,36 +599,48 @@ func (s *chickinService) autoAddFlagToProduct(ctx context.Context, tx *gorm.DB,
}
func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, desiredQty float64, actorID uint) error {
if chickin == nil || s.FifoSvc == nil {
if chickin == nil {
return nil
}
if tx == nil {
return errors.New("transaction is required")
}
if s.FifoStockV2Svc == nil {
return errors.New("fifo v2 service is not available")
}
if desiredQty < 0 {
return errors.New("desired quantity must be zero or greater")
}
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
UsableKey: chickinUsableKey,
UsableID: chickin.Id,
ProductWarehouseID: chickin.ProductWarehouseId,
Quantity: desiredQty,
AllowPending: true,
Tx: tx,
})
if err != nil {
if err := s.Repository.UpdateUsageFields(ctx, tx, chickin.Id, desiredQty, 0); err != nil {
return err
}
if err := s.Repository.UpdateUsageFields(ctx, tx, chickin.Id, result.UsageQuantity, result.PendingQuantity); err != nil {
asOf := chickin.ChickInDate
if asOf.IsZero() {
asOf = chickin.CreatedAt
}
if err := reflowChickinScope(ctx, s.FifoStockV2Svc, tx, chickin.ProductWarehouseId, &asOf); err != nil {
return err
}
if result.UsageQuantity > 0 {
var refreshed entity.ProjectChickin
if err := tx.WithContext(ctx).
Where("id = ?", chickin.Id).
Take(&refreshed).Error; err != nil {
return err
}
if refreshed.UsageQty > 0 {
decreaseLog := &entity.StockLog{
Decrease: result.UsageQuantity,
Decrease: refreshed.UsageQty,
LoggableType: string(utils.StockLogTypeChikin),
LoggableId: chickin.Id,
ProductWarehouseId: chickin.ProductWarehouseId,
LoggableId: refreshed.Id,
ProductWarehouseId: refreshed.ProductWarehouseId,
CreatedBy: actorID,
Notes: fmt.Sprintf("Chickin #%d", chickin.Id),
Notes: fmt.Sprintf("Chickin #%d", refreshed.Id),
}
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, chickin.ProductWarehouseId, 1)
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, refreshed.ProductWarehouseId, 1)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
}
@@ -658,46 +652,52 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
decreaseLog.Stock -= decreaseLog.Decrease
}
s.StockLogRepo.CreateOne(ctx, decreaseLog, nil)
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, decreaseLog, nil); err != nil {
return err
}
}
return nil
}
func (s *chickinService) ReplenishChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, targetPW *entity.ProductWarehouse, population *entity.ProjectFlockPopulation, actorID uint) error {
if chickin == nil || targetPW == nil || population == nil || s.FifoSvc == nil {
if chickin == nil || targetPW == nil || population == nil {
return nil
}
if tx == nil {
return errors.New("transaction is required")
}
if s.FifoStockV2Svc == nil {
return errors.New("fifo v2 service is not available")
}
_, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyProjectFlockPopulation,
StockableID: population.Id,
ProductWarehouseID: targetPW.Id,
Quantity: chickin.UsageQty,
Tx: tx,
})
if err != nil {
if err := tx.WithContext(ctx).
Model(&entity.ProjectFlockPopulation{}).
Where("id = ?", population.Id).
Update("total_qty", chickin.UsageQty).Error; err != nil {
return err
}
return nil
asOf := chickin.ChickInDate
if asOf.IsZero() {
asOf = chickin.CreatedAt
}
return reflowChickinScope(ctx, s.FifoStockV2Svc, tx, targetPW.Id, &asOf)
}
func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, actorID uint) error {
if chickin == nil || s.FifoSvc == nil {
if chickin == nil {
return nil
}
if tx == nil {
return errors.New("transaction is required")
}
if s.FifoStockV2Svc == nil {
return errors.New("fifo v2 service is not available")
}
var currentUsage float64
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Select("usage_qty").Scan(&currentUsage).Error; err != nil {
}
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
UsableKey: chickinUsableKey,
UsableID: chickin.Id,
Tx: tx,
}); err != nil {
return err
}
@@ -705,6 +705,14 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
return err
}
asOf := chickin.ChickInDate
if asOf.IsZero() {
asOf = chickin.CreatedAt
}
if err := reflowChickinScope(ctx, s.FifoStockV2Svc, tx, chickin.ProductWarehouseId, &asOf); err != nil {
return err
}
if currentUsage > 0 {
increaseLog := &entity.StockLog{
Increase: currentUsage,
@@ -726,7 +734,9 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
increaseLog.Stock += increaseLog.Increase
}
s.StockLogRepo.CreateOne(ctx, increaseLog, nil)
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, increaseLog, nil); err != nil {
return err
}
}
return nil
@@ -755,10 +765,3 @@ func (s chickinService) EnsureChickInExists(ctx context.Context, projectFlockKan
return fiber.NewError(fiber.StatusBadRequest, "Chick in project flock belum disetujui sehingga belum dapat membuat recording")
}
func (s *chickinService) adjustProductWarehouseQuantities(ctx context.Context, tx *gorm.DB, deltas map[uint]float64) error {
if len(deltas) == 0 {
return nil
}
return s.ProductWarehouseRepo.AdjustQuantities(ctx, deltas, func(*gorm.DB) *gorm.DB { return tx })
}
@@ -0,0 +1,87 @@
package service
import (
"context"
"fmt"
"strings"
"time"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
"gorm.io/gorm"
)
const (
chickinOutFunctionCode = "CHICKIN_OUT"
chickinUsableLane = "USABLE"
chickinSourceTable = "project_chickins"
)
func reflowChickinScope(
ctx context.Context,
fifoStockV2Svc commonSvc.FifoStockV2Service,
tx *gorm.DB,
productWarehouseID uint,
asOf *time.Time,
) error {
if fifoStockV2Svc == nil {
return fmt.Errorf("FIFO v2 service is not available")
}
if tx == nil {
return fmt.Errorf("transaction is required")
}
if productWarehouseID == 0 {
return fmt.Errorf("product warehouse id is required")
}
flagGroupCode, err := resolveChickinFlagGroupByProductWarehouse(ctx, tx, productWarehouseID)
if err != nil {
return err
}
if strings.TrimSpace(flagGroupCode) == "" {
return fmt.Errorf("flag group code is not found for product warehouse %d", productWarehouseID)
}
_, err = fifoStockV2Svc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{
FlagGroupCode: flagGroupCode,
ProductWarehouseID: productWarehouseID,
AsOf: asOf,
Tx: tx,
})
return err
}
func resolveChickinFlagGroupByProductWarehouse(ctx context.Context, tx *gorm.DB, productWarehouseID uint) (string, error) {
type row struct {
FlagGroupCode string `gorm:"column:flag_group_code"`
}
var selected row
err := tx.WithContext(ctx).
Table("fifo_stock_v2_route_rules rr").
Select("rr.flag_group_code").
Joins("JOIN fifo_stock_v2_flag_groups fg ON fg.code = rr.flag_group_code AND fg.is_active = TRUE").
Where("rr.is_active = TRUE").
Where("rr.lane = ?", chickinUsableLane).
Where("rr.function_code = ?", chickinOutFunctionCode).
Where("rr.source_table = ?", chickinSourceTable).
Where(`
EXISTS (
SELECT 1
FROM product_warehouses pw
JOIN flags f ON f.flagable_id = pw.product_id
JOIN fifo_stock_v2_flag_members fm ON fm.flag_name = f.name AND fm.is_active = TRUE
WHERE pw.id = ?
AND f.flagable_type = ?
AND fm.flag_group_code = rr.flag_group_code
)
`, productWarehouseID, entity.FlagableTypeProduct).
Order("rr.id ASC").
Limit(1).
Take(&selected).Error
if err != nil {
return "", err
}
return strings.TrimSpace(selected.FlagGroupCode), nil
}
@@ -2,7 +2,6 @@ package recordings
import (
"fmt"
"strings"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
@@ -26,7 +25,6 @@ import (
sRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
@@ -48,7 +46,6 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
productRepo := rProduct.NewProductRepository(db)
chickinRepo := rChickin.NewChickinRepository(db)
chickinDetailRepo := rChickin.NewChickinDetailRepository(db)
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
stockLogRepo := rStockLogs.NewStockLogRepository(db)
productionStandardRepo := rProductionStandard.NewProductionStandardRepository(db)
productionStandardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
@@ -61,76 +58,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
validate,
)
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
if err := fifoService.RegisterStockable(fifo.StockableConfig{
Key: fifo.StockableKeyRecordingEgg,
Table: "recording_eggs",
Columns: fifo.StockableColumns{
ID: "id",
ProductWarehouseID: "product_warehouse_id",
TotalQuantity: "total_qty",
TotalUsedQuantity: "total_used",
CreatedAt: "(SELECT r.record_datetime FROM recordings r WHERE r.id = recording_eggs.recording_id)",
},
OrderBy: []string{"(SELECT r.record_datetime FROM recordings r WHERE r.id = recording_eggs.recording_id) ASC", "id ASC"},
}); err != nil {
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
panic(fmt.Sprintf("failed to register recording egg stockable workflow: %v", err))
}
}
if err := fifoService.RegisterStockable(fifo.StockableConfig{
Key: fifo.StockableKeyRecordingDepletion,
Table: "recording_depletions",
Columns: fifo.StockableColumns{
ID: "id",
ProductWarehouseID: "product_warehouse_id",
TotalQuantity: "qty",
TotalUsedQuantity: "total_used_qty",
CreatedAt: "(SELECT r.record_datetime FROM recordings r WHERE r.id = recording_depletions.recording_id)",
},
OrderBy: []string{"(SELECT r.record_datetime FROM recordings r WHERE r.id = recording_depletions.recording_id) ASC", "id ASC"},
}); err != nil {
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
panic(fmt.Sprintf("failed to register recording depletion stockable workflow: %v", err))
}
}
if err := fifoService.RegisterUsable(fifo.UsableConfig{
Key: fifo.UsableKeyRecordingStock,
Table: "recording_stocks",
Columns: fifo.UsableColumns{
ID: "id",
ProductWarehouseID: "product_warehouse_id",
UsageQuantity: "usage_qty",
PendingQuantity: "pending_qty",
CreatedAt: "(SELECT r.record_datetime FROM recordings r WHERE r.id = recording_stocks.recording_id)",
},
}); err != nil {
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
panic(fmt.Sprintf("failed to register recording usable workflow: %v", err))
}
}
if err := fifoService.RegisterUsable(fifo.UsableConfig{
Key: fifo.UsableKeyRecordingDepletion,
Table: "recording_depletions",
Columns: fifo.UsableColumns{
ID: "id",
ProductWarehouseID: "source_product_warehouse_id",
UsageQuantity: "usage_qty",
PendingQuantity: "pending_qty",
CreatedAt: "(SELECT r.record_datetime FROM recordings r WHERE r.id = recording_depletions.recording_id)",
},
ExcludedStockables: []fifo.StockableKey{
fifo.StockableKeyTransferToLayingIn,
fifo.StockableKeyStockTransferIn,
fifo.StockableKeyAdjustmentIn,
fifo.StockableKeyPurchaseItems,
fifo.StockableKeyRecordingEgg,
},
}); err != nil {
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
panic(fmt.Sprintf("failed to register recording depletion usable workflow: %v", err))
}
}
fifoStockV2Service := commonSvc.NewFifoStockV2Service(db, utils.Log)
approvalRepo := commonRepo.NewApprovalRepository(db)
approvalService := commonSvc.NewApprovalService(approvalRepo)
@@ -169,7 +97,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
projectFlockPopulationRepo,
chickinDetailRepo,
validate,
fifoService,
fifoStockV2Service,
)
recordingService := sRecording.NewRecordingService(
@@ -179,7 +107,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
projectFlockPopulationRepo,
approvalRepo,
approvalService,
fifoService,
fifoStockV2Service,
stockLogRepo,
productionStandardService,
projectFlockService,
@@ -0,0 +1,137 @@
package service
import (
"context"
"fmt"
"strings"
"time"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
"gorm.io/gorm"
)
const (
recordingLaneUsable = "USABLE"
recordingLaneStockable = "STOCKABLE"
recordingFunctionStockOut = "RECORDING_STOCK_OUT"
recordingFunctionDepletionOut = "RECORDING_DEPLETION_OUT"
recordingFunctionDepletionIn = "RECORDING_DEPLETION_IN"
recordingFunctionEggIn = "RECORDING_EGG_IN"
recordingSourceStocks = "recording_stocks"
recordingSourceDepletions = "recording_depletions"
recordingSourceEggs = "recording_eggs"
)
func (s *recordingService) reflowRecordingScope(
ctx context.Context,
tx *gorm.DB,
productWarehouseID uint,
recordingID uint,
lane string,
functionCode string,
sourceTable string,
) error {
if s == nil || s.FifoStockV2Svc == nil {
return fmt.Errorf("FIFO v2 service is not available")
}
if tx == nil {
return fmt.Errorf("transaction is required")
}
if productWarehouseID == 0 {
return fmt.Errorf("product warehouse id is required")
}
flagGroupCode, err := resolveRecordingFlagGroupByProductWarehouse(ctx, tx, productWarehouseID, lane, functionCode, sourceTable)
if err != nil {
return err
}
if strings.TrimSpace(flagGroupCode) == "" {
return fmt.Errorf("flag group code is not found for product warehouse %d", productWarehouseID)
}
asOf, err := resolveRecordingAsOf(ctx, tx, recordingID)
if err != nil {
return err
}
_, err = s.FifoStockV2Svc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{
FlagGroupCode: flagGroupCode,
ProductWarehouseID: productWarehouseID,
AsOf: asOf,
Tx: tx,
})
return err
}
func resolveRecordingFlagGroupByProductWarehouse(
ctx context.Context,
tx *gorm.DB,
productWarehouseID uint,
lane string,
functionCode string,
sourceTable string,
) (string, error) {
type row struct {
FlagGroupCode string `gorm:"column:flag_group_code"`
}
var selected row
q := tx.WithContext(ctx).
Table("fifo_stock_v2_route_rules rr").
Select("rr.flag_group_code").
Joins("JOIN fifo_stock_v2_flag_groups fg ON fg.code = rr.flag_group_code AND fg.is_active = TRUE").
Where("rr.is_active = TRUE").
Where("rr.lane = ?", lane).
Where("rr.source_table = ?", sourceTable)
if strings.TrimSpace(functionCode) != "" {
q = q.Where("rr.function_code = ?", functionCode)
}
err := q.
Where(`
EXISTS (
SELECT 1
FROM product_warehouses pw
JOIN flags f ON f.flagable_id = pw.product_id
JOIN fifo_stock_v2_flag_members fm ON fm.flag_name = f.name AND fm.is_active = TRUE
WHERE pw.id = ?
AND f.flagable_type = ?
AND fm.flag_group_code = rr.flag_group_code
)
`, productWarehouseID, entity.FlagableTypeProduct).
Order("rr.id ASC").
Limit(1).
Take(&selected).Error
if err != nil {
return "", err
}
return strings.TrimSpace(selected.FlagGroupCode), nil
}
func resolveRecordingAsOf(ctx context.Context, tx *gorm.DB, recordingID uint) (*time.Time, error) {
if recordingID == 0 {
asOf := time.Now().UTC()
return &asOf, nil
}
type row struct {
RecordDatetime time.Time `gorm:"column:record_datetime"`
}
var selected row
if err := tx.WithContext(ctx).
Table("recordings").
Select("record_datetime").
Where("id = ?", recordingID).
Limit(1).
Take(&selected).Error; err != nil {
return nil, err
}
asOf := selected.RecordDatetime.UTC()
return &asOf, nil
}
@@ -52,7 +52,7 @@ type recordingService struct {
ProductionStandardSvc sProductionStandard.ProductionStandardService
ProjectFlockSvc sProjectFlock.ProjectflockService
ChickinSvc sChickin.ChickinService
FifoSvc commonSvc.FifoService
FifoStockV2Svc commonSvc.FifoStockV2Service
StockLogRepo rStockLogs.StockLogRepository
}
@@ -63,7 +63,7 @@ func NewRecordingService(
projectFlockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository,
approvalRepo commonRepo.ApprovalRepository,
approvalSvc commonSvc.ApprovalService,
fifoSvc commonSvc.FifoService,
fifoStockV2Svc commonSvc.FifoStockV2Service,
stockLogRepo rStockLogs.StockLogRepository,
productionStandardSvc sProductionStandard.ProductionStandardService,
projectFlockSvc sProjectFlock.ProjectflockService,
@@ -82,7 +82,7 @@ func NewRecordingService(
ProductionStandardSvc: productionStandardSvc,
ProjectFlockSvc: projectFlockSvc,
ChickinSvc: chickinSvc,
FifoSvc: fifoSvc,
FifoStockV2Svc: fifoStockV2Svc,
StockLogRepo: stockLogRepo,
}
}
@@ -8,7 +8,6 @@ import (
"strings"
"time"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
@@ -18,9 +17,6 @@ import (
"gorm.io/gorm"
)
var recordingStockUsableKey = fifo.UsableKeyRecordingStock
var recordingDepletionUsableKey = fifo.UsableKeyRecordingDepletion
const depletionUsageTolerance = 0.000001
func (s *recordingService) logStockTrace(action string, stock entity.RecordingStock, extra string) {
@@ -101,9 +97,9 @@ func (s *recordingService) consumeRecordingStocks(
if len(stocks) == 0 {
return nil
}
if s.FifoSvc == nil {
s.Log.Errorf("FIFO service is not available for consuming recording stocks")
return errors.New("fifo service is not available")
if s.FifoStockV2Svc == nil {
s.Log.Errorf("FIFO v2 service is not available for consuming recording stocks")
return errors.New("fifo v2 service is not available")
}
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
return errors.New("stock log repository is not available")
@@ -125,38 +121,52 @@ func (s *recordingService) consumeRecordingStocks(
}
desiredTotal := desired + pending
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
UsableKey: recordingStockUsableKey,
UsableID: stock.Id,
ProductWarehouseID: stock.ProductWarehouseId,
Quantity: desiredTotal,
AllowPending: true,
Tx: tx,
})
if err != nil {
s.Log.Errorf("Failed to consume FIFO stock for recording stock %d: %+v", stock.Id, err)
if err := s.Repository.UpdateStockUsage(tx, stock.Id, desiredTotal, 0); err != nil {
return err
}
if err := s.reflowRecordingScope(
ctx,
tx,
stock.ProductWarehouseId,
stock.RecordingId,
recordingLaneUsable,
recordingFunctionStockOut,
recordingSourceStocks,
); err != nil {
s.Log.Errorf("Failed to reflow FIFO v2 stock for recording stock %d: %+v", stock.Id, err)
return err
}
if err := s.Repository.UpdateStockUsage(tx, stock.Id, result.UsageQuantity, result.PendingQuantity); err != nil {
var refreshed entity.RecordingStock
if err := tx.WithContext(ctx).
Where("id = ?", stock.Id).
Take(&refreshed).Error; err != nil {
return err
}
s.logStockTrace("consume:done", stock, fmt.Sprintf("desired=%.3f used=%.3f pending=%.3f", desiredTotal, result.UsageQuantity, result.PendingQuantity))
actualUsage := 0.0
actualPending := 0.0
if refreshed.UsageQty != nil {
actualUsage = *refreshed.UsageQty
}
if refreshed.PendingQty != nil {
actualPending = *refreshed.PendingQty
}
s.logStockTrace("consume:done", refreshed, fmt.Sprintf("desired=%.3f used=%.3f pending=%.3f", desiredTotal, actualUsage, actualPending))
logDecrease := result.UsageQuantity
if result.PendingQuantity > 0 {
logDecrease += result.PendingQuantity
logDecrease := actualUsage
if actualPending > 0 {
logDecrease += actualPending
}
if logDecrease > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
log := &entity.StockLog{
ProductWarehouseId: stock.ProductWarehouseId,
ProductWarehouseId: refreshed.ProductWarehouseId,
CreatedBy: actorID,
Decrease: logDecrease,
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: stock.RecordingId,
LoggableId: refreshed.RecordingId,
Notes: note,
}
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, stock.ProductWarehouseId, 1)
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, refreshed.ProductWarehouseId, 1)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
}
@@ -187,9 +197,9 @@ func (s *recordingService) consumeRecordingDepletions(
if len(depletions) == 0 {
return nil
}
if s.FifoSvc == nil {
s.Log.Errorf("FIFO service is not available for consuming recording depletions")
return errors.New("fifo service is not available")
if s.FifoStockV2Svc == nil {
s.Log.Errorf("FIFO v2 service is not available for consuming recording depletions")
return errors.New("fifo v2 service is not available")
}
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
return errors.New("stock log repository is not available")
@@ -210,27 +220,40 @@ func (s *recordingService) consumeRecordingDepletions(
}
desired := depletion.Qty + depletion.PendingQty
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
UsableKey: recordingDepletionUsableKey,
UsableID: depletion.Id,
ProductWarehouseID: sourceWarehouseID,
Quantity: desired,
AllowPending: false,
Tx: tx,
})
if err != nil {
s.Log.Errorf("Failed to consume FIFO stock for recording depletion %d: %+v", depletion.Id, err)
if err := tx.WithContext(ctx).
Model(&entity.RecordingDepletion{}).
Where("id = ?", depletion.Id).
Updates(map[string]any{
"qty": desired,
"usage_qty": desired,
"pending_qty": 0,
}).Error; err != nil {
return err
}
if err := s.reflowRecordingScope(
ctx,
tx,
sourceWarehouseID,
depletion.RecordingId,
recordingLaneUsable,
recordingFunctionDepletionOut,
recordingSourceDepletions,
); err != nil {
s.Log.Errorf("Failed to reflow FIFO v2 stock for recording depletion %d: %+v", depletion.Id, err)
return err
}
if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, result.PendingQuantity); err != nil {
var refreshed entity.RecordingDepletion
if err := tx.WithContext(ctx).
Where("id = ?", depletion.Id).
Take(&refreshed).Error; err != nil {
return err
}
s.logDepletionTrace("consume:done", depletion, fmt.Sprintf("desired=%.3f used=%.3f pending=%.3f", desired, result.UsageQuantity, result.PendingQuantity))
s.logDepletionTrace("consume:done", refreshed, fmt.Sprintf("desired=%.3f used=%.3f pending=%.3f", desired, refreshed.UsageQty, refreshed.PendingQty))
logDecrease := result.UsageQuantity
if result.PendingQuantity > 0 {
logDecrease += result.PendingQuantity
logDecrease := refreshed.UsageQty
if refreshed.PendingQty > 0 {
logDecrease += refreshed.PendingQty
}
if logDecrease > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
log := &entity.StockLog{
@@ -238,7 +261,7 @@ func (s *recordingService) consumeRecordingDepletions(
CreatedBy: actorID,
Decrease: logDecrease,
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: depletion.RecordingId,
LoggableId: refreshed.RecordingId,
Notes: note,
}
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, sourceWarehouseID, 1)
@@ -258,20 +281,20 @@ func (s *recordingService) consumeRecordingDepletions(
}
}
destDelta := depletion.Qty + depletion.PendingQty
if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
if depletion.ProductWarehouseId == sourceWarehouseID {
destDelta := refreshed.Qty + refreshed.PendingQty
if refreshed.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
if refreshed.ProductWarehouseId == sourceWarehouseID {
continue
}
log := &entity.StockLog{
ProductWarehouseId: depletion.ProductWarehouseId,
ProductWarehouseId: refreshed.ProductWarehouseId,
CreatedBy: actorID,
Increase: destDelta,
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: depletion.RecordingId,
LoggableId: refreshed.RecordingId,
Notes: note,
}
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, depletion.ProductWarehouseId, 1)
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, refreshed.ProductWarehouseId, 1)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
}
@@ -302,9 +325,9 @@ func (s *recordingService) releaseRecordingStocks(
if len(stocks) == 0 {
return nil
}
if s.FifoSvc == nil {
s.Log.Errorf("FIFO service is not available for releasing recording stocks")
return errors.New("fifo service is not available")
if s.FifoStockV2Svc == nil {
s.Log.Errorf("FIFO v2 service is not available for releasing recording stocks")
return errors.New("fifo v2 service is not available")
}
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
return errors.New("stock log repository is not available")
@@ -314,45 +337,35 @@ func (s *recordingService) releaseRecordingStocks(
if stock.Id == 0 {
continue
}
if stock.UsageQty != nil && *stock.UsageQty > 0 {
activeCount, err := s.countActiveAllocations(ctx, tx, fifo.UsableKeyRecordingStock, stock.Id)
if err != nil {
return err
}
if activeCount == 0 {
s.Log.Warnf("recording-stock release: no active allocations, forcing usage/pending to 0 (stock_id=%d)", stock.Id)
if err := s.Repository.UpdateStockUsage(tx, stock.Id, 0, 0); err != nil {
return err
}
continue
}
if err := s.resyncStockableUsageFromAllocations(ctx, tx, fifo.UsableKeyRecordingStock, stock.Id); err != nil {
return err
}
if err := s.ensureActiveAllocations(ctx, tx, fifo.UsableKeyRecordingStock, stock.Id); err != nil {
return err
}
currentUsage := 0.0
if stock.UsageQty != nil {
currentUsage = *stock.UsageQty
}
s.logStockTrace("release:start", stock, "")
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
UsableKey: recordingStockUsableKey,
UsableID: stock.Id,
Tx: tx,
}); err != nil {
s.Log.Errorf("Failed to release FIFO stock for recording stock %d: %+v", stock.Id, err)
return err
}
if err := s.Repository.UpdateStockUsage(tx, stock.Id, 0, 0); err != nil {
return err
}
if err := s.reflowRecordingScope(
ctx,
tx,
stock.ProductWarehouseId,
stock.RecordingId,
recordingLaneUsable,
recordingFunctionStockOut,
recordingSourceStocks,
); err != nil {
s.Log.Errorf("Failed to reflow FIFO v2 release for recording stock %d: %+v", stock.Id, err)
return err
}
s.logStockTrace("release:done", stock, "")
if stock.UsageQty != nil && *stock.UsageQty > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
if currentUsage > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
log := &entity.StockLog{
ProductWarehouseId: stock.ProductWarehouseId,
CreatedBy: actorID,
Increase: *stock.UsageQty,
Increase: currentUsage,
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: stock.RecordingId,
Notes: note,
@@ -388,9 +401,9 @@ func (s *recordingService) releaseRecordingDepletions(
if len(depletions) == 0 {
return nil
}
if s.FifoSvc == nil {
s.Log.Errorf("FIFO service is not available for releasing recording depletions")
return errors.New("fifo service is not available")
if s.FifoStockV2Svc == nil {
s.Log.Errorf("FIFO v2 service is not available for releasing recording depletions")
return errors.New("fifo v2 service is not available")
}
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
return errors.New("stock log repository is not available")
@@ -400,36 +413,7 @@ func (s *recordingService) releaseRecordingDepletions(
if depletion.Id == 0 {
continue
}
if depletion.UsageQty > 0 {
activeCount, err := s.countActiveAllocations(ctx, tx, fifo.UsableKeyRecordingDepletion, depletion.Id)
if err != nil {
return err
}
if activeCount == 0 {
s.Log.Warnf("recording-depletion release: no active allocations, forcing usage/pending to 0 (depletion_id=%d)", depletion.Id)
if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, 0); err != nil {
return err
}
if err := tx.WithContext(ctx).
Table("recording_depletions").
Where("id = ?", depletion.Id).
Update("usage_qty", 0).Error; err != nil {
return err
}
continue
}
if err := s.resyncStockableUsageFromAllocations(ctx, tx, fifo.UsableKeyRecordingDepletion, depletion.Id); err != nil {
return err
}
if err := s.ensureActiveAllocations(ctx, tx, fifo.UsableKeyRecordingDepletion, depletion.Id); err != nil {
return err
}
}
s.logDepletionTrace("release:start", depletion, "")
if err := validateDepletionUsage(depletion); err != nil {
s.Log.Errorf("FIFO depletion mismatch for recording %d (depletion %d): qty=%.3f usage=%.3f pending=%.3f", depletion.RecordingId, depletion.Id, depletion.Qty, depletion.UsageQty, depletion.PendingQty)
return err
}
sourceWarehouseID := uint(0)
if depletion.SourceProductWarehouseId != nil {
@@ -438,24 +422,49 @@ func (s *recordingService) releaseRecordingDepletions(
if sourceWarehouseID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Source product warehouse tidak ditemukan untuk depletion")
}
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
UsableKey: recordingDepletionUsableKey,
UsableID: depletion.Id,
Tx: tx,
}); err != nil {
s.Log.Errorf("Failed to release FIFO stock for recording depletion %d: %+v", depletion.Id, err)
logIncrease := depletion.Qty + depletion.PendingQty
destDelta := depletion.Qty + depletion.PendingQty
if err := tx.WithContext(ctx).
Model(&entity.RecordingDepletion{}).
Where("id = ?", depletion.Id).
Updates(map[string]any{
"qty": 0,
"usage_qty": 0,
"pending_qty": 0,
}).Error; err != nil {
return err
}
if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, 0); err != nil {
if err := s.reflowRecordingScope(
ctx,
tx,
sourceWarehouseID,
depletion.RecordingId,
recordingLaneUsable,
recordingFunctionDepletionOut,
recordingSourceDepletions,
); err != nil {
s.Log.Errorf("Failed to reflow FIFO v2 source release for recording depletion %d: %+v", depletion.Id, err)
return err
}
if depletion.ProductWarehouseId != 0 {
if err := s.reflowRecordingScope(
ctx,
tx,
depletion.ProductWarehouseId,
depletion.RecordingId,
recordingLaneStockable,
recordingFunctionDepletionIn,
recordingSourceDepletions,
); err != nil {
s.Log.Errorf("Failed to reflow FIFO v2 destination release for recording depletion %d: %+v", depletion.Id, err)
return err
}
}
s.logDepletionTrace("release:done", depletion, "")
logIncrease := depletion.Qty
if depletion.PendingQty > 0 {
logIncrease += depletion.PendingQty
}
if logIncrease > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
log := &entity.StockLog{
ProductWarehouseId: sourceWarehouseID,
@@ -482,7 +491,6 @@ func (s *recordingService) releaseRecordingDepletions(
}
}
destDelta := depletion.Qty + depletion.PendingQty
if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
if depletion.ProductWarehouseId == sourceWarehouseID {
continue
@@ -618,9 +626,9 @@ func (s *recordingService) replenishRecordingEggs(
if len(eggs) == 0 {
return nil
}
if s.FifoSvc == nil {
s.Log.Errorf("FIFO service is not available for replenishing recording eggs")
return errors.New("fifo service is not available")
if s.FifoStockV2Svc == nil {
s.Log.Errorf("FIFO v2 service is not available for replenishing recording eggs")
return errors.New("fifo v2 service is not available")
}
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
return errors.New("stock log repository is not available")
@@ -631,14 +639,23 @@ func (s *recordingService) replenishRecordingEggs(
continue
}
s.logEggTrace("replenish:start", egg, "")
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyRecordingEgg,
StockableID: egg.Id,
ProductWarehouseID: egg.ProductWarehouseId,
Quantity: float64(egg.Qty),
Tx: tx,
}); err != nil {
s.Log.Errorf("Failed to replenish FIFO stock for recording egg %d: %+v", egg.Id, err)
if err := tx.WithContext(ctx).
Model(&entity.RecordingEgg{}).
Where("id = ?", egg.Id).
Update("total_qty", float64(egg.Qty)).Error; err != nil {
return err
}
if err := s.reflowRecordingScope(
ctx,
tx,
egg.ProductWarehouseId,
egg.RecordingId,
recordingLaneStockable,
recordingFunctionEggIn,
recordingSourceEggs,
); err != nil {
s.Log.Errorf("Failed to reflow FIFO v2 stock for recording egg %d: %+v", egg.Id, err)
return err
}
s.logEggTrace("replenish:done", egg, "")
@@ -681,9 +698,9 @@ func (s *recordingService) replenishRecordingDepletions(
if len(depletions) == 0 {
return nil
}
if s.FifoSvc == nil {
s.Log.Errorf("FIFO service is not available for replenishing recording depletions")
return errors.New("fifo service is not available")
if s.FifoStockV2Svc == nil {
s.Log.Errorf("FIFO v2 service is not available for replenishing recording depletions")
return errors.New("fifo v2 service is not available")
}
for _, depletion := range depletions {
@@ -691,14 +708,16 @@ func (s *recordingService) replenishRecordingDepletions(
continue
}
s.logDepletionTrace("replenish:start", depletion, "")
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyRecordingDepletion,
StockableID: depletion.Id,
ProductWarehouseID: depletion.ProductWarehouseId,
Quantity: depletion.Qty,
Tx: tx,
}); err != nil {
s.Log.Errorf("Failed to replenish FIFO stock for recording depletion %d: %+v", depletion.Id, err)
if err := s.reflowRecordingScope(
ctx,
tx,
depletion.ProductWarehouseId,
depletion.RecordingId,
recordingLaneStockable,
recordingFunctionDepletionIn,
recordingSourceDepletions,
); err != nil {
s.Log.Errorf("Failed to reflow FIFO v2 stock for recording depletion %d: %+v", depletion.Id, err)
return err
}
s.logDepletionTrace("replenish:done", depletion, "")
@@ -715,9 +734,9 @@ func (s *recordingService) reduceRecordingDepletions(
if len(depletions) == 0 {
return nil
}
if s.FifoSvc == nil {
s.Log.Errorf("FIFO service is not available for reducing recording depletions")
return errors.New("fifo service is not available")
if s.FifoStockV2Svc == nil {
s.Log.Errorf("FIFO v2 service is not available for reducing recording depletions")
return errors.New("fifo v2 service is not available")
}
for _, depletion := range depletions {
@@ -725,16 +744,44 @@ func (s *recordingService) reduceRecordingDepletions(
continue
}
s.logDepletionTrace("reduce:start", depletion, "")
if err := s.FifoSvc.AdjustStockableQuantity(ctx, commonSvc.StockAdjustRequest{
StockableKey: fifo.StockableKeyRecordingDepletion,
StockableID: depletion.Id,
ProductWarehouseID: depletion.ProductWarehouseId,
Quantity: -depletion.Qty,
Tx: tx,
}); err != nil {
s.Log.Errorf("Failed to reduce FIFO stock for recording depletion %d: %+v", depletion.Id, err)
if err := tx.WithContext(ctx).
Model(&entity.RecordingDepletion{}).
Where("id = ?", depletion.Id).
Updates(map[string]any{
"qty": 0,
"usage_qty": 0,
"pending_qty": 0,
}).Error; err != nil {
return err
}
if depletion.SourceProductWarehouseId != nil && *depletion.SourceProductWarehouseId != 0 {
if err := s.reflowRecordingScope(
ctx,
tx,
*depletion.SourceProductWarehouseId,
depletion.RecordingId,
recordingLaneUsable,
recordingFunctionDepletionOut,
recordingSourceDepletions,
); err != nil {
s.Log.Errorf("Failed to reflow FIFO v2 source stock for recording depletion %d: %+v", depletion.Id, err)
return err
}
}
if err := s.reflowRecordingScope(
ctx,
tx,
depletion.ProductWarehouseId,
depletion.RecordingId,
recordingLaneStockable,
recordingFunctionDepletionIn,
recordingSourceDepletions,
); err != nil {
s.Log.Errorf("Failed to reflow FIFO v2 destination stock for recording depletion %d: %+v", depletion.Id, err)
return err
}
s.logDepletionTrace("reduce:done", depletion, "")
}
@@ -749,9 +796,9 @@ func (s *recordingService) reduceRecordingEggs(
if len(eggs) == 0 {
return nil
}
if s.FifoSvc == nil {
s.Log.Errorf("FIFO service is not available for reducing recording eggs")
return errors.New("fifo service is not available")
if s.FifoStockV2Svc == nil {
s.Log.Errorf("FIFO v2 service is not available for reducing recording eggs")
return errors.New("fifo v2 service is not available")
}
for _, egg := range eggs {
@@ -759,14 +806,22 @@ func (s *recordingService) reduceRecordingEggs(
continue
}
s.logEggTrace("reduce:start", egg, "")
if err := s.FifoSvc.AdjustStockableQuantity(ctx, commonSvc.StockAdjustRequest{
StockableKey: fifo.StockableKeyRecordingEgg,
StockableID: egg.Id,
ProductWarehouseID: egg.ProductWarehouseId,
Quantity: -float64(egg.Qty),
Tx: tx,
}); err != nil {
s.Log.Errorf("Failed to reduce FIFO stock for recording egg %d: %+v", egg.Id, err)
if err := tx.WithContext(ctx).
Model(&entity.RecordingEgg{}).
Where("id = ?", egg.Id).
Update("total_qty", 0).Error; err != nil {
return err
}
if err := s.reflowRecordingScope(
ctx,
tx,
egg.ProductWarehouseId,
egg.RecordingId,
recordingLaneStockable,
recordingFunctionEggIn,
recordingSourceEggs,
); err != nil {
s.Log.Errorf("Failed to reflow FIFO v2 stock for recording egg %d: %+v", egg.Id, err)
return err
}
s.logEggTrace("reduce:done", egg, "")
@@ -934,9 +989,9 @@ func (s *recordingService) syncRecordingStocks(
note string,
actorID uint,
) error {
if s.FifoSvc == nil {
s.Log.Errorf("FIFO service is not available for syncing recording stocks")
return errors.New("fifo service is not available")
if s.FifoStockV2Svc == nil {
s.Log.Errorf("FIFO v2 service is not available for syncing recording stocks")
return errors.New("fifo v2 service is not available")
}
existingByWarehouse := make(map[uint][]entity.RecordingStock)
@@ -1125,9 +1180,9 @@ func (s *recordingService) rollbackRecordingInventory(ctx context.Context, tx *g
}
func (s *recordingService) requireFIFO() error {
if s.FifoSvc == nil {
s.Log.Errorf("FIFO service is not available for recording operations")
return fiber.NewError(fiber.StatusInternalServerError, "FIFO service is required for recording operations")
if s.FifoStockV2Svc == nil {
s.Log.Errorf("FIFO v2 service is not available for recording operations")
return fiber.NewError(fiber.StatusInternalServerError, "FIFO v2 service is required for recording operations")
}
return nil
}