2026-08-06 17:48:50 +08:00
SET NAMES utf8mb4 ;
USE baiyue_firstleg ;
/*
* 三期主子入仓单结构迁移。
* 仅建立结构和数据库保护,不自动拆分或回填存量业务数据。
2026-08-11 09:17:02 +08:00
* 新写入采用“来源单承接实装、仅创建余量子单”;LOADED 子单和 loaded_child_inbound_id
* 仅为历史双子单读取保留,不删除、不清洗存量记录。
2026-08-06 17:48:50 +08:00
* tenant_id 继续表示客户隔离键;数量统一使用 package_count。
*/
DROP PROCEDURE IF EXISTS phase3_inbound_add_column ;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_add_column (
IN p_table_name VARCHAR ( 64 ),
IN p_column_name VARCHAR ( 64 ),
IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema . columns
WHERE table_schema = DATABASE ()
AND table_name = p_table_name
AND column_name = p_column_name
) THEN
SET @ ddl = CONCAT ( 'ALTER TABLE `' , p_table_name , '` ADD COLUMN `' ,
p_column_name , '` ' , p_definition );
PREPARE stmt FROM @ ddl ;
EXECUTE stmt ;
DEALLOCATE PREPARE stmt ;
END IF ;
END $$
DELIMITER ;
CALL phase3_inbound_add_column (
'fl_inbound_order' , 'inbound_role' ,
"VARCHAR(16) NOT NULL DEFAULT 'MASTER' COMMENT '入仓单层级:MASTER主单、CHILD子单' AFTER inbound_code" );
CALL phase3_inbound_add_column (
'fl_inbound_order' , 'parent_inbound_id' ,
"BIGINT UNSIGNED NULL COMMENT '直属主入仓单ID;主单为空' AFTER inbound_role" );
CALL phase3_inbound_add_column (
'fl_inbound_order' , 'root_inbound_id' ,
"BIGINT UNSIGNED NULL COMMENT '根主入仓单ID;存量主单可为空,按自身ID解释' AFTER parent_inbound_id" );
CALL phase3_inbound_add_column (
'fl_inbound_order' , 'split_sequence' ,
"INT UNSIGNED NULL COMMENT '同一主单下的子单顺序号' AFTER root_inbound_id" );
CALL phase3_inbound_add_column (
'fl_inbound_order' , 'split_status' ,
"VARCHAR(24) NOT NULL DEFAULT 'NOT_SPLIT' COMMENT '拆分状态:NOT_SPLIT、PARTIALLY_SPLIT、FULLY_SPLIT' AFTER split_sequence" );
CALL phase3_inbound_add_column (
'fl_inbound_order' , 'package_count' ,
"INT UNSIGNED NULL COMMENT '入仓单箱数;三期唯一数量口径' AFTER total_package_count" );
2026-08-10 12:22:29 +08:00
CALL phase3_inbound_add_column (
'fl_inbound_order' , 'split_purpose' ,
"VARCHAR(16) NOT NULL DEFAULT 'MASTER' COMMENT '履约分片用途:MASTER、LOADED、REMAINDER' AFTER package_count" );
2026-08-06 17:48:50 +08:00
CALL phase3_inbound_add_column (
'fl_inbound_order' , 'master_order_id' ,
"BIGINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN inbound_role = 'MASTER' AND deleted = 0 THEN order_id ELSE NULL END) STORED COMMENT '仅用于约束有效主单订单唯一性'" );
2026-08-10 12:22:29 +08:00
-- 旧实现只生成未装余量子单,可无歧义映射为 REMAINDER。
UPDATE fl_inbound_order
SET split_purpose = 'REMAINDER'
WHERE inbound_role = 'CHILD'
AND split_purpose = 'MASTER' ;
2026-08-11 09:17:02 +08:00
CALL phase3_inbound_add_column (
'fl_inbound_order' , 'active_remainder_root_id' ,
"BIGINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN inbound_role = 'CHILD' AND split_purpose = 'REMAINDER' AND split_status = 'NOT_SPLIT' AND deleted = 0 THEN root_inbound_id ELSE NULL END) STORED COMMENT '有效余量唯一约束键;历史已完成分片为空'" );
2026-08-06 17:48:50 +08:00
DROP PROCEDURE IF EXISTS phase3_inbound_add_column ;
DROP PROCEDURE IF EXISTS phase3_inbound_add_index ;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_add_index (
IN p_table_name VARCHAR ( 64 ),
IN p_index_name VARCHAR ( 64 ),
IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema . statistics
WHERE table_schema = DATABASE ()
AND table_name = p_table_name
AND index_name = p_index_name
) THEN
SET @ ddl = CONCAT ( 'ALTER TABLE `' , p_table_name , '` ADD ' , p_definition );
PREPARE stmt FROM @ ddl ;
EXECUTE stmt ;
DEALLOCATE PREPARE stmt ;
END IF ;
END $$
DELIMITER ;
CALL phase3_inbound_add_index (
'fl_inbound_order' , 'uk_inbound_master_order' ,
'UNIQUE KEY `uk_inbound_master_order` (`tenant_id`, `master_order_id`)' );
CALL phase3_inbound_add_index (
'fl_inbound_order' , 'idx_inbound_root_role' ,
'KEY `idx_inbound_root_role` (`tenant_id`, `root_inbound_id`, `inbound_role`, `created_at`, `id`)' );
2026-08-11 09:17:02 +08:00
DROP PROCEDURE IF EXISTS phase3_assert_active_remainder_unique_ready ;
DELIMITER $$
CREATE PROCEDURE phase3_assert_active_remainder_unique_ready ()
BEGIN
IF EXISTS (
SELECT 1
FROM fl_inbound_order
WHERE inbound_role = 'CHILD'
AND split_purpose = 'REMAINDER'
AND split_status = 'NOT_SPLIT'
AND deleted = 0
AND root_inbound_id IS NOT NULL
GROUP BY tenant_id , root_inbound_id
HAVING COUNT ( * ) > 1
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '同一根入仓单存在多个有效余量,请先人工核对历史数据' ;
END IF ;
END $$
DELIMITER ;
CALL phase3_assert_active_remainder_unique_ready ();
DROP PROCEDURE IF EXISTS phase3_assert_active_remainder_unique_ready ;
CALL phase3_inbound_add_index (
'fl_inbound_order' , 'uk_inbound_one_active_remainder' ,
'UNIQUE KEY `uk_inbound_one_active_remainder` (`tenant_id`, `active_remainder_root_id`)' );
2026-08-06 17:48:50 +08:00
DROP PROCEDURE IF EXISTS phase3_inbound_drop_legacy_index ;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_drop_legacy_index ()
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema . statistics
WHERE table_schema = DATABASE ()
AND table_name = 'fl_inbound_order'
AND index_name = 'uk_inbound_tenant_order'
) THEN
ALTER TABLE fl_inbound_order DROP INDEX uk_inbound_tenant_order ;
END IF ;
2026-08-07 10:57:06 +08:00
IF EXISTS (
SELECT 1 FROM information_schema . statistics
WHERE table_schema = DATABASE ()
AND table_name = 'fl_inbound_order'
AND index_name = 'uk_inbound_parent_sequence'
) THEN
ALTER TABLE fl_inbound_order DROP INDEX uk_inbound_parent_sequence ;
END IF ;
2026-08-10 12:22:29 +08:00
IF EXISTS (
SELECT 1 FROM information_schema . statistics
WHERE table_schema = DATABASE ()
AND table_name = 'fl_inbound_order'
AND index_name = 'uk_inbound_one_child'
) THEN
ALTER TABLE fl_inbound_order DROP INDEX uk_inbound_one_child ;
END IF ;
2026-08-10 13:30:59 +08:00
IF EXISTS (
SELECT 1 FROM information_schema . statistics
WHERE table_schema = DATABASE ()
AND table_name = 'fl_inbound_order'
AND index_name = 'uk_inbound_one_secondary'
) THEN
ALTER TABLE fl_inbound_order DROP INDEX uk_inbound_one_secondary ;
END IF ;
2026-08-06 17:48:50 +08:00
END $$
DELIMITER ;
CALL phase3_inbound_drop_legacy_index ();
DROP PROCEDURE IF EXISTS phase3_inbound_drop_legacy_index ;
2026-08-10 12:22:29 +08:00
CALL phase3_inbound_add_index (
'fl_inbound_order' , 'uk_inbound_parent_split_sequence' ,
'UNIQUE KEY `uk_inbound_parent_split_sequence` (`tenant_id`, `parent_inbound_id`, `split_sequence`)' );
2026-08-06 17:48:50 +08:00
DROP PROCEDURE IF EXISTS phase3_inbound_add_index ;
CREATE TABLE IF NOT EXISTS fl_inbound_split_batch (
id BIGINT UNSIGNED NOT NULL COMMENT '拆分批次ID' ,
2026-08-11 18:06:26 +08:00
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键' ,
2026-08-06 17:48:50 +08:00
parent_inbound_id BIGINT UNSIGNED NOT NULL COMMENT '主入仓单ID' ,
operation_id VARCHAR ( 64 ) NOT NULL COMMENT '业务操作幂等号' ,
idempotency_key VARCHAR ( 128 ) NOT NULL COMMENT '请求幂等键' ,
expected_parent_version INT NOT NULL COMMENT '操作时主单乐观锁版本' ,
package_count INT UNSIGNED NOT NULL COMMENT '本批次拆分箱数' ,
2026-08-11 18:06:26 +08:00
status VARCHAR ( 16 ) NOT NULL DEFAULT 'PREPARED' COMMENT '拆分批次状态:PREPARED已准备、APPLIED已生效、CANCELLED已取消' ,
2026-08-06 17:48:50 +08:00
remark VARCHAR ( 500 ) NULL COMMENT '拆分原因或备注' ,
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID' ,
created_by_name VARCHAR ( 64 ) NULL COMMENT '创建人名称快照' ,
2026-08-11 18:06:26 +08:00
created_at DATETIME ( 3 ) NOT NULL DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '拆分批次创建时间' ,
2026-08-06 17:48:50 +08:00
applied_at DATETIME ( 3 ) NULL COMMENT '批次生效时间' ,
2026-08-11 18:06:26 +08:00
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增' ,
2026-08-06 17:48:50 +08:00
PRIMARY KEY ( id ),
UNIQUE KEY uk_inbound_split_operation ( tenant_id , operation_id ),
UNIQUE KEY uk_inbound_split_idempotency ( tenant_id , parent_inbound_id , idempotency_key ),
KEY idx_inbound_split_parent ( tenant_id , parent_inbound_id , created_at , id ),
CONSTRAINT chk_inbound_split_package CHECK ( package_count > 0 ),
CONSTRAINT chk_inbound_split_status CHECK ( status IN ( 'PREPARED' , 'APPLIED' , 'CANCELLED' )),
CONSTRAINT fk_inbound_split_parent FOREIGN KEY ( parent_inbound_id ) REFERENCES fl_inbound_order ( id )
) ENGINE = InnoDB COMMENT = '主入仓单拆分批次;数量口径仅为箱数' ;
CREATE TABLE IF NOT EXISTS fl_inbound_split_item (
id BIGINT UNSIGNED NOT NULL COMMENT '拆分明细ID' ,
2026-08-11 18:06:26 +08:00
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键' ,
2026-08-06 17:48:50 +08:00
split_batch_id BIGINT UNSIGNED NOT NULL COMMENT '拆分批次ID' ,
parent_inbound_id BIGINT UNSIGNED NOT NULL COMMENT '主入仓单ID' ,
child_inbound_id BIGINT UNSIGNED NOT NULL COMMENT '子入仓单ID' ,
2026-08-07 10:57:06 +08:00
line_no INT UNSIGNED NOT NULL COMMENT '同一拆分批次内的行号' ,
2026-08-06 17:48:50 +08:00
source_inbound_detail_id BIGINT UNSIGNED NULL COMMENT '订单货物明细逻辑ID' ,
2026-08-10 12:40:47 +08:00
source_measurement_id BIGINT UNSIGNED NOT NULL COMMENT '来源测量批次ID' ,
source_measurement_item_id BIGINT UNSIGNED NOT NULL COMMENT '来源测量箱规明细ID' ,
2026-08-06 17:48:50 +08:00
package_count INT UNSIGNED NOT NULL COMMENT '分配给子单的箱数' ,
2026-08-11 18:06:26 +08:00
created_at DATETIME ( 3 ) NOT NULL DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '拆分数量台账创建时间' ,
2026-08-06 17:48:50 +08:00
PRIMARY KEY ( id ),
2026-08-07 10:57:06 +08:00
UNIQUE KEY uk_inbound_split_batch_line ( tenant_id , split_batch_id , line_no ),
2026-08-10 12:40:47 +08:00
UNIQUE KEY uk_inbound_split_batch_source_child ( tenant_id , split_batch_id , source_measurement_item_id , child_inbound_id ),
2026-08-06 17:48:50 +08:00
KEY idx_inbound_split_detail ( tenant_id , source_inbound_detail_id , created_at ),
CONSTRAINT chk_inbound_split_item_package CHECK ( package_count > 0 ),
CONSTRAINT fk_inbound_split_item_batch FOREIGN KEY ( split_batch_id ) REFERENCES fl_inbound_split_batch ( id ),
CONSTRAINT fk_inbound_split_item_parent FOREIGN KEY ( parent_inbound_id ) REFERENCES fl_inbound_order ( id ),
CONSTRAINT fk_inbound_split_item_child FOREIGN KEY ( child_inbound_id ) REFERENCES fl_inbound_order ( id ),
CONSTRAINT fk_inbound_split_item_measurement FOREIGN KEY ( source_measurement_id ) REFERENCES fl_inbound_measurement ( id ),
CONSTRAINT fk_inbound_split_item_measure_item FOREIGN KEY ( source_measurement_item_id ) REFERENCES fl_inbound_measurement_item ( id )
) ENGINE = InnoDB COMMENT = '主子入仓单拆分数量台账' ;
CREATE TABLE IF NOT EXISTS fl_inbound_split_history (
id BIGINT UNSIGNED NOT NULL COMMENT '拆分历史ID' ,
2026-08-11 18:06:26 +08:00
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键' ,
2026-08-06 17:48:50 +08:00
split_batch_id BIGINT UNSIGNED NOT NULL COMMENT '拆分批次ID' ,
operation_id VARCHAR ( 64 ) NOT NULL COMMENT '历史操作幂等号' ,
2026-08-11 18:06:26 +08:00
action_type VARCHAR ( 24 ) NOT NULL COMMENT '拆分动作:CREATE创建批次、APPLY生效、CANCEL取消' ,
2026-08-06 17:48:50 +08:00
before_snapshot JSON NULL COMMENT '操作前快照' ,
after_snapshot JSON NOT NULL COMMENT '操作后快照' ,
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID' ,
operator_name VARCHAR ( 64 ) NULL COMMENT '操作人名称快照' ,
operation_reason VARCHAR ( 500 ) NULL COMMENT '操作原因' ,
2026-08-11 18:06:26 +08:00
created_at DATETIME ( 3 ) NOT NULL DEFAULT CURRENT_TIMESTAMP ( 3 ) COMMENT '不可变拆分历史创建时间' ,
2026-08-06 17:48:50 +08:00
PRIMARY KEY ( id ),
UNIQUE KEY uk_inbound_split_history_operation ( tenant_id , operation_id ),
KEY idx_inbound_split_history ( tenant_id , split_batch_id , created_at , id ),
CONSTRAINT chk_inbound_split_action CHECK ( action_type IN ( 'CREATE' , 'APPLY' , 'CANCEL' )),
CONSTRAINT fk_inbound_split_history_batch FOREIGN KEY ( split_batch_id ) REFERENCES fl_inbound_split_batch ( id )
) ENGINE = InnoDB COMMENT = '不可变主子入仓单拆分审计历史' ;
DROP PROCEDURE IF EXISTS phase3_inbound_add_column ;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_add_column (
IN p_table_name VARCHAR ( 64 ),
IN p_column_name VARCHAR ( 64 ),
IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema . columns
WHERE table_schema = DATABASE ()
AND table_name = p_table_name
AND column_name = p_column_name
) THEN
SET @ ddl = CONCAT ( 'ALTER TABLE `' , p_table_name , '` ADD COLUMN `' ,
p_column_name , '` ' , p_definition );
PREPARE stmt FROM @ ddl ;
EXECUTE stmt ;
DEALLOCATE PREPARE stmt ;
END IF ;
END $$
DELIMITER ;
CALL phase3_inbound_add_column (
'fl_inbound_measurement' , 'split_batch_id' ,
"BIGINT UNSIGNED NULL COMMENT '产生该测量批次的入仓拆分批次ID' AFTER inbound_id" );
CALL phase3_inbound_add_column (
'fl_inbound_measurement' , 'source_measurement_id' ,
"BIGINT UNSIGNED NULL COMMENT '子单继承或复核时的来源测量批次ID' AFTER split_batch_id" );
CALL phase3_inbound_add_column (
'fl_inbound_measurement_item' , 'split_item_id' ,
"BIGINT UNSIGNED NULL COMMENT '产生该子单测量明细的拆分台账ID' AFTER measurement_id" );
CALL phase3_inbound_add_column (
'fl_inbound_measurement_item' , 'source_measurement_item_id' ,
"BIGINT UNSIGNED NULL COMMENT '被拆分继承的来源测量箱规明细ID' AFTER split_item_id" );
2026-08-07 10:57:06 +08:00
CALL phase3_inbound_add_column (
'fl_inbound_split_item' , 'line_no' ,
"INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '同一拆分批次内的行号' AFTER child_inbound_id" );
2026-08-10 12:40:47 +08:00
ALTER TABLE fl_inbound_split_item
MODIFY source_measurement_id BIGINT UNSIGNED NOT NULL COMMENT '来源测量批次ID' ,
MODIFY source_measurement_item_id BIGINT UNSIGNED NOT NULL COMMENT '来源测量箱规明细ID' ;
2026-08-10 12:22:29 +08:00
CALL phase3_inbound_add_column (
'fl_inbound_split_batch' , 'loaded_child_inbound_id' ,
"BIGINT UNSIGNED NULL COMMENT '本批次实装子单ID' AFTER package_count" );
CALL phase3_inbound_add_column (
'fl_inbound_split_batch' , 'source_inbound_id' ,
"BIGINT UNSIGNED NULL COMMENT '本批次实际消耗的MASTER或REMAINDER分片ID' AFTER parent_inbound_id" );
CALL phase3_inbound_add_column (
'fl_inbound_split_batch' , 'remainder_child_inbound_id' ,
"BIGINT UNSIGNED NULL COMMENT '本批次余量子单ID' AFTER loaded_child_inbound_id" );
CALL phase3_inbound_add_column (
'fl_inbound_split_batch' , 'loaded_package_count' ,
"INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '本批次实装箱数' AFTER remainder_child_inbound_id" );
CALL phase3_inbound_add_column (
'fl_inbound_split_batch' , 'remaining_package_count' ,
"INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '本批次未装箱数' AFTER loaded_package_count" );
CALL phase3_inbound_add_column (
'fl_inbound_split_batch' , 'loading_plan_id' ,
"BIGINT UNSIGNED NULL COMMENT '关联装柜计划/柜次' AFTER remaining_package_count" );
CALL phase3_inbound_add_column (
'fl_inbound_split_batch' , 'container_booking_id' ,
"BIGINT UNSIGNED NULL COMMENT '关联柜次标识(兼容查询)' AFTER loading_plan_id" );
2026-08-06 17:48:50 +08:00
DROP PROCEDURE IF EXISTS phase3_inbound_add_column ;
DROP PROCEDURE IF EXISTS phase3_inbound_add_relation ;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_add_relation (
IN p_table_name VARCHAR ( 64 ),
IN p_constraint_name VARCHAR ( 64 ),
IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema . table_constraints
WHERE constraint_schema = DATABASE ()
AND table_name = p_table_name
AND constraint_name = p_constraint_name
) THEN
SET @ ddl = CONCAT ( 'ALTER TABLE `' , p_table_name , '` ADD CONSTRAINT `' ,
p_constraint_name , '` ' , p_definition );
PREPARE stmt FROM @ ddl ;
EXECUTE stmt ;
DEALLOCATE PREPARE stmt ;
END IF ;
END $$
DELIMITER ;
CALL phase3_inbound_add_relation (
'fl_inbound_order' , 'fk_inbound_parent_order' ,
'FOREIGN KEY (`parent_inbound_id`) REFERENCES `fl_inbound_order` (`id`)' );
CALL phase3_inbound_add_relation (
'fl_inbound_order' , 'fk_inbound_root_order' ,
'FOREIGN KEY (`root_inbound_id`) REFERENCES `fl_inbound_order` (`id`)' );
2026-08-10 12:22:29 +08:00
CALL phase3_inbound_add_relation (
'fl_inbound_split_batch' , 'fk_inbound_split_loaded_child' ,
'FOREIGN KEY (`loaded_child_inbound_id`) REFERENCES `fl_inbound_order` (`id`)' );
CALL phase3_inbound_add_relation (
'fl_inbound_split_batch' , 'fk_inbound_split_source' ,
'FOREIGN KEY (`source_inbound_id`) REFERENCES `fl_inbound_order` (`id`)' );
CALL phase3_inbound_add_relation (
'fl_inbound_split_batch' , 'fk_inbound_split_remainder_child' ,
'FOREIGN KEY (`remainder_child_inbound_id`) REFERENCES `fl_inbound_order` (`id`)' );
2026-08-06 17:48:50 +08:00
CALL phase3_inbound_add_relation (
'fl_inbound_measurement' , 'fk_measure_split_batch' ,
'FOREIGN KEY (`split_batch_id`) REFERENCES `fl_inbound_split_batch` (`id`)' );
CALL phase3_inbound_add_relation (
'fl_inbound_measurement' , 'fk_measure_source_batch' ,
'FOREIGN KEY (`source_measurement_id`) REFERENCES `fl_inbound_measurement` (`id`)' );
CALL phase3_inbound_add_relation (
'fl_inbound_measurement_item' , 'fk_measure_item_split_item' ,
'FOREIGN KEY (`split_item_id`) REFERENCES `fl_inbound_split_item` (`id`)' );
CALL phase3_inbound_add_relation (
'fl_inbound_measurement_item' , 'fk_measure_item_source_item' ,
'FOREIGN KEY (`source_measurement_item_id`) REFERENCES `fl_inbound_measurement_item` (`id`)' );
DROP PROCEDURE IF EXISTS phase3_inbound_add_relation ;
DROP PROCEDURE IF EXISTS phase3_inbound_add_index ;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_add_index (
IN p_table_name VARCHAR ( 64 ),
IN p_index_name VARCHAR ( 64 ),
IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema . statistics
WHERE table_schema = DATABASE ()
AND table_name = p_table_name
AND index_name = p_index_name
) THEN
SET @ ddl = CONCAT ( 'ALTER TABLE `' , p_table_name , '` ADD ' , p_definition );
PREPARE stmt FROM @ ddl ;
EXECUTE stmt ;
DEALLOCATE PREPARE stmt ;
END IF ;
END $$
DELIMITER ;
CALL phase3_inbound_add_index (
'fl_inbound_measurement' , 'idx_measure_split_source' ,
'KEY `idx_measure_split_source` (`tenant_id`, `split_batch_id`, `source_measurement_id`, `created_at`)' );
CALL phase3_inbound_add_index (
'fl_inbound_measurement_item' , 'idx_measure_item_split_source' ,
'KEY `idx_measure_item_split_source` (`tenant_id`, `split_item_id`, `source_measurement_item_id`)' );
2026-08-07 10:57:06 +08:00
CALL phase3_inbound_add_index (
'fl_inbound_split_item' , 'uk_inbound_split_batch_line' ,
'UNIQUE KEY `uk_inbound_split_batch_line` (`tenant_id`, `split_batch_id`, `line_no`)' );
CALL phase3_inbound_add_index (
2026-08-10 12:22:29 +08:00
'fl_inbound_split_batch' , 'idx_inbound_split_loading_plan' ,
'KEY `idx_inbound_split_loading_plan` (`tenant_id`, `loading_plan_id`, `container_booking_id`, `status`, `id`)' );
2026-08-10 12:40:47 +08:00
CALL phase3_inbound_add_index (
'fl_inbound_split_batch' , 'uk_inbound_split_source_once' ,
'UNIQUE KEY `uk_inbound_split_source_once` (`tenant_id`, `source_inbound_id`)' );
2026-08-06 17:48:50 +08:00
2026-08-07 10:57:06 +08:00
DROP PROCEDURE IF EXISTS phase3_inbound_drop_split_legacy_index ;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_drop_split_legacy_index ()
BEGIN
IF EXISTS ( SELECT 1 FROM information_schema . statistics WHERE table_schema = DATABASE ()
AND table_name = 'fl_inbound_split_item' AND index_name = 'uk_inbound_split_child' ) THEN
ALTER TABLE fl_inbound_split_item DROP INDEX uk_inbound_split_child ;
END IF ;
IF EXISTS ( SELECT 1 FROM information_schema . statistics WHERE table_schema = DATABASE ()
AND table_name = 'fl_inbound_split_item' AND index_name = 'uk_inbound_split_batch_child' ) THEN
ALTER TABLE fl_inbound_split_item DROP INDEX uk_inbound_split_batch_child ;
END IF ;
2026-08-10 12:22:29 +08:00
IF EXISTS ( SELECT 1 FROM information_schema . statistics WHERE table_schema = DATABASE ()
AND table_name = 'fl_inbound_split_item' AND index_name = 'uk_inbound_split_batch_source_item' ) THEN
ALTER TABLE fl_inbound_split_item DROP INDEX uk_inbound_split_batch_source_item ;
END IF ;
2026-08-07 10:57:06 +08:00
END $$
DELIMITER ;
CALL phase3_inbound_drop_split_legacy_index ();
DROP PROCEDURE IF EXISTS phase3_inbound_drop_split_legacy_index ;
2026-08-10 12:22:29 +08:00
CALL phase3_inbound_add_index (
'fl_inbound_split_item' , 'uk_inbound_split_batch_source_child' ,
'UNIQUE KEY `uk_inbound_split_batch_source_child` (`tenant_id`, `split_batch_id`, `source_measurement_item_id`, `child_inbound_id`)' );
DROP PROCEDURE IF EXISTS phase3_inbound_add_index ;
2026-08-07 10:57:06 +08:00
2026-08-06 17:48:50 +08:00
DROP TRIGGER IF EXISTS trg_inbound_order_parent_before_insert ;
DROP TRIGGER IF EXISTS trg_inbound_order_parent_before_update ;
DROP TRIGGER IF EXISTS trg_inbound_split_item_before_insert ;
DROP TRIGGER IF EXISTS trg_inbound_split_batch_before_insert ;
DROP TRIGGER IF EXISTS trg_inbound_split_batch_before_update ;
DROP TRIGGER IF EXISTS trg_inbound_split_item_before_update ;
DROP TRIGGER IF EXISTS trg_inbound_split_item_before_delete ;
DROP TRIGGER IF EXISTS trg_inbound_split_history_before_update ;
DROP TRIGGER IF EXISTS trg_inbound_split_history_before_delete ;
DELIMITER $$
CREATE TRIGGER trg_inbound_order_parent_before_insert
BEFORE INSERT ON fl_inbound_order
FOR EACH ROW
BEGIN
IF NEW . inbound_role NOT IN ( 'MASTER' , 'CHILD' ) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单层级非法' ;
END IF ;
IF NEW . split_status NOT IN ( 'NOT_SPLIT' , 'PARTIALLY_SPLIT' , 'FULLY_SPLIT' ) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单拆分状态非法' ;
END IF ;
2026-08-10 12:22:29 +08:00
IF NEW . split_purpose NOT IN ( 'MASTER' , 'LOADED' , 'REMAINDER' ) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单分片用途非法' ;
END IF ;
2026-08-06 17:48:50 +08:00
IF NEW . package_count IS NOT NULL AND NEW . package_count = 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单箱数必须大于0' ;
END IF ;
IF NEW . inbound_role = 'MASTER' AND NEW . parent_inbound_id IS NOT NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主入仓单不能绑定父单' ;
END IF ;
2026-08-10 12:40:47 +08:00
IF NEW . inbound_role = 'MASTER' AND NEW . split_purpose <> 'MASTER' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主入仓单用途必须为MASTER' ;
END IF ;
2026-08-06 17:48:50 +08:00
IF NEW . inbound_role = 'CHILD' THEN
IF NEW . parent_inbound_id IS NULL OR NEW . root_inbound_id IS NULL
OR NEW . split_sequence IS NULL OR NEW . package_count IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '子入仓单缺少主单、顺序号或箱数' ;
END IF ;
2026-08-11 09:17:02 +08:00
IF NEW . split_purpose <> 'REMAINDER' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '新建子入仓单只能作为余量分片' ;
2026-08-06 17:48:50 +08:00
END IF ;
IF NOT EXISTS (
SELECT 1 FROM fl_inbound_order parent_order
WHERE parent_order . id = NEW . parent_inbound_id
AND parent_order . tenant_id = NEW . tenant_id
AND parent_order . inbound_role = 'MASTER'
AND parent_order . deleted = 0
) OR NEW . root_inbound_id <> NEW . parent_inbound_id THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '子入仓单必须绑定同一客户的有效主单' ;
END IF ;
END IF ;
END $$
CREATE TRIGGER trg_inbound_order_parent_before_update
BEFORE UPDATE ON fl_inbound_order
FOR EACH ROW
BEGIN
2026-08-10 12:40:47 +08:00
IF NOT ( OLD . tenant_id <=> NEW . tenant_id )
OR NOT ( OLD . inbound_role <=> NEW . inbound_role )
2026-08-06 17:48:50 +08:00
OR NOT ( OLD . parent_inbound_id <=> NEW . parent_inbound_id )
OR NOT ( OLD . root_inbound_id <=> NEW . root_inbound_id )
2026-08-10 12:22:29 +08:00
OR NOT ( OLD . split_sequence <=> NEW . split_sequence )
OR NOT ( OLD . split_purpose <=> NEW . split_purpose ) THEN
2026-08-06 17:48:50 +08:00
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单主子归属创建后不可修改' ;
END IF ;
2026-08-10 12:22:29 +08:00
IF NEW . split_purpose NOT IN ( 'MASTER' , 'LOADED' , 'REMAINDER' ) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单分片用途非法' ;
END IF ;
2026-08-06 17:48:50 +08:00
IF NEW . inbound_role = 'CHILD'
AND ( NEW . package_count IS NULL OR NEW . package_count = 0 ) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '子入仓单箱数必须大于0' ;
END IF ;
IF NEW . split_status NOT IN ( 'NOT_SPLIT' , 'PARTIALLY_SPLIT' , 'FULLY_SPLIT' ) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单拆分状态非法' ;
END IF ;
2026-08-10 12:40:47 +08:00
IF ( NEW . inbound_role = 'CHILD' AND NEW . split_purpose = 'MASTER' )
OR ( NEW . inbound_role = 'MASTER' AND NEW . split_purpose <> 'MASTER' ) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单层级与用途不一致' ;
2026-08-06 17:48:50 +08:00
END IF ;
END $$
CREATE TRIGGER trg_inbound_split_item_before_insert
BEFORE INSERT ON fl_inbound_split_item
FOR EACH ROW
BEGIN
2026-08-10 12:40:47 +08:00
IF NEW . source_measurement_id IS NULL OR NEW . source_measurement_item_id IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分明细必须绑定真实测量来源' ;
END IF ;
2026-08-06 17:48:50 +08:00
IF NOT EXISTS (
SELECT 1
FROM fl_inbound_split_batch batch
JOIN fl_inbound_order parent_order ON parent_order . id = batch . parent_inbound_id
JOIN fl_inbound_order child_order ON child_order . id = NEW . child_inbound_id
WHERE batch . id = NEW . split_batch_id
AND batch . tenant_id = NEW . tenant_id
AND batch . parent_inbound_id = NEW . parent_inbound_id
AND batch . status = 'PREPARED'
2026-08-11 09:17:02 +08:00
AND batch . loaded_child_inbound_id IS NULL
AND batch . remainder_child_inbound_id IS NOT NULL
AND NEW . child_inbound_id = batch . remainder_child_inbound_id
2026-08-06 17:48:50 +08:00
AND parent_order . tenant_id = NEW . tenant_id
AND parent_order . inbound_role = 'MASTER'
AND child_order . tenant_id = NEW . tenant_id
AND child_order . inbound_role = 'CHILD'
2026-08-11 09:17:02 +08:00
AND child_order . split_purpose = 'REMAINDER'
2026-08-06 17:48:50 +08:00
AND child_order . parent_inbound_id = NEW . parent_inbound_id
2026-08-07 10:57:06 +08:00
AND child_order . package_count >= NEW . package_count
2026-08-06 17:48:50 +08:00
AND child_order . deleted = 0
2026-08-10 12:40:47 +08:00
AND EXISTS (
2026-08-06 17:48:50 +08:00
SELECT 1
FROM fl_inbound_measurement_item source_item
JOIN fl_inbound_measurement source_batch ON source_batch . id = source_item . measurement_id
WHERE source_item . id = NEW . source_measurement_item_id
AND source_item . tenant_id = NEW . tenant_id
AND source_item . measurement_id = NEW . source_measurement_id
2026-08-10 12:22:29 +08:00
AND source_batch . inbound_id = COALESCE ( batch . source_inbound_id , batch . parent_inbound_id )
2026-08-10 12:40:47 +08:00
)
2026-08-06 17:48:50 +08:00
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分明细与批次、主单或子单不一致' ;
END IF ;
END $$
CREATE TRIGGER trg_inbound_split_batch_before_insert
BEFORE INSERT ON fl_inbound_split_batch
FOR EACH ROW
BEGIN
IF NEW . status <> 'PREPARED' OR NEW . applied_at IS NOT NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次必须从PREPARED创建' ;
END IF ;
2026-08-11 09:17:02 +08:00
IF NEW . loaded_child_inbound_id IS NOT NULL
OR NEW . loaded_package_count = 0
OR NEW . loaded_package_count + NEW . remaining_package_count <> NEW . package_count
OR ( NEW . remaining_package_count = 0 AND NEW . remainder_child_inbound_id IS NOT NULL )
OR ( NEW . remaining_package_count > 0 AND NEW . remainder_child_inbound_id IS NULL ) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '新拆分批次必须由来源单承接实装且最多创建一个余量子单' ;
END IF ;
2026-08-06 17:48:50 +08:00
IF NOT EXISTS (
SELECT 1 FROM fl_inbound_order parent_order
WHERE parent_order . id = NEW . parent_inbound_id
AND parent_order . tenant_id = NEW . tenant_id
AND parent_order . inbound_role = 'MASTER'
AND parent_order . deleted = 0
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次主单不存在或客户归属不一致' ;
END IF ;
2026-08-10 12:40:47 +08:00
IF NEW . source_inbound_id IS NULL OR NOT EXISTS (
2026-08-10 12:22:29 +08:00
SELECT 1 FROM fl_inbound_order source_order
WHERE source_order . id = NEW . source_inbound_id
AND source_order . tenant_id = NEW . tenant_id
AND source_order . deleted = 0
2026-08-10 12:40:47 +08:00
AND (( source_order . id = NEW . parent_inbound_id
AND source_order . inbound_role = 'MASTER'
2026-08-11 09:17:02 +08:00
AND (( source_order . split_status = 'NOT_SPLIT'
AND COALESCE ( source_order . package_count , source_order . total_package_count ) = NEW . package_count )
OR ( source_order . split_status = 'FULLY_SPLIT'
AND source_order . package_count = NEW . loaded_package_count ))) OR
2026-08-10 12:22:29 +08:00
( source_order . inbound_role = 'CHILD'
AND source_order . parent_inbound_id = NEW . parent_inbound_id
2026-08-10 12:40:47 +08:00
AND source_order . split_purpose = 'REMAINDER'
2026-08-11 09:17:02 +08:00
AND (( source_order . split_status = 'NOT_SPLIT'
AND source_order . package_count = NEW . package_count )
OR ( source_order . split_status = 'FULLY_SPLIT'
AND source_order . package_count = NEW . loaded_package_count ))))
2026-08-10 12:22:29 +08:00
) THEN
2026-08-11 09:17:02 +08:00
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次来源必须是箱数守恒的根主单或余量分片' ;
2026-08-10 12:22:29 +08:00
END IF ;
IF NEW . loaded_package_count > 0 AND (
NEW . loading_plan_id IS NULL OR NEW . container_booking_id IS NULL OR NOT EXISTS (
SELECT 1 FROM fl_loading_plan plan
WHERE plan . id = NEW . loading_plan_id
AND plan . container_booking_id = NEW . container_booking_id
2026-08-10 12:40:47 +08:00
AND EXISTS ( SELECT 1 FROM fl_loading_plan_item plan_item
WHERE plan_item . loading_plan_id = plan . id
AND plan_item . account_id = NEW . tenant_id
AND plan_item . inbound_id = NEW . source_inbound_id
AND plan_item . deleted = 0 )
2026-08-10 12:22:29 +08:00
)
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '实装拆分批次必须绑定匹配的装柜计划和柜次' ;
END IF ;
2026-08-11 09:17:02 +08:00
IF NEW . remainder_child_inbound_id IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM fl_inbound_order remainder_order
WHERE remainder_order . id = NEW . remainder_child_inbound_id
AND remainder_order . tenant_id = NEW . tenant_id
AND remainder_order . parent_inbound_id = NEW . parent_inbound_id
AND remainder_order . root_inbound_id = NEW . parent_inbound_id
AND remainder_order . inbound_role = 'CHILD'
AND remainder_order . split_purpose = 'REMAINDER'
AND remainder_order . split_status = 'NOT_SPLIT'
AND remainder_order . package_count = NEW . remaining_package_count
AND remainder_order . deleted = 0
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '余量子入仓单与拆分批次数量或根归属不一致' ;
END IF ;
2026-08-06 17:48:50 +08:00
END $$
CREATE TRIGGER trg_inbound_split_batch_before_update
BEFORE UPDATE ON fl_inbound_split_batch
FOR EACH ROW
BEGIN
IF OLD . status IN ( 'APPLIED' , 'CANCELLED' ) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已结束拆分批次不可修改' ;
END IF ;
2026-08-10 12:22:29 +08:00
IF NOT ( OLD . parent_inbound_id <=> NEW . parent_inbound_id )
OR NOT ( OLD . source_inbound_id <=> NEW . source_inbound_id )
OR NOT ( OLD . loaded_child_inbound_id <=> NEW . loaded_child_inbound_id )
OR NOT ( OLD . remainder_child_inbound_id <=> NEW . remainder_child_inbound_id )
OR NOT ( OLD . package_count <=> NEW . package_count )
OR NOT ( OLD . loaded_package_count <=> NEW . loaded_package_count )
OR NOT ( OLD . remaining_package_count <=> NEW . remaining_package_count )
OR NOT ( OLD . loading_plan_id <=> NEW . loading_plan_id )
OR NOT ( OLD . container_booking_id <=> NEW . container_booking_id ) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次来源、子单、柜次与数量创建后不可修改' ;
END IF ;
2026-08-06 17:48:50 +08:00
IF NEW . status = 'APPLIED' THEN
IF NEW . applied_at IS NULL
2026-08-10 12:22:29 +08:00
OR ( NEW . loaded_package_count > 0 AND (
NEW . loading_plan_id IS NULL OR NEW . container_booking_id IS NULL OR NOT EXISTS (
SELECT 1 FROM fl_loading_plan plan
WHERE plan . id = NEW . loading_plan_id
2026-08-10 12:40:47 +08:00
AND plan . container_booking_id = NEW . container_booking_id
AND EXISTS ( SELECT 1 FROM fl_loading_plan_item plan_item
WHERE plan_item . loading_plan_id = plan . id
AND plan_item . account_id = NEW . tenant_id
AND plan_item . inbound_id = NEW . source_inbound_id
2026-08-10 13:51:13 +08:00
AND plan_item . deleted = 0 ))))
2026-08-06 17:48:50 +08:00
OR NOT EXISTS (
SELECT 1 FROM fl_inbound_order parent_order
WHERE parent_order . id = NEW . parent_inbound_id
AND parent_order . tenant_id = NEW . tenant_id
AND parent_order . inbound_role = 'MASTER'
AND parent_order . version > NEW . expected_parent_version
2026-08-10 12:40:47 +08:00
) OR NOT EXISTS (
SELECT 1 FROM fl_inbound_order source_order
WHERE source_order . id = NEW . source_inbound_id
AND source_order . tenant_id = NEW . tenant_id
AND source_order . split_status = 'FULLY_SPLIT'
2026-08-11 09:17:02 +08:00
AND source_order . package_count = NEW . loaded_package_count
2026-08-06 17:48:50 +08:00
) OR
( SELECT COALESCE ( SUM ( item . package_count ), 0 )
FROM fl_inbound_split_item item
WHERE item . tenant_id = NEW . tenant_id
2026-08-11 09:17:02 +08:00
AND item . split_batch_id = NEW . id ) <> NEW . remaining_package_count OR
2026-08-07 10:57:06 +08:00
( SELECT COUNT ( DISTINCT item . child_inbound_id )
FROM fl_inbound_split_item item
WHERE item . tenant_id = NEW . tenant_id
2026-08-11 09:17:02 +08:00
AND item . split_batch_id = NEW . id ) <> CASE WHEN NEW . remaining_package_count > 0 THEN 1 ELSE 0 END OR
( NEW . loaded_child_inbound_id IS NOT NULL ) OR
2026-08-10 12:22:29 +08:00
NEW . loaded_package_count + NEW . remaining_package_count <> NEW . package_count OR
( NEW . remaining_package_count > 0 AND NEW . remainder_child_inbound_id IS NULL ) OR
( NEW . remainder_child_inbound_id IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM fl_inbound_order c WHERE c . id = NEW . remainder_child_inbound_id
2026-08-11 09:17:02 +08:00
AND c . parent_inbound_id = NEW . parent_inbound_id AND c . root_inbound_id = NEW . parent_inbound_id
AND c . split_purpose = 'REMAINDER' AND c . split_status = 'NOT_SPLIT'
AND c . package_count = NEW . remaining_package_count AND c . deleted = 0 )) OR
EXISTS ( SELECT 1 FROM fl_inbound_split_item item
WHERE item . tenant_id = NEW . tenant_id
AND item . split_batch_id = NEW . id
AND item . child_inbound_id <> NEW . remainder_child_inbound_id ) OR
2026-08-07 10:57:06 +08:00
EXISTS ( SELECT 1
FROM fl_inbound_split_item item
JOIN fl_inbound_order child_order ON child_order . id = item . child_inbound_id
WHERE item . tenant_id = NEW . tenant_id
AND item . split_batch_id = NEW . id
GROUP BY child_order . id , child_order . package_count
HAVING child_order . package_count <> SUM ( item . package_count )) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二级子入仓单或拆分明细箱数合计不一致' ;
2026-08-06 17:48:50 +08:00
END IF ;
IF EXISTS (
SELECT 1
FROM fl_inbound_split_item split_item
JOIN fl_inbound_measurement_item source_item
ON source_item . id = split_item . source_measurement_item_id
WHERE split_item . tenant_id = NEW . tenant_id
AND split_item . split_batch_id = NEW . id
GROUP BY split_item . source_measurement_item_id , source_item . package_count
HAVING SUM ( split_item . package_count ) > source_item . package_count
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '子单承接箱数超过来源测量箱规箱数' ;
END IF ;
2026-08-11 09:17:02 +08:00
IF NOT EXISTS (
SELECT 1
FROM fl_inbound_order root_order
WHERE root_order . id = NEW . parent_inbound_id
AND root_order . tenant_id = NEW . tenant_id
AND root_order . inbound_role = 'MASTER'
AND root_order . deleted = 0
AND root_order . total_package_count =
COALESCE ( root_order . package_count , 0 ) +
COALESCE (( SELECT SUM ( child . package_count )
FROM fl_inbound_order child
WHERE child . tenant_id = NEW . tenant_id
AND child . root_inbound_id = root_order . id
AND child . inbound_role = 'CHILD'
AND child . deleted = 0 ), 0 )
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '根入仓单原始箱数与已完成分片及当前余量不守恒' ;
END IF ;
2026-08-06 17:48:50 +08:00
ELSEIF NEW . status = 'CANCELLED' THEN
IF EXISTS (
SELECT 1 FROM fl_inbound_split_item item
WHERE item . tenant_id = NEW . tenant_id
AND item . split_batch_id = NEW . id
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已有子单明细的拆分批次不可取消' ;
END IF ;
ELSEIF NEW . status <> 'PREPARED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次状态流转非法' ;
END IF ;
END $$
CREATE TRIGGER trg_inbound_split_item_before_update
BEFORE UPDATE ON fl_inbound_split_item
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分数量台账不可修改' ;
END $$
CREATE TRIGGER trg_inbound_split_item_before_delete
BEFORE DELETE ON fl_inbound_split_item
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分数量台账不可删除' ;
END $$
CREATE TRIGGER trg_inbound_split_history_before_update
BEFORE UPDATE ON fl_inbound_split_history
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分历史不可修改' ;
END $$
CREATE TRIGGER trg_inbound_split_history_before_delete
BEFORE DELETE ON fl_inbound_split_history
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分历史不可删除' ;
END $$
DELIMITER ;
DROP PROCEDURE IF EXISTS phase3_assert_inbound_parent_child_schema ;
DELIMITER $$
CREATE PROCEDURE phase3_assert_inbound_parent_child_schema ()
BEGIN
IF ( SELECT COUNT ( * ) FROM information_schema . tables
WHERE table_schema = DATABASE ()
AND table_name IN ( 'fl_inbound_split_batch' , 'fl_inbound_split_item' ,
'fl_inbound_split_history' )) <> 3 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子入仓单拆分台账未完整创建' ;
END IF ;
IF ( SELECT COUNT ( * ) FROM information_schema . columns
WHERE table_schema = DATABASE ()
AND table_name = 'fl_inbound_order'
AND column_name IN ( 'inbound_role' , 'parent_inbound_id' , 'root_inbound_id' ,
'split_sequence' , 'split_status' , 'package_count' ,
2026-08-11 09:17:02 +08:00
'master_order_id' , 'split_purpose' , 'active_remainder_root_id' )) <> 9 THEN
2026-08-06 17:48:50 +08:00
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子入仓单关系字段未完整创建' ;
END IF ;
IF ( SELECT COUNT ( DISTINCT index_name ) FROM information_schema . statistics
2026-08-10 12:46:12 +08:00
WHERE table_schema = DATABASE () AND table_name = 'fl_inbound_order'
2026-08-11 09:17:02 +08:00
AND index_name IN ( 'uk_inbound_master_order' , 'uk_inbound_parent_split_sequence' ,
'uk_inbound_one_active_remainder' )) <> 3
2026-08-10 12:46:12 +08:00
OR ( SELECT COUNT ( DISTINCT index_name ) FROM information_schema . statistics
WHERE table_schema = DATABASE () AND table_name = 'fl_inbound_split_batch'
AND index_name IN ( 'uk_inbound_split_operation' , 'uk_inbound_split_idempotency' ,
'uk_inbound_split_source_once' )) <> 3
OR ( SELECT COUNT ( DISTINCT index_name ) FROM information_schema . statistics
WHERE table_schema = DATABASE () AND table_name = 'fl_inbound_split_item'
AND index_name IN ( 'uk_inbound_split_batch_line' ,
'uk_inbound_split_batch_source_child' )) <> 2
OR ( SELECT COUNT ( DISTINCT index_name ) FROM information_schema . statistics
WHERE table_schema = DATABASE () AND table_name = 'fl_inbound_split_history'
AND index_name = 'uk_inbound_split_history_operation' ) <> 1 THEN
2026-08-06 17:48:50 +08:00
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子入仓单唯一约束未完整创建' ;
END IF ;
2026-08-10 12:46:12 +08:00
IF ( SELECT COUNT ( * ) FROM information_schema . columns
WHERE table_schema = DATABASE () AND table_name = 'fl_inbound_split_batch'
AND column_name IN ( 'source_inbound_id' , 'loaded_child_inbound_id' ,
'remainder_child_inbound_id' , 'loaded_package_count' ,
'remaining_package_count' , 'loading_plan_id' ,
'container_booking_id' )) <> 7 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次来源、子单、数量或柜次字段未完整创建' ;
END IF ;
IF ( SELECT COUNT ( * ) FROM information_schema . columns
WHERE table_schema = DATABASE () AND table_name = 'fl_inbound_split_item'
AND column_name IN ( 'source_measurement_id' , 'source_measurement_item_id' )
AND is_nullable = 'NO' ) <> 2 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分数量台账必须绑定非空测量来源' ;
END IF ;
2026-08-06 17:48:50 +08:00
IF ( SELECT COUNT ( * ) FROM information_schema . table_constraints
WHERE constraint_schema = DATABASE ()
AND constraint_name IN ( 'fk_inbound_parent_order' , 'fk_inbound_root_order' ,
2026-08-10 12:22:29 +08:00
'fk_inbound_split_source' , 'fk_inbound_split_loaded_child' , 'fk_inbound_split_remainder_child' ,
2026-08-06 17:48:50 +08:00
'fk_measure_split_batch' , 'fk_measure_source_batch' ,
2026-08-10 12:22:29 +08:00
'fk_measure_item_split_item' , 'fk_measure_item_source_item' )) <> 9 THEN
2026-08-06 17:48:50 +08:00
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子入仓单与测量关联约束未完整创建' ;
END IF ;
IF ( SELECT COUNT ( * ) FROM information_schema . triggers
WHERE trigger_schema = DATABASE ()
AND trigger_name IN ( 'trg_inbound_order_parent_before_insert' ,
'trg_inbound_order_parent_before_update' ,
'trg_inbound_split_item_before_insert' ,
'trg_inbound_split_batch_before_insert' ,
'trg_inbound_split_batch_before_update' ,
'trg_inbound_split_item_before_update' ,
'trg_inbound_split_item_before_delete' ,
'trg_inbound_split_history_before_update' ,
'trg_inbound_split_history_before_delete' )) <> 9 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子入仓单数据库保护触发器未完整创建' ;
END IF ;
IF EXISTS (
SELECT 1 FROM information_schema . statistics
WHERE table_schema = DATABASE ()
AND table_name = 'fl_inbound_order'
2026-08-10 12:46:12 +08:00
AND index_name IN ( 'uk_inbound_tenant_order' , 'uk_inbound_one_child' ,
'uk_inbound_one_secondary' , 'uk_inbound_parent_sequence' )
2026-08-06 17:48:50 +08:00
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '旧订单唯一索引仍阻止创建子入仓单' ;
END IF ;
2026-08-10 12:46:12 +08:00
IF EXISTS (
SELECT 1 FROM information_schema . statistics
WHERE table_schema = DATABASE ()
AND table_name = 'fl_inbound_split_item'
AND index_name IN ( 'uk_inbound_split_child' , 'uk_inbound_split_batch_child' ,
'uk_inbound_split_batch_source_item' )
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '旧拆分明细唯一索引仍阻止数量守恒分配' ;
END IF ;
2026-08-06 17:48:50 +08:00
END $$
DELIMITER ;
CALL phase3_assert_inbound_parent_child_schema ();
DROP PROCEDURE IF EXISTS phase3_assert_inbound_parent_child_schema ;