fix: count split picking facts in trip operations

This commit is contained in:
2026-10-23 00:38:30 +08:00
parent f74861baea
commit 40d1b06312
2 changed files with 37 additions and 4 deletions
@@ -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<String,String>(); var seen=new HashSet<String>();
long own=0,external=0; var destinations=new TreeMap<String,String>();
var cargoIdentity=new HashMap<String,List<String>>();
var shippedByPallet=new HashMap<String,Long>();
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("外接实收超过实际发车货量");
@@ -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);