-- 发货收货核对:MySQL 8 单次增量迁移(最低 8.0.16,需强制执行 CHECK)。 -- 执行顺序:20260922_test_data_batch_mysql.sql -- -> 20261005_tunnel_material_flow_mysql.sql -- -> 20261008_material_dates_mysql.sql -> 本脚本 -> 部署收货核对版本。 -- 执行前必须停写(业务接口、后台任务、导入及其他写入来源),完成备份并验证可恢复。 -- 由 DBA 审核后在明确选定的目标库单次执行;不得使用 --force 或遇错继续的 SQL 客户端模式。 -- 本脚本仅随代码交付,不在应用启动时执行;本次开发未连接数据库、未运行 SQL。 -- 警告:MySQL DDL 会隐式提交,下面的 ROLLBACK 仅能撤销 DML,不能整体回滚已完成的 DDL。 -- 中途失败应保持停写,依据备份和已完成步骤人工恢复;禁止直接重跑或启动新旧应用。 -- 旧 CONFIRMED 单只标 LEGACY;不迁移旧库存状态、不补扣库存、不造收货事件、不伪造收货时间。 -- 正式数据与各测试批次采用相同历史标记规则;所有原 test_batch_id 保持不变。 -- 权限只新增 sys_menu,不写 sys_role_menu;上线后由管理员按现场职责手动授权。 DELIMITER $$ CREATE PROCEDURE migrate_shipment_receipt_20261008() BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END; -- 所有环境、前置结构、菜单冲突检查均先于业务表 DDL。 IF DATABASE() IS NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Select the target database before migration'; END IF; IF SUBSTRING_INDEX(VERSION(), '.', 1) <> '8' OR (CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(VERSION(), '.', 2), '.', -1) AS UNSIGNED) = 0 AND CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(VERSION(), '.', 3), '.', -1) AS UNSIGNED) < 16) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'MySQL 8.0.16 or newer MySQL 8 is required'; END IF; IF (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND engine = 'InnoDB' AND table_name IN ('biz_shipment_order', 'biz_pallet_inventory', 'biz_pallet_event', 'biz_filling_record', 'biz_tunnel', 'sys_menu', 'sys_role_menu', 'sys_user')) <> 8 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Required InnoDB tables or tunnel migration are missing'; END IF; IF (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND ((table_name = 'biz_filling_record' AND column_name IN ('production_date', 'expiry_date')) OR (table_name = 'biz_pallet_inventory' AND column_name IN ('tunnel_id', 'trace_no')) OR (table_name = 'biz_pallet_event' AND column_name IN ('before_tunnel_id', 'after_tunnel_id', 'trace_no', 'batch_no_snapshot')))) <> 8 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Apply tunnel and material date migrations first'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.check_constraints WHERE constraint_schema = DATABASE() AND constraint_name = 'ck_biz_filling_record_material_dates') OR NOT EXISTS (SELECT 1 FROM information_schema.check_constraints WHERE constraint_schema = DATABASE() AND constraint_name = 'ck_biz_pallet_event_event_type' AND UPPER(check_clause) LIKE '%TUNNEL_ENTERED%') OR NOT EXISTS (SELECT 1 FROM information_schema.check_constraints WHERE constraint_schema = DATABASE() AND constraint_name = 'ck_biz_pallet_inventory_flow_state') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Required material date or flow CHECK constraints are missing'; END IF; -- 任何新增字段已存在均说明已执行或部分执行,禁止将已收货数据重新标成 LEGACY。 IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND ((table_name = 'biz_shipment_order' AND column_name IN ('receipt_state', 'received_by', 'received_at', 'receipt_idempotency_key', 'receipt_request_hash')) OR (table_name = 'biz_pallet_inventory' AND column_name = 'site_arrived_at'))) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Receipt columns already exist; do not rerun this migration'; END IF; IF EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 1119 OR perms = 'business:shipment:receive') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Menu 1119 or shipment receive permission already exists'; END IF; -- 避免已有孤立授权在插入 1119 时自动变成有效收货权限。 IF EXISTS (SELECT 1 FROM sys_role_menu WHERE menu_id = 1119) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Orphan role grants for menu 1119 must be reviewed first'; END IF; IF NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 510 AND perms = 'business:shipment:list' AND menu_type = 'C') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Shipment parent menu 510 does not match the expected contract'; END IF; ALTER TABLE biz_shipment_order ADD COLUMN receipt_state VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '收货状态;LEGACY表示历史未登记' AFTER confirmed_at, ADD COLUMN received_by BIGINT NULL COMMENT '收货核对人ID' AFTER receipt_state, ADD COLUMN received_at DATETIME NULL COMMENT '实际收货核对时间' AFTER received_by, ADD COLUMN receipt_idempotency_key VARCHAR(64) NULL COMMENT '收货确认幂等键' AFTER received_at, ADD COLUMN receipt_request_hash VARCHAR(64) NULL COMMENT '收货核对请求摘要' AFTER receipt_idempotency_key, ADD CONSTRAINT uq_biz_shipment_receipt_site_key UNIQUE (target_site_id, receipt_idempotency_key), ADD CONSTRAINT fk_biz_shipment_order_received_by FOREIGN KEY (received_by) REFERENCES sys_user (user_id), ADD CONSTRAINT ck_biz_shipment_order_receipt_state CHECK (receipt_state IN ('PENDING', 'RECEIVED', 'LEGACY')); ALTER TABLE biz_pallet_inventory ADD COLUMN site_arrived_at DATETIME NULL COMMENT '本轮实际收货核对时间;历史未登记为空' AFTER remaining_packages, DROP CHECK ck_biz_pallet_inventory_flow_state, ADD CONSTRAINT ck_biz_pallet_inventory_flow_state CHECK ( flow_state IN ('UNSTOCKED', 'EMPTY_IN_WAREHOUSE', 'FILLED_IN_WAREHOUSE', 'IN_TRANSIT', 'AT_SITE_UNUSED', 'AT_SITE_PARTIAL', 'AT_SITE_EMPTY', 'RETURNING') ); ALTER TABLE biz_pallet_event DROP CHECK ck_biz_pallet_event_event_type, ADD CONSTRAINT ck_biz_pallet_event_event_type CHECK ( event_type IN ('INBOUND_CONFIRMED', 'FILLING_CONFIRMED', 'SHIPMENT_CONFIRMED', 'SHIPMENT_RECEIVED', 'TUNNEL_ENTERED', 'USAGE_RECORDED', 'RETURN_REQUESTED', 'RETURN_CONFIRMED') ); -- DDL 已隐式提交;以下事务只覆盖历史标记及权限菜单插入。 START TRANSACTION; UPDATE biz_shipment_order SET receipt_state = 'LEGACY' WHERE status = 'CONFIRMED'; INSERT INTO sys_menu ( menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark ) VALUES ( 1119, '收货核对', 510, 6, '', '', '', '', 1, 0, 'F', '0', '0', 'business:shipment:receive', '#', 'admin', SYSDATE(), '', NULL, '' ); COMMIT; END$$ DELIMITER ; CALL migrate_shipment_receipt_20261008(); DROP PROCEDURE migrate_shipment_receipt_20261008; -- 核验:历史确认单应为 LEGACY,新草稿为 PENDING,收货时间/人员和原库存保持未登记。 SELECT status, receipt_state, COUNT(*) AS order_count FROM biz_shipment_order GROUP BY status, receipt_state; SELECT menu_id, parent_id, perms FROM sys_menu WHERE menu_id = 1119;