装柜
This commit is contained in:
+293
-396
@@ -4033,7 +4033,7 @@ CREATE TABLE IF NOT EXISTS fl_loading_pick_item (
|
||||
inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '订单货物明细ID',
|
||||
planned_package_count INT UNSIGNED NOT NULL COMMENT '计划拣货箱数;上层拆分按业务规则向上取整',
|
||||
picked_package_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已拣货箱数',
|
||||
item_status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING、PICKING、PICKED',
|
||||
item_status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING、PICKING、PICKED、PARTIAL、SKIPPED',
|
||||
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
@@ -4044,7 +4044,7 @@ CREATE TABLE IF NOT EXISTS fl_loading_pick_item (
|
||||
CONSTRAINT fk_loading_pick_item_plan_item FOREIGN KEY (loading_plan_item_id) REFERENCES fl_loading_plan_item (id),
|
||||
CONSTRAINT chk_loading_pick_item_planned CHECK (planned_package_count > 0),
|
||||
CONSTRAINT chk_loading_pick_item_count CHECK (picked_package_count <= planned_package_count),
|
||||
CONSTRAINT chk_loading_pick_item_status CHECK (item_status IN ('PENDING', 'PICKING', 'PICKED'))
|
||||
CONSTRAINT chk_loading_pick_item_status CHECK (item_status IN ('PENDING', 'PICKING', 'PICKED', 'PARTIAL', 'SKIPPED'))
|
||||
) ENGINE=InnoDB COMMENT='PDA装柜拣货明细';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_loading_pick_history (
|
||||
@@ -4053,7 +4053,9 @@ CREATE TABLE IF NOT EXISTS fl_loading_pick_history (
|
||||
pick_item_id BIGINT UNSIGNED NOT NULL COMMENT 'PDA拣货明细ID',
|
||||
operation_id VARCHAR(128) NOT NULL COMMENT '客户端幂等操作号',
|
||||
request_hash CHAR(64) NOT NULL COMMENT '请求指纹',
|
||||
action_type VARCHAR(24) NOT NULL DEFAULT 'PICK' COMMENT 'PICK、PARTIAL、SKIP',
|
||||
picked_package_count INT UNSIGNED NOT NULL COMMENT '本次拣货箱数',
|
||||
operation_reason VARCHAR(500) NULL COMMENT '部分拣货或跳拣原因',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人',
|
||||
operator_name VARCHAR(64) NULL COMMENT '操作人名称',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
@@ -4228,7 +4230,49 @@ BEGIN
|
||||
IF current_plan_status = 'LOADING' AND EXISTS (
|
||||
SELECT 1 FROM fl_loading_pick_item
|
||||
WHERE loading_plan_item_id = OLD.id AND picked_package_count > 0) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已有PDA拣货记录的装柜明细不可修改或删除';
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM fl_loading_pick_item
|
||||
WHERE loading_plan_item_id = OLD.id
|
||||
AND item_status = 'PARTIAL'
|
||||
AND NEW.deleted = 0
|
||||
AND NEW.package_count = picked_package_count
|
||||
) OR NEW.deleted <> OLD.deleted
|
||||
OR NOT (NEW.customer_name <=> OLD.customer_name)
|
||||
OR NOT (NEW.inbound_id <=> OLD.inbound_id)
|
||||
OR NOT (NEW.inbound_detail_id <=> OLD.inbound_detail_id)
|
||||
OR NOT (NEW.order_id <=> OLD.order_id)
|
||||
OR NOT (NEW.order_no <=> OLD.order_no)
|
||||
OR NOT (NEW.inbound_code <=> OLD.inbound_code)
|
||||
OR NOT (NEW.destination_warehouse_id <=> OLD.destination_warehouse_id)
|
||||
OR NOT (NEW.destination_warehouse_code <=> OLD.destination_warehouse_code)
|
||||
OR NOT (NEW.destination_warehouse_name <=> OLD.destination_warehouse_name)
|
||||
OR NOT (NEW.sku_code <=> OLD.sku_code)
|
||||
OR NOT (NEW.product_name_zh <=> OLD.product_name_zh)
|
||||
OR NOT (NEW.product_name_en <=> OLD.product_name_en)
|
||||
OR NOT (NEW.product_image_file_id <=> OLD.product_image_file_id)
|
||||
OR NOT (NEW.product_image_url <=> OLD.product_image_url)
|
||||
OR NOT (NEW.product_quantity <=> OLD.product_quantity)
|
||||
OR NOT (NEW.units_per_carton <=> OLD.units_per_carton)
|
||||
OR NOT (NEW.pallet_count <=> OLD.pallet_count)
|
||||
OR NOT (NEW.gross_weight_kg <=> OLD.gross_weight_kg)
|
||||
OR NOT (NEW.net_weight_kg <=> OLD.net_weight_kg)
|
||||
OR NOT (NEW.dimensional_weight_kg <=> OLD.dimensional_weight_kg)
|
||||
OR NOT (NEW.volume_cbm <=> OLD.volume_cbm)
|
||||
OR NOT (NEW.fba_tracking_no <=> OLD.fba_tracking_no)
|
||||
OR NOT (NEW.shipment_no <=> OLD.shipment_no)
|
||||
OR NOT (NEW.shipping_mark <=> OLD.shipping_mark)
|
||||
OR NOT (NEW.load_sequence <=> OLD.load_sequence)
|
||||
OR NOT (NEW.position_snapshot <=> OLD.position_snapshot)
|
||||
OR NOT (NEW.internal_remark <=> OLD.internal_remark)
|
||||
OR NOT (NEW.customer_remark <=> OLD.customer_remark)
|
||||
OR NOT (NEW.warehouse_remark <=> OLD.warehouse_remark)
|
||||
OR NOT (NEW.frozen_at <=> OLD.frozen_at)
|
||||
OR NOT (NEW.frozen_by <=> OLD.frozen_by)
|
||||
OR NOT (NEW.removed_by <=> OLD.removed_by)
|
||||
OR NOT (NEW.removed_by_name <=> OLD.removed_by_name)
|
||||
OR NOT (NEW.removed_at <=> OLD.removed_at) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已有PDA拣货记录的装柜明细仅允许收敛部分拣货实际箱数';
|
||||
END IF;
|
||||
END IF;
|
||||
IF OLD.frozen_at IS NOT NULL THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后的明细永久冻结';
|
||||
@@ -4287,6 +4331,28 @@ BEGIN
|
||||
OR NEW.inbound_detail_id <> OLD.inbound_detail_id THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PDA拣货明细归属不可修改';
|
||||
END IF;
|
||||
IF NEW.item_status = 'PARTIAL' AND NOT (
|
||||
OLD.item_status IN ('PENDING', 'PICKING')
|
||||
AND OLD.picked_package_count < NEW.picked_package_count
|
||||
AND NEW.picked_package_count < OLD.planned_package_count
|
||||
AND NEW.planned_package_count = OLD.planned_package_count
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '部分拣货必须在计划箱数内增加实际拣出箱数';
|
||||
END IF;
|
||||
IF NEW.item_status = 'SKIPPED' AND NOT (
|
||||
OLD.item_status = 'PENDING'
|
||||
AND OLD.picked_package_count = 0
|
||||
AND NEW.picked_package_count = 0
|
||||
AND NEW.planned_package_count = OLD.planned_package_count
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '跳拣仅允许零拣货的待拣明细';
|
||||
END IF;
|
||||
IF OLD.item_status = 'PARTIAL' AND NEW.item_status = 'PICKED' AND NOT (
|
||||
NEW.picked_package_count = OLD.picked_package_count
|
||||
AND NEW.planned_package_count = OLD.picked_package_count
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '部分拣货收敛后计划箱数必须等于实际拣出箱数';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
@@ -4575,6 +4641,117 @@ DELIMITER ;
|
||||
CALL assert_phase3_smart_loading_schema();
|
||||
DROP PROCEDURE IF EXISTS assert_phase3_smart_loading_schema;
|
||||
|
||||
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE assert_phase3_smart_loading_schema()
|
||||
BEGIN
|
||||
IF (SELECT COUNT(*) FROM information_schema.tables
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name IN ('fl_container_booking', 'fl_loading_plan', 'fl_loading_plan_item',
|
||||
'fl_container_document', 'fl_loading_plan_history',
|
||||
'fl_loading_pick_task', 'fl_loading_pick_item',
|
||||
'fl_loading_pick_history')) <> 8 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:核心表未完整创建';
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM fl_loading_plan
|
||||
WHERE plan_status NOT IN ('EDITING', 'LOADING', 'COMPLETED')) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:存在旧装柜状态';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.check_constraints
|
||||
WHERE constraint_schema = 'baiyue_firstleg'
|
||||
AND constraint_name = 'chk_loading_plan_status'
|
||||
AND check_clause LIKE '%''EDITING''%'
|
||||
AND check_clause LIKE '%''LOADING''%'
|
||||
AND check_clause LIKE '%''COMPLETED''%') <> 1 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:装柜状态约束不是最终三状态';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_container_booking'
|
||||
AND column_name IN ('booking_no', 'cabinet_no', 'warehouse_id', 'bill_of_lading_no',
|
||||
'vessel_name', 'voyage_no', 'container_type', 'capacity_cbm',
|
||||
'max_weight_kg', 'terminal_trucking_company')) <> 10 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:货柜原型字段缺失';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_plan_item'
|
||||
AND column_name IN ('account_id', 'customer_name', 'active_inbound_detail_id', 'frozen_at',
|
||||
'destination_warehouse_name', 'product_name_zh', 'product_name_en',
|
||||
'product_image_file_id', 'pallet_count', 'net_weight_kg',
|
||||
'dimensional_weight_kg', 'product_quantity', 'units_per_carton',
|
||||
'fba_tracking_no', 'shipment_no', 'shipping_mark', 'version')) <> 17 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:明细归属、清单快照或冻结字段缺失';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_plan_item'
|
||||
AND index_name = 'uk_loading_item_plan_detail' AND non_unique = 0) <> 2 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:计划内唯一活动明细锁缺失';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name = 'fl_loading_pick_item'
|
||||
AND column_name IN ('pick_task_id', 'loading_plan_item_id', 'inbound_id',
|
||||
'inbound_detail_id', 'planned_package_count',
|
||||
'picked_package_count', 'item_status', 'version')) <> 8 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:PDA拣货明细字段缺失';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = 'baiyue_firstleg' AND non_unique = 0
|
||||
AND index_name IN ('uk_loading_pick_task_plan',
|
||||
'uk_loading_pick_item_plan_item',
|
||||
'uk_loading_pick_history_operation')) <> 3 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:PDA唯一索引缺失';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.triggers
|
||||
WHERE trigger_schema = 'baiyue_firstleg'
|
||||
AND trigger_name IN ('trg_container_booking_before_update',
|
||||
'trg_loading_plan_before_update', 'trg_loading_plan_after_update',
|
||||
'trg_loading_item_before_insert', 'trg_loading_item_before_update',
|
||||
'trg_loading_item_before_delete', 'trg_container_document_before_insert',
|
||||
'trg_container_document_before_update', 'trg_container_document_before_delete',
|
||||
'trg_loading_history_before_update', 'trg_loading_history_before_delete',
|
||||
'trg_loading_pick_task_before_update',
|
||||
'trg_loading_pick_item_before_update',
|
||||
'trg_loading_pick_item_before_delete',
|
||||
'trg_loading_pick_history_before_update',
|
||||
'trg_loading_pick_history_before_delete')) <> 16 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:不可逆状态或冻结触发器缺失';
|
||||
END IF;
|
||||
IF (SELECT COUNT(DISTINCT permission_code) FROM baiyue_iam.iam_permission
|
||||
WHERE tenant_id = 0 AND app_code = 'PLATFORM' AND status = 'ACTIVE' AND deleted = 0
|
||||
AND permission_code IN (
|
||||
'platform:firstleg:container:list', 'platform:firstleg:container:view',
|
||||
'platform:firstleg:container:create', 'platform:firstleg:container:edit',
|
||||
'platform:firstleg:smart-loading:list', 'platform:firstleg:smart-loading:create',
|
||||
'platform:firstleg:smart-loading:assign', 'platform:firstleg:smart-loading:delete',
|
||||
'platform:firstleg:smart-loading:push', 'platform:firstleg:preload:list',
|
||||
'platform:firstleg:preload:view', 'platform:firstleg:preload:edit',
|
||||
'platform:firstleg:preload:complete', 'platform:firstleg:preload:confirm',
|
||||
'platform:firstleg:loading-list:list', 'platform:firstleg:loading-list:view')) <> 16 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:平台权限资源未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(DISTINCT CONCAT(role.role_code, ':', permission.permission_code))
|
||||
FROM baiyue_iam.iam_role role
|
||||
JOIN baiyue_iam.iam_role_permission grant_row ON grant_row.role_id = role.id
|
||||
JOIN baiyue_iam.iam_permission permission ON permission.id = grant_row.permission_id
|
||||
WHERE role.tenant_id = 0 AND role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR')
|
||||
AND permission.tenant_id = 0
|
||||
AND permission.permission_code IN (
|
||||
'platform:firstleg:container:list', 'platform:firstleg:container:view',
|
||||
'platform:firstleg:container:create',
|
||||
'platform:firstleg:container:edit', 'platform:firstleg:smart-loading:list',
|
||||
'platform:firstleg:smart-loading:create', 'platform:firstleg:smart-loading:assign',
|
||||
'platform:firstleg:smart-loading:delete', 'platform:firstleg:smart-loading:push',
|
||||
'platform:firstleg:preload:list', 'platform:firstleg:preload:view',
|
||||
'platform:firstleg:preload:edit', 'platform:firstleg:preload:complete',
|
||||
'platform:firstleg:preload:confirm', 'platform:firstleg:loading-list:list',
|
||||
'platform:firstleg:loading-list:view')) <> 32 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:平台角色授权未完整写入';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL assert_phase3_smart_loading_schema();
|
||||
DROP PROCEDURE IF EXISTS assert_phase3_smart_loading_schema;
|
||||
|
||||
-- ============================================================
|
||||
|
||||
-- 2026-08-06 平台公共仓库正式主数据:固定作业仓 + FBA 地址库
|
||||
@@ -5564,8 +5741,8 @@ 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', 'uk_inbound_parent_sequence',
|
||||
'UNIQUE KEY `uk_inbound_parent_sequence` (`tenant_id`, `parent_inbound_id`, `split_sequence`)');
|
||||
'fl_inbound_order', 'uk_inbound_one_child',
|
||||
'UNIQUE KEY `uk_inbound_one_child` (`tenant_id`, `parent_inbound_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`)');
|
||||
@@ -5582,6 +5759,14 @@ BEGIN
|
||||
) THEN
|
||||
ALTER TABLE fl_inbound_order DROP INDEX uk_inbound_tenant_order;
|
||||
END IF;
|
||||
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;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL phase3_inbound_drop_legacy_index();
|
||||
@@ -5618,19 +5803,22 @@ CREATE TABLE IF NOT EXISTS fl_inbound_split_item (
|
||||
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',
|
||||
line_no INT UNSIGNED NOT NULL COMMENT '同一拆分批次内的行号',
|
||||
source_inbound_detail_id BIGINT UNSIGNED NULL COMMENT '订单货物明细逻辑ID',
|
||||
source_measurement_id BIGINT UNSIGNED NULL COMMENT '来源测量批次ID',
|
||||
source_measurement_item_id BIGINT UNSIGNED NULL COMMENT '来源测量箱规明细ID',
|
||||
package_count INT UNSIGNED NOT NULL COMMENT '分配给子单的箱数',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_inbound_split_child (tenant_id, child_inbound_id),
|
||||
UNIQUE KEY uk_inbound_split_batch_child (tenant_id, split_batch_id, child_inbound_id),
|
||||
UNIQUE KEY uk_inbound_split_batch_line (tenant_id, split_batch_id, line_no),
|
||||
UNIQUE KEY uk_inbound_split_batch_source_item (tenant_id, split_batch_id, source_measurement_item_id),
|
||||
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_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 (
|
||||
@@ -5680,6 +5868,15 @@ CALL phase3_inbound_add_column(
|
||||
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");
|
||||
CALL phase3_inbound_add_column(
|
||||
'fl_inbound_split_item', 'line_no',
|
||||
"INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '同一拆分批次内的行号' AFTER child_inbound_id");
|
||||
DROP PROCEDURE IF EXISTS phase3_inbound_add_column;
|
||||
|
||||
DROP PROCEDURE IF EXISTS phase3_inbound_add_relation;
|
||||
@@ -5716,6 +5913,12 @@ CALL phase3_inbound_add_relation(
|
||||
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;
|
||||
@@ -5742,8 +5945,34 @@ 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`)');
|
||||
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(
|
||||
'fl_inbound_split_item', 'uk_inbound_split_batch_source_item',
|
||||
'UNIQUE KEY `uk_inbound_split_batch_source_item` (`tenant_id`, `split_batch_id`, `source_measurement_item_id`)');
|
||||
DROP PROCEDURE IF EXISTS phase3_inbound_add_index;
|
||||
|
||||
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;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL phase3_inbound_drop_split_legacy_index();
|
||||
DROP PROCEDURE IF EXISTS phase3_inbound_drop_split_legacy_index;
|
||||
|
||||
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;
|
||||
@@ -5762,6 +5991,9 @@ 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;
|
||||
IF NEW.package_count IS NOT NULL AND NEW.package_count = 0 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单箱数必须大于0';
|
||||
END IF;
|
||||
@@ -5773,6 +6005,9 @@ BEGIN
|
||||
OR NEW.split_sequence IS NULL OR NEW.package_count IS NULL THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '子入仓单缺少主单、顺序号或箱数';
|
||||
END IF;
|
||||
IF NEW.split_status <> 'NOT_SPLIT' THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子仅两级,子入仓单不可继续拆分';
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM fl_inbound_order parent_order
|
||||
WHERE parent_order.id = NEW.parent_inbound_id
|
||||
@@ -5799,6 +6034,17 @@ BEGIN
|
||||
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;
|
||||
IF NEW.inbound_role = 'CHILD' AND NEW.split_status <> 'NOT_SPLIT' THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子仅两级,子入仓单不可继续拆分';
|
||||
END IF;
|
||||
IF NEW.inbound_role = 'MASTER'
|
||||
AND ((NEW.package_count = 0 AND NEW.split_status <> 'FULLY_SPLIT')
|
||||
OR (NEW.split_status = 'FULLY_SPLIT' AND COALESCE(NEW.package_count, 0) <> 0)) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主单箱数归零必须与完全拆分状态同时提交';
|
||||
END IF;
|
||||
END$$
|
||||
|
||||
CREATE TRIGGER trg_inbound_split_item_before_insert
|
||||
@@ -5819,8 +6065,17 @@ BEGIN
|
||||
AND child_order.tenant_id = NEW.tenant_id
|
||||
AND child_order.inbound_role = 'CHILD'
|
||||
AND child_order.parent_inbound_id = NEW.parent_inbound_id
|
||||
AND child_order.package_count = NEW.package_count
|
||||
AND child_order.package_count >= NEW.package_count
|
||||
AND child_order.deleted = 0
|
||||
AND (NEW.source_measurement_item_id IS NULL OR EXISTS (
|
||||
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
|
||||
AND source_batch.inbound_id = NEW.parent_inbound_id
|
||||
))
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分明细与批次、主单或子单不一致';
|
||||
END IF;
|
||||
@@ -5858,14 +6113,38 @@ BEGIN
|
||||
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.package_count = 0
|
||||
AND parent_order.package_count > 0
|
||||
AND parent_order.split_status = 'PARTIALLY_SPLIT'
|
||||
AND parent_order.version > NEW.expected_parent_version
|
||||
) OR
|
||||
(SELECT COALESCE(SUM(item.package_count), 0)
|
||||
FROM fl_inbound_split_item item
|
||||
WHERE item.tenant_id = NEW.tenant_id
|
||||
AND item.split_batch_id = NEW.id) <> NEW.package_count THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主单可用箱数未归零或子单箱数合计不一致';
|
||||
AND item.split_batch_id = NEW.id) <> NEW.package_count OR
|
||||
(SELECT COUNT(DISTINCT item.child_inbound_id)
|
||||
FROM fl_inbound_split_item item
|
||||
WHERE item.tenant_id = NEW.tenant_id
|
||||
AND item.split_batch_id = NEW.id) <> 1 OR
|
||||
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 = '二级子入仓单或拆分明细箱数合计不一致';
|
||||
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;
|
||||
ELSEIF NEW.status = 'CANCELLED' THEN
|
||||
IF EXISTS (
|
||||
@@ -5937,7 +6216,8 @@ BEGIN
|
||||
IF (SELECT COUNT(*) FROM information_schema.table_constraints
|
||||
WHERE constraint_schema = DATABASE()
|
||||
AND constraint_name IN ('fk_inbound_parent_order', 'fk_inbound_root_order',
|
||||
'fk_measure_split_batch', 'fk_measure_source_batch')) <> 4 THEN
|
||||
'fk_measure_split_batch', 'fk_measure_source_batch',
|
||||
'fk_measure_item_split_item', 'fk_measure_item_source_item')) <> 6 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子入仓单与测量关联约束未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.triggers
|
||||
@@ -5965,386 +6245,3 @@ END$$
|
||||
DELIMITER ;
|
||||
CALL phase3_assert_inbound_parent_child_schema();
|
||||
DROP PROCEDURE IF EXISTS phase3_assert_inbound_parent_child_schema;
|
||||
|
||||
/*
|
||||
* 部分装柜拆单契约:
|
||||
* 1. parent_inbound_id 为空的是主单,非空的是唯一二级子单;
|
||||
* 2. 锁定阶段只记录箱数,完成阶段绑定由剩余箱数生成的子单;
|
||||
* 3. 子单不能再作为父单,锁定数量创建后不可修改。
|
||||
*/
|
||||
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_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 ADD UNIQUE KEY uk_inbound_one_secondary (tenant_id, parent_inbound_id)',
|
||||
'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 ADD CONSTRAINT fk_inbound_parent FOREIGN KEY (parent_inbound_id) REFERENCES fl_inbound_order (id)',
|
||||
'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,
|
||||
created_by_name VARCHAR(64) NULL,
|
||||
completed_by BIGINT UNSIGNED NULL,
|
||||
completed_by_name VARCHAR(64) NULL,
|
||||
completed_at DATETIME(3) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
version INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
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_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 PROCEDURE IF EXISTS unified_assert_final_extensions;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE unified_assert_final_extensions()
|
||||
BEGIN
|
||||
IF (SELECT COUNT(*) FROM information_schema.tables
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name IN ('fl_preload_plan', 'fl_split_operation',
|
||||
'fl_loading_evidence', 'fl_loading_operation',
|
||||
'fl_loading_pick_task')) <> 5 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:三期分柜、预装或装柜表未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_preload_plan'
|
||||
AND column_name IN ('id', 'tenant_id', 'inbound_id', 'status', 'version',
|
||||
'items_json', 'remark', 'created_by', 'created_by_name',
|
||||
'updated_by', 'updated_by_name', 'created_at', 'updated_at')) <> 13
|
||||
OR (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_split_operation'
|
||||
AND column_name IN ('id', 'tenant_id', 'inbound_id', 'operator_id', 'operation',
|
||||
'idempotency_key', 'status', 'result_json', 'created_at')) <> 9
|
||||
OR (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_evidence'
|
||||
AND column_name IN ('id', 'tenant_id', 'inbound_id', 'evidence_type', 'file_id',
|
||||
'file_name', 'seal_no', 'remark', 'version', 'created_by',
|
||||
'created_by_name', 'updated_by', 'updated_by_name',
|
||||
'created_at', 'updated_at')) <> 15
|
||||
OR (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_operation'
|
||||
AND column_name IN ('id', 'tenant_id', 'inbound_id', 'operator_id', 'operation',
|
||||
'idempotency_key', 'request_hash', 'status', 'result_json',
|
||||
'created_at', 'completed_at')) <> 11
|
||||
OR (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task'
|
||||
AND column_name IN ('id', 'tenant_id', 'inbound_id', 'status', 'version',
|
||||
'expected_count', 'picked_count', 'remark', 'updated_by',
|
||||
'updated_by_name', 'created_at', 'updated_at')) <> 12 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:三期分柜、预装或装柜关键字段未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name IN ('fl_preload_plan', 'fl_split_operation', 'fl_loading_operation')
|
||||
AND column_name IN ('items_json', 'result_json') AND data_type = 'json') <> 3
|
||||
OR (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_order'
|
||||
AND column_name = 'process_status' AND character_maximum_length >= 24) <> 1 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:三期 JSON 字段或入库状态字段类型不兼容';
|
||||
END IF;
|
||||
IF (SELECT COUNT(DISTINCT CONCAT(table_name, '.', index_name))
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND index_name IN ('uk_preload_tenant_inbound', 'idx_preload_status',
|
||||
'uk_split_operation', 'idx_split_operation_time',
|
||||
'uk_loading_evidence_type', 'idx_loading_evidence_time',
|
||||
'uk_loading_operation', 'idx_loading_operation_time',
|
||||
'uk_loading_pick_tenant_inbound', 'idx_loading_pick_status')) <> 10 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:三期分柜、预装或装柜索引未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.table_constraints
|
||||
WHERE constraint_schema = 'baiyue_firstleg' AND constraint_type = 'FOREIGN KEY'
|
||||
AND table_name IN ('fl_preload_plan', 'fl_split_operation',
|
||||
'fl_loading_evidence', 'fl_loading_operation',
|
||||
'fl_loading_pick_task')) <> 5 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:三期操作表入库单外键未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(DISTINCT permission_code) FROM baiyue_iam.iam_permission
|
||||
WHERE tenant_id = 0 AND app_code = 'PLATFORM' AND status = 'ACTIVE' AND deleted = 0
|
||||
AND permission_code IN (
|
||||
'firstleg:inbound:split', 'firstleg:inbound:preload',
|
||||
'firstleg:inbound:preload-view',
|
||||
'platform:firstleg:inbound:loading:edit',
|
||||
'platform:firstleg:inbound:loading:view',
|
||||
'platform:firstleg:inbound:loading:complete',
|
||||
'platform:firstleg:inbound:delivery:complete',
|
||||
'platform:firstleg:inbound:picking:edit',
|
||||
'platform:firstleg:inbound:picking:view')) <> 9 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:三期分柜、预装或装柜权限未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(DISTINCT rp.permission_id)
|
||||
FROM baiyue_iam.iam_role_permission rp
|
||||
JOIN baiyue_iam.iam_permission p ON p.id = rp.permission_id
|
||||
WHERE rp.role_id = 4000001 AND rp.tenant_id = 0
|
||||
AND p.permission_code IN (
|
||||
'firstleg:inbound:split', 'firstleg:inbound:preload',
|
||||
'firstleg:inbound:preload-view',
|
||||
'platform:firstleg:inbound:loading:edit',
|
||||
'platform:firstleg:inbound:loading:view',
|
||||
'platform:firstleg:inbound:loading:complete',
|
||||
'platform:firstleg:inbound:delivery:complete',
|
||||
'platform:firstleg:inbound:picking:edit',
|
||||
'platform:firstleg:inbound:picking:view')) <> 9 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:平台系统管理员未获得完整三期操作权限';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.tables
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name IN ('fl_fee_item', 'fl_inbound_fee_detail')) <> 2 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:费用项目或入库费用明细表未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name IN ('fl_fee_item', 'fl_inbound_fee_detail')
|
||||
AND column_name = 'tenant_id'
|
||||
AND column_comment = '当前客户ID(客户隔离键);物理字段名沿用 tenant_id,当前不表示上级租户') <> 2 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:费用表 tenant_id 客户隔离语义未同步';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name = 'fl_fee_item'
|
||||
AND column_name = 'fee_code') <> 1
|
||||
OR (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name = 'fl_inbound_fee_detail'
|
||||
AND column_name IN (
|
||||
'source_type', 'source_fee_id', 'source_fee_version', 'source_fee_no',
|
||||
'customer_id', 'customer_name', 'invoice_title',
|
||||
'chargeable_quantity', 'charge_unit', 'publication_status',
|
||||
'published_by', 'published_by_name', 'published_at', 'inbounded_at',
|
||||
'settlement_included')) <> 15 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:已发布入库运费明细字段未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name = 'fl_fee_item'
|
||||
AND index_name = 'uk_fee_item_code' AND non_unique = 0) <> 2
|
||||
OR (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name = 'fl_inbound_fee_detail'
|
||||
AND index_name = 'uk_inbound_fee_source' AND non_unique = 0) <> 3
|
||||
OR (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name = 'fl_inbound_fee_detail'
|
||||
AND index_name = 'idx_inbound_fee_published') <> 4 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:已发布入库运费明细索引未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(DISTINCT CONCAT(table_name, '.', index_name))
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND index_name IN ('idx_fee_item_query', 'idx_fee_item_name',
|
||||
'idx_inbound_fee_detail', 'idx_inbound_fee_review',
|
||||
'idx_inbound_fee_item')) <> 5 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:费用项目或入库费用明细索引未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(DISTINCT permission_code) FROM baiyue_iam.iam_permission
|
||||
WHERE tenant_id = 0 AND status = 'ACTIVE' AND deleted = 0
|
||||
AND permission_code IN (
|
||||
'firstleg:fee-item:view', 'firstleg:fee-item:create',
|
||||
'firstleg:fee-item:edit', 'firstleg:fee-item:delete',
|
||||
'firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit',
|
||||
'firstleg:inbound:fee-detail:delete', 'firstleg:inbound:fee-detail:review')) <> 8 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:费用项目或入库费用明细权限未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.tables
|
||||
WHERE table_schema IN ('baiyue_auth', 'baiyue_iam', 'baiyue_order',
|
||||
'baiyue_firstleg', 'baiyue_warehouse')
|
||||
AND table_name = 'sys_audit_outbox') <> 5 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:可靠审计 Outbox 未覆盖五个业务库';
|
||||
END IF;
|
||||
IF (SELECT COUNT(DISTINCT CONCAT(table_schema, '.', index_name))
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema IN ('baiyue_auth', 'baiyue_iam', 'baiyue_order',
|
||||
'baiyue_firstleg', 'baiyue_warehouse')
|
||||
AND table_name = 'sys_audit_outbox'
|
||||
AND index_name IN ('PRIMARY', 'idx_audit_outbox_publish',
|
||||
'idx_audit_outbox_retention')) <> 15 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:可靠审计 Outbox 主键或索引未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema IN ('baiyue_auth', 'baiyue_iam', 'baiyue_order',
|
||||
'baiyue_firstleg', 'baiyue_warehouse')
|
||||
AND table_name = 'sys_audit_outbox'
|
||||
AND column_name IN ('event_id', 'payload_json', 'status',
|
||||
'next_retry_at', 'lease_until')) <> 25 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:可靠审计 Outbox 关键字段未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.tables
|
||||
WHERE table_schema = 'baiyue_audit'
|
||||
AND table_name = 'sys_audit_dead_letter') <> 1 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:审计死信补偿表未创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics
|
||||
WHERE table_schema = 'baiyue_audit'
|
||||
AND table_name = 'sys_audit_dead_letter'
|
||||
AND index_name IN ('PRIMARY', 'uk_audit_dead_event', 'idx_audit_dead_retry')) <> 3 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:审计死信补偿表主键或索引未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_audit' AND table_name = 'sys_audit_log'
|
||||
AND column_name = 'event_id') <> 1
|
||||
OR (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics
|
||||
WHERE table_schema = 'baiyue_audit' AND table_name = 'sys_audit_log'
|
||||
AND index_name = 'uk_audit_event_id' AND non_unique = 0) <> 1 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:审计消费幂等 event_id 唯一约束未就绪';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_firstleg'
|
||||
AND table_name = 'fl_third_party_call_log'
|
||||
AND column_name = 'tenant_id' AND column_default = '0'
|
||||
AND column_comment = '第三方未知/未解析主体哨兵值;不是平台客户记录或平台作用域,不得用于业务数据授权') <> 1 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:第三方未知主体 tenant_id=0 哨兵语义未就绪';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM baiyue_warehouse.wh_warehouse WHERE tenant_id <> 0) > 0
|
||||
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_warehouse_address WHERE tenant_id <> 0) > 0
|
||||
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_service_capability WHERE tenant_id <> 0) > 0
|
||||
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_express_company WHERE tenant_id <> 0) > 0
|
||||
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_partner
|
||||
WHERE tenant_id <> 0 OR data_scope_id <> 0) > 0
|
||||
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_partner_type
|
||||
WHERE tenant_id <> 0 OR data_scope_id <> 0) > 0
|
||||
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_pallet WHERE tenant_id <> 0) > 0
|
||||
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_location WHERE tenant_id <> 0) > 0
|
||||
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_location_reservation WHERE tenant_id <> 0) > 0
|
||||
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_location_occupancy WHERE tenant_id <> 0) > 0
|
||||
OR (SELECT COUNT(*) FROM baiyue_firstleg.fl_inbound_pallet_allocation
|
||||
WHERE warehouse_resource_tenant_id <> 0) > 0 THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:平台公共仓储资源仍有 tenant_id<>0';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM baiyue_warehouse.wh_warehouse_address a
|
||||
JOIN baiyue_warehouse.wh_warehouse w ON w.id = a.warehouse_id
|
||||
WHERE a.tenant_id <> w.tenant_id
|
||||
) OR EXISTS (
|
||||
SELECT 1 FROM baiyue_warehouse.wh_service_capability c
|
||||
JOIN baiyue_warehouse.wh_warehouse w ON w.id = c.warehouse_id
|
||||
WHERE c.tenant_id <> w.tenant_id
|
||||
) OR EXISTS (
|
||||
SELECT 1 FROM baiyue_warehouse.wh_pallet p
|
||||
JOIN baiyue_warehouse.wh_warehouse w ON w.id = p.warehouse_id
|
||||
WHERE p.tenant_id <> w.tenant_id
|
||||
) OR EXISTS (
|
||||
SELECT 1 FROM baiyue_warehouse.wh_location l
|
||||
JOIN baiyue_warehouse.wh_warehouse w ON w.id = l.warehouse_id
|
||||
WHERE l.tenant_id <> w.tenant_id
|
||||
) OR EXISTS (
|
||||
SELECT 1 FROM baiyue_warehouse.wh_location_reservation r
|
||||
JOIN baiyue_warehouse.wh_warehouse w ON w.id = r.warehouse_id
|
||||
JOIN baiyue_warehouse.wh_pallet p ON p.id = r.pallet_id
|
||||
JOIN baiyue_warehouse.wh_location l ON l.id = r.location_id
|
||||
WHERE r.tenant_id <> w.tenant_id OR r.tenant_id <> p.tenant_id OR r.tenant_id <> l.tenant_id
|
||||
) OR EXISTS (
|
||||
SELECT 1 FROM baiyue_warehouse.wh_location_occupancy o
|
||||
JOIN baiyue_warehouse.wh_warehouse w ON w.id = o.warehouse_id
|
||||
JOIN baiyue_warehouse.wh_pallet p ON p.id = o.pallet_id
|
||||
JOIN baiyue_warehouse.wh_location l ON l.id = o.location_id
|
||||
JOIN baiyue_warehouse.wh_location_reservation r ON r.id = o.reservation_id
|
||||
WHERE o.tenant_id <> w.tenant_id OR o.tenant_id <> p.tenant_id
|
||||
OR o.tenant_id <> l.tenant_id OR o.tenant_id <> r.tenant_id
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:Warehouse 公共资源父子归属不一致';
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM baiyue_warehouse.wh_overseas_storage_fee_rule WHERE tenant_id = 0) THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '统一迁移失败:客户费用规则缺少客户 tenant_id';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL unified_assert_final_extensions();
|
||||
DROP PROCEDURE IF EXISTS unified_assert_final_extensions;
|
||||
|
||||
-- tenant_id=0 在此表仅为未知/未解析第三方主体;数量可非零,但不得用于业务授权。
|
||||
SELECT COUNT(*) AS unresolved_third_party_calls
|
||||
FROM baiyue_firstleg.fl_third_party_call_log
|
||||
WHERE tenant_id = 0;
|
||||
|
||||
SELECT 'PASS' AS migration_status,
|
||||
'统一增量全部执行完成;统一数据库脚本执行完成,三期出仓、费用、公共资源及可靠审计结构校验通过' AS message;
|
||||
|
||||
Reference in New Issue
Block a user