-- 入口模块菜单模板、多租户可见性、文档归属巡检脚本 -- 用途: -- 1. 检查 entry_module_menu_profile_migration.sql 是否补齐字段、索引、默认值。 -- 2. 检查入口模块 features/menu_profile 是否存在非法值或空配置。 -- 3. 检查文档 entry_module_id 回填情况和二级分组重复风险。 -- 4. 本脚本只查询,不修改数据。 -- -- 推荐执行方式: -- psql "$DATABASE_URL" -f scripts/创建sql/verify_entry_module_menu_profile.sql -- ========================================================= -- 1. 必要字段检查 -- ========================================================= SELECT table_name, column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema = current_schema() AND ( (table_name = 'leaudit_entry_modules' AND column_name IN ('menu_profile', 'features')) OR (table_name = 'leaudit_documents' AND column_name = 'entry_module_id') ) ORDER BY table_name, column_name; -- ========================================================= -- 2. 必要索引检查 -- ========================================================= SELECT schemaname, tablename, indexname, indexdef FROM pg_indexes WHERE schemaname = current_schema() AND indexname IN ( 'idx_leaudit_entry_modules_menu_profile', 'idx_leaudit_documents_entry_module_id', 'uq_leaudit_ep_groups_parent_doc_type_active' ) ORDER BY indexname; -- ========================================================= -- 3. 入口模块菜单配置总览 -- ========================================================= SELECT em.id, em.name, em.path, em.menu_profile, em.features, em.is_enabled, COALESCE( STRING_AGG(emt.tenant_code, ', ' ORDER BY emt.tenant_code) FILTER (WHERE emt.is_enabled IS TRUE), '' ) AS enabled_tenants FROM leaudit_entry_modules em LEFT JOIN leaudit_entry_module_tenants emt ON emt.entry_module_id = em.id WHERE em.deleted_at IS NULL GROUP BY em.id, em.name, em.path, em.menu_profile, em.features, em.is_enabled ORDER BY em.sort_order, em.id; -- ========================================================= -- 4. 非法 menu_profile 检查:结果应为空 -- ========================================================= SELECT id, name, path, menu_profile FROM leaudit_entry_modules WHERE deleted_at IS NULL AND menu_profile NOT IN ('document_review', 'contract', 'govdoc', 'cross_checking', 'custom') ORDER BY id; -- ========================================================= -- 5. 空 features 检查:结果应为空或只包含刻意关闭菜单的入口 -- ========================================================= SELECT id, name, path, menu_profile, features FROM leaudit_entry_modules WHERE deleted_at IS NULL AND ( features IS NULL OR jsonb_typeof(features) <> 'array' OR jsonb_array_length(features) = 0 ) ORDER BY id; -- ========================================================= -- 6. 非法 feature 编码检查:结果应为空 -- ========================================================= SELECT em.id, em.name, em.menu_profile, invalid_feature.feature FROM leaudit_entry_modules em CROSS JOIN LATERAL jsonb_array_elements_text(em.features) AS invalid_feature(feature) WHERE em.deleted_at IS NULL AND invalid_feature.feature NOT IN ( 'home', 'documents', 'upload', 'rules', 'rule_groups', 'contract_template_search', 'contract_template_list', 'govdoc_audits', 'govdoc_upload', 'cross_checking', 'cross_checking_upload', 'cross_checking_list', 'usage_stats' ) ORDER BY em.id, invalid_feature.feature; -- ========================================================= -- 7. 文档归属缺失检查 -- ========================================================= SELECT COUNT(*) AS documents_without_entry_module FROM leaudit_documents WHERE deleted_at IS NULL AND entry_module_id IS NULL; SELECT d.id, d.normalized_name AS document_name, d.type_id, dt.name AS document_type_name, d.group_id, g.name AS group_name, d.tenant_code, d.created_at FROM leaudit_documents d LEFT JOIN leaudit_document_types dt ON dt.id = d.type_id LEFT JOIN leaudit_evaluation_point_groups g ON g.id = d.group_id WHERE d.deleted_at IS NULL AND d.entry_module_id IS NULL ORDER BY d.created_at DESC NULLS LAST, d.id DESC LIMIT 50; -- ========================================================= -- 8. 文档 entry_module_id 与分组/文档类型推导不一致检查 -- ========================================================= SELECT d.id, d.normalized_name AS document_name, d.entry_module_id AS document_entry_module_id, COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) AS resolved_entry_module_id, d.type_id, d.group_id, d.tenant_code FROM leaudit_documents d LEFT JOIN leaudit_document_types dt ON dt.id = d.type_id LEFT JOIN leaudit_evaluation_point_groups g ON g.id = d.group_id LEFT JOIN leaudit_evaluation_point_groups parent ON parent.id = g.pid WHERE d.deleted_at IS NULL AND d.entry_module_id IS NOT NULL AND COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) IS NOT NULL AND d.entry_module_id <> COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) ORDER BY d.id DESC LIMIT 100; -- ========================================================= -- 9. 重复二级分组检查:结果应为空 -- ========================================================= SELECT pid, document_type_id, COUNT(*) AS duplicated_count, STRING_AGG(id::text || ':' || name, ' / ' ORDER BY id) AS groups FROM leaudit_evaluation_point_groups WHERE deleted_at IS NULL AND COALESCE(pid, 0) <> 0 AND document_type_id IS NOT NULL GROUP BY pid, document_type_id HAVING COUNT(*) > 1 ORDER BY duplicated_count DESC, pid, document_type_id; -- ========================================================= -- 10. 一级分组缺入口模块检查:兼容期允许存在,但新链路应逐步清零 -- ========================================================= SELECT id, code, name, document_type_id, sort_order, is_enabled FROM leaudit_evaluation_point_groups WHERE deleted_at IS NULL AND COALESCE(pid, 0) = 0 AND entry_module_id IS NULL ORDER BY sort_order, id;