From 6d04659ced96c7cff27ed81cfc483bf2a07ba6be Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Wed, 21 Oct 2026 18:00:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../analyze-lastleg-dependencies.test.mjs | 2 ++ .../openapi/overseas-business-v1.yaml | 5 +++++ .../DispatchAggregatePersistenceTest.java | 19 +++++++++++++++++ .../TransportTaskProjectionTest.java | 21 +++++++++++++++++++ .../transport/TransportTaskServiceTest.java | 8 +++++++ 5 files changed, 55 insertions(+) diff --git a/lastleg-service/scripts/analyze-lastleg-dependencies.test.mjs b/lastleg-service/scripts/analyze-lastleg-dependencies.test.mjs index b8baaae29..b1b7ab34f 100644 --- a/lastleg-service/scripts/analyze-lastleg-dependencies.test.mjs +++ b/lastleg-service/scripts/analyze-lastleg-dependencies.test.mjs @@ -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.')); diff --git a/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml b/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml index b1213ba0a..cae109929 100644 --- a/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml +++ b/lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml @@ -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 diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchAggregatePersistenceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchAggregatePersistenceTest.java index 672223c04..ffefff7f4 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchAggregatePersistenceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchAggregatePersistenceTest.java @@ -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)); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/transport/TransportTaskProjectionTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/transport/TransportTaskProjectionTest.java index cac9de227..8a8448185 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/transport/TransportTaskProjectionTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/transport/TransportTaskProjectionTest.java @@ -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); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/transport/TransportTaskServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/transport/TransportTaskServiceTest.java index 93563ceec..7817a3072 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/transport/TransportTaskServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/transport/TransportTaskServiceTest.java @@ -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 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);