This commit is contained in:
2026-08-26 17:09:02 +08:00
parent 6c4543c18c
commit 795e852944
16 changed files with 201 additions and 155 deletions
@@ -126,7 +126,7 @@ class ReceivingContainerContractTest {
assertTrue(yaml.contains(prefix + "/candidates:"));
assertTrue(yaml.contains(prefix + "/warehouse-profile:"));
assertTrue(yaml.contains(prefix + "/current/receipts/bulk:"));
assertTrue(yaml.contains(prefix + "/current/skus/resolve:"));
assertFalse(yaml.contains(prefix + "/current/skus/resolve:"));
assertTrue(yaml.contains(prefix + "/current/receipts/carton:"));
assertTrue(yaml.contains(prefix + "/current/complete:"));
assertTrue(yaml.contains(prefix + "/current/unload-photos/files/{fileId}/download:"));
@@ -134,8 +134,8 @@ class ReceivingContainerContractTest {
String receivingPaths = yaml.substring(yaml.indexOf(prefix), yaml.indexOf("/api/platform/v1/last-leg/orders:"));
List<String> successResponses = receivingPaths.lines().map(String::trim)
.filter(line -> line.startsWith("responses: { '200':")).toList();
assertEquals(17, successResponses.size());
assertEquals(13, successResponses.stream().filter(line -> line.contains("/schemas/Receiving")).count());
assertEquals(16, successResponses.size());
assertEquals(12, successResponses.stream().filter(line -> line.contains("/schemas/Receiving")).count());
assertEquals(4, successResponses.stream().filter(line -> line.contains("/schemas/Pda")).count());
assertTrue(receivingPaths.contains("返回 WAIT_SIGN、SIGNED 或 RECEIVING 当前柜"));
assertTrue(receivingPaths.contains("仅 PLATFORM WAREHOUSE_PICKER 可用"));
@@ -146,6 +146,10 @@ class ReceivingContainerContractTest {
assertTrue(yaml.contains("overseasWarehouseId: { type: string"));
assertTrue(yaml.contains("overseasWarehouseName: { type: string"));
assertFalse(receivingPaths.contains("photoFileIds"));
assertFalse(receivingPaths.contains("skuCode"));
assertFalse(yaml.contains("ReceivingSkuResolved"));
assertFalse(Arrays.stream(ReceivingContainerRequests.class.getDeclaredClasses())
.anyMatch(type -> "SkuResolve".equals(type.getSimpleName())));
}
private String resource(String name) throws IOException {
@@ -182,7 +182,7 @@ class ReceivingContainerServiceBehaviorTest {
void destinationModesAreHardRejectedBeforeWarehouseCalls() {
stubCurrent("OVERSEAS", "CONT-A");
assertThatThrownBy(() -> service.receiveBulk("7", "receiver",
new ReceivingContainerRequests.BulkReceive("IN-1", "SKU-1", "PAL-1", 1, "bulk-key")))
new ReceivingContainerRequests.BulkReceive("IN-1", "PAL-1", 1, "bulk-key")))
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
verifyNoInteractions(warehouse);
@@ -209,7 +209,7 @@ class ReceivingContainerServiceBehaviorTest {
stubSku();
when(warehouse.resolvePallet("PAL-1")).thenReturn(resource(101L, "FBA-1"));
assertThatThrownBy(() -> service.receiveBulk("7", "receiver",
new ReceivingContainerRequests.BulkReceive("IN-1", "SKU-1", "PAL-1", 1, "pallet-key")))
new ReceivingContainerRequests.BulkReceive("IN-1", "PAL-1", 1, "pallet-key")))
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
verify(workflow, never()).assignResolvedPallet(any(), any(), any(), any());
}
@@ -223,7 +223,7 @@ class ReceivingContainerServiceBehaviorTest {
doThrow(new ResponseStatusException(org.springframework.http.HttpStatus.CONFLICT, "over receipt"))
.when(workflow).validateBulkResolved(any(), any(), any(), anyInt());
assertThatThrownBy(() -> service.receiveBulk("7", "receiver",
new ReceivingContainerRequests.BulkReceive("IN-1", "SKU-1", "PAL-1", 2, "over-key")))
new ReceivingContainerRequests.BulkReceive("IN-1", "PAL-1", 2, "over-key")))
.isInstanceOf(ResponseStatusException.class);
verify(workflow, never()).assignResolvedPallet(any(), any(), any(), any());
@@ -291,7 +291,7 @@ class ReceivingContainerServiceBehaviorTest {
doThrow(failure).when(workflow).receiveBulkResolved(any(), any(), any(), any(), anyInt(), any(), any());
assertThatThrownBy(() -> service.receiveBulk("7", "receiver",
new ReceivingContainerRequests.BulkReceive("IN-1", "SKU-1", "PAL-1", 1, "compensate-key")))
new ReceivingContainerRequests.BulkReceive("IN-1", "PAL-1", 1, "compensate-key")))
.isSameAs(failure);
verify(workflow).compensateReservation("100", 55L, failure);
}
@@ -391,7 +391,7 @@ class ReceivingContainerServiceBehaviorTest {
}
@Test
void bulkRejectsSkuOverReceiptBeforeWarehouseLookup() {
void bulkRejectsAggregateSkuOverReceiptBeforeWarehouseLookup() {
stubCurrent("FBA", "CONT-A");
stubSku();
com.baiyue.lastleg.inboundplan.pda.entity.ReceivingSkuReceipt received =
@@ -400,11 +400,62 @@ class ReceivingContainerServiceBehaviorTest {
when(skuReceipts.listLineSku("100", 10L, "SKU-1")).thenReturn(List.of(received));
assertThatThrownBy(() -> service.receiveBulk("7", "receiver",
new ReceivingContainerRequests.BulkReceive("IN-1", "SKU-1", "PAL-1", 1, "sku-over")))
new ReceivingContainerRequests.BulkReceive("IN-1", "PAL-1", 1, "sku-over")))
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
verifyNoInteractions(warehouse);
}
@Test
void cartonResolutionSupportsFbaWithoutProjectingSku() {
stubCurrent("FBA", "CONT-A");
when(cartons.selectOne(any())).thenReturn(carton(1L, "FBA-BOX-1"));
var resolved = service.resolveCarton("7", new ReceivingContainerRequests.CartonResolve("FBA-BOX-1"));
assertThat(resolved.cartonMark()).isEqualTo("FBA-BOX-1");
assertThat(resolved.inboundCode()).isEqualTo("IN-1");
assertThat(resolved.destinationType()).isEqualTo("FBA");
}
@Test
void fbaBulkDistributesAcrossLinesAndFrozenSkusInStableLineOrder() {
InboundPlan plan = plan("CONT-A");
InboundPlanLine first = line("FBA");
InboundPlanLine second = line("FBA");
second.setId(20L); second.setOrderId(2L); second.setRequiredPackageCount(2);
LastlegOrder firstOrder = order("FBA");
LastlegOrder secondOrder = order("FBA"); secondOrder.setId(2L);
when(sessions.lockActiveByOperator("OPERATOR:7")).thenReturn(session("CONT-A", "SIGNED"));
when(plans.selectList(any())).thenReturn(List.of(plan));
when(lines.selectList(any())).thenReturn(List.of(second, first));
when(orders.selectById(1L)).thenReturn(firstOrder);
when(orders.selectById(2L)).thenReturn(secondOrder);
when(orderDetails.selectList(any())).thenReturn(List.of(detail("SKU-A", 1), detail("SKU-B", 1)),
List.of(detail("SKU-C", 2)));
when(warehouse.resolvePallet("PAL-1")).thenReturn(resource(100L, "FBA-1"));
when(pallets.selectList(any())).thenReturn(List.of(planPallet()));
when(workflow.receiveBulkResolved(any(), any(), any(), any(), anyInt(), any(), any()))
.thenAnswer(invocation -> invocation.getArgument(4));
when(sessions.updateById(any(ReceivingContainerSession.class))).thenReturn(1);
when(operations.insert(any(ReceivingContainerOperation.class))).thenReturn(1);
var result = service.receiveBulk("7", "receiver",
new ReceivingContainerRequests.BulkReceive("IN-1", "PAL-1", 3, "split-key"));
assertThat(result.acceptedPackages()).isEqualTo(3);
ArgumentCaptor<InboundPlanLine> capturedLines = ArgumentCaptor.forClass(InboundPlanLine.class);
ArgumentCaptor<Integer> capturedCounts = ArgumentCaptor.forClass(Integer.class);
verify(workflow, org.mockito.Mockito.times(3)).receiveBulkResolved(any(), any(), capturedLines.capture(), any(),
capturedCounts.capture(), any(), any());
assertThat(capturedLines.getAllValues()).containsExactly(first, first, second);
assertThat(capturedCounts.getAllValues()).containsExactly(1, 1, 1);
ArgumentCaptor<com.baiyue.lastleg.inboundplan.pda.entity.ReceivingSkuReceipt> receipts =
ArgumentCaptor.forClass(com.baiyue.lastleg.inboundplan.pda.entity.ReceivingSkuReceipt.class);
verify(skuReceipts, org.mockito.Mockito.times(3)).insert(receipts.capture());
assertThat(receipts.getAllValues()).extracting(value -> value.getSkuCode())
.containsExactly("SKU-A", "SKU-B", "SKU-C");
}
private void stubCurrent(String destinationType, String containerNo) {
InboundPlan plan = plan(containerNo);
InboundPlanLine line = line(destinationType);