fix(lastleg): align dispatch candidates with relabel reservation gate

This commit is contained in:
2026-10-02 10:34:00 +08:00
parent a291bd6d45
commit 8b9b135ab5
2 changed files with 40 additions and 2 deletions
@@ -121,9 +121,11 @@ public interface DispatchCandidateMapper {
// A warehouse-transfer pallet may contain both relabel and non-relabel inventory.
// Do not hide the whole pallet because one carton/SKU has an active relabel item;
// the remaining available inventory must still be dispatchable.
"AND (ip.creation_mode!='WAREHOUSE_TRANSFER' OR NOT EXISTS (SELECT 1 FROM ll_relabel_item rli JOIN ll_relabel_application rla ON rla.id=rli.application_id AND rla.customer_id=rli.customer_id " +
"AND (ip.creation_mode='WAREHOUSE_TRANSFER' OR NOT EXISTS (SELECT 1 FROM ll_relabel_item rli JOIN ll_relabel_application rla ON rla.id=rli.application_id AND rla.customer_id=rli.customer_id " +
"JOIN ll_inbound_plan_line rpl ON rpl.order_id=rli.source_inbound_order_id " +
"WHERE rli.customer_id=ip.customer_id AND rli.source_inbound_order_id IS NOT NULL " +
"AND rli.source_inbound_order_id=o.id AND rla.status NOT IN ('REJECTED','CANCELLED','COMPLETED'))) " +
"AND rpl.id IN (SELECT pa.line_id FROM ll_inbound_plan_pallet_allocation pa WHERE pa.pallet_id=p.id) " +
"AND rla.status NOT IN ('REJECTED','CANCELLED','COMPLETED'))) " +
"GROUP BY p.plan_id,p.id,p.warehouse_pallet_id,p.pallet_no,p.destination_warehouse_id," +
"p.destination_warehouse_code,p.destination_warehouse_name,p.allocated_volume_cbm " +
"ORDER BY p.plan_id,p.sequence_no" +
@@ -5,11 +5,47 @@ import org.apache.ibatis.annotations.Select;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
class DispatchCandidateMapperContractTest {
@Test
void availableAndLockedPalletsApplyTheSameActiveRelabelBoundary() throws Exception {
try (var connection = DriverManager.getConnection("jdbc:h2:mem:dispatch-relabel-boundary;MODE=MySQL")) {
var statement = connection.createStatement();
statement.execute("CREATE TABLE ll_inbound_plan(id BIGINT, customer_id VARCHAR, creation_mode VARCHAR)");
statement.execute("CREATE TABLE ll_inbound_plan_pallet(id BIGINT, plan_id BIGINT)");
statement.execute("CREATE TABLE ll_order(id BIGINT)");
statement.execute("CREATE TABLE ll_inbound_plan_line(id BIGINT, order_id BIGINT)");
statement.execute("CREATE TABLE ll_inbound_plan_pallet_allocation(pallet_id BIGINT, line_id BIGINT)");
statement.execute("CREATE TABLE ll_relabel_application(id BIGINT, customer_id VARCHAR, status VARCHAR)");
statement.execute("CREATE TABLE ll_relabel_item(application_id BIGINT, customer_id VARCHAR, source_inbound_order_id BIGINT)");
statement.execute("INSERT INTO ll_inbound_plan VALUES (1,'100','AUTO'),(2,'100','AUTO'),(3,'100','WAREHOUSE_TRANSFER'),(4,'100','AUTO'),(5,'100','AUTO')");
statement.execute("INSERT INTO ll_inbound_plan_pallet VALUES (11,1),(12,2),(13,3),(14,4),(15,5)");
statement.execute("INSERT INTO ll_order VALUES (21),(22),(23),(24),(25),(26)");
statement.execute("INSERT INTO ll_inbound_plan_line VALUES (31,21),(32,22),(33,23),(34,24),(35,25),(36,26)");
statement.execute("INSERT INTO ll_inbound_plan_pallet_allocation VALUES (11,31),(12,32),(13,33),(14,34),(15,35),(15,36)");
statement.execute("INSERT INTO ll_relabel_application VALUES (41,'100','PENDING_REVIEW'),(42,'100','COMPLETED'),(43,'100','PENDING_REVIEW'),(44,'200','PENDING_REVIEW'),(45,'100','PENDING_REVIEW')");
statement.execute("INSERT INTO ll_relabel_item VALUES (41,'100',21),(42,'100',22),(43,'100',23),(44,'200',24),(45,'100',25)");
for (Method method : List.of(
DispatchCandidateMapper.class.getMethod("available", String.class, List.class, String.class, String.class, String.class, String.class),
DispatchCandidateMapper.class.getMethod("lockSelected", String.class, List.class))) {
String sql = String.join(" ", method.getAnnotation(Select.class).value());
String boundary = sql.substring(sql.lastIndexOf("AND (ip.creation_mode"), sql.lastIndexOf("GROUP BY"))
.replace("#{customerId}", "'100'");
var result = statement.executeQuery("SELECT p.id FROM ll_inbound_plan_pallet p JOIN ll_inbound_plan ip ON ip.id=p.plan_id "
+ "JOIN ll_inbound_plan_pallet_allocation a ON a.pallet_id=p.id JOIN ll_inbound_plan_line l ON l.id=a.line_id "
+ "JOIN ll_order o ON o.id=l.order_id WHERE 1=1 " + boundary + " ORDER BY p.id");
var palletIds = new ArrayList<Long>();
while (result.next()) palletIds.add(result.getLong(1));
assertEquals(List.of(12L, 13L, 14L), palletIds, method.getName());
}
}
}
private static final List<String> CONSTRUCTOR_ALIASES = List.of(
"planId", "planPalletId", "warehousePalletId", "orderId", "customerId", "customerName",
"planNo", "orderType", "volumeWeightKg", "grossWeightKg", "containerNo", "appointmentAt", "expectedLoadingAt",