From 40d1b06312ad13b5f68dac0f69be16fb755afd85 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 23 Oct 2026 00:38:30 +0800 Subject: [PATCH] fix: count split picking facts in trip operations --- .../reporting/TripOperationsService.java | 14 +++++++--- .../reporting/TripOperationsServiceTest.java | 27 +++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/reporting/TripOperationsService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/reporting/TripOperationsService.java index 9460c8b9d..0826034ea 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/reporting/TripOperationsService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/reporting/TripOperationsService.java @@ -81,13 +81,19 @@ public class TripOperationsService { if(!pallets.add(pallet.warehousePalletId())) throw new IllegalStateException("发车实物托盘重复"); spaces+=pallet.palletSpaces(); } - long own=0,external=0; var destinations=new TreeMap(); var seen=new HashSet(); + long own=0,external=0; var destinations=new TreeMap(); + var cargoIdentity=new HashMap>(); var shippedByPallet=new HashMap(); for(var cargo:load.cargo()) { - if (!pallets.contains(cargo.warehousePalletId()) || !seen.add(cargo.dispatchNo()+":"+cargo.dispatchItemId())) - throw new IllegalStateException("发车装载货物未核平或重复"); - shippedByPallet.merge(cargo.dispatchNo()+":"+cargo.customerId()+":"+cargo.warehousePalletId(),(long)cargo.packages(),Long::sum); + if (!pallets.contains(cargo.warehousePalletId()) || cargo.packages()<=0 + || cargo.dispatchNo()==null || cargo.dispatchItemId()==null || cargo.customerId()==null) + throw new IllegalStateException("发车装载货物未核平"); if(cargo.destinationKey()==null || cargo.destinationLabel()==null)throw new IllegalStateException("发车货物缺少目的地依据"); + // 装车快照按真实拣货来源逐段记录,同一排车明细可含多次拣货;逐段累加但归属不能漂移。 + var identity=List.of(cargo.customerId(),cargo.warehousePalletId(),cargo.destinationKey(),cargo.destinationLabel()); + var previous=cargoIdentity.putIfAbsent(cargo.dispatchNo()+":"+cargo.dispatchItemId(),identity); + if(previous!=null && !previous.equals(identity))throw new IllegalStateException("发车装载明细归属不一致"); + shippedByPallet.merge(cargo.dispatchNo()+":"+cargo.customerId()+":"+cargo.warehousePalletId(),(long)cargo.packages(),Long::sum); destinations.put(cargo.destinationKey(),cargo.destinationLabel()); } for(var entry:externalByPallet.entrySet()) if(entry.getValue()>shippedByPallet.getOrDefault(entry.getKey(),0L))throw new IllegalStateException("外接实收超过实际发车货量"); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/fleet/reporting/TripOperationsServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/fleet/reporting/TripOperationsServiceTest.java index 986e0f673..d8cf8267a 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/fleet/reporting/TripOperationsServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/fleet/reporting/TripOperationsServiceTest.java @@ -34,6 +34,33 @@ class TripOperationsServiceTest { assertThatThrownBy(()->service.project(new TripOperationsRepository.Departure("TR-A","IN_TRANSIT",null,null,null))).hasMessageContaining("不可变历史"); assertThatThrownBy(()->service.project(new TripOperationsRepository.Departure("TR-A","IN_TRANSIT",null,DEPARTURE.replace("\"receivedPackages\":4","\"receivedPackages\":20").replace("\"packages\":4","\"packages\":20"),null))).hasMessageContaining("超过实际发车"); } + /** 同一排车明细的分次拣货必须逐段计数,实托只计一次且客户和目的地仍严格核对。 */ + @Test void splitPicksOfOneDispatchItemSumWithoutDuplicatingPhysicalPallets() throws Exception { + var mapper=new ObjectMapper().registerModule(new JavaTimeModule()); + var snapshot=mapper.readTree(DEPARTURE); + var cargo=(com.fasterxml.jackson.databind.node.ArrayNode)snapshot.path("confirmedLoad").path("cargo"); + var first=(com.fasterxml.jackson.databind.node.ObjectNode)cargo.get(0); + first.put("packages",2); + var second=first.deepCopy().put("packages",8); + cargo.add(second); + var service=new TripOperationsService(mock(TripOperationsRepository.class),mapper,mock(FleetTripCostService.class)); + var row=service.project(new TripOperationsRepository.Departure("TR-A","IN_TRANSIT",null,mapper.writeValueAsString(snapshot),null)); + assertThat(row.ownPackages()).isEqualTo(11); + assertThat(row.externalPackages()).isEqualTo(4); + assertThat(row.physicalPallets()).isEqualTo(1); + assertThat(row.palletSpaces()).isEqualTo(2); + for(String field:List.of("customerId","destinationKey","destinationLabel","warehousePalletId")) { + var invalid=snapshot.deepCopy(); + ((com.fasterxml.jackson.databind.node.ObjectNode)invalid.path("confirmedLoad").path("cargo").get(2)).put(field,"OTHER"); + String value=mapper.writeValueAsString(invalid); + assertThatThrownBy(()->service.project(new TripOperationsRepository.Departure("TR-A","IN_TRANSIT",null,value,null))) + .isInstanceOf(IllegalStateException.class).hasMessageContaining("发车装载"); + } + second.put("packages",0); + String zero=mapper.writeValueAsString(snapshot); + assertThatThrownBy(()->service.project(new TripOperationsRepository.Departure("TR-A","IN_TRANSIT",null,zero,null))) + .hasMessageContaining("未核平"); + } @Test void summaryUsesAllFilteredDeparturesNotCurrentPageAndSeparatesCurrencies() { var repo=mock(TripOperationsRepository.class);var service=new TripOperationsService(repo,new ObjectMapper().registerModule(new JavaTimeModule()),mock(FleetTripCostService.class)); var query=new TripOperationsModels.Query(1,1,Instant.parse("2026-10-01T00:00:00Z"),Instant.parse("2026-10-03T00:00:00Z"),null,null,"EXTERNAL",null);