This commit is contained in:
2026-11-06 10:41:49 +08:00
parent 44202a7bff
commit 9e99658bfa
25 changed files with 656 additions and 35 deletions
@@ -11,7 +11,7 @@ import java.util.UUID;
import static org.assertj.core.api.Assertions.*;
class MerchantTransportPhotoMapperTest {
/** 同编号、混装和撤销安排均不能扩大客户读取范围,实际查询使用正式Mapper。 */
/** 同单分托去重且分车全部保留;同编号、混装和撤销安排不扩大客户读取范围。 */
@Test void queriesOnlyExactCustomerOrderAndActiveDispatches() throws Exception {
var data=new DriverManagerDataSource("jdbc:h2:mem:"+UUID.randomUUID()+";MODE=MySQL;DB_CLOSE_DELAY=-1","sa","");
var db=new JdbcTemplate(data);
@@ -24,13 +24,13 @@ class MerchantTransportPhotoMapperTest {
db.update("INSERT INTO ll_local_looseboard_order VALUES('C1','LB1',1),('C2','LB1',2)");
db.update("INSERT INTO ll_inbound_plan VALUES(11,'C1',1),(12,'C2',2)");
db.update("INSERT INTO ll_inbound_plan_pallet VALUES(21,11,'C1'),(22,12,'C2')");
db.update("INSERT INTO ll_dispatch_order VALUES(31,'PC1','ACTIVE','DELIVERY'),(32,'PC2','CANCELLED','DELIVERY'),(33,'PC3','ACTIVE','LOCAL_TRUCK')");
db.update("INSERT INTO ll_dispatch_item VALUES(21,'C1',31),(22,'C2',31),(21,'C1',32),(22,'C1',33)");
db.update("INSERT INTO ll_dispatch_order VALUES(31,'PC1','ACTIVE','DELIVERY'),(32,'PC2','CANCELLED','DELIVERY'),(33,'PC3','ACTIVE','LOCAL_TRUCK'),(34,'PC4','ACTIVE','DELIVERY')");
db.update("INSERT INTO ll_dispatch_item VALUES(21,'C1',31),(21,'C1',31),(22,'C2',31),(21,'C1',32),(22,'C1',33),(21,'C1',34)");
db.update("INSERT INTO ll_dispatch_truck_task VALUES('PC3','C1','TR1','C1:TR1'),('PC2','C1','TR1','C1:TR1'),('PC1','C2','TR1','C2:TR1')");
var config=new MybatisConfiguration();config.addMapper(MerchantTransportPhotoMapper.class);
var factory=new MybatisSqlSessionFactoryBean();factory.setDataSource(data);factory.setConfiguration(config);factory.afterPropertiesSet();
var repository=new MerchantTransportPhotoRepository(new SqlSessionTemplate(factory.getObject()).getMapper(MerchantTransportPhotoMapper.class));
assertThat(repository.looseboardDispatches("C1","LB1")).containsExactly("PC1");
assertThat(repository.looseboardDispatches("C1","LB1")).containsExactly("PC1","PC4");
assertThat(repository.looseboardDispatches("C3","LB1")).isEmpty();
assertThat(repository.truckDispatches("C1","TR1")).containsExactly("PC3");
assertThat(repository.truckDispatches("C2","TR1")).isEmpty();
@@ -8,12 +8,64 @@ import com.baiyue.lastleg.dispatch.port.*;
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.*;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.*;
import static org.assertj.core.api.Assertions.*;
class DispatchLooseboardDisplayServiceTest {
/** 维护车辆与排车详情共用订单投影,同单两托只展示一行且未知重量不补零。 */
@Test void looseboardOrderCombinesPalletsAndRetainsAllSourceKeys() {
var repository=mock(LooseboardCargoRepository.class);
when(repository.businessSources(any())).thenReturn(List.of(new BusinessSource("A","ZY1","LB202611050003",11L),new BusinessSource("A","ZY2","LB202611050002",13L)));
var first=item("A","ZY1",11L,"ZY1");var second=item("A","ZY1",12L,"ZY1");var other=item("A","ZY2",13L,"ZY2");
first.setVolumeCbm(new BigDecimal("5"));second.setVolumeCbm(new BigDecimal("5"));other.setVolumeCbm(new BigDecimal("4"));
var result=details(repository,List.of(first,second,other));
assertThat(result).hasSize(2);assertThat(result.get(0).orderNo()).isEqualTo("LB202611050003");
assertThat(result.get(0).palletCount()).isEqualTo(2);assertThat(result.get(0).volumeCbm()).isEqualByComparingTo("10");
assertThat(result.get(0).grossWeightKg()).isNull();assertThat(result.get(0).sourceKeys()).containsExactly("PLAN:11","PLAN:12");
assertThat(result.get(1).palletCount()).isEqualTo(1);assertThat(result.get(1).volumeCbm()).isEqualByComparingTo("4");
assertThat(first.getOrderNo()).isEqualTo("ZY1");assertThat(second.getVolumeCbm()).isEqualByComparingTo("5");
}
/** 同号跨客户不能合并,普通来源不能仅因编号类似散板而变成散板。 */
@Test void sameOrderNumberKeepsCustomerAndOrdinarySourceBoundaries() {
var repository=mock(LooseboardCargoRepository.class);
when(repository.businessSources(any())).thenReturn(List.of(new BusinessSource("A","ZY1","LB1",11L),new BusinessSource("B","ZY1","LB1",12L)));
var result=details(repository,List.of(item("A","ZY1",11L,"ZY1"),item("B","ZY1",12L,"ZY1"),item("C","LB1",13L,"REAL"),item("C","LB1",14L,"REAL")));
assertThat(result).hasSize(4);assertThat(result).extracting(DispatchViews.OrderDetail::customerId).containsExactly("A","B","C","C");
assertThat(result.get(2).sourceBusinessType()).isNull();assertThat(result.get(2).containerNo()).isEqualTo("REAL");
}
/** 同一实托关联多个来源只计一托和一次整托测量;缺测量的订单总量仍未知。 */
@Test void sharedPhysicalPalletIsCountedOnceAndPartialMeasurementRemainsUnknown() {
var repository=mock(LooseboardCargoRepository.class);
when(repository.businessSources(any())).thenReturn(List.of(new BusinessSource("A","ZY1","LB1",11L)));
var first=item("A","ZY1",11L,"ZY1");var second=item("A","ZY1",12L,"ZY1");
first.setWarehousePalletId(91L);second.setWarehousePalletId(91L);
first.setGrossWeightKg(new BigDecimal("30"));second.setGrossWeightKg(new BigDecimal("30.00"));
first.setVolumeCbm(new BigDecimal("5"));second.setVolumeCbm(new BigDecimal("5.0"));
var combined=details(repository,List.of(first,second)).get(0);
assertThat(combined.palletCount()).isEqualTo(1);assertThat(combined.volumeCbm()).isEqualByComparingTo("5");assertThat(combined.grossWeightKg()).isEqualByComparingTo("30");
second.setWarehousePalletId(92L);second.setVolumeCbm(null);
var partial=details(repository,List.of(first,second)).get(0);
assertThat(partial.palletCount()).isEqualTo(2);assertThat(partial.volumeCbm()).isNull();assertThat(partial.grossWeightKg()).isEqualByComparingTo("60");
second.setWarehousePalletId(91L);second.setGrossWeightKg(new BigDecimal("31"));
assertThat(details(repository,List.of(first,second)).get(0).grossWeightKg()).isNull();
}
/** 混托多订单无法分摊测量时,不给散板复制整托重量、体积或托数。 */
@Test void sharedPalletWithMultipleOrdersDoesNotInventPerOrderMeasurements() {
var repository=mock(LooseboardCargoRepository.class);
when(repository.businessSources(any())).thenReturn(List.of(new BusinessSource("A","ZY1","LB1",11L)));
var mixed=item("A","ZY1",11L,"ZY1");mixed.setOrderRefs("ZY1,OTHER");mixed.setGrossWeightKg(new BigDecimal("30"));mixed.setVolumeCbm(new BigDecimal("5"));
var result=details(repository,List.of(mixed));
assertThat(result).hasSize(1);assertThat(result.get(0).palletCount()).isNull();assertThat(result.get(0).grossWeightKg()).isNull();assertThat(result.get(0).volumeCbm()).isNull();
}
/** 调用正式排车关联订单投影,避免为聚合规则复制一份测试实现。 */
private List<DispatchViews.OrderDetail> details(LooseboardCargoRepository repository,List<DispatchItem> cargo) {
var aggregate=new DispatchAggregateService(null,null,null,null,null,null,new ObjectMapper());
ReflectionTestUtils.setField(aggregate,"looseboardDisplay",new DispatchLooseboardDisplayService(repository));
return ReflectionTestUtils.invokeMethod(aggregate,"orderDetails",cargo);
}
/** 历史排车仍按真实关联显示LB原单;不把内部ZY号代填为入仓码或集装箱号。 */
@Test void historicalDetailsUseBusinessOrderAndNoInventedContainer() {
var repository=mock(LooseboardCargoRepository.class);
@@ -64,6 +116,7 @@ class DispatchLooseboardDisplayServiceTest {
/** 构造旧排车保存的履约快照,验证读取时纠正且不改原来源。 */
private DispatchItem item(String customer,String number,long pallet,String container) {
var item=new DispatchItem();item.setCustomerId(customer);item.setOrderNo(number);item.setOrderRefs(number);item.setPlanPalletId(pallet);item.setInboundCode(number);
item.setSourceKey("PLAN:"+pallet);
item.setSourceSnapshotJson("{\"containerNo\":\""+container+"\",\"planNo\":\"plan\"}");return item;
}
}
@@ -207,7 +207,7 @@ class ExceptionOrderLifecycleTest {
var order = order("PENDING", 1);
order.setExceptionType("RETURN"); order.setDeliveryNo("DL-1");
order.setReturnContextJson("{\"deliveryStatus\":\"DELIVERED\",\"tailId\":null,\"tailStatus\":null,\"abnormal\":null}");
var delivery = new DeliveryOrder(); delivery.setStatus("RETURN_PENDING"); delivery.setVersion(3L);
var delivery = new DeliveryOrder(); delivery.setId(71L); delivery.setStatus("RETURN_PENDING"); delivery.setVersion(3L);
var signedAt = LocalDateTime.of(2026, 9, 1, 12, 0); delivery.setSignedAt(signedAt);
when(exceptionOrders.lockByNo("1001", "EX-1")).thenReturn(order);
when(deliveryOrders.lockByNo("1001", "DL-1")).thenReturn(delivery);
@@ -36,7 +36,7 @@ class ReturnCartonReturnPersistenceTest {
var data=new DriverManagerDataSource("jdbc:h2:mem:"+UUID.randomUUID()+";MODE=MySQL;DB_CLOSE_DELAY=-1","sa","");
db=new JdbcTemplate(data);tx=new TransactionTemplate(new DataSourceTransactionManager(data));
var config=new MybatisConfiguration();config.setMapUnderscoreToCamelCase(true);
var mappers=List.of(ExceptionOrderMapper.class,ExceptionOrderCommandMapper.class,com.baiyue.lastleg.mapper.LastlegOrderMapper.class,com.baiyue.lastleg.mapper.LastlegInboundOrderMapper.class,ExceptionReturnItemMapper.class,ReturnCartonStockReceiptMapper.class,
var mappers=List.of(ExceptionWorkflowMapper.class,ExceptionOrderMapper.class,ExceptionOrderCommandMapper.class,com.baiyue.lastleg.mapper.LastlegOrderMapper.class,com.baiyue.lastleg.mapper.LastlegInboundOrderMapper.class,ExceptionReturnItemMapper.class,ReturnCartonStockReceiptMapper.class,
ReturnInventoryCartonMapper.class,ReturnInventoryCartonItemMapper.class,ReturnInventoryBalanceMapper.class,ReturnInventoryMovementMapper.class,
DeliveryOrderMapper.class,OutboundOrderMapper.class,OutboundPalletItemMapper.class,OutboundPdaPickMapper.class,OutboundPdaTaskMapper.class,DispatchAggregateMapper.class,DispatchReadMapper.class,DispatchItemMapper.class,DispatchFeeCargoMapper.class,
DispatchPdaTaskMapper.class,DispatchPdaPickMapper.class,DispatchPdaPickOrderMapper.class,DeliveryOperationMapper.class);
@@ -165,6 +165,75 @@ class ReturnCartonReturnPersistenceTest {
assertThatThrownBy(()->tx.execute(t->business.markReturn("C","OP","操作员","DL-1",foreign))).hasMessageContaining("主单不属于该派送单");
assertThat(db.queryForObject("SELECT COUNT(*) FROM ll_exception_order",Long.class)).isZero();
}
/** 同派送两张原单可一起退回,部分签收数量保留,同键重放不增异常。 */
@Test void multipleMastersOfOneDeliveryFreezeTogetherAndReplayWithoutDuplicateExceptions() {
var requests=prepareTwoMasterReturns();var business=business();
var created=tx.execute(t->business.markReturns("C","OP","操作员","DL-1",requests));
assertThat(created).hasSize(2);
assertThat(db.queryForList("SELECT last_mile_order_id FROM ll_exception_order ORDER BY last_mile_order_id",Long.class)).containsExactly(100L,101L);
assertThat(db.queryForObject("SELECT SUM(approved_return_packages) FROM ll_exception_return_item",Long.class)).isEqualTo(2);
assertThat(db.queryForObject("SELECT signed_packages FROM ll_delivery_order WHERE id=3",Long.class)).isEqualTo(2);
var replay=tx.execute(t->business.markReturns("C","OP","操作员","DL-1",requests));
assertThat(replay).allSatisfy(row->assertThat(row.replayed()).isTrue());
assertThat(db.queryForObject("SELECT COUNT(*) FROM ll_exception_order",Long.class)).isEqualTo(2);
}
/** 从排车适配器传入两张原单,真实服务分别建异常;各来源仍保持独立。 */
@Test void dispatchAdapterCreatesBothReturnsThroughRealService() {
var requests=prepareTwoMasterReturns();var business=business();
var repository=mock(com.baiyue.lastleg.exceptionorder.repository.ExceptionWorkflowRepository.class);
when(repository.deliveries(10L)).thenReturn(List.of(new com.baiyue.lastleg.exceptionorder.mapper.ExceptionWorkflowMapper.Delivery("C","DL-1")));
var adapter=new DispatchExceptionAdapter(repository,business);
var selections=requests.stream().map(r->new com.baiyue.lastleg.dispatch.port.DispatchExceptionPort.Selection("DL-1",r.lastMileOrderId(),
r.items().stream().map(i->new com.baiyue.lastleg.dispatch.port.DispatchExceptionPort.Item(i.outboundPalletItemId(),i.inventoryPositionId(),i.returnCartonId(),i.packageCount())).toList())).toList();
var command=new com.baiyue.lastleg.dispatch.port.DispatchCommands.MarkException(0L,"adapter-multiple",com.baiyue.lastleg.dispatch.port.DispatchTypes.ExceptionType.PARTIAL_RECEIPT,
com.baiyue.lastleg.dispatch.port.DispatchTypes.ReturnDirection.OVERSEAS_WAREHOUSE,"多单部分签收",List.of(),selections);
tx.executeWithoutResult(t->adapter.register(10L,"OP",command));
assertThat(db.queryForList("SELECT last_mile_order_id FROM ll_exception_order ORDER BY last_mile_order_id",Long.class)).containsExactly(100L,101L);
assertThat(db.queryForObject("SELECT SUM(approved_return_packages) FROM ll_exception_return_item",Long.class)).isEqualTo(2);
}
/** 多单退回可按任意顺序取消;最后一单取消才恢复批次前状态,签收历史保持不变。 */
@org.junit.jupiter.params.ParameterizedTest
@org.junit.jupiter.params.provider.ValueSource(booleans={false,true})
void cancellingBothMastersRestoresOriginalStateInEitherOrder(boolean reversed) {
var requests=prepareTwoMasterReturns();var business=business();
var created=new ArrayList<>(tx.execute(t->business.markReturns("C","OP","操作员","DL-1",requests)));
if(reversed)Collections.reverse(created);
for(int index=0;index<created.size();index++) {
var row=created.get(index);
tx.executeWithoutResult(t->business.cancel("C","OP",row.exceptionNo(),new ExceptionOrderRequests.Cancel(0,"cancel-"+row.id(),"取消本单退回")));
assertThat(db.queryForObject("SELECT status FROM ll_delivery_order WHERE id=3",String.class)).isEqualTo(index==0?"RETURN_PENDING":"DELIVERING");
}
assertThat(db.queryForObject("SELECT signed_packages FROM ll_delivery_order WHERE id=3",Long.class)).isEqualTo(2);
assertThat(db.queryForList("SELECT abnormal FROM ll_order",String.class)).doesNotContain("true");
}
/** 第二单借用第一单原箱时,第一单异常和冻结数量也必须回滚。 */
@Test void invalidSecondMasterRollsBackTheWholeMultiOrderReturn() {
var requests=prepareTwoMasterReturns();var business=business();
var wrong=new ExceptionOrderRequests.MarkReturn(101L,"原因","OVERSEAS_WAREHOUSE",null,requests.get(0).items(),"batch-wrong");
assertThatThrownBy(()->tx.execute(t->business.markReturns("C","OP","操作员","DL-1",List.of(requests.get(0),wrong))))
.hasMessageContaining("不属于该派送单和尾程主单");
assertThat(db.queryForObject("SELECT COUNT(*) FROM ll_exception_order",Long.class)).isZero();
assertThat(db.queryForObject("SELECT COUNT(*) FROM ll_exception_return_item",Long.class)).isZero();
assertThat(db.queryForObject("SELECT status FROM ll_delivery_order WHERE id=3",String.class)).isEqualTo("DELIVERING");
}
/** 单笔数量均合法也不能让合计退回超过部分签收后的未签数量。 */
@Test void multiOrderTotalCannotIncludeAlreadySignedPackages() {
var requests=prepareTwoMasterReturns();var business=business();
db.update("UPDATE ll_delivery_order SET signed_packages=3 WHERE id=3");
assertThatThrownBy(()->tx.execute(t->business.markReturns("C","OP","操作员","DL-1",requests))).hasMessageContaining("退回总箱数");
assertThat(db.queryForObject("SELECT COUNT(*) FROM ll_exception_order",Long.class)).isZero();
}
/** 两张原单共享派送单,但各自只拥有一只已实际出库的原箱。 */
private List<ExceptionOrderRequests.MarkReturn> prepareTwoMasterReturns() {
prepareMarkReturn();
db.update("UPDATE ll_delivery_order SET status='DELIVERING',package_count=4,signed_packages=2 WHERE id=3");
db.update("INSERT INTO ll_order(id,customer_id,order_no,delivery_order_no,outbound_order_no,overseas_warehouse_id,package_count) VALUES(101,'C','LM-2','DL-1','OUT-1','92',1)");
db.update("INSERT INTO ll_dispatch_pda_pick(id,customer_id,task_id,dispatch_item_id,return_carton_id,packages,product_quantity) VALUES(104,'C',102,4,102,1,1)");
db.update("INSERT INTO ll_dispatch_pda_pick_order(id,customer_id,pick_id,order_id,packages) VALUES(106,'C',104,101,1)");
db.update("INSERT INTO ll_return_inventory_carton(id,customer_id,overseas_warehouse_id,carton_mark,status,total_quantity) VALUES(102,'C','92','BOX-2','OUTBOUNDED',1)");
return List.of(new ExceptionOrderRequests.MarkReturn(100L,"多单部分签收","OVERSEAS_WAREHOUSE",null,List.of(new ExceptionOrderRequests.ReturnItem(5L,null,1,101L)),"batch-master-1"),
new ExceptionOrderRequests.MarkReturn(101L,"多单部分签收","OVERSEAS_WAREHOUSE",null,List.of(new ExceptionOrderRequests.ReturnItem(5L,null,1,102L)),"batch-master-2"));
}
/** 将真实已出库换标主单置为拒签,不添加普通入仓关系。 */
private void prepareMarkReturn() {
db.update("DELETE FROM ll_exception_return_item");db.update("DELETE FROM ll_exception_order");
@@ -174,7 +243,8 @@ class ReturnCartonReturnPersistenceTest {
/** 使用真实Mapper组装异常业务服务,资源和文件网关维持隔离。 */
private ExceptionOrderService business() {
var business=new ExceptionOrderService();
org.springframework.test.util.ReflectionTestUtils.setField(business,"workflow",org.mockito.Mockito.mock(com.baiyue.lastleg.exceptionorder.repository.ExceptionWorkflowRepository.class));
org.springframework.test.util.ReflectionTestUtils.setField(business,"workflow",new com.baiyue.lastleg.exceptionorder.repository.ExceptionWorkflowRepository(
map(ExceptionWorkflowMapper.class),map(ExceptionOrderMapper.class),map(com.baiyue.lastleg.mapper.LastlegOrderMapper.class)));
org.springframework.test.util.ReflectionTestUtils.setField(business,"exceptionOrders",map(ExceptionOrderMapper.class));
org.springframework.test.util.ReflectionTestUtils.setField(business,"returnItems",map(ExceptionReturnItemMapper.class));
org.springframework.test.util.ReflectionTestUtils.setField(business,"commands",map(ExceptionOrderCommandMapper.class));
@@ -18,6 +18,28 @@ import static org.mockito.Mockito.*;
/** 同筛选完整统计、旧单不回填和权限隔离的直接回归。 */
class OwnedBusinessQueryTest {
/** 自有、外配共享散板订单明细,跨客户同号不合并,目的地和托盘都不丢失。 */
@Test void detailsGroupLooseboardSourcesUsingDispatchOrderAuthorityInBothScopes() {
var repo=mock(OwnedBusinessRepository.class);var codec=OwnedBusinessFixtures.codec();var dispatch=mock(DispatchDetailReader.class);
var original=mock(com.baiyue.lastleg.dispatch.port.DispatchViews.Detail.class);
var first=OwnedBusinessFixtures.cargo().get(0);var second=OwnedBusinessFixtures.cargo().get(1);var other=OwnedBusinessFixtures.cargo().get(0);
second.setCustomerId(first.getCustomerId());second.setWarehousePalletId(32L);second.setPalletNo("PAL-32");second.setDestinationWarehouseCode("LAX9");second.setPackageCount(8);
other.setCustomerId("102");other.setOrderNo("LB-SAME-NUMBER");
when(repo.cargo(any())).thenReturn(List.of(first,second,other));when(repo.batches(any())).thenReturn(List.of());
when(dispatch.detail(any(),any())).thenReturn(original);
when(original.orderDetails()).thenReturn(List.of(new com.baiyue.lastleg.dispatch.port.DispatchViews.OrderDetail("101","客户A","LB1",null,null,2,null,null,null,first.getSourceKey(),"LOCAL_LOOSEBOARD","LB1",List.of(first.getSourceKey(),second.getSourceKey())),
new com.baiyue.lastleg.dispatch.port.DispatchViews.OrderDetail("102","客户B","LB1",null,null,1,null,null,null,other.getSourceKey(),"LOCAL_LOOSEBOARD","LB1",List.of(other.getSourceKey()))));
var service=new OwnedBusinessQueryService(repo,new OwnedBusinessProjection(codec),codec,dispatch);
for(var scope:List.of(com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessScope.OWNED,com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessScope.OUTSOURCED)) {
var source=OwnedBusinessFixtures.source();source.setPooledCargo(scope.isPooledCargo());
var result=service.detailForWrite(OwnedBusinessFixtures.actor(),source,scope);
assertEquals(2,result.cargo().size());var order=result.cargo().get(0);
assertEquals("LB1",order.orderNo());assertNull(order.inboundCode());assertEquals("9",order.packages());
assertEquals("PAL-31、PAL-32",order.palletNo());assertEquals(List.of("FBA:ONT8","FBA:LAX9"),order.destinationKeys());
assertEquals("102",result.cargo().get(1).customerId());assertEquals("LB1",result.cargo().get(1).orderNo());
}
assertEquals("ORDER-IN-A",first.getOrderNo());verify(repo,never()).update(any(),anyLong());
}
/** 当前页只有一车时统计仍来自全量,日期边界转换为UTC且共用过滤。 */
@Test void summaryDoesNotUseCurrentPageLengthAndDateUsesPlannedDeparture() {
var repo=mock(OwnedBusinessRepository.class);var codec=OwnedBusinessFixtures.codec();
@@ -47,13 +47,16 @@ class LocalLooseboardServiceTest {
@Mock LocalLooseboardOperationMapper operations;
@Mock LastlegOrderMapper lastlegOrders;
@Mock LastlegOrderStatusHistoryMapper lastlegHistories;
@Mock com.baiyue.lastleg.looseboard.port.LooseboardDispatches dispatches;
@Spy LastlegOrderNoGenerator lastlegOrderNoGenerator = com.baiyue.lastleg.service.TestDocumentNumbers.orders();
private LocalLooseboardService service;
private final Map<String, LocalLooseboardOperation> operationRows = new HashMap<>();
/** 注入真实仓储组合及客户隔离的排车查询端口。 */
@BeforeEach
void setUp() {
service = new LocalLooseboardService();
ReflectionTestUtils.setField(service, "dispatches", dispatches);
org.springframework.test.util.ReflectionTestUtils.setField(service, "fleetPool", org.mockito.Mockito.mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderQueries.class));
var repository = new LocalLooseboardRepository();
ReflectionTestUtils.setField(service, "repository", repository);
@@ -77,6 +80,19 @@ class LocalLooseboardServiceTest {
});
}
/** 无车队预订的普通散板也返回全部排车,客户和原单必须原样限定。 */
@Test
void detailIncludesAllDispatchesWithoutFleetBooking() {
when(orders.findNo("100", "LB1")).thenReturn(order(LocalLooseboardService.IN_TRANSIT, 1L));
when(dispatches.looseboardDispatches("100", "LB1")).thenReturn(List.of("PC1", "PC2"));
var detail = service.detail("100", "LB1");
assertEquals(List.of("PC1", "PC2"), detail.dispatchNos());
org.junit.jupiter.api.Assertions.assertNull(detail.summary().fleetArrangement());
verify(dispatches).looseboardDispatches("100", "LB1");
}
@Test
void createIsTenantScopedIdempotentAndStartsPendingAcceptance() {
AtomicReference<LocalLooseboardOrder> saved = new AtomicReference<>();