fix(lastleg): align outbound pallet workflows

This commit is contained in:
2026-09-07 20:51:03 +08:00
parent 4895c4f738
commit 98dafc13ca
12 changed files with 176 additions and 12 deletions
@@ -166,6 +166,7 @@ class PlatformDispatchOutboundContractTest {
for (String action : Set.of(dispatchAction, transfer, outboundDetail, outboundCancel)) {
assertFalse(action.contains("name: customerId"));
}
assertTrue(outboundDetail.contains("pallets[].palletNo"));
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:edit"));
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:cancel"));
assertTrue(yaml.contains("platform:overseas-warehouse:dispatch:transfer-delivery"));
@@ -270,19 +270,26 @@ class DeliveryTransferServiceTest {
}
@Test
void outboundDetailDoesNotExposeExcludedPalletOrOutboundFulfillmentRows() {
void outboundDetailIncludesPalletCodesForDisplay() {
OutboundOrder outbound = outbound(301L, 101L, "DSP-101", "OUT-301", "transfer-101");
DeliveryOrder delivery = delivery(401L, outbound, "DEL-401");
OutboundPalletItem pallet = pallet(501L, "IP-ONT8-20260907-000001-P001", "ONT8", "Ontario", "IN-A", 12);
pallet.setSkuCode("SKU-1");
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
when(outboundPallets.findByOutbound("TENANT-A", 301L)).thenReturn(List.of(pallet));
DeliveryResponses.OutboundDetail detail = service.outboundDetail("OUT-301");
assertEquals("OUT-301", detail.outbound().outboundNo());
assertEquals("DEL-401", detail.delivery().deliveryNo());
assertTrue(java.util.Arrays.stream(DeliveryResponses.OutboundDetail.class.getRecordComponents())
.noneMatch(component -> "pallets".equals(component.getName())));
verify(outboundPallets, never()).selectPage(any(Page.class), any());
assertEquals(1, detail.pallets().size());
assertEquals("IP-ONT8-20260907-000001-P001", detail.pallets().get(0).palletNo());
assertEquals("IN-A", detail.pallets().get(0).inboundCode());
assertEquals("ONT8", detail.pallets().get(0).destinationWarehouseCode());
assertEquals("SKU-1", detail.pallets().get(0).skuCode());
assertEquals(12, detail.pallets().get(0).packageCount());
verify(outboundPallets).findByOutbound("TENANT-A", 301L);
}
@Test
@@ -426,6 +433,53 @@ class DeliveryTransferServiceTest {
verify(pdaTasks).listByWarehouse("WH-1", null);
}
@Test
void outboundNumberResolvesDispatchWorkflowInBoundWarehouse() {
OutboundOrder outbound = outbound(301L, 401L, "DSP-401", "OUT-301", "transfer-key");
outbound.setOverseasWarehouseId("WH-1");
outbound.setOutboundType("DISPATCH_DELIVERY");
DispatchOrder dispatch = new DispatchOrder();
dispatch.setId(401L); dispatch.setDispatchNo("DSP-401"); dispatch.setOverseasWarehouseId("WH-1");
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
when(dispatchOrders.findGlobalByDispatchNo("DSP-401")).thenReturn(dispatch);
DeliveryResponses.PdaTaskResolution result = service.resolvePdaTask("WH-1", "OUT-301");
assertEquals("DISPATCH", result.workflow());
assertEquals("DSP-401", result.dispatchNo());
assertEquals("OUT-301", result.outboundNo());
}
@Test
void outboundNumberResolvesOneClickTaskInBoundWarehouse() {
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");
outbound.setOverseasWarehouseId("WH-1");
outbound.setOutboundType("ONE_CLICK_DROPSHIP");
OutboundPdaTask task = new OutboundPdaTask();
task.setCustomerId("TENANT-A"); task.setOutboundOrderId(301L); task.setTaskNo("PDA-701");
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
when(pdaTasks.findByOutboundAndWarehouse("TENANT-A", 301L, "WH-1")).thenReturn(task);
DeliveryResponses.PdaTaskResolution result = service.resolvePdaTask("WH-1", "OUT-301");
assertEquals("OUTBOUND", result.workflow());
assertEquals("PDA-701", result.taskNo());
assertEquals("OUT-301", result.outboundNo());
}
@Test
void pdaTaskResolutionDoesNotLeakAnotherWarehouse() {
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");
outbound.setOverseasWarehouseId("WH-2");
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
() -> service.resolvePdaTask("WH-1", "OUT-301"));
assertEquals(404, exception.getStatusCode().value());
verify(pdaTasks, never()).findByOutboundAndWarehouse(any(), any(), any());
}
@Test
void outboundPdaDetailReturnsEveryPickablePositionWithStableBusinessCodes() {
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");
@@ -167,6 +167,41 @@ class InboundPdaWorkflowServiceTest {
targetPallet.getWarehouseLocationId(), "assign-wrong-warehouse")));
}
@Test
void resolvedAssignmentReplacesPlanPalletNumberWithPhysicalWarehouseCode() {
targetPallet.setPalletNo("IP-ACY2-20260907-597057-P001");
targetPallet.setWarehousePalletId(null);
targetPallet.setWarehouseLocationId(null);
targetPallet.setWarehouseResourceLockId(null);
targetPallet.setAssignmentStatus("PENDING_ASSIGNMENT");
WarehouseResourceGateway.WarehousePalletReference resource =
new WarehouseResourceGateway.WarehousePalletReference(188L, "A002", 88L, 288L,
"IDLE", "ACY2", 1);
when(warehouse.reserve("100", 88L, 188L, 288L, "INBOUND_CONTAINER_DESTINATION_PALLET",
"CONT-1:WH-A", "INBOUND_CONTAINER_DESTINATION_PALLET:CONT-1:WH-A:188")).thenReturn(999L);
service.assignResolvedPallet("100", plan, targetPallet, resource);
assertEquals("A002", targetPallet.getPalletNo());
assertEquals(188L, targetPallet.getWarehousePalletId());
assertEquals(288L, targetPallet.getWarehouseLocationId());
verify(repository).update(targetPallet);
}
@Test
void resolvedAssignmentRejectsPhysicalPalletWithoutBusinessCodeBeforeReservation() {
targetPallet.setWarehouseResourceLockId(null);
WarehouseResourceGateway.WarehousePalletReference resource =
new WarehouseResourceGateway.WarehousePalletReference(188L, " ", 88L, 288L,
"IDLE", "ACY2", 1);
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
() -> service.assignResolvedPallet("100", plan, targetPallet, resource));
assertEquals(409, exception.getStatusCode().value());
verify(warehouse, never()).reserve(any(), anyLong(), anyLong(), anyLong(), any(), any(), any());
}
@Test
void cartonScanBindsOnlyExpectedCartonAndRemoveMakesItScannableAgain() {
InboundPlanCarton carton = carton("BOX-001", "UNASSIGNED", targetLine.getId(), "WH-A");
@@ -87,10 +87,12 @@ class InboundStockReceiptServiceTest {
assertEquals("Customer", request.getValue().customerName());
assertEquals("900", request.getValue().overseasWarehouseId());
assertEquals("Los Angeles", request.getValue().overseasWarehouseName());
assertEquals("PLT-21", request.getValue().palletNo());
ArgumentCaptor<InboundStockReceipt> receipt = ArgumentCaptor.forClass(InboundStockReceipt.class);
verify(receipts).insert(receipt.capture());
assertEquals(5L, receipt.getValue().getReceivedPackages());
assertEquals(10L, receipt.getValue().getInventoryPositionId());
assertEquals("PLT-21", receipt.getValue().getPalletNo());
assertEquals("INBOUND_COMPLETED", fixture.order.getCurrentStatus());
assertEquals("INBOUND_PLAN:COMPLETED", fixture.order.getSubOrderStatus());
ArgumentCaptor<LastlegOrderStatusHistory> history = ArgumentCaptor.forClass(LastlegOrderStatusHistory.class);
@@ -74,13 +74,15 @@ class PdaOperationsOpenApiContractTest {
"/api/pda/v1/last-leg/dispatches/{dispatchNo}/complete:",
"/api/pda/v1/last-leg/dispatches/{dispatchNo}/depart:",
"/api/pda/v1/last-leg/outbound-tasks:",
"/api/pda/v1/last-leg/outbound-tasks/resolve/{businessNo}:",
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}:",
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}/pick:",
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}/complete:");
for (String path : paths) assertTrue(yaml.contains(path), path);
assertEquals(32, paths.size());
assertEquals(33, paths.size());
assertTrue(yaml.contains("enum: [LOCATION, PALLET]"));
assertTrue(yaml.contains("enum: [APPROVE, REWORK]"));
assertTrue(yaml.contains("Warehouse 权威实物托盘码"));
assertTrue(yaml.contains("enum: [DAMAGED, MISSING_LABEL, LABEL_MISMATCH, UNREADABLE, OTHER]"));
assertEquals(3, occurrences(yaml, "body.code=A0404"));
assertFalse(yaml.contains("/api/pda/v1/last-leg/delivery"));