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
@@ -45,7 +45,7 @@ class InternalInboundOrderReferenceControllerTest {
private InternalInboundOrderController controller;
@Test void workbenchFiltersRejectMissingAndWrongInternalIdentity() {
var request = new com.baiyue.firstleg.dto.MerchantWorkbenchFilters.Request(11L, "LOADED");
var request = new com.baiyue.firstleg.dto.MerchantWorkbenchFilters.Request(11L, "LOADED", null);
assertThatThrownBy(() -> controller.workbenchFilters(null, request)).isInstanceOf(BizException.class);
assertThatThrownBy(() -> controller.workbenchFilters("wrong", request)).isInstanceOf(BizException.class);
org.mockito.Mockito.verifyNoInteractions(workbenchFilters);
@@ -143,4 +143,18 @@ class InternalInboundOrderReferenceControllerTest {
carrierId == null ? null : "承运商", null, null, null, null,
null, null, null, null, null, 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 com.baiyue.firstleg.dto.MerchantWorkbenchFilters.Request(11L,null,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 com.baiyue.firstleg.dto.MerchantWorkbenchFilters.Request(11L,null,ids))).isEmpty();
}
}
}
}
@@ -74,4 +74,16 @@ class UnifiedInboundOpenApiContractTest {
.containsOnlyKeys("standardNode","nodeSequence","providerCode","estimatedAt","actualAt","locationCode","locationName");
}
@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/firstleg-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/firstleg/v1/inbounds/unified-orders/workbench-filters")).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");
}
}
@@ -83,19 +83,28 @@ class MerchantWorkbenchFilterMapperTest {
}
@Test void onlyActualLoadedOwnedFragmentsAndActualClearanceAdvanceProgress() {
assertThat(mapper.progressFragments(11)).allMatch(f->f.stage()==0);
assertThat(mapper.progressFragments(11, null)).allMatch(f->f.stage()==0);
jdbc.update("INSERT INTO fl_loading_pick_task VALUES(900,100,'COMPLETED')");
jdbc.update("INSERT INTO fl_loading_pick_item SELECT 900,id,'PICKED',2 FROM fl_loading_plan_item");
assertThat(mapper.progressFragments(11)).allMatch(f->f.stage()==1).noneMatch(f->f.orderId()==103||f.orderId()==104);
assertThat(mapper.progressFragments(11, null)).allMatch(f->f.stage()==1).noneMatch(f->f.orderId()==103||f.orderId()==104);
jdbc.update("INSERT INTO fl_customs_container VALUES(200,100,'DECLARING')");
assertThat(mapper.progressFragments(11)).allMatch(f->f.stage()==1);
assertThat(mapper.progressFragments(11, null)).allMatch(f->f.stage()==1);
jdbc.update("UPDATE fl_customs_container SET status='COMPLETED'");
assertThat(mapper.progressFragments(11)).allMatch(f->f.stage()==2);
assertThat(mapper.progressFragments(11, null)).allMatch(f->f.stage()==2);
jdbc.update("INSERT INTO fl_carrier_schedule_node_fact VALUES(100,'PICKUP_READY',NULL)");
assertThat(mapper.progressFragments(11)).allMatch(f->f.stage()==2);
assertThat(mapper.progressFragments(11, null)).allMatch(f->f.stage()==2);
jdbc.update("UPDATE fl_carrier_schedule_node_fact SET actual_at=CURRENT_TIMESTAMP");
assertThat(mapper.progressFragments(11)).allMatch(f->f.stage()==3);
assertThat(mapper.progressFragments(11, null)).allMatch(f->f.stage()==3);
assertThat(mapper.progressFragments(11,List.of(101L))).singleElement().satisfies(f->{assertThat(f.orderId()).isEqualTo(101);assertThat(f.stage()).isEqualTo(3);});
jdbc.update("UPDATE fl_container_booking SET record_status='CANCELLED'");
assertThat(mapper.progressFragments(11)).allMatch(f->f.stage()==0);
assertThat(mapper.progressFragments(11, null)).allMatch(f->f.stage()==0);
}
@Test void exactOrderScopeKeepsAllItsFragmentsAndCannotReadForeignOrDeletedOrders() {
jdbc.update("INSERT INTO fl_inbound_order(id,tenant_id,order_id,deleted,tax_mode,process_status) VALUES(50,11,101,0,'TAX_REFUND','LOADED')");
assertThat(mapper.progressFragments(11,List.of(101L,103L,104L))).extracting(f->f.orderId()).containsExactly(101L,101L);
assertThat(mapper.progressFragments(11,List.of(999L))).isEmpty();
assertThat(mapper.progressFragments(22,List.of(101L))).isEmpty();
}
}
@@ -0,0 +1,26 @@
package com.baiyue.firstleg.service;
import com.baiyue.firstleg.dto.MerchantWorkbenchFilters;
import com.baiyue.firstleg.mapper.MerchantWorkbenchFilterMapper;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
class MerchantWorkbenchFilterServiceTest {
@Test void exactDetailProgressSkipsUnrelatedCustomerQueues() {
var mapper=mock(MerchantWorkbenchFilterMapper.class);
var unified=mock(UnifiedInboundQueryService.class);
var service=new MerchantWorkbenchFilterService();
ReflectionTestUtils.setField(service,"mapper",mapper);
ReflectionTestUtils.setField(service,"unified",unified);
when(mapper.progressFragments(11,List.of(101L))).thenReturn(List.of(
new MerchantWorkbenchFilters.Fragment(101,3),new MerchantWorkbenchFilters.Fragment(101,1)));
var response=service.read(new MerchantWorkbenchFilters.Request(11L,null,List.of(101L)));
assertThat(response.progress()).containsExactly(new MerchantWorkbenchFilters.Progress(101,"cleared",true));
assertThat(response.feeOrderIds()).isEmpty();assertThat(response.customsOrderIds()).isEmpty();
assertThat(response.matchingStatusOrderIds()).isNull();
verify(mapper).progressFragments(11,List.of(101L));verifyNoMoreInteractions(mapper);verifyNoInteractions(unified);
}
}