diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchAggregateService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchAggregateService.java index f800355ba..a527c7122 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchAggregateService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchAggregateService.java @@ -1307,14 +1307,14 @@ public class DispatchAggregateService implements DispatchExecutionPort,DispatchA public long pickedRemotePackages(DispatchAggregate row){return looseboardFacts==null?0:looseboardFacts.pickedRemotePackages(row);} /** 仓内实物作业范围,普通未收货计划仍保留并按原规则阻断。 */ public List warehouseItems(DispatchAggregate row){return looseboardFacts==null?repository.cargo(row.getId()):looseboardFacts.warehouseItems(row.getId());} - /** 异地取货仍占用本趟容量;使用仓内实测加散板申报,不补造实托或实测。 */ + /** 混排核对已知占用;未知重量体积不阻塞仓内出库,也不补造实测。 */ public void requireRemoteCapacity(DispatchAggregate row,List measured){ var ids=remoteItemIds(row);if(ids.isEmpty())return; var remote=repository.cargo(row.getId()).stream().filter(i->ids.contains(i.getId())).toList(); var capacity=read(row.getResourcesJson(),Resources.class).vehicle().capacity(); - if(remote.stream().anyMatch(i->i.getGrossWeightKg()==null || i.getVolumeCbm()==null) || measured.stream().anyMatch(m->m.weightKg()==null || m.volumeCbm()==null))throw conflict("混排实测与异地取货申报货量未明确"); - var weight=remote.stream().map(DispatchItem::getGrossWeightKg).reduce(java.math.BigDecimal.ZERO,java.math.BigDecimal::add).add(measured.stream().map(LoadingMeasurement::weightKg).reduce(java.math.BigDecimal.ZERO,java.math.BigDecimal::add)); - var volume=remote.stream().map(DispatchItem::getVolumeCbm).reduce(java.math.BigDecimal.ZERO,java.math.BigDecimal::add).add(measured.stream().map(LoadingMeasurement::volumeCbm).reduce(java.math.BigDecimal.ZERO,java.math.BigDecimal::add)); + // 只累加已知值作为占用下限;某项未知不能掩盖其余已知货量已经超载的事实。 + var weight=remote.stream().map(DispatchItem::getGrossWeightKg).filter(Objects::nonNull).reduce(java.math.BigDecimal.ZERO,java.math.BigDecimal::add).add(measured.stream().map(LoadingMeasurement::weightKg).filter(Objects::nonNull).reduce(java.math.BigDecimal.ZERO,java.math.BigDecimal::add)); + var volume=remote.stream().map(DispatchItem::getVolumeCbm).filter(Objects::nonNull).reduce(java.math.BigDecimal.ZERO,java.math.BigDecimal::add).add(measured.stream().map(LoadingMeasurement::volumeCbm).filter(Objects::nonNull).reduce(java.math.BigDecimal.ZERO,java.math.BigDecimal::add)); if(remote.size()+measured.stream().mapToInt(LoadingMeasurement::palletSpaces).sum()>capacity.palletSpaces() || weight.compareTo(capacity.maxLoadKg())>0 || volume.compareTo(capacity.maxVolumeCbm())>0)throw conflict("仓内装载与异地取货合计超过本车容量"); } /** 取货照片保存事务复验已确认仓内实载与当前异地申报。 */ diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchMixedCapacityTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchMixedCapacityTest.java new file mode 100644 index 000000000..0946d1bb8 --- /dev/null +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/dispatch/service/DispatchMixedCapacityTest.java @@ -0,0 +1,67 @@ +package com.baiyue.lastleg.dispatch.service; + +import com.baiyue.lastleg.dispatch.entity.DispatchAggregate; +import com.baiyue.lastleg.dispatch.entity.DispatchItem; +import com.baiyue.lastleg.dispatch.repository.DispatchAggregateRepository; +import com.baiyue.lastleg.dispatch.port.DispatchContracts.LoadingMeasurement; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; +import java.math.BigDecimal; +import java.util.List; +import java.util.Set; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** 混排仓内出库允许未知实测,但已知容量超限仍必须拒绝。 */ +class DispatchMixedCapacityTest { + /** 仓内九箱已核平时,缺少重量体积不应额外要求异地货补测。 */ + @Test void unknownMeasurementsDoNotBlockWarehouseLoading() { + var f=new Fixture(null,null); + assertThatCode(()->f.service.requireRemoteCapacity(f.row,f.measured(null,null,1))).doesNotThrowAnyException(); + } + /** 部分未知不能掩盖已知重量已超载。 */ + @Test void knownWeightStillBlocksWithUnknownVolume() { + var f=new Fixture("600",null); + assertThatThrownBy(()->f.service.requireRemoteCapacity(f.row,f.measured("401",null,1))).hasMessageContaining("超过本车容量"); + } + /** 部分未知不能掩盖已知体积已超载。 */ + @Test void knownVolumeStillBlocksWithUnknownWeight() { + var f=new Fixture(null,"30"); + assertThatThrownBy(()->f.service.requireRemoteCapacity(f.row,f.measured(null,"21",1))).hasMessageContaining("超过本车容量"); + } + /** 无重量体积依据时,明确的托位上限仍然有效。 */ + @Test void palletSpacesRemainLimited() { + var f=new Fixture(null,null); + assertThatThrownBy(()->f.service.requireRemoteCapacity(f.row,f.measured(null,null,10))).hasMessageContaining("超过本车容量"); + } + /** 已知占用恰好达到容量时允许确认。 */ + @Test void exactKnownCapacityIsAllowed() { + var f=new Fixture("600","30"); + assertThatCode(()->f.service.requireRemoteCapacity(f.row,f.measured("400","20",9))).doesNotThrowAnyException(); + } + /** 隔离容量校验,异地身份及货量均由正式依赖返回。 */ + private static class Fixture { + final DispatchAggregateService service=mock(DispatchAggregateService.class,CALLS_REAL_METHODS); + final DispatchAggregate row=new DispatchAggregate(); + /** 构造一条异地取货来源与车辆真实容量,不制造未知实测。 */ + Fixture(String weight,String volume) { + var repository=mock(DispatchAggregateRepository.class); + var facts=mock(DispatchLooseboardFacts.class); + ReflectionTestUtils.setField(service,"repository",repository); + ReflectionTestUtils.setField(service,"looseboardFacts",facts); + ReflectionTestUtils.setField(service,"json",new ObjectMapper()); + row.setId(1L); + row.setResourcesJson("{\"vehicle\":{\"capacity\":{\"palletSpaces\":10,\"maxLoadKg\":1000,\"maxVolumeCbm\":50}}}"); + var remote=new DispatchItem();remote.setId(2L); + remote.setGrossWeightKg(weight==null?null:new BigDecimal(weight)); + remote.setVolumeCbm(volume==null?null:new BigDecimal(volume)); + when(facts.remoteItemIds(1L)).thenReturn(Set.of(2L)); + when(repository.cargo(1L)).thenReturn(List.of(remote)); + } + /** 保留仓内原始可空测量值供校验使用。 */ + List measured(String weight,String volume,int spaces) { + return List.of(new LoadingMeasurement(3L,spaces,weight==null?null:new BigDecimal(weight),volume==null?null:new BigDecimal(volume),1)); + } + } +}