fix(inventory): enforce warehouse transfer reservation ownership

This commit is contained in:
2026-10-05 07:24:53 +08:00
parent 548a133de6
commit 5b4d9624a9
14 changed files with 293 additions and 62 deletions
@@ -59,6 +59,7 @@ class PdaPalletOperationServiceTest {
assertThrows(ResponseStatusException.class, () -> ReflectionTestUtils.invokeMethod(service, "requireUnallocatedRelabel", app));
}
@Mock private PdaPalletOperationMapper mapper;
@Mock private com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper inventoryCommands;
@Mock private InventoryPositionMapper positions;
@Mock private InventoryBalanceMapper balances;
@Mock private InventoryLedgerMapper ledgers;
@@ -75,6 +76,7 @@ class PdaPalletOperationServiceTest {
@BeforeEach void setUp() {
service = new PdaPalletOperationService();
ReflectionTestUtils.setField(service, "mapper", mapper);
ReflectionTestUtils.setField(service, "inventoryCommands", inventoryCommands);
ReflectionTestUtils.setField(service, "positions", positions);
ReflectionTestUtils.setField(service, "balances", balances);
ReflectionTestUtils.setField(service, "ledgers", ledgers);
@@ -187,6 +189,35 @@ class PdaPalletOperationServiceTest {
assertTrue(result.printableHtml().contains("RL-1"));
}
@Test void reservedSourceIsRejectedBeforeAnyRemoteTransition() {
var source = new PalletPhysicalGateway.PalletReference(20L, "SOURCE", 88L, 1020L,
"OCCUPIED", null, 1);
var target = new PalletPhysicalGateway.PalletReference(10L, "TARGET", 88L, 1010L,
"IDLE", null, 2);
when(physical.resolve("SOURCE")).thenReturn(source);
when(physical.resolve("TARGET")).thenReturn(target);
when(physical.resolveLocation(88L, target.locationId())).thenReturn(
new PalletPhysicalGateway.LocationReference(target.locationId(), "LOC-1010", "STORAGE", "ACTIVE"));
var row = row(100L, 20L, 1L, 5L);
when(mapper.content("88", 20L)).thenReturn(List.of(row));
when(mapper.content("88", 10L)).thenReturn(List.of());
when(mapper.palletPackages("88", 20L)).thenReturn(5L);
when(mapper.palletPackages("88", 10L)).thenReturn(0L);
when(mapper.lockPosition("88", 100L)).thenReturn(row);
when(inventoryCommands.transferReservation(row.getCustomerId(), 100L)).thenReturn(
new com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper.TransferReservation(900L, 5));
AtomicReference<PdaPalletOperation> operation = new AtomicReference<>();
doAnswer(call -> { operation.set(call.getArgument(0)); return 1; }).when(mapper).insert(any(PdaPalletOperation.class));
when(mapper.selectById(anyLong())).thenAnswer(call -> operation.get());
var request = new PalletOperationRequests.Assemble("reserved", new PalletOperationRequests.PalletExpectation("TARGET", 2),
List.of(new PalletOperationRequests.PalletExpectation("SOURCE", 1)),
List.of(new PalletOperationRequests.MoveLine(100L, 5L, 1L)), "assemble");
assertThrows(ResponseStatusException.class, () -> service.assemble(identity(), request));
verify(physical, never()).apply(anyLong(), any(), anyString(), anyLong(), anyString(), any());
verify(positions, never()).updateById(any(InventoryPosition.class));
verifyNoInteractions(ledgers);
}
@Test void localConcurrencyFailureCompensatesEveryAppliedPhysicalTransitionInReverseSafeForm() {
var source = new PalletPhysicalGateway.PalletReference(20L, "SOURCE", 88L, 1020L,
"OCCUPIED", null, 1);
@@ -203,7 +234,7 @@ class PdaPalletOperationServiceTest {
when(mapper.content("88", 10L)).thenReturn(List.of());
when(mapper.palletPackages("88", 20L)).thenReturn(5L);
when(mapper.palletPackages("88", 10L)).thenReturn(0L);
when(mapper.lockPosition("88", 100L)).thenReturn(stale);
when(mapper.lockPosition("88", 100L)).thenReturn(planned, stale);
AtomicReference<PdaPalletOperation> operation = new AtomicReference<>();
doAnswer(invocation -> { operation.set(invocation.getArgument(0)); return 1; })
@@ -304,6 +335,9 @@ class PdaPalletOperationServiceTest {
doAnswer(invocation -> { target.set(invocation.getArgument(0)); return 1; }).when(positions)
.insert(any(InventoryPosition.class));
PalletInventoryRow held = row(100L, 20L, 3L, 0L); held.setPalletNo("SOURCE"); held.setHoldPackages(2L);
when(inventoryCommands.relabelTransfer(application.getCustomerId(), application.getId())).thenReturn(900L);
when(inventoryCommands.transferReservation(application.getCustomerId(), 100L)).thenReturn(
new com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper.TransferReservation(900L, 2));
when(mapper.content("88", 20L)).thenReturn(List.of(held));
when(mapper.content("88", 10L)).thenReturn(List.of());
when(mapper.lockPosition("88", 100L)).thenReturn(held);