mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Fix transfer to laying delete and fix chikin delete with response recording
This commit is contained in:
@@ -15,6 +15,11 @@ const (
|
||||
transferLayingInFunctionCode = "TRANSFER_TO_LAYING_IN"
|
||||
transferLayingStockableLane = "STOCKABLE"
|
||||
transferLayingSourceTable = "laying_transfer_targets"
|
||||
|
||||
transferLayingOutFunctionCode = "TRANSFER_TO_LAYING_OUT"
|
||||
transferLayingUsableLane = "USABLE"
|
||||
transferLayingUsableSourceTable = "laying_transfers"
|
||||
transferLayingLegacyUsableSourceTable = "laying_transfer_sources"
|
||||
)
|
||||
|
||||
func reflowTransferLayingScope(
|
||||
@@ -85,3 +90,90 @@ func resolveTransferLayingFlagGroupByProductWarehouse(ctx context.Context, tx *g
|
||||
|
||||
return strings.TrimSpace(selected.FlagGroupCode), nil
|
||||
}
|
||||
|
||||
type transferLayingUsableRouteRule struct {
|
||||
FlagGroupCode string `gorm:"column:flag_group_code"`
|
||||
SourceTable string `gorm:"column:source_table"`
|
||||
}
|
||||
|
||||
func resolveTransferLayingUsableFlagGroupByProductWarehouse(ctx context.Context, tx *gorm.DB, productWarehouseID uint) (string, error) {
|
||||
rows := make([]transferLayingUsableRouteRule, 0)
|
||||
err := tx.WithContext(ctx).
|
||||
Table("fifo_stock_v2_route_rules rr").
|
||||
Select("rr.flag_group_code, rr.source_table").
|
||||
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 = ?", transferLayingUsableLane).
|
||||
Where("rr.function_code = ?", transferLayingOutFunctionCode).
|
||||
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").
|
||||
Find(&rows).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return validateTransferLayingUsableRouteRules(rows, productWarehouseID)
|
||||
}
|
||||
|
||||
func validateTransferLayingUsableRouteRules(rows []transferLayingUsableRouteRule, productWarehouseID uint) (string, error) {
|
||||
if len(rows) == 0 {
|
||||
return "", fmt.Errorf(
|
||||
"konfigurasi FIFO v2 TRANSFER_TO_LAYING_OUT tidak ditemukan untuk source warehouse %d",
|
||||
productWarehouseID,
|
||||
)
|
||||
}
|
||||
|
||||
var selectedFlagGroup string
|
||||
hasHeaderRule := false
|
||||
hasLegacyRule := false
|
||||
|
||||
for _, row := range rows {
|
||||
sourceTable := strings.ToLower(strings.TrimSpace(row.SourceTable))
|
||||
flagGroupCode := strings.TrimSpace(row.FlagGroupCode)
|
||||
|
||||
switch sourceTable {
|
||||
case transferLayingUsableSourceTable:
|
||||
if flagGroupCode == "" {
|
||||
return "", fmt.Errorf("konfigurasi FIFO v2 TRANSFER_TO_LAYING_OUT memiliki flag_group_code kosong")
|
||||
}
|
||||
hasHeaderRule = true
|
||||
if selectedFlagGroup == "" {
|
||||
selectedFlagGroup = flagGroupCode
|
||||
continue
|
||||
}
|
||||
if selectedFlagGroup != flagGroupCode {
|
||||
return "", fmt.Errorf(
|
||||
"konfigurasi FIFO v2 TRANSFER_TO_LAYING_OUT ambigu untuk source warehouse %d",
|
||||
productWarehouseID,
|
||||
)
|
||||
}
|
||||
case transferLayingLegacyUsableSourceTable:
|
||||
hasLegacyRule = true
|
||||
}
|
||||
}
|
||||
|
||||
if hasLegacyRule {
|
||||
return "", fmt.Errorf(
|
||||
"konfigurasi FIFO v2 legacy untuk TRANSFER_TO_LAYING_OUT masih aktif (source_table=%s)",
|
||||
transferLayingLegacyUsableSourceTable,
|
||||
)
|
||||
}
|
||||
if !hasHeaderRule {
|
||||
return "", fmt.Errorf(
|
||||
"konfigurasi FIFO v2 TRANSFER_TO_LAYING_OUT aktif untuk source_table=%s tidak ditemukan",
|
||||
transferLayingUsableSourceTable,
|
||||
)
|
||||
}
|
||||
|
||||
return selectedFlagGroup, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateTransferLayingUsableRouteRules(t *testing.T) {
|
||||
t.Run("valid header rule", func(t *testing.T) {
|
||||
flagGroup, err := validateTransferLayingUsableRouteRules([]transferLayingUsableRouteRule{
|
||||
{FlagGroupCode: "AYAM", SourceTable: transferLayingUsableSourceTable},
|
||||
}, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if flagGroup != "AYAM" {
|
||||
t.Fatalf("unexpected flag group: %s", flagGroup)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing usable header rule", func(t *testing.T) {
|
||||
_, err := validateTransferLayingUsableRouteRules(nil, 10)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "tidak ditemukan") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("legacy rule still active", func(t *testing.T) {
|
||||
_, err := validateTransferLayingUsableRouteRules([]transferLayingUsableRouteRule{
|
||||
{FlagGroupCode: "AYAM", SourceTable: transferLayingUsableSourceTable},
|
||||
{FlagGroupCode: "AYAM", SourceTable: transferLayingLegacyUsableSourceTable},
|
||||
}, 10)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "legacy") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ambiguous active header rules", func(t *testing.T) {
|
||||
_, err := validateTransferLayingUsableRouteRules([]transferLayingUsableRouteRule{
|
||||
{FlagGroupCode: "AYAM", SourceTable: transferLayingUsableSourceTable},
|
||||
{FlagGroupCode: "PAKAN", SourceTable: transferLayingUsableSourceTable},
|
||||
}, 10)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "ambigu") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1026,6 +1026,33 @@ func (s transferLayingService) Unexecute(c *fiber.Ctx, id uint) (*entity.LayingT
|
||||
}
|
||||
}
|
||||
|
||||
flagGroupCode, err := resolveTransferLayingUsableFlagGroupByProductWarehouse(
|
||||
c.Context(),
|
||||
dbTransaction,
|
||||
*transfer.SourceProductWarehouseId,
|
||||
)
|
||||
if err != nil {
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Konfigurasi FIFO v2 transfer laying tidak valid: %v", err),
|
||||
)
|
||||
}
|
||||
activeConsumeAllocCount, err := s.countActiveTransferSourceConsumeAllocations(
|
||||
c.Context(),
|
||||
dbTransaction,
|
||||
transfer.Id,
|
||||
*transfer.SourceProductWarehouseId,
|
||||
)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi alokasi FIFO source transfer laying")
|
||||
}
|
||||
if transfer.SourceUsageQty > 1e-6 && activeConsumeAllocCount == 0 {
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Unexecute transfer laying %s gagal: alokasi FIFO source tidak ditemukan", transfer.TransferNumber),
|
||||
)
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
if target.ProductWarehouseId == nil || *target.ProductWarehouseId == 0 {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Target product warehouse tidak ditemukan untuk transfer %d", transfer.Id))
|
||||
@@ -1067,21 +1094,40 @@ func (s transferLayingService) Unexecute(c *fiber.Ctx, id uint) (*entity.LayingT
|
||||
}
|
||||
}
|
||||
|
||||
asOf := normalizeDateOnlyUTC(transfer.TransferDate)
|
||||
rollbackResult, err := s.FifoStockV2Svc.Rollback(c.Context(), commonSvc.FifoStockV2RollbackRequest{
|
||||
ProductWarehouseID: *transfer.SourceProductWarehouseId,
|
||||
Usable: commonSvc.FifoStockV2Ref{
|
||||
ID: transfer.Id,
|
||||
LegacyTypeKey: fifo.UsableKeyTransferToLayingOut.String(),
|
||||
FunctionCode: transferLayingOutFunctionCode,
|
||||
},
|
||||
Reason: fmt.Sprintf("transfer laying unexecute #%s [%s]", transfer.TransferNumber, flagGroupCode),
|
||||
Tx: dbTransaction,
|
||||
})
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Gagal rollback FIFO v2 source transfer laying: %v", err))
|
||||
}
|
||||
releasedQty := 0.0
|
||||
if rollbackResult != nil {
|
||||
releasedQty = rollbackResult.ReleasedQty
|
||||
}
|
||||
if transfer.SourceUsageQty > 1e-6 && releasedQty < transfer.SourceUsageQty-1e-6 {
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf(
|
||||
"Rollback FIFO v2 source transfer laying tidak lengkap. Dibutuhkan %.3f, terlepas %.3f",
|
||||
transfer.SourceUsageQty,
|
||||
releasedQty,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if err := repoTx.PatchOne(c.Context(), transfer.Id, map[string]any{
|
||||
"source_usage_qty": 0,
|
||||
"source_pending_usage_qty": 0,
|
||||
}, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal reset kuantitas source transfer laying")
|
||||
}
|
||||
if _, err := s.FifoStockV2Svc.Reflow(c.Context(), commonSvc.FifoStockV2ReflowRequest{
|
||||
FlagGroupCode: transferToLayingFlagGroupCode,
|
||||
ProductWarehouseID: *transfer.SourceProductWarehouseId,
|
||||
AsOf: &asOf,
|
||||
Tx: dbTransaction,
|
||||
}); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Gagal rollback FIFO v2 source transfer laying: %v", err))
|
||||
}
|
||||
if err := fifoV2.ReleasePopulationConsumptionByUsable(
|
||||
c.Context(),
|
||||
dbTransaction,
|
||||
@@ -1576,6 +1622,34 @@ func (s *transferLayingService) hasDownstreamRecordingOnTarget(
|
||||
return true, normalizeDateOnlyUTC(earliest.RecordDatetime), nil
|
||||
}
|
||||
|
||||
func (s *transferLayingService) countActiveTransferSourceConsumeAllocations(
|
||||
ctx context.Context,
|
||||
tx *gorm.DB,
|
||||
transferID uint,
|
||||
productWarehouseID uint,
|
||||
) (int64, error) {
|
||||
if transferID == 0 || productWarehouseID == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if tx == nil {
|
||||
return 0, errors.New("transaction is required")
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := tx.WithContext(ctx).
|
||||
Model(&entity.StockAllocation{}).
|
||||
Where("product_warehouse_id = ?", productWarehouseID).
|
||||
Where("usable_type = ?", fifo.UsableKeyTransferToLayingOut.String()).
|
||||
Where("usable_id = ?", transferID).
|
||||
Where("status = ?", entity.StockAllocationStatusActive).
|
||||
Where("allocation_purpose = ?", entity.StockAllocationPurposeConsume).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *transferLayingService) resyncPopulationUsageByProjectFlockKandang(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error {
|
||||
if projectFlockKandangID == 0 {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user