-- 评查点分组正式迁移脚本(业务大类根版本) -- 目标: -- 一级分组 = 业务大类(合同 / 行政卷宗 / 后续新增业务) -- 二级分组 = 具体业务类型 -- 规则集 = 挂在二级分组下 -- 入口模块 = 绑定一级分组 -- -- 重要说明: -- 1. 本脚本面向当前真实库结构: -- - 已存在旧一级根(一级=具体文档类型) -- - 已存在新一级业务根 root.contract / root.casefile -- - 已存在默认二级分组 *.default / 通用 -- 2. 执行前必须先跑: -- scripts/precheck_rule_group_migration.sql -- 3. 必须先备份: -- leaudit_evaluation_point_groups -- leaudit_rule_group_bindings -- leaudit_rule_type_bindings -- leaudit_document_types -- 4. 本脚本不会立刻删除旧一级根,只做结构补齐 + 绑定迁移 + 汇总重建。 BEGIN; -- ========================================================= -- 0. 补齐结构:一级分组入口模块字段 -- ========================================================= ALTER TABLE leaudit_evaluation_point_groups ADD COLUMN IF NOT EXISTS entry_module_id BIGINT NULL REFERENCES leaudit_entry_modules(id); CREATE INDEX IF NOT EXISTS idx_leaudit_ep_groups_entry_module ON leaudit_evaluation_point_groups(entry_module_id); -- ========================================================= -- 1. 保证一级业务大类根存在,并统一编码 -- 当前统一使用: -- root.contract -> 合同 -- root.casefile -> 行政卷宗 -- ========================================================= WITH root_candidates AS ( SELECT 'root.contract'::varchar AS code, '合同'::varchar AS name, '合同业务大类'::text AS description, 1::bigint AS entry_module_id, 10::int AS sort_order UNION ALL SELECT 'root.casefile'::varchar, '行政卷宗'::varchar, '行政卷宗业务大类'::text, 2::bigint, 20::int ), upsert_roots AS ( INSERT INTO leaudit_evaluation_point_groups ( pid, code, name, description, document_type_id, entry_module_id, sort_order, is_enabled, created_at, updated_at ) SELECT 0, rc.code, rc.name, rc.description, NULL, rc.entry_module_id, rc.sort_order, TRUE, NOW(), NOW() FROM root_candidates rc WHERE NOT EXISTS ( SELECT 1 FROM leaudit_evaluation_point_groups g WHERE g.deleted_at IS NULL AND COALESCE(g.pid, 0) = 0 AND LOWER(g.code) = LOWER(rc.code) ) RETURNING id, code ) SELECT COUNT(*) AS inserted_root_count FROM upsert_roots; UPDATE leaudit_evaluation_point_groups SET name = '合同', description = '合同业务大类', entry_module_id = 1, sort_order = 10, updated_at = NOW() WHERE deleted_at IS NULL AND COALESCE(pid, 0) = 0 AND LOWER(code) = LOWER('root.contract'); UPDATE leaudit_evaluation_point_groups SET name = '行政卷宗', description = '行政卷宗业务大类', entry_module_id = 2, sort_order = 20, updated_at = NOW() WHERE deleted_at IS NULL AND COALESCE(pid, 0) = 0 AND LOWER(code) = LOWER('root.casefile'); -- ========================================================= -- 2. 优先把旧一级根直接下沉为目标二级分组 -- 这样可复用现有 code,避免唯一索引冲突 -- ========================================================= WITH target_root_map AS ( SELECT dt.id AS document_type_id, CASE WHEN dt.entry_module_id = 1 THEN ( SELECT id FROM leaudit_evaluation_point_groups WHERE deleted_at IS NULL AND COALESCE(pid, 0) = 0 AND code = 'root.contract' LIMIT 1 ) WHEN dt.entry_module_id = 2 THEN ( SELECT id FROM leaudit_evaluation_point_groups WHERE deleted_at IS NULL AND COALESCE(pid, 0) = 0 AND code = 'root.casefile' LIMIT 1 ) ELSE NULL END AS new_root_id FROM leaudit_document_types dt WHERE dt.deleted_at IS NULL ), reparented_old_roots AS ( UPDATE leaudit_evaluation_point_groups g SET pid = trm.new_root_id, entry_module_id = NULL, updated_at = NOW() FROM target_root_map trm WHERE g.deleted_at IS NULL AND COALESCE(g.pid, 0) = 0 AND g.document_type_id = trm.document_type_id AND trm.new_root_id IS NOT NULL RETURNING g.id, g.document_type_id ) SELECT COUNT(*) AS reparented_old_root_count FROM reparented_old_roots; -- ========================================================= -- 3. 如果某个文档类型没有旧一级根,再补建新的目标二级分组 -- ========================================================= WITH mapped_doc_types AS ( SELECT dt.id AS document_type_id, dt.code AS document_type_code, dt.name AS document_type_name, dt.description, dt.entry_module_id, dt.sort_order, dt.is_enabled, CASE WHEN dt.entry_module_id = 1 THEN ( SELECT id FROM leaudit_evaluation_point_groups WHERE deleted_at IS NULL AND COALESCE(pid, 0) = 0 AND code = 'root.contract' LIMIT 1 ) WHEN dt.entry_module_id = 2 THEN ( SELECT id FROM leaudit_evaluation_point_groups WHERE deleted_at IS NULL AND COALESCE(pid, 0) = 0 AND code = 'root.casefile' LIMIT 1 ) ELSE NULL END AS root_group_id FROM leaudit_document_types dt WHERE dt.deleted_at IS NULL ), insert_children AS ( INSERT INTO leaudit_evaluation_point_groups ( pid, code, name, description, document_type_id, entry_module_id, sort_order, is_enabled, created_at, updated_at ) SELECT m.root_group_id, m.document_type_code, m.document_type_name, COALESCE(m.description, m.document_type_name || '二级分组'), m.document_type_id, NULL, COALESCE(m.sort_order, 0), COALESCE(m.is_enabled, TRUE), NOW(), NOW() FROM mapped_doc_types m WHERE m.root_group_id IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM leaudit_evaluation_point_groups g JOIN leaudit_evaluation_point_groups root ON root.id = g.pid AND root.deleted_at IS NULL WHERE g.deleted_at IS NULL AND COALESCE(g.pid, 0) <> 0 AND g.document_type_id = m.document_type_id AND root.document_type_id IS NULL ) AND NOT EXISTS ( SELECT 1 FROM leaudit_evaluation_point_groups g WHERE g.deleted_at IS NULL AND LOWER(g.code) = LOWER(m.document_type_code) ) RETURNING id, document_type_id ) SELECT COUNT(*) AS inserted_child_count FROM insert_children; -- ========================================================= -- 4. 将旧一级根上的规则绑定迁到目标二级分组 -- ========================================================= WITH old_roots AS ( SELECT id AS old_group_id, document_type_id FROM leaudit_evaluation_point_groups WHERE deleted_at IS NULL AND COALESCE(pid, 0) = 0 AND document_type_id IS NOT NULL ), target_children AS ( SELECT child.id AS new_group_id, child.document_type_id FROM leaudit_evaluation_point_groups child JOIN leaudit_evaluation_point_groups root ON root.id = child.pid AND root.deleted_at IS NULL AND root.document_type_id IS NULL WHERE child.deleted_at IS NULL AND COALESCE(child.pid, 0) <> 0 AND child.document_type_id IS NOT NULL ), move_pairs AS ( SELECT old_roots.old_group_id, target_children.new_group_id FROM old_roots JOIN target_children ON target_children.document_type_id = old_roots.document_type_id ), updated_bindings AS ( UPDATE leaudit_rule_group_bindings rgb SET group_id = mp.new_group_id, updated_at = NOW() FROM move_pairs mp WHERE rgb.group_id = mp.old_group_id AND rgb.deleted_at IS NULL RETURNING rgb.id ) SELECT COUNT(*) AS moved_old_root_binding_count FROM updated_bindings; -- ========================================================= -- 5. 将旧默认子级上的规则绑定迁到目标二级分组 -- 注意:只迁“父级仍是旧一级根”的默认子级 -- ========================================================= WITH old_default_children AS ( SELECT child.id AS old_group_id, child.document_type_id FROM leaudit_evaluation_point_groups child JOIN leaudit_evaluation_point_groups parent ON parent.id = child.pid AND parent.deleted_at IS NULL WHERE child.deleted_at IS NULL AND COALESCE(child.pid, 0) <> 0 AND parent.document_type_id IS NOT NULL AND ( child.name = '通用' OR child.code LIKE '%.default' ) ), target_children AS ( SELECT child.id AS new_group_id, child.document_type_id FROM leaudit_evaluation_point_groups child JOIN leaudit_evaluation_point_groups root ON root.id = child.pid AND root.deleted_at IS NULL AND root.document_type_id IS NULL WHERE child.deleted_at IS NULL AND COALESCE(child.pid, 0) <> 0 AND child.document_type_id IS NOT NULL ), move_pairs AS ( SELECT old_default_children.old_group_id, target_children.new_group_id FROM old_default_children JOIN target_children ON target_children.document_type_id = old_default_children.document_type_id ), updated_bindings AS ( UPDATE leaudit_rule_group_bindings rgb SET group_id = mp.new_group_id, updated_at = NOW() FROM move_pairs mp WHERE rgb.group_id = mp.old_group_id AND rgb.deleted_at IS NULL AND NOT EXISTS ( SELECT 1 FROM leaudit_rule_group_bindings existing WHERE existing.deleted_at IS NULL AND existing.group_id = mp.new_group_id AND existing.rule_set_id = rgb.rule_set_id ) RETURNING rgb.id ), soft_deleted_duplicates AS ( UPDATE leaudit_rule_group_bindings rgb SET deleted_at = NOW(), updated_at = NOW(), note = COALESCE(rgb.note, '') || ' [soft-deleted after business-root migration: duplicate with target child binding]' FROM move_pairs mp WHERE rgb.group_id = mp.old_group_id AND rgb.deleted_at IS NULL AND EXISTS ( SELECT 1 FROM leaudit_rule_group_bindings existing WHERE existing.deleted_at IS NULL AND existing.group_id = mp.new_group_id AND existing.rule_set_id = rgb.rule_set_id ) RETURNING rgb.id ) SELECT (SELECT COUNT(*) FROM updated_bindings) AS moved_default_child_binding_count, (SELECT COUNT(*) FROM soft_deleted_duplicates) AS soft_deleted_duplicate_default_binding_count; -- ========================================================= -- 6. 去重修复:避免迁移后同一二级分组重复绑定同一规则集 -- 保留 priority 更高、id 更小的一条 -- ========================================================= WITH ranked AS ( SELECT id, ROW_NUMBER() OVER ( PARTITION BY group_id, rule_set_id ORDER BY priority DESC, id ASC ) AS rn FROM leaudit_rule_group_bindings WHERE deleted_at IS NULL ), soft_deleted AS ( UPDATE leaudit_rule_group_bindings rgb SET deleted_at = NOW(), updated_at = NOW() FROM ranked r WHERE rgb.id = r.id AND r.rn > 1 RETURNING rgb.id ) SELECT COUNT(*) AS deduped_binding_count FROM soft_deleted; -- ========================================================= -- 7. 清理迁移后已空置的旧默认子级 -- 条件: -- - 自身无有效规则绑定 -- - 自身是“通用 / *.default” -- - 父级已是具体业务类型节点 -- ========================================================= WITH empty_default_children AS ( SELECT child.id FROM leaudit_evaluation_point_groups child JOIN leaudit_evaluation_point_groups parent ON parent.id = child.pid AND parent.deleted_at IS NULL LEFT JOIN leaudit_rule_group_bindings rgb ON rgb.group_id = child.id AND rgb.deleted_at IS NULL WHERE child.deleted_at IS NULL AND COALESCE(child.pid, 0) <> 0 AND parent.document_type_id IS NOT NULL AND ( child.name = '通用' OR child.code LIKE '%.default' ) GROUP BY child.id HAVING COUNT(rgb.id) = 0 ), soft_deleted_groups AS ( UPDATE leaudit_evaluation_point_groups g SET deleted_at = NOW(), updated_at = NOW() FROM empty_default_children edc WHERE g.id = edc.id RETURNING g.id ) SELECT COUNT(*) AS soft_deleted_empty_default_group_count FROM soft_deleted_groups; -- ========================================================= -- 8. 重建文档类型汇总绑定表 -- ========================================================= UPDATE leaudit_rule_type_bindings SET deleted_at = NOW(), updated_at = NOW() WHERE deleted_at IS NULL; INSERT INTO leaudit_rule_type_bindings ( doc_type_id, doc_type_code, rule_set_id, binding_mode, priority, is_active, note, created_at, updated_at, region ) SELECT DISTINCT ON (child.document_type_id, rgb.rule_set_id) child.document_type_id, dt.code, rgb.rule_set_id, 'explicit', 100, TRUE, 're-built from second-level group bindings after business-root migration', NOW(), NOW(), 'default' FROM leaudit_rule_group_bindings rgb JOIN leaudit_evaluation_point_groups child ON child.id = rgb.group_id AND child.deleted_at IS NULL AND COALESCE(child.pid, 0) <> 0 JOIN leaudit_evaluation_point_groups root ON root.id = child.pid AND root.deleted_at IS NULL AND root.document_type_id IS NULL JOIN leaudit_document_types dt ON dt.id = child.document_type_id WHERE rgb.deleted_at IS NULL AND rgb.is_active = TRUE ORDER BY child.document_type_id, rgb.rule_set_id, rgb.priority DESC, rgb.id ASC; -- ========================================================= -- 9. 迁移后输出检查摘要 -- ========================================================= SELECT root.code AS root_code, root.name AS root_name, root.entry_module_id, COUNT(child.id) AS child_count FROM leaudit_evaluation_point_groups root LEFT JOIN leaudit_evaluation_point_groups child ON child.pid = root.id AND child.deleted_at IS NULL WHERE root.deleted_at IS NULL AND COALESCE(root.pid, 0) = 0 AND root.document_type_id IS NULL GROUP BY root.id, root.code, root.name, root.entry_module_id ORDER BY root.sort_order, root.id; COMMIT;