优化
This commit is contained in:
+3
-1
@@ -24,8 +24,10 @@ class TailPlanCommandOpenApiContractTest {
|
||||
int manualSchemaEnd = api.indexOf("\n PageMetadata:", manualSchemaStart);
|
||||
assertTrue(manualSchemaStart >= 0 && manualSchemaEnd > manualSchemaStart);
|
||||
assertFalse(api.substring(manualSchemaStart, manualSchemaEnd).contains("inboundCode"));
|
||||
assertFalse(api.substring(manualSchemaStart, manualSchemaEnd).contains("plannedArrivalDate"));
|
||||
assertFalse(java.util.Arrays.stream(ManualInboundPlanRequest.class.getRecordComponents())
|
||||
.anyMatch(component -> "inboundCode".equals(component.getName())));
|
||||
.anyMatch(component -> java.util.Set.of("inboundCode", "plannedArrivalDate")
|
||||
.contains(component.getName())));
|
||||
assertTrue(api.contains("/api/platform/v1/last-leg/orders/{id}/hold:"));
|
||||
assertFalse(api.contains("/api/platform/v1/last-leg/orders/{id}/detained:"));
|
||||
assertTrue(api.contains("/api/platform/v1/last-leg/orders/{id}/manual-nodes:"));
|
||||
|
||||
+1
-2
@@ -20,7 +20,6 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -106,6 +105,6 @@ class InboundPlanControllerTest {
|
||||
|
||||
private static ManualInboundPlanRequest manualRequest() {
|
||||
return new ManualInboundPlanRequest("193000000000200003", 9223372036854775807L, 0,
|
||||
LocalDate.of(2026, 9, 8), 2, "STANDARD", false, true, true, "加急入仓");
|
||||
2, "STANDARD", false, true, true, "加急入仓");
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -3,6 +3,7 @@ package com.baiyue.lastleg.inboundplan.pda.controller;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.lastleg.inboundplan.pda.dto.ReceivingContainerRequests;
|
||||
import com.baiyue.lastleg.inboundplan.pda.dto.ReceivingContainerResponses;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.DomesticLoadingEvidenceGateway;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.UnloadPhotoFileGateway;
|
||||
import com.baiyue.lastleg.inboundplan.pda.service.ReceivingContainerService;
|
||||
import com.baiyue.lastleg.pda.security.PdaWarehouseIdentity;
|
||||
@@ -90,6 +91,21 @@ class ReceivingContainerControllerTest {
|
||||
verify(service).downloadCurrentPhoto("42", "9999");
|
||||
}
|
||||
|
||||
@Test
|
||||
void downloadDomesticLoadingPhotoDelegatesWithoutExposingTheSourceFileId() {
|
||||
when(service.downloadDomesticLoadingPhoto("42", "PHOTO_STEP_1")).thenReturn(
|
||||
new DomesticLoadingEvidenceGateway.DownloadedFile(
|
||||
"domestic".getBytes(), "empty-container.jpg", MediaType.IMAGE_JPEG_VALUE));
|
||||
|
||||
ResponseEntity<byte[]> response = controller.downloadDomesticLoadingPhoto(picker(), "PHOTO_STEP_1");
|
||||
|
||||
assertThat(response.getStatusCodeValue()).isEqualTo(200);
|
||||
assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.IMAGE_JPEG);
|
||||
assertThat(response.getHeaders().getCacheControl()).isEqualTo("no-store");
|
||||
assertThat(response.getBody()).containsExactly("domestic".getBytes());
|
||||
verify(service).downloadDomesticLoadingPhoto("42", "PHOTO_STEP_1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pickerAuthorityWithoutWarehouseBindingIsForbidden() {
|
||||
AuthenticatedUser unbound = new AuthenticatedUser(42L, 0L, 0L, 8L, "receiver", "PLATFORM", "PDA",
|
||||
|
||||
+26
@@ -18,11 +18,13 @@ import com.baiyue.lastleg.inboundplan.pda.entity.ReceivingContainerOperation;
|
||||
import com.baiyue.lastleg.inboundplan.pda.entity.ReceivingContainerSession;
|
||||
import com.baiyue.lastleg.inboundplan.pda.entity.ReceivingFbaPalletCount;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.UnloadPhotoFileGateway;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.DomesticLoadingEvidenceGateway;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
|
||||
import com.baiyue.lastleg.inboundplan.pda.mapper.ReceivingContainerOperationMapper;
|
||||
import com.baiyue.lastleg.inboundplan.pda.mapper.ReceivingContainerSessionMapper;
|
||||
import com.baiyue.lastleg.inboundplan.pda.mapper.ReceivingFbaPalletCountMapper;
|
||||
import com.baiyue.lastleg.inboundplan.pda.mapper.ReceivingSkuReceiptMapper;
|
||||
import com.baiyue.lastleg.inboundplan.pda.mapper.ContainerLoadingEvidenceMapper;
|
||||
import com.baiyue.lastleg.dropship.mapper.DropshipCartonMapper;
|
||||
import com.baiyue.lastleg.dropship.mapper.DropshipOrderProfileMapper;
|
||||
import com.baiyue.lastleg.dropship.entity.DropshipCarton;
|
||||
@@ -71,6 +73,8 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
@Mock DropshipOrderProfileMapper dropshipProfiles;
|
||||
@Mock WarehouseResourceGateway warehouse;
|
||||
@Mock UnloadPhotoFileGateway unloadPhotos;
|
||||
@Mock DomesticLoadingEvidenceGateway domesticEvidenceFiles;
|
||||
@Mock ContainerLoadingEvidenceMapper domesticEvidence;
|
||||
@Mock InboundPdaWorkflowService workflow;
|
||||
private ReceivingContainerService service;
|
||||
|
||||
@@ -93,11 +97,14 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
ReflectionTestUtils.setField(service, "dropshipProfiles", dropshipProfiles);
|
||||
ReflectionTestUtils.setField(service, "warehouse", warehouse);
|
||||
ReflectionTestUtils.setField(service, "unloadPhotos", unloadPhotos);
|
||||
ReflectionTestUtils.setField(service, "domesticEvidenceFiles", domesticEvidenceFiles);
|
||||
ReflectionTestUtils.setField(service, "domesticEvidence", domesticEvidence);
|
||||
ReflectionTestUtils.setField(service, "workflow", workflow);
|
||||
ReflectionTestUtils.setField(service, "sourceObjectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
service.initializeObjectMapper();
|
||||
org.mockito.Mockito.lenient().when(shippingGates.receivingAvailable(any(), any())).thenReturn(1);
|
||||
org.mockito.Mockito.lenient().when(pallets.selectCount(any())).thenReturn(1L);
|
||||
org.mockito.Mockito.lenient().when(domesticEvidence.listByContainer(any())).thenReturn(List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -904,6 +911,25 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
verifyNoInteractions(warehouse);
|
||||
}
|
||||
|
||||
@Test
|
||||
void domesticLoadingPhotoDownloadIsScopedToTheCurrentPhysicalContainer() {
|
||||
ReceivingContainerSession current = session("CONT-A", "SIGNED");
|
||||
current.setSourceContainerBookingId("91");
|
||||
when(sessions.findActiveByOperator("OPERATOR:7")).thenReturn(current);
|
||||
var evidence = new com.baiyue.lastleg.inboundplan.pda.entity.ContainerLoadingEvidence();
|
||||
evidence.setSourceContainerBookingId("91"); evidence.setEvidenceType("SEAL");
|
||||
evidence.setSourceFileId(501L); evidence.setFileName("seal.jpg");
|
||||
when(domesticEvidence.find("91", "SEAL")).thenReturn(evidence);
|
||||
var downloaded = new DomesticLoadingEvidenceGateway.DownloadedFile(
|
||||
new byte[]{1, 2}, "seal.jpg", "image/jpeg");
|
||||
when(domesticEvidenceFiles.download(91L, 501L)).thenReturn(downloaded);
|
||||
|
||||
assertThat(service.downloadDomesticLoadingPhoto("7", "SEAL")).isSameAs(downloaded);
|
||||
verify(domesticEvidenceFiles).download(91L, 501L);
|
||||
assertThatThrownBy(() -> service.downloadDomesticLoadingPhoto("7", "UNKNOWN"))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("400");
|
||||
}
|
||||
|
||||
private void stubCurrent(String destinationType, String containerNo) {
|
||||
InboundPlan plan = plan(containerNo);
|
||||
InboundPlanLine line = line(destinationType);
|
||||
|
||||
+1
-1
@@ -409,7 +409,7 @@ class ContainerInboundPlanConversionCoordinatorTest {
|
||||
}
|
||||
|
||||
private ManualInboundPlanRequest manualRequest() {
|
||||
return new ManualInboundPlanRequest("100", 11L, 0, LocalDate.of(2026, 9, 10),
|
||||
return new ManualInboundPlanRequest("100", 11L, 0,
|
||||
1, "WOOD", false, false, false, "自动生成");
|
||||
}
|
||||
|
||||
|
||||
+33
-7
@@ -93,6 +93,10 @@ class InboundPlanServiceTest {
|
||||
org.mockito.Mockito.lenient().when(orderMapper.lockContainerWarehouseFacts(any(), any())).thenReturn(List.of(
|
||||
new InboundPlanOrderMapper.ContainerWarehouseRef(
|
||||
"100", 2L, "BOOKING-1", "193000000000200003")));
|
||||
org.mockito.Mockito.lenient().when(orderMapper.findSourceContainerBookingId(any(), anyLong()))
|
||||
.thenReturn("BOOKING-1");
|
||||
org.mockito.Mockito.lenient().when(orderMapper.containerEstimatedWarehouseTimes(any()))
|
||||
.thenReturn(List.of(LocalDateTime.of(2026, 9, 15, 8, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -108,6 +112,7 @@ class InboundPlanServiceTest {
|
||||
ArgumentCaptor<InboundPlan> plan = ArgumentCaptor.forClass(InboundPlan.class);
|
||||
verify(planMapper).insert(plan.capture());
|
||||
assertEquals(2L, plan.getValue().getSourceOrderId());
|
||||
assertEquals(LocalDate.of(2026, 9, 15), plan.getValue().getPlannedArrivalDate());
|
||||
assertEquals(1, plan.getValue().getLineCount());
|
||||
assertEquals("PENDING_ASSIGNMENT", plan.getValue().getStatus());
|
||||
assertEquals(1, plan.getValue().getPalletCount());
|
||||
@@ -149,7 +154,7 @@ class InboundPlanServiceTest {
|
||||
when(orderMapper.markPlannedManual(eq("100"), eq(2L), any(), eq(2), eq(true), eq(0), any()))
|
||||
.thenReturn(1);
|
||||
ManualInboundPlanRequest request = new ManualInboundPlanRequest("100", 2L, 0,
|
||||
LocalDate.of(2026, 9, 15), 2, "STANDARD", true, true, true, "优先收货");
|
||||
2, "STANDARD", true, true, true, "优先收货");
|
||||
|
||||
var result = service.convertManual(request, "manual-2", "900");
|
||||
|
||||
@@ -167,6 +172,27 @@ class InboundPlanServiceTest {
|
||||
verify(orderMapper).lockContainerWarehouseFacts("100", 2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void manualPlanRejectsMissingOrInconsistentContainerArrivalTime() {
|
||||
ManualInboundPlanRequest request = new ManualInboundPlanRequest("100", 2L, 0,
|
||||
2, "STANDARD", true, true, true, "优先收货");
|
||||
when(orderMapper.containerEstimatedWarehouseTimes("BOOKING-1")).thenReturn(List.of());
|
||||
|
||||
ResponseStatusException missing = assertThrows(ResponseStatusException.class,
|
||||
() -> service.convertManual(request, "manual-missing-arrival", "900"));
|
||||
assertEquals(409, missing.getStatusCode().value());
|
||||
assertEquals("来源柜预计到仓时间缺失,请先补齐柜级时间", missing.getReason());
|
||||
|
||||
when(orderMapper.containerEstimatedWarehouseTimes("BOOKING-1")).thenReturn(List.of(
|
||||
LocalDateTime.of(2026, 9, 15, 8, 0), LocalDateTime.of(2026, 9, 16, 8, 0)));
|
||||
ResponseStatusException inconsistent = assertThrows(ResponseStatusException.class,
|
||||
() -> service.convertManual(request, "manual-inconsistent-arrival", "900"));
|
||||
assertEquals(409, inconsistent.getStatusCode().value());
|
||||
assertEquals("来源柜预计到仓时间不一致,请先修正柜级时间", inconsistent.getReason());
|
||||
verify(orderMapper, never()).lockOrder(any(), any());
|
||||
verify(planMapper, never()).insert(any(InboundPlan.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void manualPlanMayExceedButCannotUndercutPreviewPalletCount() {
|
||||
SourceOrderSnapshot source = new SourceOrderSnapshot(2L, "100", "MSCU6639870", "RK-2",
|
||||
@@ -175,7 +201,7 @@ class InboundPlanServiceTest {
|
||||
null, null, "Amazon ACY2", 0, 0, false, 0);
|
||||
when(orderMapper.lockOrder("100", 2L)).thenReturn(List.of(source));
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class, () -> service.convertManual(
|
||||
new ManualInboundPlanRequest("100", 2L, 0, LocalDate.of(2026, 9, 15),
|
||||
new ManualInboundPlanRequest("100", 2L, 0,
|
||||
2, "WOOD", false, false, false, null), "manual-below-preview", "900"));
|
||||
|
||||
assertEquals(400, error.getStatusCode().value());
|
||||
@@ -192,7 +218,7 @@ class InboundPlanServiceTest {
|
||||
new DropshipPalletSnapshot("DP-260914-0001", 10L, List.of())));
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class, () -> service.convertManual(
|
||||
new ManualInboundPlanRequest("100", 2L, 0, LocalDate.of(2026, 9, 15),
|
||||
new ManualInboundPlanRequest("100", 2L, 0,
|
||||
2, "WOOD", false, false, false, null), "dropship-pallet-mismatch", "900"));
|
||||
|
||||
assertEquals(400, error.getStatusCode().value());
|
||||
@@ -212,7 +238,7 @@ class InboundPlanServiceTest {
|
||||
.thenReturn(1);
|
||||
|
||||
var result = service.convertManual(new ManualInboundPlanRequest("100", 2L, 0,
|
||||
LocalDate.of(2026, 9, 15), 1, "WOOD", false, false, false, null),
|
||||
1, "WOOD", false, false, false, null),
|
||||
"dropship-exact-pallet-count", "900");
|
||||
|
||||
assertEquals(1, result.palletCount());
|
||||
@@ -266,7 +292,7 @@ class InboundPlanServiceTest {
|
||||
.thenReturn(1);
|
||||
|
||||
var result = service.convertManual(new ManualInboundPlanRequest("100", 2L, 0,
|
||||
LocalDate.of(2026, 9, 15), 3, "STANDARD", true, false, false, null),
|
||||
3, "STANDARD", true, false, false, null),
|
||||
"manual-id-only", "900");
|
||||
|
||||
assertEquals("US-LAX-01", result.overseasWarehouseCode());
|
||||
@@ -282,7 +308,7 @@ class InboundPlanServiceTest {
|
||||
new InboundPlanOrderMapper.ContainerWarehouseRef("200", 3L, "BOOKING-1", "193000000000200004")));
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class, () -> service.convertManual(
|
||||
new ManualInboundPlanRequest("100", 2L, 0, LocalDate.of(2026, 9, 15),
|
||||
new ManualInboundPlanRequest("100", 2L, 0,
|
||||
1, "STANDARD", false, false, false, null), "mixed-warehouse", "900"));
|
||||
|
||||
assertEquals(409, error.getStatusCode().value());
|
||||
@@ -300,7 +326,7 @@ class InboundPlanServiceTest {
|
||||
when(orderMapper.lockOrder("100", 2L)).thenReturn(List.of(held));
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class, () -> service.convertManual(
|
||||
new ManualInboundPlanRequest("100", 2L, 5, LocalDate.of(2026, 9, 15),
|
||||
new ManualInboundPlanRequest("100", 2L, 5,
|
||||
1, "STANDARD", false, false, false, null),
|
||||
"manual-held", "900"));
|
||||
|
||||
|
||||
+22
-2
@@ -3,8 +3,8 @@ import com.baiyue.common.event.*; import com.baiyue.lastleg.entity.*; import com
|
||||
@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
|
||||
class LoadingCompletedConsumerTest {
|
||||
private final Map<String,String> frozenContainerNos=new HashMap<>();
|
||||
@Mock LastlegEventInboxMapper inbox; @Mock LastlegOrderMapper orders; @Mock LastlegInboundOrderMapper inboundOrders; @Mock LastlegOrderDetailMapper details; @Mock LastlegOrderStatusHistoryMapper histories; @Mock InboundPlanCartonMapper cartons; @Mock DropshipOrderService dropshipOrders; @Mock InventoryCommandService inventory; @Mock com.baiyue.lastleg.shipping.mapper.ContainerShippingGateMapper shippingGates; @Mock LastlegOrderNoDailySequenceMapper sequences; ObjectMapper json=new ObjectMapper().findAndRegisterModules(); LoadingCompletedConsumer service;
|
||||
@BeforeEach void setup(){ service=new LoadingCompletedConsumer(); ReflectionTestUtils.setField(service,"inboxMapper",inbox); ReflectionTestUtils.setField(service,"orderMapper",orders); ReflectionTestUtils.setField(service,"inboundOrderMapper",inboundOrders); ReflectionTestUtils.setField(service,"detailMapper",details); ReflectionTestUtils.setField(service,"historyMapper",histories); ReflectionTestUtils.setField(service,"cartonMapper",cartons); ReflectionTestUtils.setField(service,"dropshipOrders",dropshipOrders); ReflectionTestUtils.setField(service,"inventory",inventory); ReflectionTestUtils.setField(service,"shippingGates",shippingGates); ReflectionTestUtils.setField(service,"objectMapper",json); LastlegOrderNoGenerator generator=new LastlegOrderNoGenerator(); ReflectionTestUtils.setField(generator,"sequenceMapper",sequences); ReflectionTestUtils.setField(service,"orderNoGenerator",generator); lenient().when(sequences.currentValue()).thenReturn(1L); lenient().doAnswer(i->{ LastlegOrder o=i.getArgument(0); o.setId(Math.abs(o.getSourceInboundId().hashCode())+1L); return 1; }).when(orders).insert(any(LastlegOrder.class)); lenient().when(orders.lockContainerNumbers(anyString())).thenReturn(List.of()); lenient().when(shippingGates.initialize(anyString(),anyString(),anyString(),anyLong(),anyLong())).thenAnswer(i->{ String key=i.getArgument(0); String value=i.getArgument(2); return frozenContainerNos.putIfAbsent(key,value)==null?1:0; }); lenient().when(shippingGates.lock(anyString())).thenAnswer(i->{ String key=i.getArgument(0); String value=frozenContainerNos.get(key); if(value==null)return null; var gate=new com.baiyue.lastleg.shipping.entity.ContainerShippingGate(); gate.setContainerBookingId(key); gate.setContainerNo(value); return gate; }); }
|
||||
@Mock LastlegEventInboxMapper inbox; @Mock LastlegOrderMapper orders; @Mock LastlegInboundOrderMapper inboundOrders; @Mock LastlegOrderDetailMapper details; @Mock LastlegOrderStatusHistoryMapper histories; @Mock InboundPlanCartonMapper cartons; @Mock DropshipOrderService dropshipOrders; @Mock InventoryCommandService inventory; @Mock com.baiyue.lastleg.shipping.mapper.ContainerShippingGateMapper shippingGates; @Mock com.baiyue.lastleg.inboundplan.pda.mapper.ContainerLoadingEvidenceMapper loadingEvidence; @Mock LastlegOrderNoDailySequenceMapper sequences; ObjectMapper json=new ObjectMapper().findAndRegisterModules(); LoadingCompletedConsumer service;
|
||||
@BeforeEach void setup(){ service=new LoadingCompletedConsumer(); ReflectionTestUtils.setField(service,"inboxMapper",inbox); ReflectionTestUtils.setField(service,"orderMapper",orders); ReflectionTestUtils.setField(service,"inboundOrderMapper",inboundOrders); ReflectionTestUtils.setField(service,"detailMapper",details); ReflectionTestUtils.setField(service,"historyMapper",histories); ReflectionTestUtils.setField(service,"cartonMapper",cartons); ReflectionTestUtils.setField(service,"dropshipOrders",dropshipOrders); ReflectionTestUtils.setField(service,"inventory",inventory); ReflectionTestUtils.setField(service,"shippingGates",shippingGates); ReflectionTestUtils.setField(service,"loadingEvidence",loadingEvidence); ReflectionTestUtils.setField(service,"objectMapper",json); LastlegOrderNoGenerator generator=new LastlegOrderNoGenerator(); ReflectionTestUtils.setField(generator,"sequenceMapper",sequences); ReflectionTestUtils.setField(service,"orderNoGenerator",generator); lenient().when(sequences.currentValue()).thenReturn(1L); lenient().doAnswer(i->{ LastlegOrder o=i.getArgument(0); o.setId(Math.abs(o.getSourceInboundId().hashCode())+1L); return 1; }).when(orders).insert(any(LastlegOrder.class)); lenient().when(orders.lockContainerNumbers(anyString())).thenReturn(List.of()); lenient().when(shippingGates.initialize(anyString(),anyString(),anyString(),anyLong(),anyLong())).thenAnswer(i->{ String key=i.getArgument(0); String value=i.getArgument(2); return frozenContainerNos.putIfAbsent(key,value)==null?1:0; }); lenient().when(shippingGates.lock(anyString())).thenAnswer(i->{ String key=i.getArgument(0); String value=frozenContainerNos.get(key); if(value==null)return null; var gate=new com.baiyue.lastleg.shipping.entity.ContainerShippingGate(); gate.setContainerBookingId(key); gate.setContainerNo(value); return gate; }); }
|
||||
@Test void createsPendingOrdersAndInTransitSnapshotsWithoutInboundPlans(){ EventEnvelope event=event(); assertEquals(2,service.consume(event)); assertThrows(NoSuchFieldException.class,()->LoadingCompletedConsumer.class.getDeclaredField("inboundPlans")); verify(orders,times(2)).insert(any(LastlegOrder.class)); ArgumentCaptor<InventoryCommandRequests.InTransit> inTransit=ArgumentCaptor.forClass(InventoryCommandRequests.InTransit.class); verify(inventory,times(2)).registerInTransit(eq("100"),eq("SYSTEM"),inTransit.capture()); assertEquals(Set.of("RK1","RK2"),inTransit.getAllValues().stream().map(InventoryCommandRequests.InTransit::inboundOrderNo).collect(java.util.stream.Collectors.toSet())); verify(details,times(2)).insert(any(LastlegOrderDetail.class)); verify(histories,times(2)).insert(any(LastlegOrderStatusHistory.class)); ArgumentCaptor<LastlegOrder> c=ArgumentCaptor.forClass(LastlegOrder.class); verify(orders,times(2)).insert(c.capture()); assertEquals(Set.of("in-1","in-2"),c.getAllValues().stream().map(LastlegOrder::getSourceInboundId).collect(java.util.stream.Collectors.toSet())); assertTrue(c.getAllValues().stream().allMatch(o->"PENDING_ASSIGNMENT".equals(o.getCurrentStatus()))); assertTrue(c.getAllValues().stream().allMatch(o->o.getInboundOrderNo()==null)); assertTrue(c.getAllValues().stream().allMatch(o->o.getId()!=null)); assertTrue(c.getAllValues().stream().allMatch(o->"MSCU6639870".equals(o.getContainerNo()))); assertTrue(c.getAllValues().stream().allMatch(o->"FBA".equals(o.getDestinationType()))); assertTrue(c.getAllValues().stream().allMatch(o->"Customer 100".equals(o.getCustomerName()))); assertTrue(c.getAllValues().stream().allMatch(o->"ow-1".equals(o.getSourceWarehouseId())&&"193000000000200003".equals(o.getOverseasWarehouseId())&&"US-LAX-01".equals(o.getOverseasWarehouseCode())&&"洛杉矶仓".equals(o.getOverseasWarehouseName())&&Integer.valueOf(0).equals(o.getReceivingWarehouseVersion()))); ArgumentCaptor<LastlegInboundOrder> snapshots=ArgumentCaptor.forClass(LastlegInboundOrder.class); verify(inboundOrders,times(2)).insert(snapshots.capture()); assertTrue(snapshots.getAllValues().stream().allMatch(o->"100".equals(o.getCustomerId())&&"FBA".equals(o.getDestinationType()))); assertEquals(1,c.getAllValues().stream().map(LastlegOrder::getOrderNo).distinct().count()); assertTrue(c.getAllValues().get(0).getOrderNo().matches("FL-WH-[0-9]{8}-[0-9]{6}")); }
|
||||
@Test void oneContainerWith15InboundRowsSharesOneStableContainerOrderNumber(){ EventEnvelope source=event(); com.fasterxml.jackson.databind.node.ObjectNode node=source.payload().deepCopy(); com.fasterxml.jackson.databind.node.ArrayNode rows=json.createArrayNode(); for(int index=1;index<=15;index++){ com.fasterxml.jackson.databind.node.ObjectNode row=node.path("orders").get(0).deepCopy(); row.put("sourceInboundId","in-"+index).put("rootInboundId","root-"+index).put("sourceOrderId","order-"+index).put("sourceOrderNo","FL-"+index).put("rootOrderId","root-order-"+index).put("inboundCode","RK-"+index); rows.add(row); } node.set("orders",rows); String hash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(node)); EventEnvelope fifteen=new EventEnvelope("123e4567-e89b-12d3-a456-426614174015",source.eventType(),source.eventVersion(),source.occurredAt(),source.sourceRegion(),source.targetRegion(),source.tenantId(),source.aggregateType(),source.aggregateId(),source.aggregateVersion(),source.handoverVersion(),source.traceId(),source.correlationId(),source.causationId(),node,hash); assertEquals(15,service.consume(fifteen)); ArgumentCaptor<LastlegOrder> captured=ArgumentCaptor.forClass(LastlegOrder.class); verify(orders,times(15)).insert(captured.capture()); assertEquals(1,captured.getAllValues().stream().map(LastlegOrder::getOrderNo).distinct().count()); assertTrue(captured.getAllValues().get(0).getOrderNo().matches("^FL-WH-\\d{8}-\\d{6}$")); verify(sequences,never()).nextValue(any(java.time.LocalDate.class)); }
|
||||
@Test void rejectsMissingOwnerSnapshotBeforeAnyLastlegWrite(){ EventEnvelope source=event(); com.fasterxml.jackson.databind.node.ObjectNode node=source.payload().deepCopy(); ((com.fasterxml.jackson.databind.node.ObjectNode)node.path("orders").get(0)).put("customerName"," "); String hash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(node)); EventEnvelope missing=new EventEnvelope(source.eventId(),source.eventType(),source.eventVersion(),source.occurredAt(),source.sourceRegion(),source.targetRegion(),source.tenantId(),source.aggregateType(),source.aggregateId(),source.aggregateVersion(),source.handoverVersion(),source.traceId(),source.correlationId(),source.causationId(),node,hash); IllegalArgumentException error=assertThrows(IllegalArgumentException.class,()->service.consume(missing)); assertTrue(error.getMessage().contains("尾程事件负载无效")||error.getMessage().contains("货主快照")); verify(inbox,never()).insert(any(LastlegEventInbox.class)); verify(orders,never()).insert(any(LastlegOrder.class)); }
|
||||
@@ -241,6 +241,26 @@ class LoadingCompletedConsumerTest {
|
||||
return new EventEnvelope(original.eventId(),original.eventType(),2,original.occurredAt(),original.sourceRegion(),original.targetRegion(),original.tenantId(),original.aggregateType(),original.aggregateId(),original.aggregateVersion(),2L,original.traceId(),original.correlationId(),original.causationId(),node,hash);
|
||||
}
|
||||
private EventEnvelope event(){ var item1=new FirstLegLoadingPlanCompletedV1.Item("d-1","SKU-1","产品","Product",5,50,1,new BigDecimal("10"),new BigDecimal("9"),new BigDecimal("12"),new BigDecimal("1.2"),"wh-1","ONT8","FBA ONT8","FBA-1","SHIP-1","MARK-1"); var item2=new FirstLegLoadingPlanCompletedV1.Item("d-2","SKU-2","产品2","Product2",3,30,2,new BigDecimal("8"),new BigDecimal("7"),new BigDecimal("9"),new BigDecimal("0.8"),"wh-2","LAX9","FBA LAX9","FBA-2","SHIP-2","MARK-2"); var p=new FirstLegLoadingPlanCompletedV1("plan-1","LP1","book-1","MSCU6639870","CAB1","ow-1","洛杉矶海外仓",List.of(new FirstLegLoadingPlanCompletedV1.Order("in-1","root-1","ord-1","FL1","root-o1","RK1","wh-1","ONT8","FBA ONT8",5,50,1,new BigDecimal("10"),new BigDecimal("9"),new BigDecimal("1.2"),List.of(item1),"100","Customer 100","FIRST_LEG",List.of()),new FirstLegLoadingPlanCompletedV1.Order("in-2","root-2","ord-2","FL2","root-o2","RK2","wh-2","LAX9","FBA LAX9",3,30,2,new BigDecimal("8"),new BigDecimal("7"),new BigDecimal("0.8"),List.of(item2),"100","Customer 100","FIRST_LEG",List.of()))); JsonNode node=json.valueToTree(p); String hash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(node)); return new EventEnvelope("123e4567-e89b-12d3-a456-426614174000",EventTypes.FIRST_LEG_LOADING_PLAN_COMPLETED,1,Instant.now(),RegionCode.CN,RegionCode.OVERSEAS,"100","LOADING_PLAN","plan-1",2,1L,"123e4567-e89b-12d3-a456-426614174001","FIRSTLEG_LOADING_COMPLETED:plan-1:100:1",null,node,hash); }
|
||||
@Test void projectsFiveDomesticLoadingPhotosOncePerPhysicalContainer(){
|
||||
EventEnvelope source=event(); com.fasterxml.jackson.databind.node.ObjectNode node=(com.fasterxml.jackson.databind.node.ObjectNode)v2Payload(source);
|
||||
var values=node.putArray("loadingEvidence"); int fileId=10;
|
||||
for(String type:List.of("PHOTO_STEP_1","PHOTO_STEP_2","PHOTO_STEP_3","PHOTO_STEP_4","SEAL")) values.addObject().put("evidenceType",type).put("fileId",fileId++).put("fileName",type+".jpg");
|
||||
String hash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(node));
|
||||
EventEnvelope event=new EventEnvelope(source.eventId(),source.eventType(),3,source.occurredAt(),source.sourceRegion(),source.targetRegion(),source.tenantId(),source.aggregateType(),source.aggregateId(),source.aggregateVersion(),3L,source.traceId(),source.correlationId(),source.causationId(),node,hash);
|
||||
assertEquals(2,service.consume(event));
|
||||
ArgumentCaptor<com.baiyue.lastleg.inboundplan.pda.entity.ContainerLoadingEvidence> captured=ArgumentCaptor.forClass(com.baiyue.lastleg.inboundplan.pda.entity.ContainerLoadingEvidence.class);
|
||||
verify(loadingEvidence,times(5)).insert(captured.capture());
|
||||
assertEquals(Set.of("PHOTO_STEP_1","PHOTO_STEP_2","PHOTO_STEP_3","PHOTO_STEP_4","SEAL"),captured.getAllValues().stream().map(com.baiyue.lastleg.inboundplan.pda.entity.ContainerLoadingEvidence::getEvidenceType).collect(java.util.stream.Collectors.toSet()));
|
||||
assertTrue(captured.getAllValues().stream().allMatch(value->"book-1".equals(value.getSourceContainerBookingId())));
|
||||
}
|
||||
@Test void rejectsV3EventWithoutTheFiveDomesticLoadingPhotos(){
|
||||
EventEnvelope source=event(); JsonNode node=v2Payload(source);
|
||||
String hash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(node));
|
||||
EventEnvelope event=new EventEnvelope(source.eventId(),source.eventType(),3,source.occurredAt(),source.sourceRegion(),source.targetRegion(),source.tenantId(),source.aggregateType(),source.aggregateId(),source.aggregateVersion(),3L,source.traceId(),source.correlationId(),source.causationId(),node,hash);
|
||||
IllegalArgumentException error=assertThrows(IllegalArgumentException.class,()->service.consume(event));
|
||||
assertTrue(error.getMessage().contains("五类国内装柜照片"));
|
||||
verify(loadingEvidence,never()).insert(any(com.baiyue.lastleg.inboundplan.pda.entity.ContainerLoadingEvidence.class));
|
||||
}
|
||||
private JsonNode v2Payload(EventEnvelope source){ com.fasterxml.jackson.databind.node.ObjectNode node=source.payload().deepCopy(); node.set("sourceContainerBookingId",node.remove("containerBookingId")); return node; }
|
||||
private EventEnvelope eventForPayload(FirstLegLoadingPlanCompletedV1 payload, long version){ JsonNode node=json.valueToTree(payload); for(JsonNode order:node.path("orders")) for(JsonNode pallet:order.path("pallets")) addManifest(pallet,order.path("packageCount").asInt()); String hash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(node)); return new EventEnvelope("123e4567-e89b-12d3-a456-426614174099",EventTypes.FIRST_LEG_LOADING_PLAN_COMPLETED,1,Instant.now(),RegionCode.CN,RegionCode.OVERSEAS,"100","LOADING_PLAN",payload.loadingPlanId(),2,version,"123e4567-e89b-12d3-a456-426614174098","FIRSTLEG_LOADING_COMPLETED:"+payload.loadingPlanId()+":100:1",null,node,hash); }
|
||||
private void addManifest(JsonNode pallet,int packageCount){
|
||||
|
||||
+2
-2
@@ -43,7 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
class TaillegRealConsumerAcceptanceTest {
|
||||
static final String CUSTOMER = "990022";
|
||||
static final String MARKER = "REAL-TAIL-20260822-R3";
|
||||
static final String EVENT_KEY = "FIRSTLEG_LOADING_COMPLETED:990022022:990022:2";
|
||||
static final String EVENT_KEY = "FIRSTLEG_LOADING_COMPLETED:990022022:990022:3";
|
||||
static final LocalDateTime RELABEL_DEADLINE = LocalDateTime.of(2026, 8, 29, 11, 1, 21, 628_000_000);
|
||||
static final LocalDateTime TRANSFER_ETA = LocalDateTime.of(2026, 9, 1, 11, 1, 21, 674_000_000);
|
||||
static final LocalDateTime RETURN_RELABEL_ETA = LocalDateTime.of(2026, 9, 1, 11, 1, 21, 756_000_000);
|
||||
@@ -62,7 +62,7 @@ class TaillegRealConsumerAcceptanceTest {
|
||||
InboundPlanService.class, InventoryCommandService.class, RelabelApplicationService.class,
|
||||
WarehouseTransferService.class, LocalLooseboardService.class})
|
||||
@MapperScan({"com.baiyue.lastleg.mapper", "com.baiyue.lastleg.dropship.mapper",
|
||||
"com.baiyue.lastleg.inboundplan.mapper", "com.baiyue.lastleg.inventory.mapper",
|
||||
"com.baiyue.lastleg.inboundplan.mapper", "com.baiyue.lastleg.inboundplan.pda.mapper", "com.baiyue.lastleg.inventory.mapper",
|
||||
"com.baiyue.lastleg.inventory.command.mapper", "com.baiyue.lastleg.relabel.mapper",
|
||||
"com.baiyue.lastleg.transfer.mapper", "com.baiyue.lastleg.looseboard.mapper"})
|
||||
static class AcceptanceApplication {
|
||||
|
||||
Reference in New Issue
Block a user