feat(appointments): support scoped receiving and stable trip continuation
This commit is contained in:
+15
@@ -17,6 +17,12 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class FleetTransitEvidenceTest {
|
||||
@Test void removingOriginalPhysicalItemDelegatesToDispatchAuthority() {
|
||||
var service=mock(com.baiyue.lastleg.dispatch.service.DispatchService.class);
|
||||
org.springframework.test.util.ReflectionTestUtils.setField(adapter,"dispatchService",service);
|
||||
adapter.removeItem("PC-1",41L);
|
||||
verify(service).removeItem("PC-1",41L);verifyNoMoreInteractions(service);
|
||||
}
|
||||
@BeforeAll static void initializeDeliveryMetadata() {
|
||||
com.baomidou.mybatisplus.core.metadata.TableInfoHelper.initTableInfo(
|
||||
new org.apache.ibatis.builder.MapperBuilderAssistant(new com.baomidou.mybatisplus.core.MybatisConfiguration(), "fleet-transit-evidence"),
|
||||
@@ -72,11 +78,20 @@ class FleetTransitEvidenceTest {
|
||||
@Test void unsignedReturnRequiresImmutableReturnOperation() {
|
||||
delivery.setStatus("RETURNED"); delivery.setUpdatedAt(LocalDateTime.now());
|
||||
assertThrows(org.springframework.web.server.ResponseStatusException.class, () -> adapter.transitEvidence("PC-1", false));
|
||||
assertThrows(org.springframework.web.server.ResponseStatusException.class, () -> adapter.terminalEvidence("PC-1", false));
|
||||
var returned = LocalDateTime.of(2026, 10, 3, 10, 0);
|
||||
when(operations.returnedAt("CUSTOMER-A", 41L)).thenReturn(returned);
|
||||
when(operations.returnReceipts("CUSTOMER-A", 41L)).thenReturn(List.of(new com.baiyue.lastleg.delivery.model.DeliveryReturnReceiptFact(1L, 51L, 61L, 10, returned)));
|
||||
assertEquals(returned.atZone(ZoneId.systemDefault()).toInstant(),
|
||||
adapter.transitEvidence("PC-1", false).deliveries().get(0).cargo().terminalAt());
|
||||
var terminal=adapter.terminalEvidence("PC-1",false);
|
||||
assertEquals("CUSTOMER-A",terminal.customerId());assertEquals(10,terminal.shippedPackages());
|
||||
var returnedDelivery=terminal.deliveries().get(0);
|
||||
assertEquals("FBA:ONT8",returnedDelivery.destinationKey());assertEquals(10,returnedDelivery.packages());
|
||||
assertEquals(returned.atZone(ZoneId.systemDefault()).toInstant(),returnedDelivery.terminalAt());
|
||||
assertEquals("RETURNED",returnedDelivery.status());assertEquals(0L,returnedDelivery.recordVersion());
|
||||
assertEquals(List.of(new com.baiyue.lastleg.fleet.trip.FleetDispatchGateway.ReturnedReceipt("51","61",10,
|
||||
returned.atZone(ZoneId.systemDefault()).toInstant())),returnedDelivery.receipts());
|
||||
}
|
||||
|
||||
@Test void crossCustomerAndUnbalancedFactsFailClosed() {
|
||||
|
||||
+34
@@ -54,6 +54,40 @@ import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class FleetTripPersistenceTest {
|
||||
@Test void originalBatchReferencesSurviveRemovalReentryAndLaterDispatchReassignment() throws Exception {
|
||||
String number=assignedTrip();dispatch(11,"CLIENT-A","92");dispatch(12,"CLIENT-B","92");
|
||||
var allocations=mock(com.baiyue.lastleg.appointmentwork.AppointmentWorkDispatchPort.class);
|
||||
ReflectionTestUtils.setField(service,"appointmentAllocations",allocations);
|
||||
var a=List.of(batchReference("1","CLIENT-A",6),batchReference("2","CLIENT-A",4));
|
||||
var originalB=List.of(batchReference("3","CLIENT-B",10));
|
||||
var current=new java.util.HashMap<Long,List<com.baiyue.lastleg.appointmentwork.AppointmentWorkDispatchPort.BatchReference>>();current.put(11L,a);current.put(12L,originalB);
|
||||
when(allocations.references(anyList())).thenAnswer(call->{List<Long> ids=call.getArgument(0);return ids.stream().collect(java.util.stream.Collectors.toMap(id->id,id->current.getOrDefault(id,List.of())));});
|
||||
service.bindDispatch(number,"1",new FleetTripModels.BindingCommand(1L,"DP-11","加入甲"));
|
||||
var both=service.bindDispatch(number,"1",new FleetTripModels.BindingCommand(2L,"DP-12","加入乙"));
|
||||
assertThat(both.dispatches().get(0).appointmentBatches()).containsExactlyElementsOf(a);assertThat(both.previousDispatches()).isEmpty();
|
||||
service.unbindDispatch(number,"1",new FleetTripModels.BindingCommand(3L,"DP-12","乙后送"));
|
||||
current.put(12L,List.of(batchReference("4","CLIENT-B",4),batchReference("5","CLIENT-B",6)));
|
||||
var reopened=service.detail(number);
|
||||
assertThat(reopened.dispatches()).extracting(FleetTripModels.DispatchBinding::customerId).containsExactly("CLIENT-A");
|
||||
assertThat(reopened.previousDispatches()).hasSize(1);
|
||||
var previous=reopened.previousDispatches().get(0);assertThat(previous.dispatchNo()).isEqualTo("DP-12");assertThat(previous.customerId()).isEqualTo("CLIENT-B");
|
||||
assertThat(previous.recordVersion()).isEqualTo(3);assertThat(previous.recordedAt()).isNotNull();assertThat(previous.appointmentBatches()).isEqualTo(originalB);
|
||||
var rebound=service.bindDispatch(number,"1",new FleetTripModels.BindingCommand(4L,"DP-12","按当前后送批重入"));
|
||||
assertThat(rebound.dispatches().get(1).appointmentBatches()).isEqualTo(current.get(12L));
|
||||
assertThat(rebound.previousDispatches()).hasSize(1);assertThat(rebound.previousDispatches().get(0).appointmentBatches()).isEqualTo(originalB);
|
||||
var audit=service.history(number,1,20).records().stream().filter(h->h.recordVersion()==3).findFirst().orElseThrow();
|
||||
assertThat(audit.snapshot().dispatches().get(1).appointmentBatches()).isEqualTo(originalB);
|
||||
assertThat(rebound.appointmentHistoryUnavailableReason()).isNull();
|
||||
var json=new ObjectMapper().findAndRegisterModules();
|
||||
var incomplete=json.valueToTree(audit.snapshot());((com.fasterxml.jackson.databind.node.ObjectNode)incomplete.path("dispatches").get(1)).remove("appointmentBatches");
|
||||
jdbc.update("UPDATE ll_fleet_trip_history SET snapshot_json=? WHERE trip_id=(SELECT id FROM ll_fleet_trip WHERE trip_no=?) AND record_version=3",json.writeValueAsString(incomplete),number);
|
||||
var readable=service.detail(number);assertThat(readable.dispatches()).hasSize(2);assertThat(readable.dispatches().get(1).appointmentBatches()).isEqualTo(current.get(12L));
|
||||
assertThat(readable.previousDispatches()).isEmpty();assertThat(readable.appointmentHistoryUnavailableReason()).contains("追踪不完整");
|
||||
}
|
||||
private static com.baiyue.lastleg.appointmentwork.AppointmentWorkDispatchPort.BatchReference batchReference(String id,String customer,int packages) {
|
||||
return new com.baiyue.lastleg.appointmentwork.AppointmentWorkDispatchPort.BatchReference("FBA_APPOINTMENT:"+id,"AP-"+id,customer,
|
||||
List.of(new com.baiyue.lastleg.appointmentwork.AppointmentWorkDispatchPort.BatchSource("ORDER:"+customer,"FBA:ONT8",packages)));
|
||||
}
|
||||
private JdbcTemplate jdbc;
|
||||
private FleetTripService service;
|
||||
private FleetTripPreparationService preparation;
|
||||
|
||||
+9
@@ -11,6 +11,15 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class AppointmentWorkTransportProviderTest {
|
||||
@Test void linkedVehicleCarriesIndependentCargoAndCommonPlanVersions() {
|
||||
var mapper=mock(AppointmentWorkTripMapper.class);var commitments=mock(AppointmentWorkCommitmentService.class);
|
||||
var trip=new com.baiyue.lastleg.fleet.trip.FleetTripEntity();trip.setId(10L);trip.setTripNo("TR-10");
|
||||
when(mapper.trip(10)).thenReturn(trip);when(commitments.impactToken("TRIP:10")).thenReturn("TRIP:10:2");
|
||||
when(commitments.impactToken("TRIP_PLAN:10")).thenReturn("TRIP_PLAN:10:0");
|
||||
var request=new FactRequest("W",Type.TRANSPORT,Flow.TRANSFER,"C",null,null,"10",List.of(),List.of(),List.of());
|
||||
var fact=new AppointmentWorkTransportProvider(mapper,mock(FleetWarehouseClient.class),commitments,"token").read(List.of(request)).get(0);
|
||||
assertEquals("TRIP:10:2",fact.impactToken());assertEquals("TRIP_PLAN:10:0",fact.dependentImpactToken());
|
||||
}
|
||||
@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();
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.baiyue.lastleg.fleet.trip.work;
|
||||
|
||||
import com.baiyue.lastleg.appointmentwork.*;
|
||||
import com.baiyue.lastleg.fleet.trip.FleetTripEntity;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class AppointmentWorkTripChangesTest {
|
||||
@Test void membershipAndRouteChangesReviewVehicleWhileOnlyCommonPlanChangesAffectOtherAppointments() {
|
||||
var rows=new HashMap<String,AppointmentWorkMapper.CommitmentRow>();
|
||||
var persistence=mock(AppointmentWorkMapper.class);
|
||||
when(persistence.lockCommitment(anyString())).thenAnswer(c->rows.get(c.getArgument(0)));
|
||||
when(persistence.commitment(anyString())).thenAnswer(c->rows.get(c.getArgument(0)));
|
||||
when(persistence.insertCommitment(anyString(),anyString())).thenAnswer(c->{
|
||||
var row=new AppointmentWorkMapper.CommitmentRow();row.setEntityKey(c.getArgument(0));row.setStateJson(c.getArgument(1));
|
||||
rows.put(row.getEntityKey(),row);return 1;
|
||||
});
|
||||
when(persistence.updateCommitment(anyString(),anyString(),anyLong())).thenAnswer(c->{
|
||||
var row=rows.get(c.getArgument(0));row.setStateJson(c.getArgument(1));row.setImpactVersion(c.getArgument(2));return 1;
|
||||
});
|
||||
var commitments=new AppointmentWorkCommitmentService(persistence,new ObjectMapper().findAndRegisterModules());
|
||||
var mapper=mock(AppointmentWorkTripMapper.class);
|
||||
var changes=new AppointmentWorkTripChanges(mock(AppointmentWorkChanges.class),commitments,mapper);
|
||||
var trip=new FleetTripEntity();trip.setId(10L);trip.setVehicleId(20L);trip.setDriverId(30L);trip.setStatus("PLANNED");
|
||||
trip.setStopsJson("[A,B,C]");trip.setPlannedDepartureAt(LocalDateTime.parse("2026-10-17T10:00:00"));
|
||||
trip.setExpectedDriverEndAt(LocalDateTime.parse("2026-10-17T20:00:00"));
|
||||
when(mapper.batches(10)).thenReturn(List.of("A","B","C"));changes.changed(trip);
|
||||
assertEquals("TRIP:10:0",commitments.impactToken("TRIP:10"));assertEquals("TRIP_PLAN:10:0",commitments.impactToken("TRIP_PLAN:10"));
|
||||
when(mapper.batches(10)).thenReturn(List.of("C","B","A"));changes.changed(trip);
|
||||
assertEquals("TRIP:10:0",commitments.impactToken("TRIP:10"));
|
||||
when(mapper.batches(10)).thenReturn(List.of("A","C"));trip.setStopsJson("[A,C]");changes.changed(trip);
|
||||
assertEquals("TRIP:10:1",commitments.impactToken("TRIP:10"));assertEquals("TRIP_PLAN:10:0",commitments.impactToken("TRIP_PLAN:10"));
|
||||
changes.changed(trip);assertEquals("TRIP:10:1",commitments.impactToken("TRIP:10"));
|
||||
trip.setVehicleId(21L);changes.changed(trip);assertEquals("TRIP_PLAN:10:1",commitments.impactToken("TRIP_PLAN:10"));
|
||||
trip.setDriverId(31L);changes.changed(trip);assertEquals("TRIP_PLAN:10:2",commitments.impactToken("TRIP_PLAN:10"));
|
||||
trip.setPlannedDepartureAt(trip.getPlannedDepartureAt().plusHours(1));changes.changed(trip);
|
||||
assertEquals("TRIP_PLAN:10:3",commitments.impactToken("TRIP_PLAN:10"));
|
||||
trip.setStatus("CANCELLED");changes.changed(trip);assertEquals("TRIP_PLAN:10:4",commitments.impactToken("TRIP_PLAN:10"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user