feat(firstleg): enforce single remainder split schema

This commit is contained in:
2026-08-11 09:17:02 +08:00
parent 4630186a56
commit 5913c3c3ef
5 changed files with 232 additions and 68 deletions
@@ -22,10 +22,13 @@ class InboundParentChildSqlContractTest {
.contains("'fl_inbound_order', 'package_count'")
.contains("'fl_inbound_order', 'split_purpose'")
.contains("SET split_purpose = 'REMAINDER'")
.contains("active_remainder_root_id")
.contains("GENERATED ALWAYS AS (CASE WHEN inbound_role = 'MASTER'")
.contains("GENERATED ALWAYS AS (CASE WHEN inbound_role = 'CHILD'")
.contains("DROP INDEX uk_inbound_tenant_order")
.contains("uk_inbound_master_order")
.contains("uk_inbound_parent_split_sequence")
.contains("uk_inbound_one_active_remainder")
.contains("DROP INDEX uk_inbound_parent_sequence");
}
@@ -54,17 +57,23 @@ class InboundParentChildSqlContractTest {
}
@Test
void protectsOwnershipAndTotals() {
void protectsSingleRemainderWritesAndConservation() {
assertThat(sql)
.contains("子入仓单必须绑定同一客户的有效主单")
.contains("parent_order.version > NEW.expected_parent_version")
.contains("NEW.loaded_package_count")
.contains("NEW.remaining_package_count")
.contains("NEW.loaded_package_count + NEW.remaining_package_count <> NEW.package_count")
.contains("NEW.loaded_child_inbound_id IS NOT NULL")
.contains("NEW.child_inbound_id = batch.remainder_child_inbound_id")
.contains("source_order.package_count = NEW.loaded_package_count")
.contains("item.child_inbound_id <> NEW.remainder_child_inbound_id")
.contains("<> NEW.remaining_package_count")
.contains("根入仓单原始箱数与已完成分片及当前余量不守恒")
.contains("COALESCE(source_order.package_count, source_order.total_package_count) = NEW.package_count")
.contains("AND plan_item.deleted = 0))))\n OR NOT EXISTS (")
.contains("source_batch.inbound_id = COALESCE(batch.source_inbound_id, batch.parent_inbound_id)")
.contains("拆分批次来源必须是箱数匹配且未消费的根主单或余量子单")
.contains("拆分批次来源必须是箱数守恒的根主单或余量分片")
.contains("SUM(split_item.package_count) > source_item.package_count")
.contains("子单承接箱数超过来源测量箱规箱数")
.contains("child_order.package_count >= NEW.package_count")
@@ -75,13 +84,14 @@ class InboundParentChildSqlContractTest {
}
@Test
void validatesMultiChildAndMultiSkuTotals() {
void validatesSingleRemainderAndMultiSkuTotals() {
assertThat(sql)
.contains("uk_inbound_parent_split_sequence")
.contains("uk_inbound_one_active_remainder")
.contains("uk_inbound_split_batch_line")
.contains("uk_inbound_split_batch_source_child")
.contains("COUNT(DISTINCT item.child_inbound_id)")
.contains("NOT BETWEEN 1 AND 2")
.contains("<> CASE WHEN NEW.remaining_package_count > 0 THEN 1 ELSE 0 END")
.contains("child_order.package_count <> SUM(item.package_count)");
}
@@ -108,6 +118,7 @@ class InboundParentChildSqlContractTest {
assertThat(sql)
.contains("table_name = 'fl_inbound_split_batch'")
.contains("uk_inbound_split_source_once")
.contains("uk_inbound_one_active_remainder")
.contains("table_name = 'fl_inbound_split_item'")
.contains("uk_inbound_split_batch_source_child")
.contains("is_nullable = 'NO'")
@@ -107,6 +107,22 @@ class Phase3FinalUnifiedSqlContractTest {
.doesNotContain("UNIQUE KEY `uk_inbound_split_batch_source_item`");
}
@Test
void unifiedEntryEnforcesSingleRemainderWritesAndKeepsLegacyColumns() throws Exception {
String sql = normalise(Files.readString(resolveUnified()));
assertThat(sql)
.contains("'fl_inbound_order', 'active_remainder_root_id'")
.contains("uk_inbound_one_active_remainder")
.contains("uk_inbound_split_source_once")
.contains("NEW.loaded_child_inbound_id IS NOT NULL")
.contains("NEW.child_inbound_id = batch.remainder_child_inbound_id")
.contains("source_order.package_count = NEW.loaded_package_count")
.contains("根入仓单原始箱数与已完成分片及当前余量不守恒")
.contains("'fl_inbound_split_batch', 'loaded_child_inbound_id'")
.contains("fk_inbound_split_loaded_child");
}
private static void assertBlock(String unified, String marker, String nextMarker, String modulePath)
throws Exception {
int start = unified.indexOf(marker);
@@ -16,7 +16,7 @@ class SplitPreloadSqlContractTest {
}
@Test
void limitsSecondaryInboundDepth() throws Exception {
void limitsNewChildrenToRootBasedRemainders() throws Exception {
String sql = moduleMigration();
assertThat(sql)
.contains("parent_inbound_id")
@@ -25,7 +25,8 @@ class SplitPreloadSqlContractTest {
.contains("split_status")
.contains("uk_inbound_parent_split_sequence")
.contains("fk_inbound_parent_order")
.contains("子入仓单必须标记LOADED或REMAINDER")
.contains("新建子入仓单只能作为余量分片")
.contains("uk_inbound_one_active_remainder")
.contains("入仓单主子归属创建后不可修改");
}