This commit is contained in:
2026-10-21 18:00:13 +08:00
parent 044812cd90
commit 6d04659ced
5 changed files with 55 additions and 0 deletions
@@ -82,6 +82,8 @@ test('fleet stays a business slice and its policies have no infrastructure depen
'TransportTaskProjection->trip.FleetTripEntity', 'TransportTaskProjection->trip.TransportTripChanged',
'TransportTaskService->business.FleetPoolOrderQueries', 'TransportTaskService->driver.DriverService',
'TransportTaskService->reporting.TripOperationsEvidenceGateway',
// 取货照片沿既有运输边界读取车次身份/时间,并复用司机资格与证照授权;不开放 Mapper。
'TransportReviewPhotos->driver.DriverService', 'TransportReviewPhotos->trip.FleetTripEntity',
].some(reference => `${edge.source}->${edge.target}` === `com.baiyue.lastleg.dispatch.transport.${reference.replace('->', '->com.baiyue.lastleg.fleet.')}`)),
'dispatch must use fleet ports and guards, not its persistence mappers');
const incomingEdges = report.classDependencies.filter(edge => edge.source.startsWith('com.baiyue.lastleg.transfer.') && edge.target.startsWith('com.baiyue.lastleg.fleet.'));
@@ -5253,6 +5253,11 @@ components:
type: object
x-java-record: DispatchContracts.DriverEvidence
properties:
masterLicenseFileId:
type: string
pattern: ^[1-9][0-9]*$
nullable: true
description: 取货排车显式冻结的司机主档驾驶证ID,包含在响应fileIds中;其余取货证照为本车上传。普通派送沿既有MASTER证照语义。
photoFileIds:
type: array
maxItems: 20
@@ -86,6 +86,25 @@ public class DispatchAggregatePersistenceTest {
verify(e.files,never()).download(anyLong(),anyLong());verifyNoInteractions(e.dispatchFiles);
}
/** 取货已保存的补传证照保持本车归属,只有明确主档引用可以读取司机证照。 */
@Test void pickupSavedLicenseUploadsDoNotBecomeMasterLicenseReferences() throws Exception {
var e=new DriverEvidenceFixture();var number=e.dispatch(ResourceMode.MASTER,71L,List.of(81L,91L));
var row=e.f.repository.find(number);
var resources=e.f.json.readValue(row.getResourcesJson(),DispatchContracts.Resources.class);
var d=resources.driver();var proof=d.evidence();
var next=new DispatchContracts.Resources(resources.vehicle(),new DispatchContracts.DriverSnapshot(d.mode(),d.driverId(),d.name(),d.phone(),
new DispatchContracts.DriverEvidence(proof.licenseNo(),proof.licenseExpiresOn(),proof.fileIds(),proof.expectedTaskEndAt(),proof.photoFileIds(),81L)));
e.f.db.update("UPDATE ll_dispatch_order SET task_type='PICKUP', resources_json=? WHERE dispatch_no=?",e.f.json.writeValueAsString(next),number);
var kind=com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER;
var uploaded=new com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.File(91L,"pickup.png","image/png",3L);
when(e.dispatchFiles.metadata(row.getId(),kind,91L)).thenReturn(uploaded);
assertThat(e.f.service.evidenceMetadata(e.f.actor,number,kind,91L)).isEqualTo(uploaded);
verifyNoInteractions(e.driverRows,e.history,e.files);
e.allowFile(81L);
assertThat(e.f.service.evidenceMetadata(e.f.actor,number,kind,81L).fileId()).isEqualTo(81L);
verify(e.files).metadata(71L,81L);
}
/** MASTER改外配的编辑尚未保存时,新上传文件只走本车归属,不误借旧司机白名单。 */
@Test void unsavedExternalUploadOnMasterDispatchRetainsOriginalDispatchFileReads() throws Exception {
var e=new DriverEvidenceFixture();var number=e.dispatch(ResourceMode.MASTER,71L,List.of(81L));
@@ -15,6 +15,27 @@ import static org.mockito.ArgumentMatchers.*;
import static org.assertj.core.api.Assertions.*;
class TransportTaskProjectionTest {
/** 照片写入使审核失效,后续出发、到仓、结案均保留同一资源的照片。 */
@Test void photosInvalidateReviewAndSurviveExecutionProjection() throws Exception {
var roots=mock(DispatchAggregateRepository.class);var links=mock(TransportTaskRepository.class);
var json=new ObjectMapper().findAndRegisterModules();
var projection=new TransportTaskProjection(roots,links,mock(UsTruckOrderRepository.class),mock(DispatchNumberGenerator.class),json);
org.springframework.test.util.ReflectionTestUtils.setField(projection,"pickupFacts",mock(com.baiyue.lastleg.fleet.pickup.FleetPickupRepository.class));
var row=new DispatchAggregate();row.setId(2L);row.setDispatchNo("PC1");row.setTaskType("PICKUP");row.setStatus("ACTIVE");row.setRecordVersion(10L);row.setReviewRound(1L);row.setReviewState("APPROVED");row.setStage("LOADING");
var oldReview=new com.baiyue.lastleg.dispatch.port.DispatchContracts.Review(com.baiyue.lastleg.dispatch.port.DispatchTypes.ReviewStatus.APPROVED,null,"OP",java.time.Instant.now(),1,"basis",null,null);
row.setDispatchReviewJson(json.writeValueAsString(oldReview));row.setLoadingReviewJson(json.writeValueAsString(oldReview));
row.setResourcesJson("{\"vehicle\":{\"mode\":\"MASTER\",\"vehicleId\":7,\"evidenceFileIds\":[]},\"driver\":{\"mode\":\"MASTER\",\"driverId\":8}}");
when(roots.lock("PC1")).thenReturn(row);when(roots.saveState(eq(row),anyLong())).thenReturn(1);when(links.byTrip(1L)).thenReturn(row);
var photos=new com.baiyue.lastleg.dispatch.port.DispatchContracts.DriverEvidence("LICENSE",java.time.LocalDate.of(2099,1,1),java.util.List.of(33L),java.time.Instant.parse("2098-01-01T00:00:00Z"),java.util.List.of(22L),33L);
projection.savePhotos("PC1","OP",java.util.List.of(21L),photos);
assertThat(row.getReviewState()).isEqualTo("DRAFT");assertThat(row.getRecordVersion()).isEqualTo(11L);assertThat(row.getStage()).isEqualTo("BEFORE_LOADING");
var trip=new FleetTripEntity();trip.setId(1L);trip.setTripNo("TR1");trip.setTripPurpose("PICKUP");trip.setVehicleId(7L);trip.setDriverId(8L);trip.setStatus("IN_TRANSIT");
projection.changed(new TransportTripChanged(trip,"PICKUP_PICKED","装货","OP"));
trip.setStatus("COMPLETED");projection.changed(new TransportTripChanged(trip,"PICKUP_ARRIVED","到仓","OP"));
var saved=json.readValue(row.getResourcesJson(),com.baiyue.lastleg.dispatch.port.DispatchContracts.Resources.class);
assertThat(saved.vehicle().evidenceFileIds()).containsExactly(21L);assertThat(saved.driver().evidence()).isEqualTo(photos);
assertThat(row.getStage()).isEqualTo("DELIVERED");
}
/** 已出发也不能跳过照片阶段,全部取货照片确认后才进入派送中。 */
@Test void oldDepartureDoesNotSkipPickupPhotosStage() {
var roots=mock(DispatchAggregateRepository.class);var links=mock(TransportTaskRepository.class);var pickups=mock(com.baiyue.lastleg.fleet.pickup.FleetPickupRepository.class);
@@ -22,6 +22,14 @@ class TransportTaskServiceTest {
DispatchAggregateRepository roots;TransportTaskRepository links;FleetTripRepository tripRepo;FleetTripService trips;
UsTruckOrderRepository trucks;TripOperationsEvidenceGateway files;TransportTaskService service;FleetTripEntity trip;UsTruckOrder order;
final Map<String,String> commands=new HashMap<>();
/** 审核已改变的旧页面不能通过仍相同的车次版本覆盖资源和照片。 */
@Test void staleDispatchVersionCannotAssignEvenWhenTripVersionMatches() {
var root=roots.find("PC1");root.setTaskType("PICKUP");root.setRecordVersion(9L);when(roots.lock("PC1")).thenReturn(root);
trip.setTripPurpose("PICKUP");trip.setStatus("PLANNED");
var photos=mock(TransportReviewPhotos.class);org.springframework.test.util.ReflectionTestUtils.setField(service,"reviewPhotos",photos);
assertThatThrownBy(()->service.assign("PC1","OP",new TransportTaskModels.Assign(3L,7L,8L,"保存",8L,null,null))).hasMessageContaining("刷新");
verify(trips,never()).assign(anyString(),anyString(),any());verifyNoInteractions(photos);
}
@BeforeEach void setup(){
roots=mock(DispatchAggregateRepository.class);links=mock(TransportTaskRepository.class);tripRepo=mock(FleetTripRepository.class);trips=mock(FleetTripService.class);
trucks=mock(UsTruckOrderRepository.class);files=mock(TripOperationsEvidenceGateway.class);