207 lines
12 KiB
SQL
207 lines
12 KiB
SQL
SET NAMES utf8mb4;
|
|
USE baiyue_firstleg;
|
|
|
|
/* 三期分柜与预装清单;独立于现有测量、计费表,可重复部署。 */
|
|
CREATE TABLE IF NOT EXISTS fl_preload_plan (
|
|
id BIGINT UNSIGNED NOT NULL COMMENT '预装清单ID',
|
|
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
|
|
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '关联入库单逻辑ID',
|
|
status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '预装状态:PENDING待确认、CONFIRMING确认中、CONFIRMED已确认',
|
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增',
|
|
items_json JSON NOT NULL COMMENT '可编辑分柜明细JSON快照;人工确认后作为预装依据',
|
|
remark VARCHAR(1000) NULL COMMENT '预装备注',
|
|
confirmed_by BIGINT UNSIGNED NULL COMMENT '确认预装的操作人ID',
|
|
confirmed_by_name VARCHAR(64) NULL COMMENT '确认预装的操作人姓名快照',
|
|
confirmed_at DATETIME(3) NULL COMMENT '预装确认时间',
|
|
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建预装清单的操作人ID',
|
|
created_by_name VARCHAR(64) NULL COMMENT '创建操作人姓名快照',
|
|
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改操作人ID',
|
|
updated_by_name VARCHAR(64) NULL COMMENT '最后修改操作人姓名快照',
|
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '预装清单创建时间',
|
|
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '预装清单最后更新时间',
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_preload_tenant_inbound (tenant_id, inbound_id),
|
|
KEY idx_preload_status (tenant_id, status, updated_at),
|
|
CONSTRAINT fk_preload_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
|
|
) ENGINE=InnoDB COMMENT='订单分柜预装清单';
|
|
|
|
CREATE TABLE IF NOT EXISTS fl_split_operation (
|
|
id BIGINT UNSIGNED NOT NULL COMMENT '幂等记录ID',
|
|
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
|
|
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '操作目标入库单逻辑ID',
|
|
operator_id BIGINT UNSIGNED NOT NULL COMMENT '执行分柜或预装写操作的平台用户ID',
|
|
operation VARCHAR(40) NOT NULL COMMENT '分柜或预装写操作类型',
|
|
idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端幂等键,同一操作重试复用',
|
|
status VARCHAR(16) NOT NULL DEFAULT 'PROCESSING' COMMENT '幂等处理状态:PROCESSING处理中、SUCCESS成功',
|
|
result_json JSON NULL COMMENT '操作结果JSON快照,用于幂等重放',
|
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '幂等操作声明创建时间',
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_split_operation (tenant_id, inbound_id, operator_id, operation, idempotency_key),
|
|
KEY idx_split_operation_time (tenant_id, inbound_id, created_at),
|
|
CONSTRAINT fk_split_operation_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
|
|
) ENGINE=InnoDB COMMENT='分柜操作幂等与审计索引';
|
|
|
|
/*
|
|
* 部分装柜拆单契约:
|
|
* 本文件仅保留历史部分锁定明细;主单、LOADED/REMAINDER 多分片及层级约束
|
|
* 统一由 phase3-inbound-parent-child-schema.sql 维护。
|
|
*/
|
|
DROP PROCEDURE IF EXISTS phase3_split_add_column;
|
|
DELIMITER $$
|
|
CREATE PROCEDURE phase3_split_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 @phase3_split_ddl = CONCAT('ALTER TABLE `', p_table_name, '` ADD COLUMN `',
|
|
p_column_name, '` ', p_definition);
|
|
PREPARE phase3_split_stmt FROM @phase3_split_ddl;
|
|
EXECUTE phase3_split_stmt;
|
|
DEALLOCATE PREPARE phase3_split_stmt;
|
|
END IF;
|
|
END$$
|
|
DELIMITER ;
|
|
|
|
CALL phase3_split_add_column('fl_preload_plan', 'confirmed_by',
|
|
'BIGINT UNSIGNED NULL COMMENT ''确认预装的操作人ID'' AFTER remark');
|
|
CALL phase3_split_add_column('fl_preload_plan', 'confirmed_by_name',
|
|
'VARCHAR(64) NULL COMMENT ''确认预装的操作人姓名快照'' AFTER confirmed_by');
|
|
CALL phase3_split_add_column('fl_preload_plan', 'confirmed_at',
|
|
'DATETIME(3) NULL COMMENT ''预装确认时间'' AFTER confirmed_by_name');
|
|
|
|
CALL phase3_split_add_column('fl_inbound_order', 'parent_inbound_id',
|
|
'BIGINT UNSIGNED NULL COMMENT ''生成本单的主入库单ID;仅二级子单有值'' AFTER id');
|
|
CALL phase3_split_add_column('fl_inbound_order', 'split_level',
|
|
'TINYINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN parent_inbound_id IS NULL THEN 0 ELSE 1 END) STORED COMMENT ''拆单层级:0主单、1二级子单'' AFTER parent_inbound_id');
|
|
CALL phase3_split_add_column('fl_inbound_order', 'split_status',
|
|
'VARCHAR(24) NOT NULL DEFAULT ''NOT_SPLIT'' COMMENT ''NOT_SPLIT/PARTIALLY_SPLIT/FULLY_SPLIT'' AFTER split_level');
|
|
DROP PROCEDURE IF EXISTS phase3_split_add_column;
|
|
|
|
SET @phase3_split_index_exists = (
|
|
SELECT COUNT(*) FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_order'
|
|
AND index_name = 'uk_inbound_one_secondary'
|
|
);
|
|
SET @phase3_split_ddl = IF(@phase3_split_index_exists > 0,
|
|
'ALTER TABLE fl_inbound_order DROP INDEX uk_inbound_one_secondary',
|
|
'SELECT 1');
|
|
PREPARE phase3_split_stmt FROM @phase3_split_ddl;
|
|
EXECUTE phase3_split_stmt;
|
|
DEALLOCATE PREPARE phase3_split_stmt;
|
|
|
|
SET @phase3_split_fk_exists = (
|
|
SELECT COUNT(*) FROM information_schema.table_constraints
|
|
WHERE constraint_schema = DATABASE() AND table_name = 'fl_inbound_order'
|
|
AND constraint_name = 'fk_inbound_parent' AND constraint_type = 'FOREIGN KEY'
|
|
);
|
|
SET @phase3_split_ddl = IF(@phase3_split_fk_exists > 0,
|
|
'ALTER TABLE fl_inbound_order DROP FOREIGN KEY fk_inbound_parent',
|
|
'SELECT 1');
|
|
PREPARE phase3_split_stmt FROM @phase3_split_ddl;
|
|
EXECUTE phase3_split_stmt;
|
|
DEALLOCATE PREPARE phase3_split_stmt;
|
|
|
|
CREATE TABLE IF NOT EXISTS fl_inbound_split_detail (
|
|
id BIGINT UNSIGNED NOT NULL COMMENT '部分锁定明细ID',
|
|
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
|
|
parent_inbound_id BIGINT UNSIGNED NOT NULL COMMENT '主入库单ID',
|
|
child_inbound_id BIGINT UNSIGNED NULL COMMENT '完成拆单后生成的二级子入库单ID',
|
|
inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '原订单货物明细ID,不建立跨库外键',
|
|
source_package_count INT UNSIGNED NOT NULL COMMENT '拆分前该明细总箱数快照',
|
|
locked_package_count INT UNSIGNED NOT NULL COMMENT '本次装柜锁定箱数',
|
|
remaining_package_count INT UNSIGNED GENERATED ALWAYS AS (source_package_count - locked_package_count) STORED COMMENT '锁定后剩余箱数,由来源箱数减锁定箱数生成',
|
|
status VARCHAR(16) NOT NULL DEFAULT 'LOCKED' COMMENT '分柜明细状态:LOCKED已锁定、COMPLETED已完成拆分',
|
|
operation_id VARCHAR(128) NOT NULL COMMENT '部分锁定操作幂等号',
|
|
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建部分锁定明细的操作人ID',
|
|
created_by_name VARCHAR(64) NULL COMMENT '创建操作人姓名快照',
|
|
completed_by BIGINT UNSIGNED NULL COMMENT '完成拆分的操作人ID',
|
|
completed_by_name VARCHAR(64) NULL COMMENT '完成操作人姓名快照',
|
|
completed_at DATETIME(3) NULL COMMENT '部分拆分完成时间',
|
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '部分锁定明细创建时间',
|
|
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '部分锁定明细最后更新时间',
|
|
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增',
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_inbound_split_parent_detail (tenant_id, parent_inbound_id, inbound_detail_id),
|
|
UNIQUE KEY uk_inbound_split_child_detail (tenant_id, child_inbound_id, inbound_detail_id),
|
|
UNIQUE KEY uk_inbound_split_operation (operation_id),
|
|
KEY idx_inbound_split_status (tenant_id, parent_inbound_id, status),
|
|
CONSTRAINT fk_inbound_split_detail_parent FOREIGN KEY (parent_inbound_id) REFERENCES fl_inbound_order (id),
|
|
CONSTRAINT fk_inbound_split_detail_child FOREIGN KEY (child_inbound_id) REFERENCES fl_inbound_order (id),
|
|
CONSTRAINT chk_inbound_split_partial CHECK (
|
|
source_package_count > 1 AND locked_package_count > 0
|
|
AND locked_package_count < source_package_count
|
|
),
|
|
CONSTRAINT chk_legacy_inbound_split_status CHECK (status IN ('LOCKED', 'COMPLETED')),
|
|
CONSTRAINT chk_inbound_split_completion CHECK (
|
|
(status = 'LOCKED' AND child_inbound_id IS NULL AND completed_at IS NULL)
|
|
OR (status = 'COMPLETED' AND child_inbound_id IS NOT NULL AND completed_at IS NOT NULL)
|
|
)
|
|
) ENGINE=InnoDB COMMENT='按箱数部分锁定及剩余箱数生成二级子入库单的不可变契约';
|
|
|
|
DROP TRIGGER IF EXISTS trg_inbound_split_before_insert;
|
|
DROP TRIGGER IF EXISTS trg_inbound_split_before_update;
|
|
DROP TRIGGER IF EXISTS trg_inbound_split_detail_before_insert;
|
|
DROP TRIGGER IF EXISTS trg_inbound_split_detail_before_update;
|
|
DROP TRIGGER IF EXISTS trg_inbound_split_detail_before_delete;
|
|
DELIMITER $$
|
|
CREATE TRIGGER trg_inbound_split_detail_before_insert
|
|
BEFORE INSERT ON fl_inbound_split_detail
|
|
FOR EACH ROW
|
|
BEGIN
|
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '旧部分锁定台账已退役,请使用主子分片批次';
|
|
END$$
|
|
|
|
CREATE TRIGGER trg_inbound_split_detail_before_update
|
|
BEFORE UPDATE ON fl_inbound_split_detail
|
|
FOR EACH ROW
|
|
BEGIN
|
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '旧部分锁定台账只读,不允许更新';
|
|
END$$
|
|
|
|
CREATE TRIGGER trg_inbound_split_detail_before_delete
|
|
BEFORE DELETE ON fl_inbound_split_detail
|
|
FOR EACH ROW
|
|
BEGIN
|
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '部分锁定与拆单明细不可删除';
|
|
END$$
|
|
DELIMITER ;
|
|
|
|
-- 主子关系及不可回退规则由 phase3-inbound-parent-child-schema.sql 统一维护。
|
|
-- 旧触发器要求“一父一子”,会阻止 LOADED/REMAINDER 多分片,迁移完成后必须移除。
|
|
DROP TRIGGER IF EXISTS trg_inbound_split_before_insert;
|
|
DROP TRIGGER IF EXISTS trg_inbound_split_before_update;
|
|
|
|
DROP PROCEDURE IF EXISTS phase3_split_assert_contract;
|
|
DELIMITER $$
|
|
CREATE PROCEDURE phase3_split_assert_contract()
|
|
BEGIN
|
|
IF (SELECT COUNT(*) FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_order'
|
|
AND column_name IN ('parent_inbound_id', 'split_level', 'split_status')) <> 3 THEN
|
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆单迁移失败:入库单主子关系字段未完整创建';
|
|
END IF;
|
|
IF (SELECT COUNT(*) FROM information_schema.tables
|
|
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_split_detail') <> 1 THEN
|
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆单迁移失败:部分锁定明细表未创建';
|
|
END IF;
|
|
IF (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND index_name IN ('uk_inbound_split_parent_detail',
|
|
'uk_inbound_split_child_detail', 'uk_inbound_split_operation')) <> 3 THEN
|
|
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_split_detail_before_insert',
|
|
'trg_inbound_split_detail_before_update',
|
|
'trg_inbound_split_detail_before_delete')) <> 3 THEN
|
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆单迁移失败:部分锁定触发器未完整创建';
|
|
END IF;
|
|
END$$
|
|
DELIMITER ;
|
|
CALL phase3_split_assert_contract();
|
|
DROP PROCEDURE IF EXISTS phase3_split_assert_contract;
|