From 6fc32574c7ab8701ab58c7475c10a8b574b0ec56 Mon Sep 17 00:00:00 2001
From: ouyangxq <775153912@qq.com>
Date: Mon, 5 Oct 2026 04:09:15 +0800
Subject: [PATCH] fix(lastleg): exclude cancelled dispatches from inbound
status
---
.../mapper/InboundPlanQueryMapper.java | 13 ++-
...oundPlanDispatchStatusPersistenceTest.java | 102 ++++++++++++++++++
.../InboundPlanQueryMapperContractTest.java | 4 +-
.../service/InboundPlanPaginationTest.java | 3 +-
4 files changed, 116 insertions(+), 6 deletions(-)
create mode 100644 lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanDispatchStatusPersistenceTest.java
diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanQueryMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanQueryMapper.java
index f428897ce..c2b77aaef 100644
--- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanQueryMapper.java
+++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanQueryMapper.java
@@ -10,6 +10,12 @@ import java.util.List;
@Mapper
public interface InboundPlanQueryMapper {
+ String SCHEDULED_DISPATCH = "EXISTS (SELECT 1 FROM ll_dispatch_item di" +
+ " JOIN ll_dispatch_order dispatch ON dispatch.id=di.dispatch_order_id" +
+ " AND dispatch.customer_id=di.customer_id" +
+ " WHERE di.customer_id=p.customer_id AND di.order_id=l.order_id" +
+ " AND dispatch.status<>'CANCELLED')";
+
String FROM_AND_FILTER = " FROM ll_inbound_plan_line l" +
" JOIN ll_inbound_plan p ON p.id=l.plan_id" +
" JOIN ll_order o ON o.id=l.order_id" +
@@ -31,8 +37,8 @@ public interface InboundPlanQueryMapper {
" AND l.fba_warehouse_code LIKE CONCAT('%',#{q.fbaWarehouseCode},'%')" +
" AND ai.id IS NULL" +
" AND a.appointment_status=#{q.appointmentStatus}" +
- " AND EXISTS (SELECT 1 FROM ll_dispatch_item di WHERE di.customer_id=#{customerId} AND di.order_id=l.order_id)" +
- " AND NOT EXISTS (SELECT 1 FROM ll_dispatch_item di WHERE di.customer_id=#{customerId} AND di.order_id=l.order_id)" +
+ " AND " + SCHEDULED_DISPATCH + "" +
+ " AND NOT " + SCHEDULED_DISPATCH + "" +
" AND p.created_at >=#{q.createdFrom}" +
" AND p.created_at <=#{q.createdTo}";
@@ -52,8 +58,7 @@ public interface InboundPlanQueryMapper {
"p.urgent_flag urgent,p.status status,a.appointment_no appointmentNo," +
"a.appointment_status appointmentStatus,a.appointment_at appointmentAt," +
"a.expected_loading_at expectedLoadingAt," +
- "CASE WHEN EXISTS (SELECT 1 FROM ll_dispatch_item di WHERE " +
- "di.customer_id=#{customerId} AND di.order_id=l.order_id)" +
+ "CASE WHEN " + SCHEDULED_DISPATCH +
" THEN 'SCHEDULED' ELSE 'UNSCHEDULED' END dispatchStatus,p.created_at createdAt,p.version version" +
FROM_AND_FILTER + " ORDER BY p.created_at DESC,l.id DESC LIMIT #{limit} OFFSET #{offset}")
List rows(@Param("customerId") String customerId,
diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanDispatchStatusPersistenceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanDispatchStatusPersistenceTest.java
new file mode 100644
index 000000000..e77c9dd02
--- /dev/null
+++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanDispatchStatusPersistenceTest.java
@@ -0,0 +1,102 @@
+package com.baiyue.lastleg.inboundplan.mapper;
+
+import com.baomidou.mybatisplus.core.MybatisConfiguration;
+import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
+import com.baiyue.lastleg.inboundplan.dto.InboundPlanListResponse;
+import com.baiyue.lastleg.inboundplan.dto.InboundPlanRequests;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class InboundPlanDispatchStatusPersistenceTest {
+ private JdbcTemplate db;
+ private InboundPlanQueryMapper mapper;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ var data = new DriverManagerDataSource("jdbc:h2:mem:" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1", "sa", "");
+ db = new JdbcTemplate(data);
+ db.execute("CREATE TABLE ll_inbound_plan(id BIGINT,plan_no VARCHAR(50),customer_id VARCHAR(20),overseas_warehouse_id BIGINT,assigned_flag BOOLEAN,merge_required BOOLEAN,urgent_flag BOOLEAN,status VARCHAR(30),created_at TIMESTAMP,version INT)");
+ db.execute("CREATE TABLE ll_inbound_plan_line(id BIGINT,plan_id BIGINT,order_id BIGINT,inbound_code VARCHAR(50),destination_warehouse_id VARCHAR(30),destination_warehouse_code VARCHAR(30),destination_warehouse_name VARCHAR(50),fba_warehouse_code VARCHAR(30),required_package_count INT,volume_cbm DECIMAL(18,3),planned_pallet_count INT)");
+ db.execute("CREATE TABLE ll_order(id BIGINT,customer_id VARCHAR(20),lx_code VARCHAR(50),order_type VARCHAR(30),overseas_warehouse_code VARCHAR(30),overseas_warehouse_name VARCHAR(50),customer_name VARCHAR(50))");
+ db.execute("CREATE TABLE ll_inbound_fba_appointment_item(id BIGINT,line_id BIGINT,appointment_id BIGINT)");
+ db.execute("CREATE TABLE ll_inbound_fba_appointment(id BIGINT,customer_id VARCHAR(20),created_at TIMESTAMP,appointment_no VARCHAR(50),appointment_status VARCHAR(30),appointment_at TIMESTAMP,expected_loading_at TIMESTAMP)");
+ db.execute("CREATE TABLE ll_inbound_plan_pallet_allocation(line_id BIGINT,picked_package_count INT)");
+ 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,customer_id VARCHAR(20),status VARCHAR(30))");
+ 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')");
+ var config = new MybatisConfiguration();
+ config.addMapper(InboundPlanQueryMapper.class);
+ var factory = new MybatisSqlSessionFactoryBean();
+ factory.setDataSource(data);
+ factory.setConfiguration(config);
+ mapper = new SqlSessionTemplate(factory.getObject()).getMapper(InboundPlanQueryMapper.class);
+ }
+
+ @Test
+ void cancelledDispatchRetainsHistoryButDoesNotMarkOrFilterTheInboundAsScheduled() {
+ dispatch(10, "A", "CANCELLED", "A");
+ assertStatus("UNSCHEDULED");
+ assertThat(db.queryForObject("SELECT COUNT(*) FROM ll_dispatch_item", Integer.class)).isEqualTo(1);
+ }
+
+ @Test
+ void replacementDispatchCountsOnceAndCancellingItRestoresUnscheduled() {
+ dispatch(10, "A", "CANCELLED", "A");
+ dispatch(11, "A", "PENDING_DEPARTURE", "A");
+ dispatch(12, "A", "PICKING", "A");
+ assertStatus("SCHEDULED");
+ db.update("UPDATE ll_dispatch_order SET status='CANCELLED'");
+ assertStatus("UNSCHEDULED");
+ }
+
+ @Test
+ void departedAndCompletedDispatchesRemainScheduled() {
+ dispatch(10, "A", "DEPARTED", "A");
+ assertStatus("SCHEDULED");
+ db.update("UPDATE ll_dispatch_order SET status='COMPLETED'");
+ assertStatus("SCHEDULED");
+ }
+
+ @Test
+ void otherCustomerOrMissingDispatchCannotMarkThisInboundScheduled() {
+ dispatch(10, "B", "PENDING_DEPARTURE", "B");
+ dispatch(11, "B", "PENDING_DEPARTURE", "A");
+ db.update("INSERT INTO ll_dispatch_item VALUES(12,'A',999,3)");
+ assertStatus("UNSCHEDULED");
+ assertThat(mapper.rows("B", query(null), 0, 20)).isEmpty();
+ assertThat(mapper.count("B", query(null))).isZero();
+ }
+
+ private void dispatch(long id, String owner, String status, String itemOwner) {
+ db.update("INSERT INTO ll_dispatch_order VALUES(?,?,?)", id, owner, status);
+ db.update("INSERT INTO ll_dispatch_item VALUES(?,?,?,3)", id, itemOwner, id);
+ }
+
+ private void assertStatus(String status) {
+ for (String customer : new String[]{null, "A"}) {
+ assertThat(mapper.rows(customer, query(null), 0, 20))
+ .extracting(InboundPlanListResponse::dispatchStatus).containsExactly(status);
+ assertThat(mapper.rows(customer, query(status), 0, 20)).hasSize(1);
+ assertThat(mapper.count(customer, query(status))).isEqualTo(1);
+ String opposite = "SCHEDULED".equals(status) ? "UNSCHEDULED" : "SCHEDULED";
+ assertThat(mapper.rows(customer, query(opposite), 0, 20)).isEmpty();
+ assertThat(mapper.count(customer, query(opposite))).isZero();
+ }
+ }
+
+ private static InboundPlanRequests.Query query(String status) {
+ return new InboundPlanRequests.Query(1, 20, null, null, null, null, null, null,
+ null, null, null, status, null, null);
+ }
+}
diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanQueryMapperContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanQueryMapperContractTest.java
index cb5fe1ea8..4adc78fa9 100644
--- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanQueryMapperContractTest.java
+++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanQueryMapperContractTest.java
@@ -19,6 +19,8 @@ class InboundPlanQueryMapperContractTest {
assertTrue(sql.contains("l.order_id orderId,l.inbound_code inboundCode,o.lx_code lxCode,o.order_type orderType"));
assertTrue(sql.contains("p.overseas_warehouse_id overseasWarehouseId"));
assertTrue(sql.contains("p.overseas_warehouse_id=#{q.overseasWarehouseId}"));
- assertTrue(sql.contains("WHERE di.customer_id=#{customerId} AND di.order_id=l.order_id"));
+ assertTrue(sql.contains("di.customer_id=p.customer_id AND di.order_id=l.order_id"));
+ assertTrue(sql.contains("dispatch.customer_id=di.customer_id"));
+ assertTrue(sql.contains("dispatch.status<>'CANCELLED'"));
}
}
diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanPaginationTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanPaginationTest.java
index 3db84f19c..00890c2a2 100644
--- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanPaginationTest.java
+++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanPaginationTest.java
@@ -82,7 +82,8 @@ class InboundPlanPaginationTest {
.getMethod("rows", String.class, InboundPlanRequests.Query.class, long.class, long.class)
.getAnnotation(org.apache.ibatis.annotations.Select.class).value()) + InboundPlanQueryMapper.FROM_AND_FILTER;
assertThat(sql).contains("a2.customer_id=#{customerId}", "ORDER BY a2.created_at DESC,ai2.id DESC LIMIT 1");
- assertThat(sql).contains("di.customer_id=#{customerId} AND di.order_id=l.order_id");
+ assertThat(sql).contains("di.customer_id=p.customer_id AND di.order_id=l.order_id",
+ "dispatch.customer_id=di.customer_id", "dispatch.status<>'CANCELLED'");
assertThat(sql).contains("p.overseas_warehouse_id overseasWarehouseId", "o.overseas_warehouse_code overseasWarehouseCode",
"o.overseas_warehouse_name overseasWarehouseName",
"p.overseas_warehouse_id=#{q.overseasWarehouseId}");