perf(lastleg): batch live appointment work facts and filter candidates
This commit is contained in:
+32
@@ -0,0 +1,32 @@
|
||||
package com.baiyue.lastleg.appointmentplanning;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import java.util.*;
|
||||
import java.util.stream.IntStream;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class AppointmentCargoVerificationBatchTest {
|
||||
@Test void inspectionUsesTwoBoundedBatchesAndRetainsPerSourcePrerequisitesAndAllocations() {
|
||||
var sources=mock(AppointmentSourceService.class);var mapper=mock(AppointmentCargoVerificationMapper.class);
|
||||
var service=new AppointmentCargoVerificationService(sources,mapper,new ObjectMapper());
|
||||
var refs=IntStream.rangeClosed(1,200).mapToObj(i->new AppointmentCoverageService.SourceRef("C","ORDER:"+i,"FBA:ONT8")).toList();
|
||||
when(sources.inspectBatch(anyList())).thenAnswer(call->call.<List<AppointmentCoverageService.SourceRef>>getArgument(0).stream().map(ref->
|
||||
ref.sourceKey().equals("ORDER:2")?new AppointmentSourceService.Resolution(ref,null,new ResponseStatusException(HttpStatus.BAD_REQUEST,"接仓点不在启用的美国仓目录中")):
|
||||
new AppointmentSourceService.Resolution(ref,new AppointmentSourceService.Source(ref.sourceKey(),ref.customerId(),Long.valueOf(ref.sourceKey().substring(6)),"IN",null,"CONTAINER","100","仓",ref.destinationKey(),"ONT8",10,"ORDER:1",true,List.of()),null)).toList());
|
||||
when(mapper.allocatedBatch(anyList())).thenAnswer(call->call.<List<AppointmentCoverageService.SourceRef>>getArgument(0).stream().filter(r->r.sourceKey().equals("ORDER:1"))
|
||||
.map(r->new AppointmentCargoVerificationMapper.SourceAllocation(r.customerId(),r.sourceKey(),4,0)).toList());
|
||||
var result=service.inspectBatch(refs);
|
||||
assertEquals(200,result.size());assertEquals(6L,result.get(0).requireContext().availablePackages());
|
||||
assertEquals("接仓点不在启用的美国仓目录中",assertThrows(ResponseStatusException.class,()->result.get(1).requireContext()).getReason());
|
||||
assertEquals(10L,result.get(199).requireContext().availablePackages());
|
||||
verify(sources,times(2)).inspectBatch(argThat(batch->batch.size()==100));
|
||||
verify(mapper,times(2)).latestBatch(anyList());verify(mapper,times(2)).allocatedBatch(anyList());verifyNoMoreInteractions(mapper,sources);
|
||||
when(sources.inspectBatch(anyList())).thenThrow(new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,"目录不可用"));
|
||||
assertEquals(503,assertThrows(ResponseStatusException.class,()->service.inspectBatch(refs)).getStatusCode().value());
|
||||
}
|
||||
}
|
||||
+9
@@ -31,6 +31,7 @@ class AppointmentCoverageMapperTest {
|
||||
s.execute("INSERT INTO ll_appointment_cargo_verification(id,customer_id,source_key,destination_key,version,package_count,shipment_references_json,operator_id,command_key,request_hash,created_at) VALUES(1,'A','ORDER:1','FBA:ONT8',1,19,'[]','operator','one',REPEAT('a',64),CURRENT_TIMESTAMP),(2,'A','ORDER:1','FBA:ONT8',2,20,'[\"FBA-A\"]','operator','two',REPEAT('b',64),CURRENT_TIMESTAMP),(3,'B','ORDER:1','FBA:ONT8',1,8,'[]','operator','one',REPEAT('c',64),CURRENT_TIMESTAMP)");
|
||||
}
|
||||
var config=new Configuration(new Environment("test",new JdbcTransactionFactory(),ds));config.setMapUnderscoreToCamelCase(true);config.addMapper(AppointmentCoverageMapper.class);
|
||||
config.addMapper(AppointmentCargoVerificationMapper.class);
|
||||
try(var session=new SqlSessionFactoryBuilder().build(config).openSession(true)) {
|
||||
var mapper=session.getMapper(AppointmentCoverageMapper.class);
|
||||
var source=mapper.sources(List.of(new AppointmentCoverageMapper.OrderRef("A",1L))).get(0);assertFalse(source.shipmentReferencesMissing());assertEquals(20,source.packages());
|
||||
@@ -42,6 +43,14 @@ class AppointmentCoverageMapperTest {
|
||||
assertTrue(mapper.sources(List.of(new AppointmentCoverageMapper.OrderRef("B",2L))).get(0).shipmentReferencesMissing());
|
||||
var evidence=mapper.verifications(List.of(new AppointmentCoverageService.SourceRef("A","ORDER:1","FBA:ONT8")));
|
||||
assertEquals(1,evidence.size());assertEquals(2,evidence.get(0).getVersion());assertEquals(20,evidence.get(0).getPackageCount());
|
||||
var cargo=session.getMapper(AppointmentCargoVerificationMapper.class);
|
||||
var refs=List.of(new AppointmentCoverageService.SourceRef("A","ORDER:1","FBA:ONT8"),new AppointmentCoverageService.SourceRef("B","ORDER:1","FBA:ONT8"),new AppointmentCoverageService.SourceRef("D","BOOKING:B-1","FBA:ONT8"));
|
||||
var latest=cargo.latestBatch(refs);assertEquals(2,latest.size());
|
||||
assertEquals(2,latest.stream().filter(r->r.getCustomerId().equals("A")).findFirst().orElseThrow().getVersion());
|
||||
assertEquals(1,latest.stream().filter(r->r.getCustomerId().equals("B")).findFirst().orElseThrow().getVersion());
|
||||
var allocations=cargo.allocatedBatch(refs);assertEquals(2,allocations.size());
|
||||
assertEquals(new AppointmentCargoVerificationMapper.SourceAllocation("A","ORDER:1",20,0),allocations.stream().filter(r->r.customerId().equals("A")).findFirst().orElseThrow());
|
||||
assertEquals(new AppointmentCargoVerificationMapper.SourceAllocation("D","BOOKING:B-1",0,1),allocations.stream().filter(r->r.customerId().equals("D")).findFirst().orElseThrow());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -25,6 +25,14 @@ class AppointmentSourceServiceTest {
|
||||
var refs=List.of(new AppointmentCoverageService.SourceRef("A","ORDER:1","FBA:ONT8"),new AppointmentCoverageService.SourceRef("B","ORDER:2","FBA:ONT8"));
|
||||
var result=service.resolveBatch(refs);assertEquals(2,result.size());assertEquals(20,result.get(1).packages());
|
||||
verify(orders,times(1)).selectBatchIds(anyCollection());verify(shipping,times(1)).selectBatchIds(anyCollection());verify(warehouse,times(1)).catalog("token");verify(gateway,times(1)).resolveOrders(anyList());verifyNoMoreInteractions(gateway);
|
||||
b.setOverseasWarehouseId("unknown");
|
||||
var inspected=service.inspectBatch(refs);assertNotNull(inspected.get(0).requireSource());
|
||||
assertEquals("接仓点不在启用的美国仓目录中",inspected.get(1).error().getReason());
|
||||
assertThrows(ResponseStatusException.class,()->service.resolveBatch(refs));
|
||||
var unauthorized=service.inspectBatch(List.of(new AppointmentCoverageService.SourceRef("OTHER","ORDER:1","FBA:ONT8")));
|
||||
assertEquals(404,assertThrows(ResponseStatusException.class,()->unauthorized.get(0).requireSource()).getStatusCode().value());
|
||||
when(warehouse.catalog("token")).thenThrow(new IllegalStateException("offline"));
|
||||
assertEquals(503,assertThrows(ResponseStatusException.class,()->service.inspectBatch(refs)).getStatusCode().value());
|
||||
assertThrows(ResponseStatusException.class,()->service.resolveBatch(List.of(refs.get(0),refs.get(0))));
|
||||
assertThrows(ResponseStatusException.class,()->service.resolveBatch(List.of(new AppointmentCoverageService.SourceRef("OTHER","ORDER:1","FBA:ONT8"))));
|
||||
}
|
||||
|
||||
+3
@@ -35,6 +35,9 @@ class AppointmentWorkPersistenceTest {
|
||||
assertEquals("运营甲",store.replay(state.getWorkItemKey(),"same-key").getOperatorName());
|
||||
assertThrows(RuntimeException.class,()->store.history(state,new Actor("2","运营乙",ACTOR.permissions()),"SAVE_FOLLOWUP","其他人重复键","same-key","other",Map.of("version",2)));
|
||||
assertEquals(1,store.history(state.getWorkItemKey()).size());
|
||||
mapper.insertCommitment("FBA_APPOINTMENT:1","{}");mapper.updateCommitment("FBA_APPOINTMENT:1","{}",3);
|
||||
var tokens=new AppointmentWorkCommitmentService(mapper,new ObjectMapper()).impactTokens(List.of("FBA_APPOINTMENT:1","FBA_APPOINTMENT:2"));
|
||||
assertEquals(Map.of("FBA_APPOINTMENT:1","FBA_APPOINTMENT:1:3","FBA_APPOINTMENT:2","FBA_APPOINTMENT:2:0"),tokens);
|
||||
}
|
||||
}
|
||||
@Test void transactionRollbackCannotLeaveHalfGeneratedRoute() throws Exception {
|
||||
|
||||
+54
@@ -12,6 +12,60 @@ import static com.baiyue.lastleg.appointmentwork.AppointmentWorkModels.*;
|
||||
import static com.baiyue.lastleg.appointmentwork.AppointmentWorkFactGateway.*;
|
||||
|
||||
class AppointmentWorkQueryServiceTest {
|
||||
@Test void followTodayPageOneSizeEightDoesNotReadUnrelatedRuntimeFactsOrPaginateBeforeCompletionChecks() {
|
||||
var f=new WorkTestFixture();
|
||||
for(int index=0;index<1000;index++) f.add(f.state("unrelated-"+index,Type.WAREHOUSE_RECEIPT,Flow.TRANSFER,null));
|
||||
var expected=new HashSet<String>();
|
||||
var prior=f.state("due-1",Type.PICKUP,Flow.TRANSFER,null);f.add(prior);expected.add(prior.getWorkItemKey());
|
||||
for(int index=1;index<=10;index++) {
|
||||
var state=f.state("due-"+index,Type.DELIVERY,Flow.TRANSFER,"inbound");state.setFollowupDate(LocalDate.of(2026,10,14));
|
||||
if(index==1) state.setPrerequisites(List.of(prior.getWorkItemKey()));
|
||||
if(index==10) f.facts.put(state.getWorkItemKey(),fact(state,null,null,ExecutionStatus.COMPLETED,new Actual(Instant.parse("2026-10-14T12:00:00Z"),null,"America/Los_Angeles")));
|
||||
f.add(state);expected.add(state.getWorkItemKey());
|
||||
}
|
||||
var future=f.state("future",Type.DELIVERY,Flow.TRANSFER,"inbound");future.setFollowupDate(LocalDate.of(2027,1,1));f.add(future);
|
||||
// Exact parameters from /items?view=FOLLOW_TODAY&page=1&size=8.
|
||||
var page=f.queries.items(View.FOLLOW_TODAY,1,8,null,null,null,null,null,null,ACTOR);
|
||||
assertEquals(9,page.total());assertEquals(8,page.records().size());assertEquals(2,page.pages());
|
||||
assertTrue(page.records().stream().allMatch(i->i.executionStatus()==ExecutionStatus.PENDING));
|
||||
verify(f.gateway).facts(argThat(states->states.stream().map(AppointmentWorkState::getWorkItemKey).collect(java.util.stream.Collectors.toSet()).equals(expected)));
|
||||
}
|
||||
@Test void followTodayPreservesAuthoritativeDateLineZonesAndUndatedPendingHandoffs() {
|
||||
var f=new WorkTestFixture();
|
||||
var tomorrow=LocalDate.of(2026,10,16);
|
||||
var east=f.state("east",Type.DELIVERY,Flow.TRANSFER,"inbound");east.setFollowupDate(tomorrow);f.add(east);
|
||||
var west=f.state("west",Type.DELIVERY,Flow.TRANSFER,"inbound");west.setFollowupDate(tomorrow);f.add(west);
|
||||
// Stored timezone is Los Angeles; the live source is already on the following local day.
|
||||
f.facts.put(east.getWorkItemKey(),fact(east,new Slot(tomorrow,null,null,null,"Pacific/Kiritimati"),null,ExecutionStatus.PENDING,null));
|
||||
var handoff=f.state("return",Type.EMPTY_RETURN,Flow.TRANSFER,null);
|
||||
var owner=new Responsibility(ResponsibilityKind.USER,"1","运营甲");
|
||||
handoff.setReturnHandoff(new ReturnHandoff(1,owner,owner,HandoffStatus.PENDING_ACCEPTANCE,"交接"));f.add(handoff);
|
||||
var query=new AppointmentWorkQueryService(f.store,f.gateway,Clock.fixed(Instant.parse("2026-10-15T12:00:00Z"),ZoneOffset.UTC));
|
||||
var page=query.items(View.FOLLOW_TODAY,1,8,null,null,null,null,null,null,ACTOR);
|
||||
assertEquals(Set.of(east.getWorkItemKey(),handoff.getWorkItemKey()),page.records().stream().map(WorkItem::workItemKey).collect(java.util.stream.Collectors.toSet()));
|
||||
}
|
||||
@Test void calendarLoadsOnlyRequestedTypesButUsesCurrentAuthorityDatesAndStillChecksScope() {
|
||||
var f=new WorkTestFixture();
|
||||
var receipt=f.state("1",Type.WAREHOUSE_RECEIPT,Flow.TRANSFER,null);receipt.setTarget(slot("2025-01-01"));f.add(receipt);
|
||||
var pickup=f.state("1",Type.PICKUP,Flow.TRANSFER,null);f.add(pickup);
|
||||
receipt.setPrerequisites(List.of(pickup.getWorkItemKey()));f.store.save(receipt);
|
||||
f.facts.put(receipt.getWorkItemKey(),fact(receipt,slot("2026-10-16"),null,ExecutionStatus.PENDING,null));
|
||||
assertEquals(1,f.queries.calendar(LocalDate.of(2026,10,1),LocalDate.of(2026,10,31),Flow.TRANSFER,Set.of(Type.WAREHOUSE_RECEIPT),ACTOR).total());
|
||||
verify(f.gateway).facts(argThat(states->states.size()==1 && states.get(0).getWorkItemKey().equals(receipt.getWorkItemKey())));
|
||||
verify(f.gateway).discover(null,1);
|
||||
var reader=new Actor("2","无原业务权限",Set.of(LIST));
|
||||
assertEquals(0,f.queries.calendar(LocalDate.of(2026,10,1),LocalDate.of(2026,10,31),null,Set.of(Type.WAREHOUSE_RECEIPT),reader).total());
|
||||
}
|
||||
@Test void filteredItemsLoadPrerequisiteClosureWithoutIncludingUnrelatedContainers() {
|
||||
var f=new WorkTestFixture();
|
||||
var pickup=f.state("1",Type.PICKUP,Flow.TRANSFER,null);f.add(pickup);
|
||||
var retired=f.state("1",Type.ROUTE,Flow.UNKNOWN,null);retired.setRetired(true);retired.setReplacementKeys(List.of(pickup.getWorkItemKey()));f.add(retired);
|
||||
var receipt=f.state("1",Type.WAREHOUSE_RECEIPT,Flow.TRANSFER,null);receipt.setPrerequisites(List.of(retired.getWorkItemKey()));receipt.setObservedFingerprint("old");f.add(receipt);
|
||||
f.add(f.state("2",Type.DELIVERY,Flow.TRANSFER,"other"));
|
||||
var items=f.queries.items(View.ALL,1,20,null,null,Set.of(Type.WAREHOUSE_RECEIPT),"1",null,null,ACTOR);
|
||||
assertEquals(1,items.total());assertEquals(pickup.getWorkItemKey(),items.records().get(0).nextAction().targetWorkItemKey());
|
||||
verify(f.gateway).facts(argThat(states->states.stream().map(AppointmentWorkState::getWorkItemKey).collect(java.util.stream.Collectors.toSet()).equals(Set.of(pickup.getWorkItemKey(),receipt.getWorkItemKey()))));
|
||||
}
|
||||
@Test void globalSummaryRetainsUnknownRoutesAndReturnAfterDeliveryAcrossCalendarFilters() {
|
||||
var f=new WorkTestFixture(); var route=f.state("1",Type.ROUTE,Flow.UNKNOWN,null); route.setFollowupDate(LocalDate.of(2026,10,14)); f.add(route);
|
||||
var returns=f.state("2",Type.EMPTY_RETURN,Flow.DIRECT,null); returns.setTarget(slot("2026-09-01")); f.add(returns);
|
||||
|
||||
+2
@@ -87,6 +87,8 @@ class FleetTripAuthorizationTest {
|
||||
}
|
||||
@Configuration @EnableMethodSecurity
|
||||
static class Config {
|
||||
@Bean com.baiyue.lastleg.fleet.trip.work.AppointmentWorkTripChanges workTripChanges() { return mock(com.baiyue.lastleg.fleet.trip.work.AppointmentWorkTripChanges.class); }
|
||||
@Bean com.baiyue.lastleg.appointmentwork.AppointmentWorkDispatchPort workDispatches() { return mock(com.baiyue.lastleg.appointmentwork.AppointmentWorkDispatchPort.class); }
|
||||
@Bean FleetTripCalendarService calendar() { return mock(FleetTripCalendarService.class); }
|
||||
@Bean FleetCapacityOfferCommandMapper commands() { return mock(FleetCapacityOfferCommandMapper.class); }
|
||||
@Bean FleetDispatchPlanningGateway planning() { return mock(FleetDispatchPlanningGateway.class); }
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.baiyue.lastleg.fleet.trip.work;
|
||||
|
||||
import com.baiyue.lastleg.appointmentwork.AppointmentWorkCommitmentService;
|
||||
import com.baiyue.lastleg.fleet.driver.FleetWarehouseClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
import static com.baiyue.lastleg.appointmentwork.AppointmentWorkFactGateway.*;
|
||||
import static com.baiyue.lastleg.appointmentwork.AppointmentWorkModels.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class AppointmentWorkTransportProviderTest {
|
||||
@Test void unassignedTransportWorkDoesNotQueryTripsOrWarehousesPerSource() {
|
||||
var mapper=mock(AppointmentWorkTripMapper.class);var warehouses=mock(FleetWarehouseClient.class);var commitments=mock(AppointmentWorkCommitmentService.class);
|
||||
var requests=IntStream.range(0,200).mapToObj(i->new FactRequest("W"+i,Type.TRANSPORT,Flow.TRANSFER,"C"+i,"INBOUND:"+i,null,null,List.of(),List.of(),List.of())).toList();
|
||||
var facts=new AppointmentWorkTransportProvider(mapper,warehouses,commitments,"token").read(requests);
|
||||
assertEquals(200,facts.size());assertTrue(facts.stream().allMatch(f->!f.planExists() && f.executionStatus()==ExecutionStatus.PENDING));
|
||||
verifyNoInteractions(mapper,warehouses,commitments);
|
||||
}
|
||||
}
|
||||
+10
@@ -62,6 +62,16 @@ class InboundFbaAppointmentServiceTest {
|
||||
SourceFbaAppointmentRequest request(String key,FbaAppointmentDetails details,String... sources) {return new SourceFbaAppointmentRequest(requestRefs(sources),"100","ONT8",details,key);}
|
||||
FbaAppointmentResponse create(String key) {return service.createFromSources("operator",request(key,pending(null),"ORDER:1","ORDER:2"));}
|
||||
|
||||
@Test void workBatchSnapshotsReadOnlyHeadersAndItemsWithoutRunningRepartitionEligibility() {
|
||||
var original=create("snapshot");
|
||||
when(headers.selectBatchIds(List.of(original.id()))).thenReturn(List.copyOf(storedHeaders));
|
||||
clearInvocations(headers,items,query,sources);
|
||||
assertEquals(List.of(original),service.batchSnapshots(List.of(original.batchKey())));
|
||||
verify(headers).selectBatchIds(List.of(original.id()));verify(items).selectList(any());verifyNoMoreInteractions(headers,items);verifyNoInteractions(query,sources);
|
||||
assertThrows(ResponseStatusException.class,()->service.batchSnapshots(List.of("DISPATCH:1")));
|
||||
assertThrows(ResponseStatusException.class,()->service.batchSnapshots(List.of("FBA_APPOINTMENT:999")));
|
||||
}
|
||||
|
||||
@Test void sourceRequestKeepsWireShapeAndPassesAllSourceFieldsToResolution() throws Exception {
|
||||
var json=new ObjectMapper().findAndRegisterModules();
|
||||
var input=request("source-json",pending(null),"ORDER:1","ORDER:2");
|
||||
|
||||
+3
-1
@@ -81,7 +81,9 @@ class InboundPlanPaginationTest {
|
||||
String sql = String.join("", InboundPlanQueryMapper.class
|
||||
.getMethod("rows", String.class, InboundPlanRequests.Query.class, long.class, long.class)
|
||||
.getAnnotation(org.apache.ibatis.annotations.Select.class).value()) + InboundPlanQueryMapper.FROM_AND_FILTER;
|
||||
assertThat(sql).contains("ai2.customer_id=p.customer_id", "ORDER BY a2.created_at DESC,ai2.id DESC LIMIT 1");
|
||||
assertThat(sql).contains("ai2.customer_id=p.customer_id", "ai2.fba_warehouse_code=l.fba_warehouse_code",
|
||||
"a2.replacement_nos_json IS NULL", "ORDER BY ai2.active DESC,a2.created_at DESC,ai2.id DESC LIMIT 1");
|
||||
assertThat(sql).contains("COUNT(DISTINCT current_item.appointment_id)", "current_item.active=1", "THEN 'MULTIPLE'");
|
||||
assertThat(sql).contains("p.customer_id=#{customerId}", "o.customer_id=p.customer_id");
|
||||
assertThat(sql).doesNotContain("a2.customer_id=#{customerId}");
|
||||
assertThat(sql).contains("di.customer_id=p.customer_id AND di.order_id=l.order_id",
|
||||
|
||||
+24
-3
@@ -1,6 +1,8 @@
|
||||
package com.baiyue.lastleg.inboundplan.work;
|
||||
|
||||
import com.baiyue.lastleg.appointmentplanning.AppointmentCargoVerificationService;
|
||||
import com.baiyue.lastleg.appointmentplanning.AppointmentCoverageService.SourceRef;
|
||||
import com.baiyue.lastleg.appointmentplanning.AppointmentSourceService.Resolution;
|
||||
import com.baiyue.lastleg.appointmentwork.AppointmentWorkCommitmentService;
|
||||
import com.baiyue.lastleg.inboundplan.service.InboundFbaAppointmentService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -12,9 +14,28 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class AppointmentWorkDeliveryProviderTest {
|
||||
@Test void establishedFbaBatchesShareSnapshotsDeliveriesAndImpactReads() {
|
||||
var appointments=mock(InboundFbaAppointmentService.class);var cargo=mock(AppointmentCargoVerificationService.class);
|
||||
var commitments=mock(AppointmentWorkCommitmentService.class);var deliveries=mock(AppointmentWorkDeliveryMapper.class);var sources=mock(AppointmentWorkSourceMapper.class);
|
||||
var requests=java.util.stream.IntStream.rangeClosed(1,200).mapToObj(i->new FactRequest("W"+i,Type.DELIVERY,Flow.TRANSFER,"1",null,"FBA_APPOINTMENT:"+i,null,List.of(),List.of(),List.of())).toList();
|
||||
var keys=requests.stream().map(FactRequest::batchKey).toList();
|
||||
var snapshots=keys.stream().map(key->{
|
||||
var snapshot=mock(com.baiyue.lastleg.inboundplan.dto.FbaAppointmentResponse.class);
|
||||
when(snapshot.batchKey()).thenReturn(key);when(snapshot.id()).thenReturn(Long.valueOf(key.substring(16)));when(snapshot.appointmentStatus()).thenReturn("PENDING");
|
||||
return snapshot;
|
||||
}).toList();
|
||||
when(appointments.batchSnapshots(keys)).thenReturn(snapshots);
|
||||
when(commitments.impactTokens(keys)).thenReturn(keys.stream().collect(java.util.stream.Collectors.toMap(k->k,k->k+":0")));
|
||||
var result=new AppointmentWorkDeliveryProvider(appointments,cargo,commitments,deliveries,sources).read(requests);
|
||||
assertEquals(200,result.size());assertTrue(result.stream().allMatch(f->f.planExists() && f.executionStatus()==ExecutionStatus.PENDING));
|
||||
verify(appointments).batchSnapshots(keys);verify(deliveries).batches(keys);verify(commitments).impactTokens(keys);
|
||||
verifyNoMoreInteractions(appointments,deliveries,commitments);verifyNoInteractions(cargo,sources);
|
||||
when(appointments.batchSnapshots(keys)).thenReturn(List.of());
|
||||
assertThrows(org.springframework.web.server.ResponseStatusException.class,()->new AppointmentWorkDeliveryProvider(appointments,cargo,commitments,deliveries,sources).read(requests));
|
||||
}
|
||||
@Test void handledPrerequisiteCommitsTheCallerTransactionThroughTheRealCargoProxy() {
|
||||
var sourceService=mock(com.baiyue.lastleg.appointmentplanning.AppointmentSourceService.class);
|
||||
when(sourceService.resolve("ORDER:11","C",null)).thenThrow(new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.BAD_REQUEST,"当前货源类型不支持FBA预约"));
|
||||
when(sourceService.inspectBatch(List.of(new SourceRef("C","ORDER:11",null)))).thenReturn(List.of(new Resolution(new SourceRef("C","ORDER:11",null),null,new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.BAD_REQUEST,"当前货源类型不支持FBA预约"))));
|
||||
var cargo=new AppointmentCargoVerificationService(sourceService,mock(com.baiyue.lastleg.appointmentplanning.AppointmentCargoVerificationMapper.class),new com.fasterxml.jackson.databind.ObjectMapper());
|
||||
var ds=new org.h2.jdbcx.JdbcDataSource();ds.setURL("jdbc:h2:mem:work-read-tx-"+java.util.UUID.randomUUID()+";DB_CLOSE_DELAY=-1");
|
||||
var manager=new org.springframework.jdbc.datasource.DataSourceTransactionManager(ds);
|
||||
@@ -33,11 +54,11 @@ class AppointmentWorkDeliveryProviderTest {
|
||||
var cargo=mock(AppointmentCargoVerificationService.class);var sources=mock(AppointmentWorkSourceMapper.class);
|
||||
var provider=new AppointmentWorkDeliveryProvider(mock(InboundFbaAppointmentService.class),cargo,mock(AppointmentWorkCommitmentService.class),mock(AppointmentWorkDeliveryMapper.class),sources);
|
||||
var request=new FactRequest("W",Type.DELIVERY,Flow.TRANSFER,"1","INBOUND:1",null,null,List.of(),List.of(),List.of(new SourceReference("C","ORDER:11",null)));
|
||||
when(cargo.inspect("ORDER:11","C",null)).thenThrow(new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.BAD_REQUEST,"接仓点不在启用的美国仓目录中"));
|
||||
when(cargo.inspectBatch(List.of(new SourceRef("C","ORDER:11",null)))).thenReturn(List.of(new AppointmentCargoVerificationService.Inspection(new SourceRef("C","ORDER:11",null),null,new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.BAD_REQUEST,"接仓点不在启用的美国仓目录中"))));
|
||||
var fact=provider.read(List.of(request)).get(0);
|
||||
assertEquals(ExecutionStatus.PENDING,fact.executionStatus());assertNull(fact.action());assertNull(fact.actual());
|
||||
assertEquals(List.of("SOURCE_CONFIGURATION"),fact.missingFields());assertEquals(List.of("接仓点不在启用的美国仓目录中"),fact.blockReasons());
|
||||
doThrow(new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE,"服务不可用")).when(cargo).inspect("ORDER:11","C",null);
|
||||
doThrow(new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE,"服务不可用")).when(cargo).inspectBatch(List.of(new SourceRef("C","ORDER:11",null)));
|
||||
assertThrows(org.springframework.web.server.ResponseStatusException.class,()->provider.read(List.of(request)));
|
||||
}
|
||||
@Test void privateCompletionCoversThisDispatchPerSourceWithoutCompletingTheWholeOrder() {
|
||||
|
||||
+24
-1
@@ -42,8 +42,31 @@ class AppointmentWorkDomainMapperTest {
|
||||
assertEquals(0,dispatch.allocated("FBA_APPOINTMENT:1","C","ORDER:1","FBA:ONT8",1));
|
||||
var deliveries=session.getMapper(AppointmentWorkDeliveryMapper.class);assertTrue(deliveries.batch("FBA_APPOINTMENT:1").isEmpty());assertTrue(deliveries.customer("1","1").isEmpty());
|
||||
assertTrue(deliveries.privateBatch("DISPATCH:1").isEmpty());
|
||||
assertTrue(deliveries.batches(List.of("FBA_APPOINTMENT:1","FBA_APPOINTMENT:2")).isEmpty());
|
||||
var sources=session.getMapper(AppointmentWorkSourceMapper.class);assertTrue(sources.containerSources("1").isEmpty());assertTrue(sources.privateDispatches("1").isEmpty());assertTrue(sources.privateBatch("DISPATCH:1").isEmpty());
|
||||
var receiving=session.getMapper(AppointmentWorkReceivingMapper.class);assertTrue(receiving.plans("1").isEmpty());assertNull(receiving.session("1"));
|
||||
var receiving=session.getMapper(AppointmentWorkReceivingMapper.class);assertTrue(receiving.plans(List.of("1")).isEmpty());assertTrue(receiving.sessions(List.of("1")).isEmpty());
|
||||
try(var sql=session.getConnection().createStatement()) {
|
||||
sql.execute("INSERT INTO ll_order(id,order_no,customer_id,source_region,source_inbound_id,source_event_id,source_loading_plan_id,source_payload_hash,source_container_booking_id) VALUES(101,'O-1','A','TEST','I-1','E-1','P-1','HASH','1'),(102,'O-2','B','TEST','I-2','E-2','P-2','HASH','2')");
|
||||
sql.execute("INSERT INTO ll_inbound_plan(id,plan_no,customer_id,source_order_id,container_no,planned_arrival_date) VALUES(201,'IP-1','A',101,'C-1','2026-10-16'),(202,'IP-2','B',102,'C-2','2026-10-17')");
|
||||
sql.execute("INSERT INTO ll_inbound_plan_line(id,customer_id,plan_id,order_id,inbound_code) VALUES(301,'A',201,101,'IN-1'),(302,'B',202,102,'IN-2'),(303,'WRONG',201,102,'WRONG')");
|
||||
sql.execute("INSERT INTO ll_receiving_container_session(id,source_container_booking_id,container_fact_hash,container_no,overseas_warehouse_id,overseas_warehouse_code,overseas_warehouse_name,operator_key,status,created_at) VALUES(401,'1','HASH','C-1',1,'W','W','A','COMPLETED','2026-10-15 01:00:00'),(402,'1','HASH','C-1',1,'W','W','A','RECEIVING','2026-10-15 02:00:00'),(403,'1','HASH','C-1',1,'W','W','A','SIGNED','2026-10-15 02:00:00'),(404,'2','HASH','C-2',1,'W','W','B','WAIT_SIGN','2026-10-15 01:00:00')");
|
||||
}
|
||||
session.clearCache();
|
||||
var plans=receiving.plans(List.of("1","2"));assertEquals(2,plans.size());
|
||||
assertEquals("1",plans.get(0).getSourceContainerBookingId());assertEquals("IP-1",plans.get(0).getPlanNo());assertEquals(java.time.LocalDate.of(2026,10,16),plans.get(0).getPlannedArrivalDate());
|
||||
assertEquals("2",plans.get(1).getSourceContainerBookingId());
|
||||
var sourceBatch=sources.containerSourcesBatch(List.of("1","2"));assertEquals(2,sourceBatch.size());
|
||||
assertEquals("1",sourceBatch.get(0).containerBookingId());assertEquals(sources.containerSources("1").get(0),sourceBatch.get(0).source());
|
||||
var sessions=receiving.sessions(List.of("1","2"));assertEquals(2,sessions.size());
|
||||
assertEquals(403L,sessions.stream().filter(s->"1".equals(s.getSourceContainerBookingId())).findFirst().orElseThrow().getId());
|
||||
try(var sql=session.getConnection().createStatement()) {
|
||||
sql.execute("INSERT INTO ll_dispatch_order(id,customer_id,dispatch_no,status) VALUES(501,'A','D-1','PENDING_DEPARTURE'),(502,'B','D-2','CANCELLED'),(503,'B','D-3','PENDING_DEPARTURE')");
|
||||
sql.execute("INSERT INTO ll_dispatch_appointment_allocation(dispatch_id,dispatch_item_id,plan_pallet_id,batch_key,customer_id,source_key,destination_key,package_count) VALUES(501,601,701,'FBA_APPOINTMENT:1','A','ORDER:101','FBA:ONT8',4),(501,602,702,'FBA_APPOINTMENT:1','B','ORDER:102','FBA:ONT8',6),(502,603,703,'FBA_APPOINTMENT:1','B','ORDER:102','FBA:ONT8',2),(503,604,704,'FBA_APPOINTMENT:2','B','ORDER:102','FBA:ONT8',3)");
|
||||
}
|
||||
session.clearCache();
|
||||
var batchDeliveries=deliveries.batches(List.of("FBA_APPOINTMENT:1","FBA_APPOINTMENT:2"));assertEquals(3,batchDeliveries.size());
|
||||
assertEquals(deliveries.batch("FBA_APPOINTMENT:1"),batchDeliveries.stream().filter(d->d.batchKey().equals("FBA_APPOINTMENT:1")).map(AppointmentWorkDeliveryMapper.BatchDelivery::delivery).toList());
|
||||
assertEquals(deliveries.batch("FBA_APPOINTMENT:2"),batchDeliveries.stream().filter(d->d.batchKey().equals("FBA_APPOINTMENT:2")).map(AppointmentWorkDeliveryMapper.BatchDelivery::delivery).toList());
|
||||
var trips=session.getMapper(AppointmentWorkTripMapper.class);assertTrue(trips.batchTrips("FBA_APPOINTMENT:1").isEmpty());assertTrue(trips.batches(1).isEmpty());assertNull(trips.trip(1));
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -13,9 +13,10 @@ import static com.baiyue.lastleg.appointmentwork.AppointmentWorkModels.*;
|
||||
class AppointmentWorkReceivingProviderTest {
|
||||
@Test void receivingIsInProgressWithoutInventingAnActualCompletionAndTargetCarriesRealIdentity() {
|
||||
var mapper=mock(AppointmentWorkReceivingMapper.class); var provider=new AppointmentWorkReceivingProvider(mapper);
|
||||
var plan=new InboundPlan(); plan.setId(10L);plan.setPlanNo("IP-REAL");plan.setCustomerId("C");plan.setPlannedArrivalDate(LocalDate.of(2026,10,16));
|
||||
var plan=new AppointmentWorkReceivingMapper.ContainerPlan(); plan.setSourceContainerBookingId("1");plan.setId(10L);plan.setPlanNo("IP-REAL");plan.setCustomerId("C");plan.setPlannedArrivalDate(LocalDate.of(2026,10,16));
|
||||
var session=new ReceivingContainerSession();session.setId(2L);session.setStatus("RECEIVING");session.setReceivingAt(LocalDateTime.of(2026,10,15,10,0));
|
||||
when(mapper.plans("1")).thenReturn(List.of(plan)); when(mapper.session("1")).thenReturn(session);
|
||||
session.setSourceContainerBookingId("1");
|
||||
when(mapper.plans(List.of("1"))).thenReturn(List.of(plan)); when(mapper.sessions(List.of("1"))).thenReturn(List.of(session));
|
||||
var request=new FactRequest("work",Type.WAREHOUSE_RECEIPT,Flow.TRANSFER,"1",null,null,null,List.of(),List.of(),List.of());
|
||||
var fact=provider.read(List.of(request)).get(0);
|
||||
assertEquals(ExecutionStatus.IN_PROGRESS,fact.executionStatus()); assertNull(fact.actual());
|
||||
@@ -23,4 +24,14 @@ class AppointmentWorkReceivingProviderTest {
|
||||
session.setStatus("COMPLETED"); session.setCompletedAt(LocalDateTime.of(2026,10,16,12,0));
|
||||
fact=provider.read(List.of(request)).get(0); assertEquals(ExecutionStatus.COMPLETED,fact.executionStatus()); assertEquals(session.getCompletedAt(),fact.actual().localOccurredAt());
|
||||
}
|
||||
@Test void twoQueriesCoverTwoHundredContainersAndKeepTheirFactsSeparate() {
|
||||
var mapper=mock(AppointmentWorkReceivingMapper.class); var provider=new AppointmentWorkReceivingProvider(mapper);
|
||||
var requests=java.util.stream.IntStream.range(1,201).mapToObj(i->new FactRequest("W"+i,Type.WAREHOUSE_RECEIPT,Flow.TRANSFER,""+i,null,null,null,List.of(),List.of(),List.of())).toList();
|
||||
var plan=new AppointmentWorkReceivingMapper.ContainerPlan();plan.setSourceContainerBookingId("2");plan.setId(20L);plan.setPlanNo("IP-2");plan.setPlannedArrivalDate(LocalDate.of(2026,10,16));
|
||||
when(mapper.plans(org.mockito.ArgumentMatchers.anyList())).thenReturn(List.of(plan));
|
||||
var facts=provider.read(requests);assertEquals(200,facts.size());assertNull(facts.get(0).target());assertEquals(plan.getPlannedArrivalDate(),facts.get(1).target().date());
|
||||
verify(mapper).plans(requests.stream().map(FactRequest::containerBookingId).toList());verify(mapper).sessions(requests.stream().map(FactRequest::containerBookingId).toList());verifyNoMoreInteractions(mapper);
|
||||
when(mapper.plans(org.mockito.ArgumentMatchers.anyList())).thenReturn(java.util.Collections.nCopies(1001,plan));
|
||||
assertThrows(org.springframework.web.server.ResponseStatusException.class,()->provider.read(requests));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user