fix(lastleg): exclude cancelled dispatches from inbound status

This commit is contained in:
2026-10-05 04:09:15 +08:00
parent d7981f7575
commit 6fc32574c7
4 changed files with 116 additions and 6 deletions
@@ -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 {
"<if test='q.fbaWarehouseCode != null and q.fbaWarehouseCode != \"\"'> AND l.fba_warehouse_code LIKE CONCAT('%',#{q.fbaWarehouseCode},'%')</if>" +
"<choose><when test='q.appointmentStatus == \"UNAPPOINTED\"'> AND ai.id IS NULL</when>" +
"<when test='q.appointmentStatus != null and q.appointmentStatus != \"\"'> AND a.appointment_status=#{q.appointmentStatus}</when></choose>" +
"<choose><when test='q.dispatchStatus == \"SCHEDULED\"'> AND EXISTS (SELECT 1 FROM ll_dispatch_item di WHERE <if test='customerId != null and customerId != \"\"'>di.customer_id=#{customerId} AND </if>di.order_id=l.order_id)</when>" +
"<when test='q.dispatchStatus == \"UNSCHEDULED\"'> AND NOT EXISTS (SELECT 1 FROM ll_dispatch_item di WHERE <if test='customerId != null and customerId != \"\"'>di.customer_id=#{customerId} AND </if>di.order_id=l.order_id)</when></choose>" +
"<choose><when test='q.dispatchStatus == \"SCHEDULED\"'> AND " + SCHEDULED_DISPATCH + "</when>" +
"<when test='q.dispatchStatus == \"UNSCHEDULED\"'> AND NOT " + SCHEDULED_DISPATCH + "</when></choose>" +
"<if test='q.createdFrom != null'> AND p.created_at &gt;=#{q.createdFrom}</if>" +
"<if test='q.createdTo != null'> AND p.created_at &lt;=#{q.createdTo}</if>";
@@ -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 " +
"<if test='customerId != null and customerId != \"\"'>di.customer_id=#{customerId} AND </if>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}</script>")
List<InboundPlanListResponse> rows(@Param("customerId") String customerId,
@@ -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);
}
}
@@ -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 <if test='customerId != null and customerId != \"\"'>di.customer_id=#{customerId} AND </if>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&lt;&gt;'CANCELLED'"));
}
}
@@ -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 </if>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&lt;&gt;'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}");