Revert "Merge branch 'fix/implement-fifo-v2' into 'dev/fifo-v2'"

This reverts merge request !340
This commit is contained in:
Hafizh A. Y.
2026-02-27 09:37:03 +00:00
parent 915302c445
commit f6c88b773d
28 changed files with 163 additions and 1468 deletions
@@ -29,8 +29,6 @@ import (
var chickinUsableKey = fifo.UsableKeyProjectChickin
const chickinFunctionCodeOut = "CHICKIN_OUT"
type ChickinService interface {
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error)
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectChickin, error)
@@ -164,32 +162,25 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
if productWarehouse.Product.Id != 0 {
requiredFlags := make([]utils.FlagType, 0, 2)
var requiredFlag utils.FlagType
if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryGrowing) {
requiredFlags = append(requiredFlags, utils.FlagAyam, utils.FlagDOC)
requiredFlag = utils.FlagDOC
} else if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryLaying) {
requiredFlags = append(requiredFlags, utils.FlagAyam, utils.FlagPullet)
requiredFlag = utils.FlagPullet
} else {
return nil, fmt.Errorf("invalid flock category for chickin")
}
hasRequiredFlag := false
for _, flag := range productWarehouse.Product.Flags {
currentFlag := utils.FlagType(flag.Name)
for _, requiredFlag := range requiredFlags {
if currentFlag == requiredFlag {
hasRequiredFlag = true
break
}
}
if hasRequiredFlag {
if utils.FlagType(flag.Name) == requiredFlag {
hasRequiredFlag = true
break
}
}
if !hasRequiredFlag {
requiredText := strings.Join(utils.FlagTypesToStrings(requiredFlags), " or ")
return nil, fmt.Errorf("product warehouse %d cannot be used for %s chickin. Product must have %s flag (product ID: %d, warehouse ID: %d)", chickinReq.ProductWarehouseId, projectFlockKandang.ProjectFlock.Category, requiredText, productWarehouse.Product.Id, productWarehouse.Id)
return nil, fmt.Errorf("product warehouse %d cannot be used for %s chickin. Product must have %s flag (product ID: %d, warehouse ID: %d)", chickinReq.ProductWarehouseId, projectFlockKandang.ProjectFlock.Category, requiredFlag, productWarehouse.Product.Id, productWarehouse.Id)
}
}
@@ -492,9 +483,9 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
var targetFlag utils.FlagType
if category == string(utils.ProjectFlockCategoryGrowing) {
targetFlag = utils.FlagAyam
targetFlag = utils.FlagPullet
} else if category == string(utils.ProjectFlockCategoryLaying) {
targetFlag = utils.FlagAyam
targetFlag = utils.FlagLayer
} else {
continue
}
@@ -630,29 +621,8 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
return nil
}
route, err := commonSvc.ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
ctx,
tx,
chickin.ProductWarehouseId,
chickinFunctionCodeOut,
commonSvc.FifoStockV2LaneUsable,
)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve FIFO v2 chickin route")
}
if route == nil {
return fiber.NewError(
fiber.StatusBadRequest,
fmt.Sprintf("Product warehouse %d tidak mendukung transaksi Chickin pada matrix FIFO v2", chickin.ProductWarehouseId),
)
}
usableKey := fifo.UsableKey(strings.TrimSpace(route.LegacyTypeKey))
if usableKey == "" {
usableKey = chickinUsableKey
}
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
UsableKey: usableKey,
UsableKey: chickinUsableKey,
UsableID: chickin.Id,
ProductWarehouseID: chickin.ProductWarehouseId,
Quantity: desiredQty,
@@ -718,34 +688,13 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
return nil
}
route, err := commonSvc.ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
ctx,
tx,
chickin.ProductWarehouseId,
chickinFunctionCodeOut,
commonSvc.FifoStockV2LaneUsable,
)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve FIFO v2 chickin route")
}
if route == nil {
return fiber.NewError(
fiber.StatusBadRequest,
fmt.Sprintf("Product warehouse %d tidak mendukung transaksi Chickin pada matrix FIFO v2", chickin.ProductWarehouseId),
)
}
usableKey := fifo.UsableKey(strings.TrimSpace(route.LegacyTypeKey))
if usableKey == "" {
usableKey = chickinUsableKey
}
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: usableKey,
UsableKey: chickinUsableKey,
UsableID: chickin.Id,
Tx: tx,
}); err != nil {
@@ -312,7 +312,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
return nil, err
}
feedIDs := recordingutil.CollectWarehouseIDs(req.Stocks, func(st validation.Stock) uint { return st.ProductWarehouseId })
if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "PRE-STARTER", "STARTER", "FINISHER", "OVK", "OBAT", "VITAMIN", "KIMIA"}, "feed"); err != nil {
if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "OVK"}, "feed"); err != nil {
return nil, err
}
depletionIDs := recordingutil.CollectWarehouseIDs(req.Depletions, func(d validation.Depletion) uint { return d.ProductWarehouseId })
@@ -320,7 +320,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
return nil, err
}
eggIDs := recordingutil.CollectWarehouseIDs(req.Eggs, func(e validation.Egg) uint { return e.ProductWarehouseId })
if err := s.ensureProductWarehousesByFlags(ctx, eggIDs, []string{"TELUR", "TELUR-UTUH", "TELUR-PUTIH", "TELUR-RETAK", "TELUR-PECAH", "TELUR-PAPACAL", "TELUR-JUMBO"}, "egg"); err != nil {
if err := s.ensureProductWarehousesByFlags(ctx, eggIDs, []string{"TELUR-UTUH", "TELUR-PECAH", "TELUR-PUTIH", "TELUR-RETAK", "TELUR"}, "egg"); err != nil {
return nil, err
}
actorID, err := m.ActorIDFromContext(c)
@@ -512,7 +512,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
return err
}
feedIDs := recordingutil.CollectWarehouseIDs(req.Stocks, func(st validation.Stock) uint { return st.ProductWarehouseId })
if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "PRE-STARTER", "STARTER", "FINISHER", "OVK", "OBAT", "VITAMIN", "KIMIA"}, "feed"); err != nil {
if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "OVK"}, "feed"); err != nil {
return err
}
if err := s.syncRecordingStocks(ctx, tx, recordingEntity.Id, existingStocks, req.Stocks, note, actorID); err != nil {
@@ -613,7 +613,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
return err
}
eggIDs := recordingutil.CollectWarehouseIDs(req.Eggs, func(e validation.Egg) uint { return e.ProductWarehouseId })
if err := s.ensureProductWarehousesByFlags(ctx, eggIDs, []string{"TELUR", "TELUR-UTUH", "TELUR-PUTIH", "TELUR-RETAK", "TELUR-PECAH", "TELUR-PAPACAL", "TELUR-JUMBO"}, "egg"); err != nil {
if err := s.ensureProductWarehousesByFlags(ctx, eggIDs, []string{"TELUR-UTUH", "TELUR-PECAH", "TELUR-PUTIH", "TELUR-RETAK", "TELUR"}, "egg"); err != nil {
return err
}
if err := ensureRecordingEggsUnused(existingEggs); err != nil {
@@ -21,93 +21,7 @@ import (
var recordingStockUsableKey = fifo.UsableKeyRecordingStock
var recordingDepletionUsableKey = fifo.UsableKeyRecordingDepletion
const (
depletionUsageTolerance = 0.000001
recordingFunctionCodeStockOut = "RECORDING_STOCK_OUT"
recordingFunctionCodeDepletionOut = "RECORDING_DEPLETION_OUT"
recordingFunctionCodeDepletionIn = "RECORDING_DEPLETION_IN"
recordingFunctionCodeRecordingEggIn = "RECORDING_EGG_IN"
)
func (s *recordingService) resolveRecordingUsableKey(
ctx context.Context,
tx *gorm.DB,
productWarehouseID uint,
functionCode string,
fallback fifo.UsableKey,
actionLabel string,
) (fifo.UsableKey, error) {
if productWarehouseID == 0 {
return "", fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product warehouse tidak valid untuk transaksi %s", actionLabel))
}
route, err := commonSvc.ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
ctx,
tx,
productWarehouseID,
functionCode,
commonSvc.FifoStockV2LaneUsable,
)
if err != nil {
return "", fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve FIFO v2 recording route")
}
if route == nil {
return "", fiber.NewError(
fiber.StatusBadRequest,
fmt.Sprintf("Product warehouse %d tidak mendukung transaksi %s pada matrix FIFO v2", productWarehouseID, actionLabel),
)
}
usableKey := fifo.UsableKey(strings.TrimSpace(route.LegacyTypeKey))
if usableKey == "" {
usableKey = fallback
}
if usableKey == "" {
return "", fiber.NewError(fiber.StatusInternalServerError, "FIFO v2 recording route misconfiguration")
}
return usableKey, nil
}
func (s *recordingService) resolveRecordingStockableKey(
ctx context.Context,
tx *gorm.DB,
productWarehouseID uint,
functionCode string,
fallback fifo.StockableKey,
actionLabel string,
) (fifo.StockableKey, error) {
if productWarehouseID == 0 {
return "", fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product warehouse tidak valid untuk transaksi %s", actionLabel))
}
route, err := commonSvc.ResolveFifoStockV2RouteByProductWarehouseIDAndLane(
ctx,
tx,
productWarehouseID,
functionCode,
commonSvc.FifoStockV2LaneStockable,
)
if err != nil {
return "", fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve FIFO v2 recording route")
}
if route == nil {
return "", fiber.NewError(
fiber.StatusBadRequest,
fmt.Sprintf("Product warehouse %d tidak mendukung transaksi %s pada matrix FIFO v2", productWarehouseID, actionLabel),
)
}
stockableKey := fifo.StockableKey(strings.TrimSpace(route.LegacyTypeKey))
if stockableKey == "" {
stockableKey = fallback
}
if stockableKey == "" {
return "", fiber.NewError(fiber.StatusInternalServerError, "FIFO v2 recording route misconfiguration")
}
return stockableKey, nil
}
const depletionUsageTolerance = 0.000001
func (s *recordingService) logStockTrace(action string, stock entity.RecordingStock, extra string) {
if s == nil || s.Log == nil {
@@ -211,20 +125,8 @@ func (s *recordingService) consumeRecordingStocks(
}
desiredTotal := desired + pending
usableKey, err := s.resolveRecordingUsableKey(
ctx,
tx,
stock.ProductWarehouseId,
recordingFunctionCodeStockOut,
recordingStockUsableKey,
"Recording (Stock)",
)
if err != nil {
return err
}
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
UsableKey: usableKey,
UsableKey: recordingStockUsableKey,
UsableID: stock.Id,
ProductWarehouseID: stock.ProductWarehouseId,
Quantity: desiredTotal,
@@ -307,21 +209,9 @@ func (s *recordingService) consumeRecordingDepletions(
return fiber.NewError(fiber.StatusBadRequest, "Source product warehouse tidak ditemukan untuk depletion")
}
usableKey, err := s.resolveRecordingUsableKey(
ctx,
tx,
sourceWarehouseID,
recordingFunctionCodeDepletionOut,
recordingDepletionUsableKey,
"Recording Depletion (Source)",
)
if err != nil {
return err
}
desired := depletion.Qty + depletion.PendingQty
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
UsableKey: usableKey,
UsableKey: recordingDepletionUsableKey,
UsableID: depletion.Id,
ProductWarehouseID: sourceWarehouseID,
Quantity: desired,
@@ -424,20 +314,8 @@ func (s *recordingService) releaseRecordingStocks(
if stock.Id == 0 {
continue
}
usableKey, err := s.resolveRecordingUsableKey(
ctx,
tx,
stock.ProductWarehouseId,
recordingFunctionCodeStockOut,
recordingStockUsableKey,
"Recording (Stock)",
)
if err != nil {
return err
}
if stock.UsageQty != nil && *stock.UsageQty > 0 {
activeCount, err := s.countActiveAllocations(ctx, tx, usableKey, stock.Id)
activeCount, err := s.countActiveAllocations(ctx, tx, fifo.UsableKeyRecordingStock, stock.Id)
if err != nil {
return err
}
@@ -448,16 +326,16 @@ func (s *recordingService) releaseRecordingStocks(
}
continue
}
if err := s.resyncStockableUsageFromAllocations(ctx, tx, usableKey, stock.Id); err != nil {
if err := s.resyncStockableUsageFromAllocations(ctx, tx, fifo.UsableKeyRecordingStock, stock.Id); err != nil {
return err
}
if err := s.ensureActiveAllocations(ctx, tx, usableKey, stock.Id); err != nil {
if err := s.ensureActiveAllocations(ctx, tx, fifo.UsableKeyRecordingStock, stock.Id); err != nil {
return err
}
}
s.logStockTrace("release:start", stock, "")
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
UsableKey: usableKey,
UsableKey: recordingStockUsableKey,
UsableID: stock.Id,
Tx: tx,
}); err != nil {
@@ -522,28 +400,8 @@ func (s *recordingService) releaseRecordingDepletions(
if depletion.Id == 0 {
continue
}
sourceWarehouseID := uint(0)
if depletion.SourceProductWarehouseId != nil {
sourceWarehouseID = *depletion.SourceProductWarehouseId
}
if sourceWarehouseID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Source product warehouse tidak ditemukan untuk depletion")
}
usableKey, err := s.resolveRecordingUsableKey(
ctx,
tx,
sourceWarehouseID,
recordingFunctionCodeDepletionOut,
recordingDepletionUsableKey,
"Recording Depletion (Source)",
)
if err != nil {
return err
}
if depletion.UsageQty > 0 {
activeCount, err := s.countActiveAllocations(ctx, tx, usableKey, depletion.Id)
activeCount, err := s.countActiveAllocations(ctx, tx, fifo.UsableKeyRecordingDepletion, depletion.Id)
if err != nil {
return err
}
@@ -560,10 +418,10 @@ func (s *recordingService) releaseRecordingDepletions(
}
continue
}
if err := s.resyncStockableUsageFromAllocations(ctx, tx, usableKey, depletion.Id); err != nil {
if err := s.resyncStockableUsageFromAllocations(ctx, tx, fifo.UsableKeyRecordingDepletion, depletion.Id); err != nil {
return err
}
if err := s.ensureActiveAllocations(ctx, tx, usableKey, depletion.Id); err != nil {
if err := s.ensureActiveAllocations(ctx, tx, fifo.UsableKeyRecordingDepletion, depletion.Id); err != nil {
return err
}
}
@@ -573,8 +431,15 @@ func (s *recordingService) releaseRecordingDepletions(
return err
}
sourceWarehouseID := uint(0)
if depletion.SourceProductWarehouseId != nil {
sourceWarehouseID = *depletion.SourceProductWarehouseId
}
if sourceWarehouseID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Source product warehouse tidak ditemukan untuk depletion")
}
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
UsableKey: usableKey,
UsableKey: recordingDepletionUsableKey,
UsableID: depletion.Id,
Tx: tx,
}); err != nil {
@@ -765,20 +630,9 @@ func (s *recordingService) replenishRecordingEggs(
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
continue
}
stockableKey, err := s.resolveRecordingStockableKey(
ctx,
tx,
egg.ProductWarehouseId,
recordingFunctionCodeRecordingEggIn,
fifo.StockableKeyRecordingEgg,
"Recording Egg",
)
if err != nil {
return err
}
s.logEggTrace("replenish:start", egg, "")
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: stockableKey,
StockableKey: fifo.StockableKeyRecordingEgg,
StockableID: egg.Id,
ProductWarehouseID: egg.ProductWarehouseId,
Quantity: float64(egg.Qty),
@@ -836,20 +690,9 @@ func (s *recordingService) replenishRecordingDepletions(
if depletion.Id == 0 || depletion.ProductWarehouseId == 0 || depletion.Qty <= 0 {
continue
}
stockableKey, err := s.resolveRecordingStockableKey(
ctx,
tx,
depletion.ProductWarehouseId,
recordingFunctionCodeDepletionIn,
fifo.StockableKeyRecordingDepletion,
"Recording Depletion (Destination)",
)
if err != nil {
return err
}
s.logDepletionTrace("replenish:start", depletion, "")
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: stockableKey,
StockableKey: fifo.StockableKeyRecordingDepletion,
StockableID: depletion.Id,
ProductWarehouseID: depletion.ProductWarehouseId,
Quantity: depletion.Qty,
@@ -881,20 +724,9 @@ func (s *recordingService) reduceRecordingDepletions(
if depletion.Id == 0 || depletion.ProductWarehouseId == 0 || depletion.Qty <= 0 {
continue
}
stockableKey, err := s.resolveRecordingStockableKey(
ctx,
tx,
depletion.ProductWarehouseId,
recordingFunctionCodeDepletionIn,
fifo.StockableKeyRecordingDepletion,
"Recording Depletion (Destination)",
)
if err != nil {
return err
}
s.logDepletionTrace("reduce:start", depletion, "")
if err := s.FifoSvc.AdjustStockableQuantity(ctx, commonSvc.StockAdjustRequest{
StockableKey: stockableKey,
StockableKey: fifo.StockableKeyRecordingDepletion,
StockableID: depletion.Id,
ProductWarehouseID: depletion.ProductWarehouseId,
Quantity: -depletion.Qty,
@@ -926,20 +758,9 @@ func (s *recordingService) reduceRecordingEggs(
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
continue
}
stockableKey, err := s.resolveRecordingStockableKey(
ctx,
tx,
egg.ProductWarehouseId,
recordingFunctionCodeRecordingEggIn,
fifo.StockableKeyRecordingEgg,
"Recording Egg",
)
if err != nil {
return err
}
s.logEggTrace("reduce:start", egg, "")
if err := s.FifoSvc.AdjustStockableQuantity(ctx, commonSvc.StockAdjustRequest{
StockableKey: stockableKey,
StockableKey: fifo.StockableKeyRecordingEgg,
StockableID: egg.Id,
ProductWarehouseID: egg.ProductWarehouseId,
Quantity: -float64(egg.Qty),