feat: update audit platform workspace

This commit is contained in:
wren
2026-05-25 09:50:01 +08:00
parent ba8e93c0d3
commit 68d0b4c878
73 changed files with 12196 additions and 367 deletions
@@ -0,0 +1,67 @@
-- 入口模块菜单模板、功能清单、文档入口归属迁移脚本
-- 目标:
-- 1. 入口模块用 menu_profile/features 控制左侧菜单,不再靠名称包含“合同/公文”判断。
-- 2. 文档记录补 entry_module_id,后续列表、上传、统计、质量校验可以按入口模块过滤。
-- 3. 二级分组增加父级内文档类型唯一约束,避免规则命中不稳定。
BEGIN;
ALTER TABLE leaudit_entry_modules
ADD COLUMN IF NOT EXISTS menu_profile VARCHAR(64) NOT NULL DEFAULT 'document_review',
ADD COLUMN IF NOT EXISTS features JSONB NOT NULL DEFAULT '[]'::jsonb;
ALTER TABLE leaudit_documents
ADD COLUMN IF NOT EXISTS entry_module_id BIGINT NULL REFERENCES leaudit_entry_modules(id);
CREATE INDEX IF NOT EXISTS idx_leaudit_entry_modules_menu_profile
ON leaudit_entry_modules(menu_profile)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_leaudit_documents_entry_module_id
ON leaudit_documents(entry_module_id);
CREATE UNIQUE INDEX IF NOT EXISTS uq_leaudit_ep_groups_parent_doc_type_active
ON leaudit_evaluation_point_groups(pid, document_type_id)
WHERE deleted_at IS NULL
AND COALESCE(pid, 0) <> 0
AND document_type_id IS NOT NULL;
UPDATE leaudit_entry_modules
SET
menu_profile = CASE
WHEN path IN ('/govdoc/audits', '/govdoc', '/govdoc-audit') THEN 'govdoc'
WHEN path IN ('/contract-template', '/contract-template/list', '/contract-template/search') THEN 'contract'
ELSE COALESCE(NULLIF(menu_profile, ''), 'document_review')
END,
features = CASE
WHEN path IN ('/govdoc/audits', '/govdoc', '/govdoc-audit')
THEN '["home","govdoc_audits","govdoc_upload","rule_groups"]'::jsonb
WHEN path IN ('/contract-template', '/contract-template/list', '/contract-template/search')
THEN '["home","documents","upload","rules","contract_template_search","contract_template_list"]'::jsonb
WHEN features IS NULL OR features = '[]'::jsonb
THEN '["home","documents","upload","rules","rule_groups"]'::jsonb
ELSE features
END
WHERE deleted_at IS NULL;
UPDATE leaudit_documents d
SET entry_module_id = COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id)
FROM leaudit_document_types dt
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.type_id = dt.id
AND d.entry_module_id IS NULL;
COMMIT;
-- 验证入口模块菜单字段:
-- SELECT id, name, path, menu_profile, features
-- FROM leaudit_entry_modules
-- WHERE deleted_at IS NULL
-- ORDER BY sort_order, id;
-- 验证仍未回填入口模块归属的文档:
-- SELECT COUNT(*) AS documents_without_entry_module
-- FROM leaudit_documents
-- WHERE deleted_at IS NULL
-- AND entry_module_id IS NULL;
@@ -0,0 +1,66 @@
-- ============================================================================
-- 入口模块管理权限收口:仅系统超级管理员维护入口模块
-- 说明:
-- 1. 不在代码里硬编码角色名,运行时仍然只认 RBAC 权限点。
-- 2. 本脚本只调整默认 RBAC 数据,把 admin / provincial_admin 的入口模块管理能力移除。
-- 3. 若后续确需给某个角色开放入口模块管理,请通过角色权限页面重新分配。
-- 4. 可重复执行。
-- ============================================================================
BEGIN;
-- 保证系统超级管理员拥有入口模块路由。
INSERT INTO role_route (role_id, route_id, permission, status, created_at, updated_at)
SELECT r.id, sr.id, 'RW', 1, NOW(), NOW()
FROM roles r
JOIN sys_routes sr ON sr.route_path = '/entry-modules' AND sr.deleted_at IS NULL
WHERE r.role_key = 'super_admin'
ON CONFLICT (role_id, route_id) DO UPDATE SET
permission = EXCLUDED.permission,
status = EXCLUDED.status,
updated_at = NOW();
-- 保证系统超级管理员拥有入口模块全部权限点。
INSERT INTO role_permissions (role_id, permission_id, grant_type, data_scope, created_at, updated_at)
SELECT r.id, p.id, 'GRANT', 'ALL', NOW(), NOW()
FROM roles r
JOIN permissions p ON p.permission_key LIKE 'entry_module:%'
WHERE r.role_key = 'super_admin'
ON CONFLICT (role_id, permission_id) DO UPDATE SET
grant_type = EXCLUDED.grant_type,
data_scope = EXCLUDED.data_scope,
updated_at = NOW();
-- 移除地区管理员 / 旧省级管理员的入口模块权限点。
DELETE FROM role_permissions rp
USING roles r, permissions p
WHERE rp.role_id = r.id
AND rp.permission_id = p.id
AND r.role_key IN ('admin', 'provincial_admin')
AND p.permission_key LIKE 'entry_module:%';
-- 移除地区管理员 / 旧省级管理员的入口模块管理菜单。
DELETE FROM role_route rr
USING roles r, sys_routes sr
WHERE rr.role_id = r.id
AND rr.route_id = sr.id
AND r.role_key IN ('admin', 'provincial_admin')
AND sr.route_path = '/entry-modules';
COMMIT;
-- 验证结果:应只看到 super_admin 拥有入口模块权限。
SELECT r.role_key, p.permission_key
FROM roles r
JOIN role_permissions rp ON rp.role_id = r.id
JOIN permissions p ON p.id = rp.permission_id
WHERE p.permission_key LIKE 'entry_module:%'
ORDER BY r.role_key, p.permission_key;
-- 验证结果:admin / provincial_admin 不应再拥有 /entry-modules 路由。
SELECT r.role_key, sr.route_path, rr.permission, rr.status
FROM roles r
JOIN role_route rr ON rr.role_id = r.id
JOIN sys_routes sr ON sr.id = rr.route_id
WHERE sr.route_path = '/entry-modules'
ORDER BY r.role_key;
@@ -0,0 +1,145 @@
-- 修复入口模块归属历史数据
-- 执行目标:
-- 1. 回填可通过业务类型/规则分组明确推导的 leaudit_documents.entry_module_id。
-- 2. 回填可通过子业务类型明确推导的一级/二级业务分组 entry_module_id。
-- 3. 软删除无文档、无规则绑定的 Playwright/test 残留规则分组。
-- 4. 补齐正式案卷入口的默认功能菜单。
--
-- 安全边界:
-- 1. 不处理无法推导的旧公文示例。
-- 2. 不把数据强行塞到 entry_module_id=1。
-- 3. 不物理删除数据,只做软删除或确定性回填。
BEGIN;
-- 1. 回填历史文档 entry_module_id:只处理能推导到“未删除入口模块”的文档。
WITH resolved_documents AS (
SELECT
d.id,
COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) AS resolved_entry_module_id
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
JOIN leaudit_entry_modules em
ON em.id = COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id)
AND em.deleted_at IS NULL
WHERE d.deleted_at IS NULL
AND d.entry_module_id IS NULL
AND COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) IS NOT NULL
)
UPDATE leaudit_documents d
SET entry_module_id = rd.resolved_entry_module_id,
updated_at = NOW()
FROM resolved_documents rd
WHERE d.id = rd.id;
-- 2. 回填一级业务大类 entry_module_id:只处理所有子业务类型都指向同一个未删除入口模块的一级分组。
WITH resolvable_roots AS (
SELECT
root.id AS root_id,
MIN(child_dt.entry_module_id) AS resolved_entry_module_id
FROM leaudit_evaluation_point_groups root
JOIN leaudit_evaluation_point_groups child
ON child.pid = root.id
AND child.deleted_at IS NULL
JOIN leaudit_document_types child_dt
ON child_dt.id = child.document_type_id
AND child_dt.deleted_at IS NULL
JOIN leaudit_entry_modules em
ON em.id = child_dt.entry_module_id
AND em.deleted_at IS NULL
WHERE root.deleted_at IS NULL
AND COALESCE(root.pid, 0) = 0
AND root.entry_module_id IS NULL
GROUP BY root.id
HAVING COUNT(DISTINCT child_dt.entry_module_id) = 1
)
UPDATE leaudit_evaluation_point_groups root
SET entry_module_id = rr.resolved_entry_module_id,
updated_at = NOW()
FROM resolvable_roots rr
WHERE root.id = rr.root_id;
-- 3. 二级业务类型继承父级入口模块:只处理父级已明确归属、子级为空的记录。
UPDATE leaudit_evaluation_point_groups child
SET entry_module_id = parent.entry_module_id,
updated_at = NOW()
FROM leaudit_evaluation_point_groups parent
WHERE child.deleted_at IS NULL
AND parent.deleted_at IS NULL
AND child.pid = parent.id
AND COALESCE(parent.pid, 0) = 0
AND parent.entry_module_id IS NOT NULL
AND child.entry_module_id IS NULL;
-- 4. 软删除无引用的测试残留规则分组。
WITH candidate_groups AS (
SELECT g.id
FROM leaudit_evaluation_point_groups g
WHERE g.deleted_at IS NULL
AND (
g.code = 'testmzceshi'
OR g.code LIKE 'pw.%'
)
),
safe_groups AS (
SELECT cg.id
FROM candidate_groups cg
WHERE NOT EXISTS (
SELECT 1
FROM leaudit_documents d
WHERE d.deleted_at IS NULL
AND d.group_id = cg.id
)
AND NOT EXISTS (
SELECT 1
FROM leaudit_rule_group_bindings b
WHERE b.deleted_at IS NULL
AND b.group_id = cg.id
)
AND NOT EXISTS (
SELECT 1
FROM leaudit_evaluation_point_groups child
JOIN leaudit_documents d
ON d.group_id = child.id
AND d.deleted_at IS NULL
WHERE child.deleted_at IS NULL
AND child.pid = cg.id
)
AND NOT EXISTS (
SELECT 1
FROM leaudit_evaluation_point_groups child
JOIN leaudit_rule_group_bindings b
ON b.group_id = child.id
AND b.deleted_at IS NULL
WHERE child.deleted_at IS NULL
AND child.pid = cg.id
)
)
UPDATE leaudit_evaluation_point_groups g
SET deleted_at = NOW(),
updated_at = NOW()
FROM safe_groups sg
WHERE g.id = sg.id;
-- 5. 补齐案卷智能评查的通用文档评查功能菜单。
UPDATE leaudit_entry_modules
SET features = '["home", "documents", "upload", "rules", "rule_groups"]'::jsonb,
updated_at = NOW()
WHERE deleted_at IS NULL
AND id = 2
AND name = '案卷智能评查'
AND (
features IS NULL
OR jsonb_typeof(features) <> 'array'
OR jsonb_array_length(features) = 0
);
COMMIT;
-- 执行后建议立即运行:
-- psql "$DATABASE_URL" -f scripts/创建sql/verify_entry_module_menu_profile.sql
@@ -0,0 +1,15 @@
-- 回填旧公文示例文档入口归属
-- 这 3 条是旧 govdoc 链路数据,只补 entry_module_id,不伪造 type_id/group_id。
BEGIN;
UPDATE leaudit_documents
SET entry_module_id = 3,
updated_at = NOW()
WHERE id IN (46, 47, 48)
AND deleted_at IS NULL
AND entry_module_id IS NULL
AND engine_type = 'govdoc'
AND review_scope = 'govdoc';
COMMIT;
@@ -0,0 +1,36 @@
-- 企查查企业主体信息缓存表。
CREATE TABLE IF NOT EXISTS qcc_company_info (
id BIGSERIAL PRIMARY KEY,
search_key VARCHAR(200) NOT NULL,
credit_code VARCHAR(64),
company_name VARCHAR(255),
enterprise JSONB,
dishonesty JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_qcc_company_search_key
ON qcc_company_info (search_key)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_qcc_company_credit_code
ON qcc_company_info (credit_code)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_qcc_company_name
ON qcc_company_info (company_name)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_qcc_company_created_at
ON qcc_company_info (created_at)
WHERE deleted_at IS NULL;
COMMENT ON TABLE qcc_company_info IS '企查查企业主体信息缓存表';
COMMENT ON COLUMN qcc_company_info.search_key IS '原始查询关键词';
COMMENT ON COLUMN qcc_company_info.credit_code IS '统一社会信用代码';
COMMENT ON COLUMN qcc_company_info.company_name IS '企业名称';
COMMENT ON COLUMN qcc_company_info.enterprise IS '企查查工商信息原始响应 Result';
COMMENT ON COLUMN qcc_company_info.dishonesty IS '企查查失信核查原始响应 Result';
@@ -6,7 +6,7 @@
-- 3. 兼容历史库中 entry_modules 时间字段命名差异
--
-- 说明:
-- - 当前模块入口统一收口到 /govdoc/list,与模块路由 seed 保持一致。
-- - 当前模块入口统一收口到 /govdoc/audits,与前端真实列表路由保持一致。
-- - 若后续前端改成其他入口页,请同步更新本脚本中的 v_target_path。
-- - 页面访问权限与左侧菜单路由仍依赖 sys_routes / role_route
-- 这部分建议由配套脚本 seed_govdoc_routes.sql 单独维护。
@@ -18,7 +18,7 @@ DO $$
DECLARE
v_name text := '内部公文';
v_description text := '内部公文处理与格式审查入口';
v_target_path text := '/govdoc/list';
v_target_path text := '/govdoc/audits';
v_icon_path text := 'documents/mz/static/img/entry_module_3.png';
v_sort_order integer := 30;
v_areas jsonb := '[
+11 -11
View File
@@ -103,16 +103,16 @@ VALUES
NULL
),
(
'/govdoc/list',
'govdoc.list',
'govdoc.list',
'/govdoc/audits',
'govdoc.audits',
'govdoc.audits',
(SELECT id FROM root_route),
'公文列表',
'ri-file-list-3-line',
2,
FALSE,
TRUE,
'{"group":"govdoc","module":"govdoc","page":"list"}'::jsonb,
'{"group":"govdoc","module":"govdoc","page":"audits"}'::jsonb,
0,
NOW(),
NOW(),
@@ -187,7 +187,7 @@ SET
parent_id = root.id,
route_title = CASE
WHEN child.route_path = '/govdoc/upload' THEN '上传公文'
WHEN child.route_path = '/govdoc/list' THEN '公文列表'
WHEN child.route_path = '/govdoc/audits' THEN '公文列表'
WHEN child.route_path = '/govdoc/detail' THEN '公文详情'
WHEN child.route_path = '/govdoc/rules' THEN '规则配置'
WHEN child.route_path = '/govdoc/settings' THEN '模块配置'
@@ -204,7 +204,7 @@ WHERE child.deleted_at IS NULL
AND root.route_path = '/govdoc'
AND child.route_path IN (
'/govdoc/upload',
'/govdoc/list',
'/govdoc/audits',
'/govdoc/detail',
'/govdoc/rules',
'/govdoc/settings'
@@ -229,7 +229,7 @@ route_map AS (
AND route_path IN (
'/govdoc',
'/govdoc/upload',
'/govdoc/list',
'/govdoc/audits',
'/govdoc/detail',
'/govdoc/rules',
'/govdoc/settings'
@@ -239,27 +239,27 @@ seed(role_key, route_path, permission, status) AS (
VALUES
('super_admin', '/govdoc', 'RW', 1),
('super_admin', '/govdoc/upload', 'RW', 1),
('super_admin', '/govdoc/list', 'RW', 1),
('super_admin', '/govdoc/audits', 'RW', 1),
('super_admin', '/govdoc/detail', 'RW', 1),
('super_admin', '/govdoc/rules', 'RW', 1),
('super_admin', '/govdoc/settings', 'RW', 1),
('provincial_admin', '/govdoc', 'RW', 1),
('provincial_admin', '/govdoc/upload', 'RW', 1),
('provincial_admin', '/govdoc/list', 'RW', 1),
('provincial_admin', '/govdoc/audits', 'RW', 1),
('provincial_admin', '/govdoc/detail', 'RW', 1),
('provincial_admin', '/govdoc/rules', 'RW', 1),
('provincial_admin', '/govdoc/settings', 'RW', 1),
('admin', '/govdoc', 'RW', 1),
('admin', '/govdoc/upload', 'RW', 1),
('admin', '/govdoc/list', 'RW', 1),
('admin', '/govdoc/audits', 'RW', 1),
('admin', '/govdoc/detail', 'RW', 1),
('admin', '/govdoc/rules', 'R', 1),
('common', '/govdoc', 'R', 1),
('common', '/govdoc/upload', 'R', 1),
('common', '/govdoc/list', 'R', 1),
('common', '/govdoc/audits', 'R', 1),
('common', '/govdoc/detail', 'R', 1),
('common', '/govdoc/rules', 'R', 1)
)
+8 -21
View File
@@ -91,11 +91,6 @@ VALUES
('rules:binding_create:write', 'rules', 'binding_create', 'write', '创建规则绑定', '创建规则绑定', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 49, NULL, '/api/rule-sets/{rule_type}/bindings', 'POST', NULL),
('rules:binding_update:write', 'rules', 'binding_update', 'write', '更新规则绑定', '更新规则绑定', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 50, NULL, '/api/rule-sets/bindings/{binding_id}', 'PUT', NULL),
('rules:binding_delete:delete', 'rules', 'binding_delete', 'delete', '删除规则绑定', '删除规则绑定', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 51, NULL, '/api/rule-sets/bindings/{binding_id}', 'DELETE', NULL),
('evaluation_point:list:read', 'evaluation_point', 'list', 'read', '查看评查点列表', '评查点列表', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 52, NULL, '/api/v3/evaluation-points', 'GET', NULL),
('evaluation_point:detail:read', 'evaluation_point', 'detail', 'read', '查看评查点详情', '评查点详情', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 53, NULL, '/api/v3/evaluation-points/{id}', 'GET', NULL),
('evaluation_point:create:write', 'evaluation_point', 'create', 'write', '创建评查点', '创建评查点', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 54, NULL, '/api/v3/evaluation-points', 'POST', NULL),
('evaluation_point:update:write', 'evaluation_point', 'update', 'write', '更新评查点', '更新评查点', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 55, NULL, '/api/v3/evaluation-points/{id}', 'PUT', NULL),
('evaluation_point:delete:delete', 'evaluation_point', 'delete', 'delete', '删除评查点', '删除评查点', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 56, NULL, '/api/v3/evaluation-points/{id}', 'DELETE', NULL),
('cross_review:task:create', 'cross_review', 'task', 'create', '创建交叉评查任务', '创建交叉评查任务', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 57, NULL, '/api/v3/cross-review/tasks', 'POST', NULL),
('cross_review:task:read', 'cross_review', 'task', 'read', '查看交叉评查任务', '查看交叉评查任务', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 58, NULL, '/api/v3/cross-review/tasks/query', 'POST', NULL),
('cross_review:progress:view', 'cross_review', 'progress', 'view', '查看交叉评查任务进度', '查看交叉评查任务进度', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 59, NULL, '/api/v3/cross-review/tasks/{task_id}/progress', 'GET', NULL),
@@ -124,7 +119,9 @@ VALUES
('rag:conversation:delete', 'rag', 'conversation', 'delete', '删除 RAG 会话', '删除 RAG 会话', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 89, NULL, '/api/v3/rag/chat/conversations/{ConversationId}', 'DELETE', NULL),
('rag:message:feedback', 'rag', 'message', 'feedback', '反馈 RAG 消息', '反馈 RAG 消息', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 90, NULL, '/api/v3/rag/chat/messages/{MessageId}/feedback', 'POST', NULL),
('rag:dataset:read', 'rag', 'dataset', 'read', '查看 RAG 知识库', '查看 RAG 知识库', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 91, NULL, '/api/v3/rag/datasets/my', 'GET', NULL),
('document:document:update', 'document', 'document', 'update', '修改文档基本信息', '修改文档', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 92, NULL, '/api/documents/edit', 'POST', NULL)
('document:document:update', 'document', 'document', 'update', '修改文档基本信息', '修改文档', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 92, NULL, '/api/documents/edit', 'POST', NULL),
('qichacha:company:query', 'qichacha', 'company', 'query', '查询企业主体信息', '查询企业主体信息', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 93, NULL, '/api/v2/qichacha/company', 'POST', NULL),
('qichacha:status:read', 'qichacha', 'status', 'read', '查看企业主体缓存状态', '查看企业主体缓存状态', 'API', TRUE, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL, NULL, 94, NULL, '/api/v2/qichacha/status', 'GET', NULL)
ON CONFLICT (permission_key) DO UPDATE SET
module = EXCLUDED.module,
resource = EXCLUDED.resource,
@@ -245,11 +242,6 @@ seed(role_key, permission_key, grant_type, data_scope) AS (
('super_admin', 'rules:binding_create:write', 'GRANT', 'ALL'),
('super_admin', 'rules:binding_update:write', 'GRANT', 'ALL'),
('super_admin', 'rules:binding_delete:delete', 'GRANT', 'ALL'),
('super_admin', 'evaluation_point:list:read', 'GRANT', 'ALL'),
('super_admin', 'evaluation_point:detail:read', 'GRANT', 'ALL'),
('super_admin', 'evaluation_point:create:write', 'GRANT', 'ALL'),
('super_admin', 'evaluation_point:update:write', 'GRANT', 'ALL'),
('super_admin', 'evaluation_point:delete:delete', 'GRANT', 'ALL'),
('super_admin', 'cross_review:task:create', 'GRANT', 'ALL'),
('super_admin', 'cross_review:task:read', 'GRANT', 'ALL'),
('super_admin', 'cross_review:progress:view', 'GRANT', 'ALL'),
@@ -276,6 +268,8 @@ seed(role_key, permission_key, grant_type, data_scope) AS (
('super_admin', 'rbac:permissions:read', 'GRANT', 'ALL'),
('super_admin', 'rbac:role_permissions:write', 'GRANT', 'ALL'),
('super_admin', 'rbac:role_routes:write', 'GRANT', 'ALL'),
('super_admin', 'qichacha:company:query', 'GRANT', 'ALL'),
('super_admin', 'qichacha:status:read', 'GRANT', 'ALL'),
('provincial_admin', 'auth:me:read', 'GRANT', 'ALL'),
('provincial_admin', 'documents:upload:write', 'GRANT', 'ALL'),
@@ -298,11 +292,6 @@ seed(role_key, permission_key, grant_type, data_scope) AS (
('provincial_admin', 'rules:binding_create:write', 'GRANT', 'ALL'),
('provincial_admin', 'rules:binding_update:write', 'GRANT', 'ALL'),
('provincial_admin', 'rules:binding_delete:delete', 'GRANT', 'ALL'),
('provincial_admin', 'evaluation_point:list:read', 'GRANT', 'ALL'),
('provincial_admin', 'evaluation_point:detail:read', 'GRANT', 'ALL'),
('provincial_admin', 'evaluation_point:create:write', 'GRANT', 'ALL'),
('provincial_admin', 'evaluation_point:update:write', 'GRANT', 'ALL'),
('provincial_admin', 'evaluation_point:delete:delete', 'GRANT', 'ALL'),
('provincial_admin', 'cross_review:task:create', 'GRANT', 'ALL'),
('provincial_admin', 'cross_review:task:read', 'GRANT', 'ALL'),
('provincial_admin', 'cross_review:progress:view', 'GRANT', 'ALL'),
@@ -329,6 +318,8 @@ seed(role_key, permission_key, grant_type, data_scope) AS (
('provincial_admin', 'rbac:permissions:read', 'GRANT', 'ALL'),
('provincial_admin', 'rbac:role_permissions:write', 'GRANT', 'ALL'),
('provincial_admin', 'rbac:role_routes:write', 'GRANT', 'ALL'),
('provincial_admin', 'qichacha:company:query', 'GRANT', 'ALL'),
('provincial_admin', 'qichacha:status:read', 'GRANT', 'ALL'),
('admin', 'auth:me:read', 'GRANT', 'DEPT'),
('admin', 'documents:upload:write', 'GRANT', 'DEPT'),
@@ -360,11 +351,6 @@ seed(role_key, permission_key, grant_type, data_scope) AS (
('admin', 'cross_review:proposal:read', 'GRANT', 'DEPT'),
('admin', 'cross_review:proposal:delete', 'GRANT', 'DEPT'),
('admin', 'cross_review:proposal:vote', 'GRANT', 'DEPT'),
('admin', 'evaluation_point:list:read', 'GRANT', 'DEPT'),
('admin', 'evaluation_point:detail:read', 'GRANT', 'DEPT'),
('admin', 'evaluation_point:create:write', 'GRANT', 'DEPT'),
('admin', 'evaluation_point:update:write', 'GRANT', 'DEPT'),
('admin', 'evaluation_point:delete:delete', 'GRANT', 'DEPT'),
('admin', 'rag:app:read', 'GRANT', 'DEPT'),
('admin', 'rag:chat:use', 'GRANT', 'DEPT'),
('admin', 'rag:conversation:read', 'GRANT', 'DEPT'),
@@ -374,6 +360,7 @@ seed(role_key, permission_key, grant_type, data_scope) AS (
('admin', 'rag:dataset:read', 'GRANT', 'DEPT'),
('admin', 'users:list:read', 'GRANT', 'DEPT'),
('admin', 'users:update:write', 'GRANT', 'DEPT'),
('admin', 'qichacha:company:query', 'GRANT', 'DEPT'),
('common', 'auth:me:read', 'GRANT', 'SELF'),
('common', 'documents:upload:write', 'GRANT', 'SELF'),
@@ -0,0 +1,209 @@
-- 入口模块菜单模板、多租户可见性、文档归属巡检脚本
-- 用途:
-- 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;