This commit is contained in:
2026-09-10 17:11:33 +08:00
parent 1ee32b7a6f
commit 2e6a18c58e
54 changed files with 2094 additions and 167 deletions
@@ -24,7 +24,7 @@ class PdaPalletOperationControllerContractTest {
.map(Method::getDeclaredAnnotations).flatMap(Arrays::stream)
.filter(PostMapping.class::isInstance).map(PostMapping.class::cast)
.flatMap(annotation -> Arrays.stream(annotation.value())).collect(Collectors.toSet());
assertEquals(Set.of("/pallets/resolve", "/assemble", "/disassemble", "/swap", "/unbind"), paths);
assertEquals(Set.of("/pallets/resolve", "/assemble", "/disassemble", "/relabel-disassemble", "/swap", "/unbind"), paths);
}
@Test void palletContentAnnotationUsesNativeGreaterThanOperator() throws Exception {
@@ -13,6 +13,11 @@ import com.baiyue.lastleg.palletops.mapper.PdaPalletOperationMapper;
import com.baiyue.lastleg.palletops.model.PalletInventoryRow;
import com.baiyue.lastleg.pda.security.PdaWarehouseIdentity;
import com.baiyue.lastleg.pda.warehouse.service.PdaWarehouseService;
import com.baiyue.lastleg.relabel.entity.RelabelApplication;
import com.baiyue.lastleg.relabel.entity.RelabelItem;
import com.baiyue.lastleg.relabel.mapper.RelabelApplicationMapper;
import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper;
import com.baiyue.lastleg.relabel.service.RelabelPalletMutationService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -43,6 +48,9 @@ class PdaPalletOperationServiceTest {
@Mock private InventoryLedgerMapper ledgers;
@Mock private PalletPhysicalGateway physical;
@Mock private PdaWarehouseService history;
@Mock private RelabelApplicationMapper relabelApplications;
@Mock private RelabelItemMapper relabelItems;
@Mock private RelabelPalletMutationService relabelPalletMutations;
private PdaPalletOperationService service;
@BeforeEach void setUp() {
@@ -53,6 +61,9 @@ class PdaPalletOperationServiceTest {
ReflectionTestUtils.setField(service, "ledgers", ledgers);
ReflectionTestUtils.setField(service, "physical", physical);
ReflectionTestUtils.setField(service, "history", history);
ReflectionTestUtils.setField(service, "relabelApplications", relabelApplications);
ReflectionTestUtils.setField(service, "relabelItems", relabelItems);
ReflectionTestUtils.setField(service, "relabelPalletMutations", relabelPalletMutations);
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
service.configureTransactions(new NoOpTransactionManager());
}
@@ -157,6 +168,83 @@ class PdaPalletOperationServiceTest {
verifyNoInteractions(physical);
}
@Test void relabelDisassembleMovesOnlySelectedHeldCarton() {
var sourcePallet = new PalletPhysicalGateway.PalletReference(20L, "SOURCE", 88L, 1020L,
"OCCUPIED", null, 1);
var targetPallet = new PalletPhysicalGateway.PalletReference(10L, "TARGET", 88L, 1010L,
"IDLE", null, 2);
when(physical.resolve("SOURCE")).thenReturn(sourcePallet);
when(physical.resolve("TARGET")).thenReturn(targetPallet);
when(physical.resolveLocation(eq(88L), anyLong())).thenAnswer(invocation ->
new PalletPhysicalGateway.LocationReference(invocation.getArgument(1), "LOC-" + invocation.getArgument(1),
"STORAGE", "ACTIVE"));
when(physical.apply(eq(88L), any(), anyString(), anyLong(), anyString(), any()))
.thenAnswer(invocation -> transition(invocation.getArgument(1), invocation.getArgument(2)));
RelabelApplication application = new RelabelApplication(); application.setId(600L);
application.setCustomerId("100"); application.setRelabelNo("RL-1"); application.setStatus("READY_FOR_OUTBOUND");
application.setOverseasWarehouseId("88"); application.setOutboundOrderNo("OUT-1");
when(relabelApplications.findAnyByRelabelNo("RL-1")).thenReturn(application);
when(relabelApplications.lockAnyByRelabelNo("RL-1")).thenReturn(application);
RelabelItem selected = relabelItem(701L, "CARTON-A", 100L);
when(relabelItems.selectList(any())).thenReturn(List.of(selected));
InventoryPosition source = position(100L, 20L, "SOURCE", 2L, 3L);
AtomicReference<InventoryPosition> target = new AtomicReference<>();
when(positions.selectById(anyLong())).thenAnswer(invocation -> {
long id = invocation.getArgument(0);
return id == 100L ? source : target.get();
});
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(mapper.content("88", 20L)).thenReturn(List.of(held));
when(mapper.content("88", 10L)).thenReturn(List.of());
when(mapper.lockPosition("88", 100L)).thenReturn(held);
when(mapper.palletPackages("88", 20L)).thenReturn(2L, 1L);
when(mapper.palletPackages("88", 10L)).thenReturn(0L, 1L);
InventoryBalance balance = new InventoryBalance(); balance.setId(200L); balance.setCustomerId("100");
balance.setOverseasWarehouseId("88"); when(balances.selectById(200L)).thenReturn(balance);
AtomicReference<PdaPalletOperation> operation = new AtomicReference<>();
doAnswer(invocation -> { operation.set(invocation.getArgument(0)); return 1; }).when(mapper)
.insert(any(PdaPalletOperation.class));
when(mapper.selectById(anyLong())).thenAnswer(invocation -> operation.get());
var result = service.relabelDisassemble(identity(), new PalletOperationRequests.RelabelDisassemble(
"relabel-move-1", "RL-1", new PalletOperationRequests.PalletExpectation("SOURCE", 1),
new PalletOperationRequests.PalletExpectation("TARGET", 2), List.of("CARTON-A"), "split"));
assertEquals("COMPLETED", result.status());
assertEquals(1L, source.getHoldPackages());
assertEquals(0L, source.getAvailablePackages());
assertNotNull(target.get()); assertEquals(1L, target.get().getHoldPackages());
verify(relabelPalletMutations).moveSelectedItems(eq(application), anyMap(), argThat(targets ->
targets.size() == 1 && targets.get(100L) == target.get()));
verify(balances, never()).updateById(any(InventoryBalance.class));
assertEquals("RELABEL_PALLET_DISASSEMBLE", operation.get().getOperationType());
}
@Test void relabelDisassembleReplayDoesNotRevalidateChangedInventoryOrPallets() throws Exception {
var request = new PalletOperationRequests.RelabelDisassemble("relabel-replay", "RL-1",
new PalletOperationRequests.PalletExpectation("SOURCE", 1),
new PalletOperationRequests.PalletExpectation("TARGET", 2), List.of("CARTON-A"), "split");
PdaPalletOperation existing = new PdaPalletOperation(); existing.setId(900L);
existing.setOperationType("RELABEL_PALLET_DISASSEMBLE"); existing.setStatus("COMPLETED");
existing.setRequestHash(ReflectionTestUtils.invokeMethod(service, "hash", request));
var stored = new com.baiyue.lastleg.palletops.dto.PalletOperationResponses.Operation(
900L, "RELABEL_PALLET_DISASSEMBLE", "relabel-replay", "COMPLETED", false,
LocalDateTime.now(), List.of());
existing.setResultJson(new ObjectMapper().findAndRegisterModules().writeValueAsString(stored));
when(mapper.findOperation("7", "relabel-replay")).thenReturn(existing);
var replay = service.relabelDisassemble(identity(), request);
assertTrue(replay.replayed());
verifyNoInteractions(physical, relabelApplications, relabelItems, positions, balances, ledgers,
relabelPalletMutations);
}
private static PdaWarehouseIdentity identity() { return new PdaWarehouseIdentity("7", "picker", 88L); }
private static PalletPhysicalGateway.PalletReference pallet(long id, String code, long warehouse, int version) {
return new PalletPhysicalGateway.PalletReference(id, code, warehouse, 1000L + id,
@@ -178,6 +266,21 @@ class PdaPalletOperationServiceTest {
value.setPickedPackages(0L); value.setReceivingPackages(0L); value.setVersion(version); return value;
}
private static InventoryPosition position(long id, long palletId, String palletNo, long hold, long version) {
InventoryPosition value = new InventoryPosition(); value.setId(id); value.setCustomerId("100");
value.setBalanceId(200L); value.setOverseasWarehouseId("88"); value.setPalletId(palletId);
value.setPalletNo(palletNo); value.setBatchNo("DEFAULT"); value.setWarehouseLocationId(1020L);
value.setWarehouseLocationCode("LOC-1020"); value.setAvailablePackages(0L); value.setHoldPackages(hold);
value.setAllocatedPackages(0L); value.setPickedPackages(0L); value.setReceivingPackages(0L);
value.setStatus("OCCUPIED"); value.setVersion(version); return value;
}
private static RelabelItem relabelItem(long id, String cartonMark, long positionId) {
RelabelItem value = new RelabelItem(); value.setId(id); value.setApplicationId(600L); value.setCustomerId("100");
value.setCartonMark(cartonMark); value.setNotifiedPackageCount(1); value.setSourceInventoryPositionId(positionId);
value.setDestinationFbaWarehouseCode("ONT8"); return value;
}
private static final class NoOpTransactionManager extends AbstractPlatformTransactionManager {
@Override protected Object doGetTransaction() { return new Object(); }
@Override protected void doBegin(Object transaction, TransactionDefinition definition) { }