fix(order): project exact customer fulfillment progress in detail

This commit is contained in:
2026-10-15 19:46:34 +08:00
parent 097bec33b1
commit ae33780dae
27 changed files with 391 additions and 50 deletions
@@ -11,10 +11,36 @@ class InternalCustomerOrderProgressControllerTest {
@Test void validatesInternalIdentityBeforeReadingCustomerFacts() {
var service=mock(CustomerOrderProgressService.class);
var controller=new InternalCustomerOrderProgressController(new InternalServiceTokenGuard("test-token"),service);
var request=new CustomerOrderProgress.Request(11L);
var request=new CustomerOrderProgress.Request(11L, null);
assertThatThrownBy(()->controller.read(null,request)).isInstanceOf(org.springframework.web.server.ResponseStatusException.class);
assertThatThrownBy(()->controller.read("wrong",request)).isInstanceOf(org.springframework.web.server.ResponseStatusException.class);
verifyNoInteractions(service);
controller.read("test-token",request);verify(service).read(11);
controller.read("test-token",request);verify(service).read(11, null);
}
@Test void optionalExactScopeRejectsEmptyOversizeNullOrNonPositiveItems() {
try(var factory=jakarta.validation.Validation.buildDefaultValidatorFactory()) {
var validator=factory.getValidator();
for(java.util.List<Long> ids: java.util.List.of(java.util.List.<Long>of(),java.util.List.of(0L),
java.util.Arrays.asList(1L,null),java.util.stream.LongStream.rangeClosed(1,201).boxed().toList())) {
assertThat(validator.validate(new CustomerOrderProgress.Request(11L,ids))).isNotEmpty();
}
for(java.util.List<Long> ids: java.util.Arrays.asList(null,java.util.List.of(1L),java.util.stream.LongStream.rangeClosed(1,200).boxed().toList())) {
assertThat(validator.validate(new CustomerOrderProgress.Request(11L,ids))).isEmpty();
}
}
}
@Test @SuppressWarnings("unchecked") void documentsBoundedOptionalExactOrderScope() throws Exception {
var document=(java.util.Map<String,Object>)new org.yaml.snakeyaml.Yaml().load(
new org.springframework.core.io.ClassPathResource("openapi/lastleg-customer-order-progress-v1.yaml").getContentAsString(java.nio.charset.StandardCharsets.UTF_8));
var paths=(java.util.Map<String,Object>)document.get("paths");
var operation=(java.util.Map<String,Object>)((java.util.Map<String,Object>)paths.get("/internal/lastleg/v1/customer-order-progress")).get("post");
var content=(java.util.Map<String,Object>)((java.util.Map<String,Object>)operation.get("requestBody")).get("content");
var schema=(java.util.Map<String,Object>)((java.util.Map<String,Object>)content.get("application/json")).get("schema");
var ids=(java.util.Map<String,Object>)((java.util.Map<String,Object>)schema.get("properties")).get("orderIds");
assertThat(ids).containsEntry("minItems",1).containsEntry("maxItems",200).containsEntry("nullable",true);
assertThat((java.util.List<String>)schema.get("required")).doesNotContain("orderIds");
}
}
@@ -38,21 +38,23 @@ class CustomerOrderProgressMapperTest {
mapper=new SqlSessionTemplate(factory.getObject()).getMapper(CustomerOrderProgressMapper.class);
}
@Test void plannedSharedPalletIncludesEveryOwnSourceAndExcludesCancellation() {
var sources=mapper.sources("11");assertThat(sources).hasSize(2);
var sources=mapper.sources("11", null);assertThat(sources).hasSize(2);
assertThat(sources.get(0).scheduledPackages()).isEqualTo(10);
assertThat(sources.get(1).scheduledPackages()).isEqualTo(20);
assertThat(mapper.sources("11",java.util.List.of("101"))).containsExactly(sources.get(0));
assertThat(sources).allMatch(s->s.deliveringPackages()==0);
jdbc.update("UPDATE ll_dispatch_order SET status='CANCELLED'");
assertThat(mapper.sources("11")).allMatch(s->s.scheduledPackages()==0);
assertThat(mapper.sources("22")).hasSize(1).allMatch(s->s.scheduledPackages()==0);
assertThat(mapper.sources("11", null)).allMatch(s->s.scheduledPackages()==0);
assertThat(mapper.sources("22", null)).hasSize(1).allMatch(s->s.scheduledPackages()==0);
}
@Test void receivedPalletUsesInventoryAndActualPicksWithoutCountingBothPlanAndStock() {
jdbc.update("UPDATE ll_dispatch_item SET warehouse_pallet_id=800");
jdbc.update("INSERT INTO ll_inventory_balance VALUES(300,'11',1),(301,'11',2)");
jdbc.update("INSERT INTO ll_inventory_position VALUES(400,'11',800,'W1',300,6),(401,'11',800,'W1',301,20)");
jdbc.update("INSERT INTO ll_dispatch_pda_pick VALUES(600,'11',100,400,4,700)");
var sources=mapper.sources("11");assertThat(sources.get(0).scheduledPackages()).isEqualTo(10);
var sources=mapper.sources("11", null);assertThat(sources.get(0).scheduledPackages()).isEqualTo(10);
assertThat(sources.get(1).scheduledPackages()).isEqualTo(20);
assertThat(mapper.sources("11",java.util.List.of("101"))).containsExactly(sources.get(0));
}
@Test void deliveryRequiresActualDepartureOutboundAndMatchingDestinationAttribution() {
jdbc.update("INSERT INTO ll_dispatch_pda_task VALUES(700,'11',10)");
@@ -60,15 +62,26 @@ class CustomerOrderProgressMapperTest {
jdbc.update("INSERT INTO ll_dispatch_pda_pick_order VALUES(900,600,'11',1,4)");
jdbc.update("INSERT INTO ll_outbound_order VALUES(10000,'11','WAIT_OUTBOUND')");
jdbc.update("INSERT INTO ll_delivery_order VALUES(10001,'11',10,10000,'DELIVERING','CODE:FBA1')");
assertThat(mapper.sources("11")).allMatch(s->s.deliveringPackages()==0);
assertThat(mapper.sources("11", null)).allMatch(s->s.deliveringPackages()==0);
jdbc.update("UPDATE ll_dispatch_order SET status='DEPARTED',depart_at=CURRENT_TIMESTAMP");
assertThat(mapper.sources("11")).allMatch(s->s.deliveringPackages()==0);
assertThat(mapper.sources("11", null)).allMatch(s->s.deliveringPackages()==0);
jdbc.update("UPDATE ll_outbound_order SET status='OUTBOUNDED'");
assertThat(mapper.sources("11").get(0).deliveringPackages()).isEqualTo(4);
assertThat(mapper.sources("11").get(1).deliveringPackages()).isZero();
assertThat(mapper.sources("11", null).get(0).deliveringPackages()).isEqualTo(4);
assertThat(mapper.sources("11",java.util.List.of("101"))).containsExactly(mapper.sources("11",null).get(0));
assertThat(mapper.sources("11", null).get(1).deliveringPackages()).isZero();
jdbc.update("UPDATE ll_delivery_order SET delivery_group_key='CODE:FBA2'");
assertThat(mapper.sources("11")).allMatch(s->s.deliveringPackages()==0);
assertThat(mapper.sources("11", null)).allMatch(s->s.deliveringPackages()==0);
jdbc.update("UPDATE ll_delivery_order SET delivery_group_key='CODE:FBA1',status='RETURN_PENDING'");
assertThat(mapper.sources("11")).allMatch(s->s.deliveringPackages()==0);
assertThat(mapper.sources("11", null)).allMatch(s->s.deliveringPackages()==0);
}
@Test void exactSourceOrderScopePreservesAllOwnInboundFragmentsAndExcludesForeignOrders() {
jdbc.update("INSERT INTO ll_order VALUES(4,'11','101','204','FIRSTLEG','FBA',5)");
var own=mapper.sources("11",java.util.List.of("101","103"));
assertThat(own).extracting(s->s.orderId()).containsExactly("101","101");
assertThat(own).extracting(s->s.scheduledPackages()).containsExactly(10L,0L);
assertThat(mapper.sources("22",java.util.List.of("101"))).isEmpty();
assertThat(mapper.sources("11",java.util.List.of("999"))).isEmpty();
}
}
@@ -15,4 +15,13 @@ class CustomerOrderProgressServiceTest {
assertThat(result.get(1).stage()).isEqualTo("delivering");assertThat(result.get(1).partial()).isFalse();
assertThat(result.get(2).partial()).isTrue();assertThat(result.get(3).partial()).isTrue();
}
@Test void boundedSourceIdsReachTheMapperBeforeProjection() {
var mapper=org.mockito.Mockito.mock(com.baiyue.lastleg.mapper.CustomerOrderProgressMapper.class);
org.mockito.Mockito.when(mapper.sources("11",List.of("101"))).thenReturn(List.of(new Source("101",2,2,0)));
assertThat(new CustomerOrderProgressService(mapper).read(11,List.of(101L)).progress()).singleElement()
.isEqualTo(new com.baiyue.lastleg.dto.CustomerOrderProgress.Progress(101,"scheduled",false));
org.mockito.Mockito.verify(mapper).sources("11",List.of("101"));org.mockito.Mockito.verifyNoMoreInteractions(mapper);
}
}