mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
213 lines
8.0 KiB
Go
213 lines
8.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
|
fifoStockV2 "gitlab.com/mbugroup/lti-api.git/internal/common/service/fifo_stock_v2"
|
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
transferSvc "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
|
|
)
|
|
|
|
func TestValidateAdjustmentGatherAgainstAllowedIDsEligible(t *testing.T) {
|
|
result := validateAdjustmentGatherAgainstAllowedIDs(100, []uint{11, 12}, []commonSvc.FifoStockV2GatherRow{
|
|
{SourceTable: "adjustment_stocks", SourceID: 11, AvailableQuantity: 70},
|
|
{SourceTable: "adjustment_stocks", SourceID: 12, AvailableQuantity: 40},
|
|
})
|
|
|
|
if result.Status != "eligible" {
|
|
t.Fatalf("expected eligible, got %+v", result)
|
|
}
|
|
if result.VerifiedQty != 100 {
|
|
t.Fatalf("expected verified qty 100, got %v", result.VerifiedQty)
|
|
}
|
|
}
|
|
|
|
func TestValidateAdjustmentGatherAgainstAllowedIDsRejectsMixedSource(t *testing.T) {
|
|
result := validateAdjustmentGatherAgainstAllowedIDs(100, []uint{11}, []commonSvc.FifoStockV2GatherRow{
|
|
{SourceTable: "adjustment_stocks", SourceID: 11, AvailableQuantity: 60},
|
|
{SourceTable: "recording_eggs", SourceID: 21, AvailableQuantity: 50},
|
|
})
|
|
|
|
if result.Status != "skipped" {
|
|
t.Fatalf("expected skipped, got %+v", result)
|
|
}
|
|
if result.Reason != "mixed_fifo_source_recording_eggs" {
|
|
t.Fatalf("unexpected reason: %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestBuildAdjustmentMigrationPlanUsesValidator(t *testing.T) {
|
|
opts := &adjustmentCommandOptions{RunID: "egg-adjustment-cutover-test"}
|
|
farmID := uint(25)
|
|
farmName := "Gudang Farm Jamali"
|
|
rows := []adjustmentLegacyEggRow{
|
|
{
|
|
LocationID: 16,
|
|
LocationName: "Jamali",
|
|
SourceWarehouseID: 46,
|
|
SourceWarehouseName: "Gudang Jamali 1",
|
|
FarmWarehouseID: &farmID,
|
|
FarmWarehouseName: &farmName,
|
|
ProductWarehouseID: 101,
|
|
ProductID: 8,
|
|
ProductName: "Telur Utuh",
|
|
RemainingQty: 120,
|
|
CurrentPWQty: 150,
|
|
AdjustmentIDs: []uint{1},
|
|
},
|
|
{
|
|
LocationID: 16,
|
|
LocationName: "Jamali",
|
|
SourceWarehouseID: 46,
|
|
SourceWarehouseName: "Gudang Jamali 1",
|
|
FarmWarehouseID: &farmID,
|
|
FarmWarehouseName: &farmName,
|
|
ProductWarehouseID: 102,
|
|
ProductID: 9,
|
|
ProductName: "Telur Putih",
|
|
RemainingQty: 20,
|
|
CurrentPWQty: 40,
|
|
AdjustmentIDs: []uint{2},
|
|
},
|
|
{
|
|
LocationID: 16,
|
|
LocationName: "Jamali",
|
|
SourceWarehouseID: 46,
|
|
SourceWarehouseName: "Gudang Jamali 1",
|
|
ProductWarehouseID: 103,
|
|
ProductID: 10,
|
|
ProductName: "Telur Pecah",
|
|
RemainingQty: 10,
|
|
CurrentPWQty: 10,
|
|
AdjustmentIDs: []uint{3},
|
|
},
|
|
}
|
|
validator := &fakeAdjustmentCandidateValidator{
|
|
byProduct: map[string]adjustmentCandidateValidation{
|
|
"Telur Utuh": {Status: "eligible", VerifiedQty: 120},
|
|
"Telur Putih": {Status: "skipped", Reason: "mixed_fifo_source_recording_eggs", VerifiedQty: 10},
|
|
},
|
|
}
|
|
|
|
reportRows, groups := buildAdjustmentMigrationPlan(context.Background(), opts, map[uint]adjustmentLocationTiming{
|
|
16: {LocationID: 16, LocationName: "Jamali", Status: "CLEAN_CUTOVER"},
|
|
}, rows, validator)
|
|
|
|
if len(reportRows) != 3 {
|
|
t.Fatalf("expected 3 report rows, got %d", len(reportRows))
|
|
}
|
|
if len(groups) != 1 || len(groups[0].Rows) != 1 {
|
|
t.Fatalf("expected only one eligible grouped row, got %+v", groups)
|
|
}
|
|
if reportRows[0].Status != "eligible" || reportRows[0].VerifiedQty != 120 {
|
|
t.Fatalf("unexpected first row: %+v", reportRows[0])
|
|
}
|
|
if reportRows[1].Reason != "mixed_fifo_source_recording_eggs" {
|
|
t.Fatalf("unexpected second row reason: %+v", reportRows[1])
|
|
}
|
|
if reportRows[2].Reason != "missing_farm_warehouse" {
|
|
t.Fatalf("expected missing farm warehouse skip, got %+v", reportRows[2])
|
|
}
|
|
}
|
|
|
|
func TestExecuteAdjustmentApplyRevalidatesRowsAndAppliesSubset(t *testing.T) {
|
|
opts := &adjustmentCommandOptions{
|
|
RunID: "egg-adjustment-cutover-apply",
|
|
CutoverDate: time.Date(2026, 4, 7, 0, 0, 0, 0, time.UTC),
|
|
ActorID: 99,
|
|
}
|
|
group := adjustmentTransferGroup{
|
|
LocationID: 16,
|
|
LocationName: "Jamali",
|
|
SourceWarehouseID: 46,
|
|
SourceWarehouseName: "Gudang Jamali 1",
|
|
FarmWarehouseID: 25,
|
|
FarmWarehouseName: "Gudang Farm Jamali",
|
|
Rows: []*adjustmentMigrationReportRow{
|
|
{LocationID: 16, LocationName: "Jamali", SourceWarehouseID: 46, SourceWarehouseName: "Gudang Jamali 1", FarmWarehouseID: uintPtr(25), FarmWarehouseName: strPtr("Gudang Farm Jamali"), ProductWarehouseID: 101, ProductID: 8, ProductName: "Telur Utuh", RemainingQty: 120, CurrentPWQty: 150, AdjustmentIDs: []uint{1}, Status: "eligible"},
|
|
{LocationID: 16, LocationName: "Jamali", SourceWarehouseID: 46, SourceWarehouseName: "Gudang Jamali 1", FarmWarehouseID: uintPtr(25), FarmWarehouseName: strPtr("Gudang Farm Jamali"), ProductWarehouseID: 102, ProductID: 9, ProductName: "Telur Putih", RemainingQty: 20, CurrentPWQty: 40, AdjustmentIDs: []uint{2}, Status: "eligible"},
|
|
},
|
|
}
|
|
validator := &fakeAdjustmentCandidateValidator{
|
|
byProduct: map[string]adjustmentCandidateValidation{
|
|
"Telur Utuh": {Status: "eligible", VerifiedQty: 120},
|
|
"Telur Putih": {Status: "skipped", Reason: "mixed_fifo_source_recording_eggs", VerifiedQty: 10},
|
|
},
|
|
}
|
|
executor := &fakeAdjustmentSystemTransferExecutor{
|
|
createResponses: []*entity.StockTransfer{
|
|
{Id: 1001, MovementNumber: "PND-LTI-1001"},
|
|
},
|
|
}
|
|
|
|
summary, err := executeAdjustmentApply(context.Background(), executor, validator, opts, []adjustmentTransferGroup{group})
|
|
if err != nil {
|
|
t.Fatalf("expected no fatal apply error, got %v", err)
|
|
}
|
|
if summary.GroupsApplied != 1 {
|
|
t.Fatalf("expected 1 applied group, got %+v", summary)
|
|
}
|
|
if summary.RowsApplied != 1 || summary.RowsFailed != 1 {
|
|
t.Fatalf("unexpected summary: %+v", summary)
|
|
}
|
|
if len(executor.createRequests) != 1 {
|
|
t.Fatalf("expected 1 create request, got %d", len(executor.createRequests))
|
|
}
|
|
if len(executor.createRequests[0].Products) != 1 || executor.createRequests[0].Products[0].ProductID != 8 {
|
|
t.Fatalf("expected only Telur Utuh to be transferred, got %+v", executor.createRequests[0].Products)
|
|
}
|
|
}
|
|
|
|
type fakeAdjustmentCandidateValidator struct {
|
|
byProduct map[string]adjustmentCandidateValidation
|
|
errByProduct map[string]error
|
|
}
|
|
|
|
func (f *fakeAdjustmentCandidateValidator) ValidateCandidate(ctx context.Context, row adjustmentLegacyEggRow) (adjustmentCandidateValidation, error) {
|
|
if err, ok := f.errByProduct[row.ProductName]; ok {
|
|
return adjustmentCandidateValidation{}, err
|
|
}
|
|
if result, ok := f.byProduct[row.ProductName]; ok {
|
|
return result, nil
|
|
}
|
|
return adjustmentCandidateValidation{Status: "eligible", VerifiedQty: row.RemainingQty}, nil
|
|
}
|
|
|
|
type fakeAdjustmentSystemTransferExecutor struct {
|
|
createRequests []*transferSvc.SystemTransferRequest
|
|
createResponses []*entity.StockTransfer
|
|
createErrors []error
|
|
deletedTransferIDs []uint
|
|
deleteErrors map[uint]error
|
|
}
|
|
|
|
func (f *fakeAdjustmentSystemTransferExecutor) CreateSystemTransfer(ctx context.Context, req *transferSvc.SystemTransferRequest) (*entity.StockTransfer, error) {
|
|
f.createRequests = append(f.createRequests, req)
|
|
idx := len(f.createRequests) - 1
|
|
if idx < len(f.createErrors) && f.createErrors[idx] != nil {
|
|
return nil, f.createErrors[idx]
|
|
}
|
|
if idx < len(f.createResponses) && f.createResponses[idx] != nil {
|
|
return f.createResponses[idx], nil
|
|
}
|
|
return &entity.StockTransfer{Id: uint64(1000 + idx), MovementNumber: "PND-LTI-DEFAULT"}, nil
|
|
}
|
|
|
|
func (f *fakeAdjustmentSystemTransferExecutor) DeleteSystemTransfer(ctx context.Context, id uint, actorID uint) error {
|
|
f.deletedTransferIDs = append(f.deletedTransferIDs, id)
|
|
if f.deleteErrors == nil {
|
|
return nil
|
|
}
|
|
return f.deleteErrors[id]
|
|
}
|
|
|
|
func uintPtr(v uint) *uint { return &v }
|
|
func strPtr(v string) *string { return &v }
|
|
|
|
var _ adjustmentCandidateValidator = (*fakeAdjustmentCandidateValidator)(nil)
|
|
var _ adjustmentSystemTransferExecutor = (*fakeAdjustmentSystemTransferExecutor)(nil)
|
|
var _ commonSvc.FifoStockV2Lane = fifoStockV2.LaneStockable
|