fix(lastleg): exclude unreceived pallets after inbound completion
This commit is contained in:
+30
-20
@@ -16,12 +16,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class DispatchCandidateMapperContractTest {
|
||||
/** 执行正式计划/实收判定与离港门禁,覆盖自动预留、人工覆盖、混合来源及库存耗尽。 */
|
||||
/** 执行正式候选和提交资格,覆盖在途预排、收货结束后的空预留托盘及真实库存。 */
|
||||
@Test
|
||||
void reservedPalletsCanBePlannedAfterDepartureWithoutInventingReceivedStock() throws Exception {
|
||||
try (var connection = DriverManager.getConnection("jdbc:h2:mem:dispatch-prearrival;MODE=MySQL")) {
|
||||
var db = connection.createStatement();
|
||||
db.execute("CREATE TABLE ll_inbound_plan(id BIGINT,customer_id VARCHAR)");
|
||||
db.execute("CREATE TABLE ll_inbound_plan(id BIGINT,customer_id VARCHAR,status VARCHAR DEFAULT 'PDA_PENDING')");
|
||||
db.execute("CREATE TABLE ll_inbound_plan_pallet(id BIGINT,plan_id BIGINT,warehouse_pallet_id BIGINT,assignment_status VARCHAR,actual_package_count INT)");
|
||||
db.execute("CREATE TABLE ll_inbound_plan_line(id BIGINT,customer_id VARCHAR,order_id BIGINT)");
|
||||
db.execute("CREATE TABLE ll_inbound_plan_pallet_allocation(pallet_id BIGINT,line_id BIGINT)");
|
||||
@@ -29,8 +29,8 @@ class DispatchCandidateMapperContractTest {
|
||||
db.execute("CREATE TABLE ll_order_manual_node(order_id BIGINT,customer_id VARCHAR,node_code VARCHAR,node_at TIMESTAMP)");
|
||||
db.execute("CREATE TABLE ll_container_shipping_gate(container_booking_id BIGINT,node_sequence INT,actual_at TIMESTAMP)");
|
||||
db.execute("CREATE TABLE ll_inventory_position(customer_id VARCHAR,pallet_id BIGINT,available_packages INT)");
|
||||
for (int id = 1; id <= 9; id++) {
|
||||
db.execute("INSERT INTO ll_inbound_plan VALUES (" + id + ",'A')");
|
||||
for (int id = 1; id <= 14; id++) {
|
||||
db.execute("INSERT INTO ll_inbound_plan(id,customer_id) VALUES (" + id + ",'A')");
|
||||
db.execute("INSERT INTO ll_inbound_plan_pallet VALUES (" + id + "," + id + "," + (100 + id) + ",'RESERVED',0)");
|
||||
db.execute("INSERT INTO ll_inbound_plan_line VALUES (" + id + ",'A'," + id + ")");
|
||||
db.execute("INSERT INTO ll_inbound_plan_pallet_allocation VALUES (" + id + "," + id + ")");
|
||||
@@ -44,24 +44,34 @@ class DispatchCandidateMapperContractTest {
|
||||
db.execute("INSERT INTO ll_order_manual_node VALUES (7,'A','ACTUAL_DEPARTURE',CURRENT_TIMESTAMP),(9,'B','ACTUAL_DEPARTURE',CURRENT_TIMESTAMP)");
|
||||
db.execute("INSERT INTO ll_inbound_plan_line VALUES (20,'A',2)");
|
||||
db.execute("INSERT INTO ll_inbound_plan_pallet_allocation VALUES (8,20)");
|
||||
// 同一计划原分两托,最终21箱全收在一托;另一预留托盘不能继续冒充可预排货物。
|
||||
db.execute("UPDATE ll_inbound_plan SET status='COMPLETED' WHERE id IN (10,13,14)");
|
||||
db.execute("UPDATE ll_inbound_plan_pallet SET plan_id=10,assignment_status='ASSIGNED',actual_package_count=21 WHERE id=11");
|
||||
db.execute("INSERT INTO ll_inventory_position VALUES ('A',111,21)");
|
||||
db.execute("UPDATE ll_inbound_plan SET status='PARTIAL_INBOUND' WHERE id=12");
|
||||
db.execute("UPDATE ll_inbound_plan_pallet SET warehouse_pallet_id=NULL WHERE id=13");
|
||||
db.execute("UPDATE ll_inbound_plan_pallet SET assignment_status='ASSIGNED' WHERE id=14");
|
||||
String plan = DispatchCandidateMapper.PLAN_ONLY;
|
||||
String eligible = "AND (NOT " + plan + " OR (" + DispatchCandidateMapper.DEPARTED_PLAN + ")) "
|
||||
+ "AND (" + plan + " OR EXISTS (SELECT 1 FROM ll_inventory_position pos "
|
||||
+ "WHERE pos.customer_id=ip.customer_id AND pos.pallet_id=p.warehouse_pallet_id AND pos.available_packages > 0))";
|
||||
var ids = new ArrayList<Long>();
|
||||
try (var rows = db.executeQuery("SELECT p.id,CASE WHEN " + plan + " THEN NULL ELSE p.warehouse_pallet_id END AS physical,"
|
||||
+ "CASE WHEN " + plan + " THEN 'PLAN' ELSE 'RECEIVED' END AS stage "
|
||||
+ "FROM ll_inbound_plan_pallet p JOIN ll_inbound_plan ip ON ip.id=p.plan_id WHERE 1=1 " + eligible + " ORDER BY p.id")) {
|
||||
while (rows.next()) {
|
||||
long id = rows.getLong("id"); ids.add(id);
|
||||
assertEquals(id == 3 ? "RECEIVED" : "PLAN", rows.getString("stage"));
|
||||
assertEquals(id == 3 ? 103L : null, rows.getObject("physical", Long.class));
|
||||
}
|
||||
}
|
||||
assertEquals(List.of(1L, 3L, 7L), ids);
|
||||
// 查询和写入选中来源共用同一判定;不允许仅在列表展示在途但提交时又要求实收。
|
||||
// 直接执行两条生产SQL中的资格片段,避免仅验证测试自行拼出的规则。
|
||||
for (String sql : List.of(DispatchCandidateMapper.AVAILABLE_PLAN_BODY, DispatchCandidateMapper.SELECTED_PLAN_SQL)) {
|
||||
assertTrue(sql.contains(eligible.replace("> 0", "> 0").replace("pos.customer_id=ip.customer_id", sql.contains("WHERE pos.customer_id=#{customerId}") ? "pos.customer_id=#{customerId}" : "pos.customer_id=ip.customer_id")));
|
||||
int start = sql.indexOf("AND (NOT " + plan + " OR (");
|
||||
int stockGate = sql.indexOf("AND (" + plan + " OR EXISTS", start);
|
||||
int end = sql.indexOf("AND NOT EXISTS (SELECT 1 FROM ll_", stockGate);
|
||||
String eligible = sql.substring(start, end).replace(">", ">")
|
||||
.replace("#{customerId}", "'A'");
|
||||
var ids = new ArrayList<Long>();
|
||||
try (var rows = db.executeQuery("SELECT p.id,p.actual_package_count,CASE WHEN " + plan + " THEN NULL ELSE p.warehouse_pallet_id END AS physical,"
|
||||
+ "CASE WHEN " + plan + " THEN 'PLAN' ELSE 'RECEIVED' END AS stage "
|
||||
+ "FROM ll_inbound_plan_pallet p JOIN ll_inbound_plan ip ON ip.id=p.plan_id WHERE 1=1 " + eligible + " ORDER BY p.id")) {
|
||||
while (rows.next()) {
|
||||
long id = rows.getLong("id"); ids.add(id);
|
||||
assertEquals(id == 3 || id == 11 ? "RECEIVED" : "PLAN", rows.getString("stage"));
|
||||
assertEquals(id == 3 || id == 11 ? 100L + id : null, rows.getObject("physical", Long.class));
|
||||
if (id == 11) assertEquals(21, rows.getInt("actual_package_count"));
|
||||
}
|
||||
}
|
||||
// 完成入库或已确认短少后,空预留托盘不可排车,真实实收托盘继续按库存校验。
|
||||
assertEquals(List.of(1L, 3L, 7L, 11L), ids);
|
||||
assertTrue(sql.contains("CASE WHEN " + plan + " THEN NULL ELSE p.warehouse_pallet_id END AS warehousePalletId"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user