This commit is contained in:
2026-11-05 16:04:10 +08:00
parent 5a8918e2e4
commit d1ade7e9dd
22 changed files with 316 additions and 37 deletions
@@ -9,6 +9,23 @@ import static org.assertj.core.api.Assertions.*;
/** 本车同目的仓跨客户只填一次,先约自动带号,先排可暂缺。 */
class DispatchDestinationAppointmentsTest {
/** 已预约的编号不能因尚无有效收货确认而丢失,也不能冒充确认时间。 */
@Test void registeredBatchNumberIsVisibleWithoutInventingReceivingConfirmation() {
var batch=new com.baiyue.lastleg.dispatch.mapper.DispatchReadMapper.BatchRow(1L,2L,"FBA_APPOINTMENT:1","AP-1","A","SOURCE","FBA:ONT8",22,"ISA-BOOKED","SUCCESS","ONT8",java.time.LocalDateTime.parse("2026-11-05T10:00:00"),1L);
var booked=DispatchDestinationAppointments.booked(batch,null);
assertThat(booked.status()).isEqualTo("REGISTERED");
assertThat(booked.confirmedAt()).isNull();
var rows=resolve(List.of(item("A","ONT8")),maintenance(new DestinationAppointment("FBA:ONT8",null)),List.of(booked));
assertThat(rows).singleElement().satisfies(a->{
assertThat(a.appointmentNo()).isEqualTo("ISA-BOOKED");
assertThat(a.status()).isEqualTo("REGISTERED");assertThat(a.confirmedAt()).isNull();
});
var window=new com.baiyue.lastleg.appointmentwork.AppointmentWorkDispatchPort.ReceivingWindow("FBA_APPOINTMENT:1","AP-1","A","FBA:ONT8","ISA-CURRENT",java.time.Instant.parse("2026-11-06T10:00:00Z"),null,"AG-1",1);
var confirmed=DispatchDestinationAppointments.booked(batch,window);
assertThat(confirmed.appointmentNo()).isEqualTo("ISA-CURRENT");
assertThat(confirmed.confirmedAt()).isEqualTo(window.startAt());
assertThat(confirmed.status()).isEqualTo("SUCCESS");
}
@Test void groupsMultipleCustomersAndPalletsByDestination() {
var rows=resolve(List.of(item("A","ONT8"),item("B","ONT8"),item("A","LGB8")),null,List.of());
assertThat(rows).extracting(FbaAppointment::destinationWarehouseCode).containsExactly("LGB8","ONT8");
@@ -62,6 +62,13 @@ class DispatchPdaReadPersistenceTest {
assertThat(task.items()).allSatisfy(item->assertThat(item.positions()).hasSize(1));
assertThatThrownBy(()->f.tx.execute(s->queries.task(new DispatchContracts.Actor("foreign",ActorMode.PDA,"99"),created.dispatchNo())))
.isInstanceOf(com.baiyue.common.core.exception.BizException.class).hasMessageContaining("当前绑定仓无此排车作业");
for (String type : List.of("LOCAL_TRUCK", "PICKUP")) {
f.db.update("UPDATE ll_dispatch_order SET task_type=?", type);
assertThat(f.tx.<List<DispatchViews.PdaTask>>execute(s->queries.list(actor,null))).isEmpty();
assertThatThrownBy(()->f.tx.execute(s->queries.task(actor,created.dispatchNo())))
.isInstanceOf(com.baiyue.common.core.exception.BizException.class);
}
f.db.update("UPDATE ll_dispatch_order SET task_type='DELIVERY'");
f.db.update("DELETE FROM ll_dispatch_pda_pick_order WHERE customer_id='202'");
assertThat(f.tx.execute(s->queries.task(actor,created.dispatchNo())).pickingReconciled()).isFalse();
}
@@ -32,7 +32,7 @@ class InboundPlanDispatchStatusPersistenceTest {
db.execute("CREATE TABLE ll_order_detail(order_id BIGINT,customer_id VARCHAR(20),gross_weight_kg DECIMAL(18,3),net_weight_kg DECIMAL(18,3))");
db.execute("CREATE TABLE ll_inbound_order(id BIGINT,customer_id VARCHAR(20),gross_weight_kg DECIMAL(18,3),net_weight_kg DECIMAL(18,3))");
db.execute("CREATE TABLE ll_dispatch_item(id BIGINT,customer_id VARCHAR(20),dispatch_order_id BIGINT,order_id BIGINT)");
db.execute("CREATE TABLE ll_dispatch_order(id BIGINT,status VARCHAR(30),stage VARCHAR(30))");
db.execute("CREATE TABLE ll_dispatch_order(id BIGINT,status VARCHAR(30),stage VARCHAR(30),task_type VARCHAR(30) DEFAULT 'DELIVERY')");
db.update("INSERT INTO ll_inbound_plan VALUES(1,'LIP-1','A',90,TRUE,FALSE,FALSE,'RECEIVED',CURRENT_TIMESTAMP,0)");
db.update("INSERT INTO ll_inbound_plan_line VALUES(2,1,3,'IN-1','91','ABE4','ABE4','ABE4',1,1,1)");
db.update("INSERT INTO ll_order VALUES(3,'A','LX-1','FIRST_LEG','LA','洛杉矶仓','客户A')");
@@ -64,6 +64,15 @@ class InboundPlanDispatchStatusPersistenceTest {
assertStatus("UNSCHEDULED");
}
@Test
void pickupTripDoesNotCountAsOutboundScheduling() {
dispatch(10, "ACTIVE", "DELIVERED", "A");
db.update("UPDATE ll_dispatch_order SET task_type='PICKUP'");
assertStatus("UNSCHEDULED");
dispatch(11, "ACTIVE", "BEFORE_LOADING", "A");
assertStatus("SCHEDULED");
}
@Test
void departedAndCompletedDispatchesRemainScheduled() {
dispatch(10, "ACTIVE", "DELIVERING", "A");
@@ -110,7 +119,7 @@ class InboundPlanDispatchStatusPersistenceTest {
}
private void dispatch(long id, String status, String stage, String itemOwner) {
db.update("INSERT INTO ll_dispatch_order VALUES(?,?,?)", id, status, stage);
db.update("INSERT INTO ll_dispatch_order(id,status,stage) VALUES(?,?,?)", id, status, stage);
db.update("INSERT INTO ll_dispatch_item VALUES(?,?,?,3)", id, itemOwner, id);
}
@@ -53,6 +53,18 @@ class LastlegOrderSailingStageTest {
assertEquals(List.of(3L, 4L, 7L, 8L, 9L), find(db, null, SailingStage.AFTER_DEPARTURE, null));
assertEquals(List.of(3L, 4L, 7L, 9L), find(db, "A", SailingStage.AFTER_DEPARTURE, null));
assertEquals(List.of(3L, 7L, 9L), find(db, "A", SailingStage.AFTER_DEPARTURE, "PENDING_ASSIGNMENT"));
// 去重仓储执行真实Mapper,沿用客户隔离和人工覆盖后的实际开船事实。
sql.execute("ALTER TABLE ll_order ADD source_order_no VARCHAR(64)");
sql.execute("UPDATE ll_order SET source_order_no=CONCAT('SOURCE-',id)");
var config = new MybatisConfiguration(); config.setMapUnderscoreToCamelCase(true);
config.addMapper(com.baiyue.lastleg.mapper.LastlegOrderMapper.class);
var factory = new com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean();
factory.setDataSource(new org.springframework.jdbc.datasource.DriverManagerDataSource("jdbc:h2:mem:sailing;MODE=MySQL", "", ""));
factory.setConfiguration(config);
var mapper = new org.mybatis.spring.SqlSessionTemplate(factory.getObject()).getMapper(com.baiyue.lastleg.mapper.LastlegOrderMapper.class);
assertEquals(java.util.Set.of("A:SOURCE-3", "A:SOURCE-4", "A:SOURCE-7", "A:SOURCE-9"),
new com.baiyue.lastleg.repository.PreDepartureOrderRepository(mapper).departedKeys("A"));
assertEquals(java.util.stream.LongStream.rangeClosed(1, 14).boxed().toList(), find(db, null, null, null)); // Discard applications have their own workflow and are excluded.
}
}
@@ -79,6 +79,7 @@ class LastlegOrderServiceTest {
@Mock WarehouseResourceGateway warehouseResourceGateway;
@Mock ContainerShippingGateMapper containerShippingGates;
@Mock OrderPreDepartureClient preDeparture;
@Mock com.baiyue.lastleg.repository.PreDepartureOrderRepository preDepartureOrders;
@Mock ReturnOrderReferenceResolver returnOrderReferences;
@Mock com.baiyue.lastleg.returnreceipt.service.ReturnReceiptService returnReceipts;
LastlegOrderService service;
@@ -118,6 +119,27 @@ class LastlegOrderServiceTest {
assertEquals(new java.math.BigDecimal("22"), summary.netWeightKg());
}
/** 已清关来源即使订单服务仍返回开船前记录,也应先去重再分页。 */
@Test void excludesDepartedSourcesBeforePagination() {
when(preDepartureOrders.departedKeys("101")).thenReturn(java.util.Set.of("101:EXXHK2611050001"));
var departed = preDepartureRow(1L, "EXXHK2611050001");
var waiting = preDepartureRow(2L, "WAITING-2");
when(preDeparture.page(any(), eq(101L), any(), any(), eq(1), eq(2000)))
.thenReturn(ApiResponse.success(new PageResult<>(List.of(departed, waiting), 1, 2000, 2, 1)));
var query = new LastlegOrderRequests.Query(1L, 1L, null, null, null, null, null, null,
null, null, null, null, null, null, null, null,
LastlegOrderRequests.SailingStage.BEFORE_DEPARTURE, null, null, null);
var page = service.page("101", query);
assertEquals(1L, page.total());
assertEquals(1L, page.pages());
assertEquals("WAITING-2", page.records().get(0).sourceOrderNo());
}
private OrderPreDepartureClient.Row preDepartureRow(long id, String no) {
return new OrderPreDepartureClient.Row(id,101L,no,"IN-"+id,"WAIT_SPLIT", "客户",null,null,null,
null,2,null,null,null,null,null,null,null,null,null,null);
}
@BeforeEach
void setUp() {
com.baomidou.mybatisplus.core.metadata.TableInfoHelper.initTableInfo(
@@ -144,6 +166,7 @@ class LastlegOrderServiceTest {
ReflectionTestUtils.setField(service, "warehouseResourceGateway", warehouseResourceGateway);
ReflectionTestUtils.setField(service, "containerShippingGates", containerShippingGates);
ReflectionTestUtils.setField(service, "orderPreDepartureClient", preDeparture);
ReflectionTestUtils.setField(service, "preDepartureOrders", preDepartureOrders);
lenient().when(preDeparture.page(any(), any(), any(), any(), anyInt(), anyInt()))
.thenReturn(ApiResponse.success(new PageResult<>(List.of(), 1, 2000, 0, 0)));
}