Files
leaudit-platform-backend/scripts/migrate_rule_groups_to_business_roots.sql
T

463 lines
15 KiB
PL/PgSQL

-- 评查点分组正式迁移脚本(业务大类根版本)
-- 目标:
-- 一级分组 = 业务大类(合同 / 行政卷宗 / 后续新增业务)
-- 二级分组 = 具体业务类型
-- 规则集 = 挂在二级分组下
-- 入口模块 = 绑定一级分组
--
-- 重要说明:
-- 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;