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}");