# 入口模块菜单配置与多租户重构实施计划 > **给后续执行开发的人:** 按本文任务逐步实施,不要跳着改。本文只做入口模块、菜单、文档归属、多租户范围的重构计划,不直接改动运行时规则评查逻辑。 **目标:** 让“入口模块”真正成为用户能理解的业务工作台,菜单显示、文档过滤、上传范围、规则分组、租户可见性都从入口模块配置出发,不再依赖“名称里是否包含合同/公文”这种硬编码判断。 **总体架构:** 保留 `leaudit_entry_modules` 作为首页入口模块主表,保留现有 `leaudit_entry_module_tenants` 作为租户可见性关系表。在入口模块上增加“菜单模板字段”`menu_profile` 和“功能清单字段”`features`,用它们决定该入口模块显示哪些功能菜单。角色权限系统继续负责页面和接口权限,入口模块只负责“业务工作台编排”和“业务范围”。 **涉及技术:** 后端接口、数据库原生查询、数据库迁移脚本、前端页面、前端侧边栏、现有角色权限系统、现有租户识别逻辑。 --- ## 一、当前根因 现在系统的问题不是用户没配置好,而是代码在“猜业务类型”。 当前错误判断包括: - 合同菜单靠“入口模块名称里有没有合同两个字”判断。 - 公文菜单靠“入口模块名称里有没有公文两个字”或固定路径判断。 - 公文列表页面强行显示成“内部公文”。 - 公文列表页面虽然读取了浏览器地址里的 `entryModuleId`,但没有传给后端列表接口。 - 文档列表和规则页面部分支持 `entryModuleId`,但侧边栏菜单不认它。 - 租户可见性已经有 `leaudit_entry_module_tenants`,但菜单功能没有和入口模块、租户上下文形成闭环。 目标链路应该变成: ```text 当前用户租户 -> 可见入口模块 -> 用户选择入口模块 入口模块 -> 菜单模板字段/功能清单字段 -> 生成左侧菜单 入口模块 -> 文档类型 -> 上传/列表/规则分组范围 文档 -> entry_module_id + type_id + group_id + tenant_code -> 后续评查/统计/列表过滤 ``` ## 二、用户应该怎么理解 管理员配置入口模块时,只需要按业务语言操作: 1. 新建入口模块,例如 `入口模块-测试`。 2. 选择菜单模板:合同工作台、公文工作台、交叉评查工作台、通用文档评查、自定义。 3. 勾选功能:文档列表、文件上传、规则配置、规则分组、模板搜索、模板列表、公文列表、公文上传等。 4. 分配租户:云浮、揭阳、梅州、公共资源域等。 5. 绑定一级文档类型。 6. 在规则分组里配置二级运行子类型和规则绑定。 用户不应该被迫把入口模块命名为“合同xxx”或“公文xxx”才能显示对应功能。 ## 三、核心数据模型 ### 3.1 保留现有表 继续使用: - `leaudit_entry_modules`:入口模块主表,表示首页业务入口。 - `leaudit_entry_module_tenants`:入口模块和租户的可见关系。 - `leaudit_document_types`:文档类型,通过 `entry_module_id` 归属入口模块。 - `leaudit_evaluation_point_groups`:规则分组树。 - `leaudit_rule_group_bindings`:运行时规则绑定唯一事实源。 - `leaudit_documents`:文档记录,后续需要补 `entry_module_id`。 ### 3.2 入口模块新增字段 给 `leaudit_entry_modules` 增加: ```sql 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; CREATE INDEX IF NOT EXISTS idx_leaudit_entry_modules_menu_profile ON leaudit_entry_modules(menu_profile) WHERE deleted_at IS NULL; ``` 字段解释: - `menu_profile`:菜单模板字段,比如合同工作台、公文工作台、通用文档评查。 - `features`:功能清单字段,表示该入口模块启用哪些功能菜单。 ### 3.3 文档表新增字段 给 `leaudit_documents` 增加: ```sql 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_documents_entry_module_id ON leaudit_documents(entry_module_id); ``` 作用: - 文档创建后明确知道自己来自哪个入口模块。 - 文档列表、公文列表、统计、后续质量校验都可以按入口模块过滤。 ## 四、菜单模板与功能编码 ### 4.1 菜单模板 固定使用这些编码: ```text document_review 通用文档评查 contract 合同工作台 govdoc 内部公文工作台 cross_checking 交叉评查工作台 custom 自定义工作台 ``` ### 4.2 功能编码 固定使用这些编码: ```text 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 使用统计 ``` ### 4.3 默认功能 如果老数据没有配置功能清单字段,就按菜单模板字段自动补默认值: ```json { "document_review": ["home", "documents", "upload", "rules", "rule_groups"], "contract": ["home", "documents", "upload", "rules", "contract_template_search", "contract_template_list"], "govdoc": ["home", "govdoc_audits", "govdoc_upload", "rule_groups"], "cross_checking": ["cross_checking", "cross_checking_upload", "cross_checking_list"], "custom": ["home", "documents"] } ``` ## 五、多租户规则 这里必须沿用你当前项目已有模型,不新造租户体系。 ### 5.1 入口模块可见性 入口模块是否对某个租户可见,继续由 `leaudit_entry_module_tenants` 控制。 判断规则: ```text 用户 tenant_code 命中 leaudit_entry_module_tenants.tenant_code 并且 leaudit_entry_module_tenants.is_enabled = true 并且 leaudit_entry_modules.is_enabled = true 并且入口模块没有 deleted_at ``` 特殊情况: - `PUBLIC` 可以作为公共入口模块。 - 超级管理员或全局管理员可以跨租户查看。 - 旧的地区字段暂时保留兼容,但不再作为新逻辑主模型。 ### 5.2 功能菜单是否需要租户级差异 第一阶段不做“同一个入口模块不同租户显示不同功能”。 第一阶段规则: ```text 入口模块功能清单对所有可见租户一致 租户只控制入口模块是否可见 ``` 如果后续真有需求,例如同一个入口模块云浮显示模板搜索、梅州不显示模板搜索,再扩展: ```sql ALTER TABLE leaudit_entry_module_tenants ADD COLUMN IF NOT EXISTS features_override JSONB NULL, ADD COLUMN IF NOT EXISTS menu_profile_override VARCHAR(64) NULL; ``` 最终生效逻辑: ```text 租户关系表 features_override 有值 -> 用租户覆盖配置 否则入口模块 features 有值 -> 用入口模块配置 否则按 menu_profile 默认功能生成 ``` 第一阶段先不要上这个复杂度。 ### 5.3 入口模块配置权限 入口模块是系统级业务工作台配置,但不要在代码里硬编码“只有某个角色名能配置”。正确口径是:谁被分配了入口模块管理权限,谁才能配置入口模块。 也就是说: ```text 不要写死 role_key = super_admin 不要写死 role_key = provincial_admin 不要写死市区管理员一定不能操作 ``` 后端只认权限点: ```text entry_module:list:read 查看入口模块列表 entry_module:detail:read 查看入口模块详情 entry_module:create:write 创建入口模块 entry_module:update:write 编辑入口模块 entry_module:delete:delete 删除入口模块 entry_module:image:write 上传或替换入口模块图标 ``` 推荐默认分配策略: ```text 系统超级管理员角色:默认分配入口模块全部管理权限 省级管理员角色:默认不分配创建/编辑/删除入口模块权限,是否开放由实际授权决定 市区管理员角色:默认不分配创建/编辑/删除入口模块权限,是否开放由实际授权决定 ``` 所以最终判断不是“这个人是不是超级管理员”,而是: ```text 能不能创建入口模块 -> 看有没有 entry_module:create:write 能不能编辑入口模块 -> 看有没有 entry_module:update:write 能不能删除入口模块 -> 看有没有 entry_module:delete:delete 能不能上传图标 -> 看有没有 entry_module:image:write ``` 三层边界必须分清: ```text RBAC 权限点:控制谁能配置入口模块 入口模块租户关系:控制哪个租户能看到/使用入口模块 入口模块 features + 页面/API 权限:控制进入入口后能看到和使用哪些功能 ``` 举例: ```text 某个市区管理员没有 entry_module:create:write 即使他属于云浮租户,也不能新建入口模块 某个市区管理员有 entry_module:create:write 他就可以进入入口模块管理页执行创建动作 但创建出来的入口能被哪些租户看到,仍然必须写入 leaudit_entry_module_tenants ``` ## 六、一级分组、二级分组、规则绑定设计 这一块是本次重构最关键的业务边界。入口模块只解决“用户从哪个工作台进来”,规则分组解决“这个工作台下有哪些业务类型”,规则绑定解决“这个业务类型运行哪套规则”。 ### 6.1 最终业务口径 固定使用两层分组,不继续发散: ```text 入口模块 -> 一级分组:业务大类 -> 二级分组:具体运行类型 -> 规则绑定:绑定规则集 ``` 举例: ```text 入口模块:合同评查 一级分组:合同 二级分组:建设工程合同、买卖合同、租赁合同 规则绑定:建设工程合同 -> 建设工程合同规则集 ``` 再举例: ```text 入口模块:内部公文 一级分组:内部公文 二级分组:请示、通知、会议纪要 规则绑定:请示 -> 请示规则集 ``` ### 6.2 一级分组职责 一级分组只表达业务大类,不直接参与运行时规则命中。 数据库仍然复用: ```text leaudit_evaluation_point_groups ``` 一级分组判断: ```text pid = 0 ``` 一级分组字段规则: ```text entry_module_id 必填,表示这个一级分组属于哪个入口模块 document_type_id 为空,不直接绑定具体文档类型 name 使用业务大类名称,例如 合同、内部公文、行政卷宗 ``` 也就是说,入口模块页面里的“规则分组”应该先看到这个入口模块下的一级分组,而不是全系统所有分组。 ### 6.3 二级分组职责 二级分组才是实际运行类型,也就是用户上传、列表过滤、评查执行真正要落的 `groupId`。 二级分组判断: ```text pid != 0 ``` 二级分组字段规则: ```text pid 指向一级分组 id document_type_id 必填,表示这个二级分组对应哪个具体文档类型 entry_module_id 可以冗余保存,也可以从一级分组继承,但查询时必须能按入口模块过滤 ``` 前端显示时,用户理解成: ```text 一级分组 = 大类 二级分组 = 具体要评查的文档类型/运行类型 ``` 后端理解成: ```text 文档最终必须落到二级分组 group_id ``` ### 6.4 规则绑定职责 规则绑定只允许挂二级分组,不允许挂一级分组。 继续使用: ```text leaudit_rule_group_bindings ``` 绑定关系: ```text leaudit_rule_group_bindings.group_id = 二级分组 id leaudit_rule_group_bindings.rule_set_id = 规则集 id ``` 禁止口径: ```text 不要把规则集挂在一级分组 不要再按入口模块名称猜规则集 不要再绕回旧的文档类型规则绑定表作为主链路 ``` 运行时唯一主链路: ```text document.group_id -> leaudit_rule_group_bindings.group_id -> rule_set_id -> 当前可执行规则版本 ``` ### 6.5 多租户下规则怎么生效 分组树本身不建议第一阶段按租户复制一份。否则云浮、梅州、揭阳每个租户一套树,后面维护会爆炸。 第一阶段建议: ```text 入口模块租户关系:控制哪个租户看得到哪个入口模块 一级/二级分组:表达入口模块下的业务结构 规则绑定 tenant_code/scope_type:控制不同租户实际运行哪套规则集 ``` 也就是说: ```text 同一个二级分组,可以存在多条规则绑定 ``` 例如: ```text 二级分组:建设工程合同 云浮租户 -> 云浮建设工程合同规则集 梅州租户 -> 梅州建设工程合同规则集 省级兜底 -> 通用建设工程合同规则集 ``` 运行时选择顺序: ```text 优先当前租户绑定 没有当前租户绑定,再走省级/公共兜底绑定 仍然没有,就提示该二级分组未配置有效规则 ``` ### 6.6 上传、列表、评查的字段落点 上传时必须落这些字段: ```text entry_module_id:从哪个入口进来 type_id:具体文档类型 group_id:二级分组 id tenant_code:当前租户 ``` 如果上传时只传了文档类型,没有传二级分组,后端只能在“该文档类型只有一个可用二级分组”时兜底推断。只要有多个二级分组,就必须让用户明确选择,否则规则命中会不稳定。 列表过滤顺序: ```text 先按 tenant_code 做租户边界 再按 entry_module_id 做入口模块范围 再按 group_id/type_id 做具体业务过滤 ``` 评查执行顺序: ```text 读取文档 group_id 确认 group_id 是二级分组 按 group_id + tenant_code 找有效规则绑定 取 rule_set 当前可执行版本 执行评查 ``` ### 6.7 后台页面怎么改 入口模块管理页: ```text 只负责入口模块本身、菜单功能、租户可见性 不在这里直接绑规则集 ``` 文档类型管理页: ```text 负责维护具体文档类型属于哪个入口模块 可以辅助展示它被哪个二级分组使用 ``` 规则分组页: ```text 进入某入口模块后,只显示该入口模块下的一级分组 一级分组下面维护二级分组 二级分组上配置规则绑定 ``` 规则绑定页或弹窗: ```text 只能从二级分组进入 可以配置当前租户绑定、省级兜底绑定、公共绑定 保存到 leaudit_rule_group_bindings ``` ### 6.8 必须加的校验 后端保存规则分组时必须校验: ```text 一级分组 pid = 0 时,必须有 entry_module_id 一级分组不允许绑定 rule_set 二级分组 pid != 0 时,必须有 document_type_id 二级分组所属一级分组必须和当前入口模块一致 二级分组才允许写 leaudit_rule_group_bindings 上传文档时传入的 group_id 必须是二级分组 上传文档时 group_id 对应的入口模块必须等于 entry_module_id ``` 建议补充数据库约束或唯一索引: ```sql 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; ``` 作用是避免同一个一级分组下面重复挂同一个文档类型,导致上传时无法稳定推断二级分组。 ### 6.9 旧数据兼容策略 当前系统里可能还有旧结构: ```text 一级分组 = 具体文档类型 二级分组 = 通用 ``` 这个是过渡结构,不要继续加深。 兼容策略: ```text 短期:后端查询同时兼容旧结构,避免线上功能直接断 中期:迁移成 一级=业务大类、二级=具体文档类型 长期:规则绑定只认二级分组,旧结构下线 ``` 迁移示例: ```text 旧: 一级:建设工程合同 二级:通用 规则绑定:通用 -> 规则集 新: 一级:合同 二级:建设工程合同 规则绑定:建设工程合同 -> 原规则集 ``` 这一段迁移可以参考已有脚本: ```text scripts/创建sql/migrate_rule_groups_to_business_roots.sql scripts/创建sql/precheck_rule_group_migration.sql ``` ## 七、后端改造范围 ### 7.1 后端请求对象 文件: ```text fastapi_modules/fastapi_leaudit/domian/Dto/entryModuleDto.py ``` 给 `EntryModuleCreateDTO` 和 `EntryModuleUpdateDTO` 增加: ```python menu_profile: str | None = Field(None, description="菜单模板:document_review/contract/govdoc/cross_checking/custom") features: list[str] | None = Field(None, description="启用功能编码列表") ``` ### 7.2 后端返回对象 文件: ```text fastapi_modules/fastapi_leaudit/domian/vo/entryModuleAdminVo.py fastapi_modules/fastapi_leaudit/domian/vo/homeVo.py ``` 入口模块管理返回对象增加: ```python menu_profile: str = Field("document_review", description="菜单模板") features: list[str] = Field(default_factory=list, description="启用功能编码列表") business_scope: EntryModuleBusinessScopeVO = Field(default_factory=EntryModuleBusinessScopeVO, description="业务范围摘要") ``` 首页入口返回对象增加: ```python menuProfile: str = Field("document_review", description="菜单模板") features: list[str] = Field(default_factory=list, description="启用功能编码列表") tenantCode: str | None = Field(None, description="当前命中的租户编码") ``` ### 7.3 入口模块管理服务 文件: ```text fastapi_modules/fastapi_leaudit/services/impl/entryModuleAdminServiceImpl.py ``` 要做: 1. 确保菜单模板字段和功能清单字段存在。 2. 创建入口模块时保存菜单模板和功能清单。 3. 更新入口模块时保存菜单模板和功能清单。 4. 查询列表和详情时返回这两个字段。 5. 查询列表和详情时返回业务范围摘要 `business_scope`,用于前端展示已绑定业务大类和业务类型数量。 6. 校验菜单模板和功能清单只能使用允许的编码。 7. 继续沿用现有入口模块权限点做接口控制,不要额外硬编码角色名。 8. 如果当前用户没有对应权限点,后端直接返回 403,不能只靠前端隐藏按钮。 允许值: ```python _ALLOWED_MENU_PROFILES = {"document_review", "contract", "govdoc", "cross_checking", "custom"} _ALLOWED_FEATURES = { "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", } ``` ### 7.4 首页入口服务 文件: ```text fastapi_modules/fastapi_leaudit/services/impl/homeServiceImpl.py ``` 要做: 1. 保持现有 `leaudit_entry_module_tenants` 过滤逻辑不变。 2. 查询入口模块时带出菜单模板和功能清单。 3. 返回给首页,前端点击入口时存起来。 4. 返回当前命中的租户编码,方便前端继续传递上下文。 ### 7.5 文档服务 文件: ```text fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py fastapi_modules/fastapi_leaudit/controllers/documentController.py ``` 要做: 1. 上传接口接收 `entryModuleId`。 2. 创建文档时写入 `entry_module_id`。 3. 如果前端没传,则按: ```text 二级分组 entry_module_id -> 一级分组 entry_module_id -> 文档类型 entry_module_id ``` 兜底解析。 4. 文档列表过滤时使用: ```sql COALESCE(d.entry_module_id, eg.entry_module_id, eg_parent.entry_module_id, dt.entry_module_id) = :entry_module_id ``` ### 7.6 公文服务 文件: ```text fastapi_modules/fastapi_leaudit/controllers/govdocController.py fastapi_modules/fastapi_leaudit/services/impl/govdocServiceImpl.py ``` 要做: 1. `/api/govdoc/documents` 增加 `entry_module_id` 查询参数。 2. `GovdocServiceImpl.ListDocuments` 增加 `EntryModuleId` 参数。 3. 查询时按: ```sql COALESCE(d.entry_module_id, dt.entry_module_id) = :entry_module_id ``` 过滤。 4. 继续保留现有: ```sql COALESCE(d.engine_type, 'leaudit') = 'govdoc' ``` 也就是说: ```text 公文列表 = 文档引擎类型是公文 + 当前入口模块范围 ``` ### 7.7 规则分组服务 文件: ```text fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py fastapi_modules/fastapi_leaudit/services/impl/ruleConfigServiceImpl.py ``` 要做: 1. 规则组列表按 `entry_module_id` 过滤。 2. 过滤表达式统一用: ```sql COALESCE(g.entry_module_id, parent.entry_module_id, dt.entry_module_id) ``` 3. 入口模块只决定“用户看到哪些规则组”,不参与运行时选规则。 4. 运行时选规则继续保持: ```text document.group_id -> leaudit_rule_group_bindings -> rule_set/rule_version ``` ## 八、前端改造范围 ### 8.0 前端 UI 风格约束 本次前端改造必须和当前系统 UI 设计、样式、配色保持统一,不允许为了新功能另起一套视觉风格。 统一要求: ```text 继续使用当前系统已有的 Card、Button、Table、FilterPanel、FilterSelect、SearchFilter、Pagination、Modal 等组件 继续使用当前页面已有的 CSS 变量和主题色 继续沿用现有后台管理页的布局、间距、圆角、阴影、表格操作列样式 继续沿用现有 toast、loading、empty state、403 提示样式 新增表单项要和当前入口模块编辑页的表单布局一致 新增功能勾选区要像现有权限/配置类页面一样清晰,不做花哨卡片风格 ``` 禁止: ```text 不要新增一套独立主题色 不要引入新的 UI 框架 不要写和当前后台风格不一致的大面积渐变、特殊字体、复杂动效 不要把入口模块管理页改成和系统其他管理页完全不同的视觉 不要为了功能勾选单独设计一套复杂组件 ``` 具体页面要求: ```text 入口模块列表页:沿用当前表格、筛选、操作按钮风格 入口模块编辑页:沿用当前表单分组风格,只增加菜单模板和功能勾选 侧边栏:沿用当前菜单高亮、图标、折叠、权限过滤表现 规则分组页:沿用当前树/列表/弹窗样式,不重做视觉 上传页:只补入口模块上下文和二级分组选择,不重做上传区视觉 公文列表页:只改标题和过滤参数,不重做列表视觉 ``` 如果必须新增样式,优先放在现有对应页面样式文件里,并复用当前 CSS 变量,例如: ```text var(--color-primary-text) var(--color-primary-text-muted) var(--color-border) var(--color-surface) ``` ### 8.1 入口模块前端接口类型 文件: ```text legal-platform-frontend/lib/api/legacy/entry-modules/entry-modules.ts legal-platform-frontend/lib/api/legacy/entry-modules/request-body.ts ``` 增加类型: ```ts export type EntryModuleMenuProfile = "document_review" | "contract" | "govdoc" | "cross_checking" | "custom"; export type EntryModuleFeature = | "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"; ``` 入口模块类型增加: ```ts menu_profile?: EntryModuleMenuProfile | string; features?: EntryModuleFeature[] | string[]; business_scope?: { category_count: number; business_type_count: number; categories: string[]; } | null; ``` 请求体增加: ```ts menu_profile?: string | null; features?: string[]; ``` ### 8.2 入口模块编辑页 文件: ```text legal-platform-frontend/app/(audit)/entry-modules/new/EntryModuleNewClient.tsx ``` 页面增加两个配置区: 1. 菜单模板选择: ```text 通用文档评查 合同工作台 内部公文工作台 自定义工作台 ``` 2. 功能勾选: ```text 文档列表 文件上传 规则配置 规则分组 模板搜索 模板列表 公文列表 公文上传 交叉评查 创建交叉评查任务 交叉评查任务列表 使用统计 ``` 用户选择菜单模板后,自动带出默认功能,用户可以再调整。 权限显示规则: ```text 没有 entry_module:create:write:隐藏新建入口模块按钮 没有 entry_module:update:write:隐藏或禁用保存修改按钮 没有 entry_module:delete:delete:隐藏删除按钮 没有 entry_module:image:write:隐藏或禁用上传图标能力 ``` 注意:前端隐藏只是体验优化,最终以后端权限点校验为准。 ### 8.3 首页点击入口 文件: ```text legal-platform-frontend/app/page.tsx ``` 点击入口模块时,除了原来的 `selectedModuleId/selectedModuleName`,还要保存: ```ts { id, name, targetPath, menuProfile, features, tenantCode, documentTypeIds, iconPath } ``` 建议统一封装到新文件: ```text legal-platform-frontend/lib/auth/entry-module-context.ts ``` 同时 URL 必须带: ```text entryModuleId=xxx documentTypeIds=1,2,3 ``` 不能只依赖 sessionStorage,否则刷新和复制链接会丢上下文。 ### 8.4 侧边栏菜单 文件: ```text legal-platform-frontend/components/layout/Sidebar.tsx legal-platform-frontend/lib/auth/entry-module-menu.ts ``` 新增 `entry-module-menu.ts`,把功能编码转成菜单: ```ts documents -> 文档列表 /documents upload -> 文件上传 /files/upload rules -> 规则配置 /rules-test/list rule_groups -> 规则分组 /rule-groups contract_template_search -> 模板搜索 /contract-template/search contract_template_list -> 模板列表 /contract-template/list govdoc_audits -> 公文列表 /govdoc/audits govdoc_upload -> 公文上传 /govdoc/upload cross_checking -> 交叉评查 /cross-checking cross_checking_upload -> 创建任务 /cross-checking/upload cross_checking_list -> 评查任务列表 /cross-checking/list ``` 侧边栏必须删除这些判断: ```ts selectedModuleName.includes("合同") selectedModuleName.includes("公文") shouldUseGovdocAuditMenu(effectiveSelectedModuleName) ``` 改成: ```text 读取当前入口模块功能清单 功能清单决定显示哪些菜单 角色权限系统再过滤用户没权限的菜单 ``` 重点: ```text 功能清单不能绕过角色权限系统 ``` 如果入口模块启用了“模板列表”,但用户角色没有模板列表页面权限,仍然不能显示或访问。 入口模块管理菜单本身也要按权限点显示: ```text 有 entry_module:list:read 才显示入口模块管理菜单 没有 entry_module:list:read 即使知道路径也不能正常加载数据 ``` ### 8.5 公文列表页面 文件: ```text legal-platform-frontend/components/govdoc-audit/audits.tsx legal-platform-frontend/lib/api/govdoc-audit/api.ts ``` 要做: 1. 从 URL 或入口模块上下文读取 `entryModuleId`。 2. 调用 `api.listAudits()` 时传入 `entryModuleId`。 3. `api.listAudits()` 转成后端参数: ```text entry_module_id=xxx ``` 4. 页面标题/侧边栏显示当前入口模块名,不再写死“内部公文”。 ### 8.6 上传页面 文件: ```text legal-platform-frontend/app/(audit)/files/upload/FilesUploadClient.tsx ``` 要做: 1. 上传时读取当前入口模块上下文。 2. 上传表单数据增加: ```text entryModuleId ``` 3. 继续保留: ```text typeId groupId tenantCode ``` 上传后的文档必须具备完整归属: ```text entry_module_id + type_id + group_id + tenant_code ``` ## 九、SQL 迁移脚本 建议新增: ```text scripts/创建sql/entry_module_menu_profile_migration.sql ``` 内容: ```sql 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; ``` 旧数据回填: ```sql 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 = '[]'::jsonb THEN '["home","documents","upload","rules","rule_groups"]'::jsonb ELSE features END WHERE deleted_at IS NULL; ``` 文档归属回填: ```sql 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; ``` 验证脚本: ```sql 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; ``` ## 十、实施顺序 ### 第一阶段:先修用户能看到的问题 目标: ```text 入口模块名字不含合同/公文,也能按配置显示正确菜单 ``` 任务: 1. 后端入口模块请求对象和返回对象增加菜单模板、功能清单。 2. 入口模块管理页支持菜单模板和功能勾选。 3. 首页返回入口模块时带菜单模板、功能清单。 4. 首页点击入口时保存完整上下文。 5. 侧边栏改成按功能清单生成菜单。 6. 删除侧边栏按名称包含“合同/公文”的判断。 ### 第二阶段:列表和上传真正按入口过滤 目标: ```text 进入哪个入口模块,就只看这个入口模块下的文档和公文 ``` 任务: 1. 文档上传保存 `entry_module_id`。 2. 普通文档列表按 `entry_module_id` 过滤。 3. 公文列表按 `entry_module_id` 过滤。 4. 公文列表前端传 `entryModuleId`。 5. 页面刷新后仍能从 URL 恢复入口上下文。 ### 第三阶段:规则分组范围统一 目标: ```text 规则分组页面只显示当前入口模块相关规则组 ``` 任务: 1. 规则分组列表支持 `entry_module_id`。 2. 前端规则分组页面从 URL/session 读取当前入口。 3. 规则组创建时继承当前入口模块。 4. 二级分组绑定具体文档类型。 5. 规则绑定仍然只挂二级分组。 6. 上传和评查接口校验 `group_id` 必须是二级分组。 7. 规则绑定保存接口拒绝一级分组。 ### 第四阶段:旧逻辑下线 目标: ```text 不再靠名字和路径猜业务类型 ``` 任务: 1. 删除 `includes("合同")` 决定合同菜单的逻辑。 2. 删除 `includes("公文")` 决定公文菜单的逻辑。 3. 删除 `/govdoc` 强制显示“内部公文”的逻辑。 4. 保留路径字段只作为跳转地址。 5. 旧地区字段保留兼容,但新写入只走租户关系。 ## 十一、验收标准 ### 11.1 合同入口验收 配置: ```text 入口模块名称:入口模块-测试 menu_profile:contract features:contract_template_search, contract_template_list 租户:云浮 ``` 预期: ```text 云浮用户首页能看到该入口 点击后左侧显示模板搜索、模板列表 入口名字不包含“合同”也正常显示 其他未绑定租户看不到该入口 没有角色权限的用户仍然不能访问模板页面 ``` ### 11.2 公文入口验收 配置: ```text 入口模块名称:入口模块-测试 route_path:/govdoc/audits menu_profile:govdoc features:govdoc_audits 租户:梅州 ``` 预期: ```text 梅州用户首页能看到该入口 点击进入 /govdoc/audits 页面显示入口模块-测试,而不是固定显示内部公文 公文列表请求带 entry_module_id 列表只显示该入口模块范围内的公文 ``` ### 11.3 文档列表验收 配置: ```text 入口 A 绑定文档类型 A 入口 B 绑定文档类型 B 同一个租户下分别上传文档 ``` 预期: ```text 从入口 A 进入只看到 A 的文档 从入口 B 进入只看到 B 的文档 刷新页面后过滤条件不丢 ``` ### 11.3.1 交叉评查入口验收 配置: ```text 入口模块名称:入口模块-测试交叉评查 route_path:/cross-checking menu_profile:cross_checking features:cross_checking, cross_checking_upload, cross_checking_list 租户:目标租户或 PUBLIC ``` 预期: ```text 目标租户首页能看到该入口 点击进入 /cross-checking 侧边栏显示交叉评查、创建任务、评查任务列表 没有交叉评查路由权限的用户即使入口模块启用交叉评查功能,也不能看到或访问交叉评查菜单 ``` ### 11.4 多租户验收 配置: ```text 入口模块 X 只绑定云浮 入口模块 Y 只绑定梅州 入口模块 Z 绑定 PUBLIC ``` 预期: ```text 云浮用户看到 X 和 Z 梅州用户看到 Y 和 Z 普通用户看不到其他租户入口 超级管理员可以跨租户管理 角色权限仍然控制页面和接口能不能访问 ``` ### 11.5 规则分组和规则绑定验收 配置: ```text 入口模块:合同评查 一级分组:合同 二级分组:建设工程合同 规则绑定:建设工程合同 -> 建设工程合同规则集 租户:云浮 ``` 预期: ```text 从合同评查入口进入,只看到合同一级分组 建设工程合同显示在合同一级分组下面 规则绑定只能在建设工程合同这个二级分组上配置 一级分组合同不能直接绑定规则集 上传建设工程合同后,文档 group_id 等于建设工程合同二级分组 id 评查时按 group_id + tenant_code 命中云浮对应规则集 ``` ### 11.6 入口模块配置权限验收 配置: ```text 角色 A 分配 entry_module:list:read 角色 A 不分配 entry_module:create:write / entry_module:update:write / entry_module:delete:delete 角色 B 分配 entry_module:list:read / entry_module:create:write / entry_module:update:write 角色 B 不分配 entry_module:delete:delete ``` 预期: ```text 角色 A 可以查看入口模块列表 角色 A 看不到新建、编辑保存、删除入口模块能力 角色 A 直接调用创建/编辑/删除接口返回 403 角色 B 可以查看入口模块列表 角色 B 可以创建和编辑入口模块 角色 B 不能删除入口模块 角色 B 直接调用删除接口返回 403 ``` 关键验收点: ```text 系统不通过 role_key 硬编码判断谁能配置入口模块 入口模块配置权限完全由 RBAC 权限点分配决定 入口模块租户关系只控制可见和使用,不控制谁能配置 ``` ## 十二、非目标 这次重构不做: - 不删除角色权限系统。 - 不删除 `leaudit_rule_type_bindings`。 - 不改变“文档二级分组 -> 规则绑定 -> 规则集版本”的运行时主链路。 - 不把入口模块变成权限系统。 - 不第一阶段支持“同一入口不同租户不同菜单”。 - 不硬编码某个角色名才能配置入口模块,入口模块管理能力由权限点分配决定。 运行时规则仍然保持: ```text document.group_id -> leaudit_rule_group_bindings -> rule_set/rule_version ``` 入口模块只负责: ```text 用户从哪里进来 左侧显示哪些菜单 文档/规则/上传属于哪个业务范围 ``` ## 十三、风险控制 1. `menu_profile/features` 设置默认值,保证老入口模块不崩。 2. 旧地区字段暂时保留兼容,避免历史数据立即失效。 3. 功能清单生成菜单后还要经过角色权限过滤,不能绕过权限。 4. 浏览器地址参数必须带 `entryModuleId`,不能只靠浏览器临时缓存。 5. 文档上传必须保存 `entry_module_id/type_id/group_id/tenant_code`,后续列表和统计才稳定。 6. 第一阶段先解决菜单和入口体验,规则运行链路不乱动。 7. 一级分组只做业务大类,二级分组才做运行类型,规则绑定只能挂二级分组。 8. 入口模块管理不能靠前端隐藏按钮保证安全,后端必须按权限点返回 403。 ## 十四、Superpowers 执行任务清单 本节是给后续开发直接执行的任务清单。执行时必须从上到下推进,每完成一个小任务就验证一次,不要一次性大改完再排错。 ### 14.1 执行原则 - [x] 先做数据库和后端返回字段,再做前端展示。 - [x] 先解决入口模块菜单显示错误,再处理列表和上传过滤。 - [x] 不改运行时评查主链路,只补入口模块、分组、文档归属。 - [x] 不硬编码角色名判断入口模块管理权限,只使用 RBAC 权限点。 - [x] 每一步都保留旧数据兼容,避免老入口模块、老文档、旧规则分组直接失效。 ### 14.2 数据库任务 - [x] 新增迁移脚本 `scripts/创建sql/entry_module_menu_profile_migration.sql`。 - [x] 给 `leaudit_entry_modules` 增加 `menu_profile` 字段。 - [x] 给 `leaudit_entry_modules` 增加 `features` 字段。 - [x] 给 `leaudit_documents` 增加 `entry_module_id` 字段。 - [x] 给 `leaudit_documents.entry_module_id` 增加索引。 - [x] 给 `leaudit_entry_modules.menu_profile` 增加索引。 - [x] 给 `leaudit_evaluation_point_groups(pid, document_type_id)` 增加二级分组唯一索引。 - [x] 回填老入口模块的菜单模板和功能清单。 - [x] 尝试按二级分组、一级分组、文档类型回填历史文档的 `entry_module_id`。 - [x] 写验证 SQL,检查入口模块字段、文档归属、重复二级分组。 ### 14.3 后端入口模块任务 - [x] 修改 `fastapi_modules/fastapi_leaudit/domian/Dto/entryModuleDto.py`,创建和更新请求增加 `menu_profile/features`。 - [x] 修改 `fastapi_modules/fastapi_leaudit/domian/vo/entryModuleAdminVo.py`,入口模块管理返回增加 `menu_profile/features`。 - [x] 修改 `entryModuleAdminVo.py` 和 `entryModuleAdminServiceImpl.py`,入口模块列表和详情返回 `business_scope` 业务范围摘要。 - [x] 修改 `fastapi_modules/fastapi_leaudit/domian/vo/homeVo.py`,首页入口返回增加 `menuProfile/features/tenantCode`。 - [x] 修改 `fastapi_modules/fastapi_leaudit/services/impl/entryModuleAdminServiceImpl.py`,创建入口模块时保存菜单模板和功能清单。 - [x] 修改 `entryModuleAdminServiceImpl.py`,更新入口模块时保存菜单模板和功能清单。 - [x] 修改 `entryModuleAdminServiceImpl.py`,列表和详情查询返回菜单模板和功能清单。 - [x] 修改 `entryModuleAdminServiceImpl.py`,列表和详情查询返回已绑定业务大类数量、业务类型数量和大类名称列表。 - [x] 在 `entryModuleAdminServiceImpl.py` 增加菜单模板、功能编码白名单校验。 - [x] 保留 `entryModuleController.py` 现有权限点校验,不新增角色名硬编码。 - [x] 确认没有权限点时,创建、编辑、删除、上传图标接口都返回 403。 ### 14.4 后端首页和入口上下文任务 - [x] 修改 `fastapi_modules/fastapi_leaudit/services/impl/homeServiceImpl.py`,首页入口查询带出 `menu_profile/features`。 - [x] 首页入口仍按 `leaudit_entry_module_tenants` 过滤当前租户可见入口。 - [x] 首页入口返回当前命中的租户编码。 - [x] 首页入口返回文档类型范围,方便前端拼接上下文。 - [x] 保持 `PUBLIC` 入口模块兼容。 - [x] 保持老地区字段兼容,但新链路以租户关系为准。 ### 14.5 后端文档和公文任务 - [x] 修改文档上传接口,接收 `entryModuleId`。 - [x] 修改 `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py`,创建文档时写入 `entry_module_id`。 - [x] 上传时校验 `group_id` 是二级分组。 - [x] 上传时校验 `group_id` 所属入口模块与 `entryModuleId` 一致。 - [x] 当前端没传 `group_id` 时,只允许在唯一可用二级分组场景下兜底推断。 - [x] 普通文档列表支持按 `entry_module_id` 过滤。 - [x] 修改 `fastapi_modules/fastapi_leaudit/controllers/govdocController.py`,公文列表接收 `entry_module_id`。 - [x] 修改 `fastapi_modules/fastapi_leaudit/services/impl/govdocServiceImpl.py`,公文列表按 `entry_module_id` 过滤。 - [x] 公文列表继续保留公文引擎类型过滤。 ### 14.6 后端规则分组和规则绑定任务 - [x] 修改 `fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py`,规则分组列表支持按入口模块过滤。 - [ ] 一级分组创建时必须有 `entry_module_id`。 - [x] 一级分组不允许绑定规则集。 - [x] 二级分组创建时必须有 `document_type_id`。 - [x] 二级分组必须挂在当前入口模块下的一级分组下面。 - [x] 规则绑定保存接口只允许传二级分组 `group_id`。 - [x] 规则绑定保存接口拒绝一级分组。 - [x] 规则绑定仍保存到 `leaudit_rule_group_bindings`。 - [x] 评查运行时继续按 `document.group_id -> leaudit_rule_group_bindings -> rule_set` 命中规则。 - [x] 不把 `leaudit_rule_type_bindings` 重新作为新主链路。 ### 14.7 前端入口模块管理任务 - [x] 所有新增 UI 必须沿用当前系统组件、CSS 变量、表格、表单、弹窗、按钮风格,不另起视觉体系。 - [x] 修改 `legal-platform-frontend/lib/api/legacy/entry-modules/entry-modules.ts`,增加菜单模板和功能清单类型。 - [x] 修改 `legal-platform-frontend/lib/api/legacy/entry-modules/entry-modules.ts`,入口模块类型增加 `business_scope` 摘要字段。 - [x] 修改 `legal-platform-frontend/lib/api/legacy/entry-modules/request-body.ts`,请求体支持菜单模板和功能清单。 - [x] 修改 `legal-platform-frontend/app/(audit)/entry-modules/EntryModulesClient.tsx`,列表展示菜单模板和功能摘要。 - [x] 修改 `EntryModulesClient.tsx`,业务范围列按 `business_scope` 展示真实大类和业务类型数量,不再写死“待配置”。 - [x] 修改 `legal-platform-frontend/app/(audit)/entry-modules/new/EntryModuleNewClient.tsx`,增加菜单模板选择。 - [x] 修改 `EntryModuleNewClient.tsx`,增加功能勾选区域。 - [x] 选择菜单模板时自动带出默认功能。 - [x] 新建按钮按 `entry_module:create:write` 显示。 - [x] 保存按钮按 `entry_module:update:write` 显示或禁用。 - [x] 删除按钮按 `entry_module:delete:delete` 显示。 - [x] 图标上传按 `entry_module:image:write` 显示或禁用。 - [x] 前端只做体验控制,后端权限点校验必须保留。 ### 14.8 前端首页和侧边栏任务 - [x] 修改 `legal-platform-frontend/app/page.tsx`,点击入口模块时保存完整入口上下文。 - [x] 新增 `legal-platform-frontend/lib/auth/entry-module-context.ts`,统一读写入口模块上下文。 - [x] 入口跳转 URL 带 `entryModuleId`。 - [x] 入口跳转 URL 带 `documentTypeIds`。 - [x] 修改 `legal-platform-frontend/lib/auth/entry-module-menu.ts`,把功能编码转换为菜单项。 - [x] 入口模块支持 `cross_checking` 菜单模板和交叉评查功能编码。 - [x] 修改 `legal-platform-frontend/components/layout/Sidebar.tsx`,侧边栏按入口模块功能清单生成菜单。 - [x] 删除 `selectedModuleName.includes("合同")`。 - [x] 删除 `selectedModuleName.includes("公文")`。 - [x] 删除按 `/govdoc` 路径强行判断公文菜单的逻辑。 - [x] 侧边栏生成菜单后继续经过用户页面权限过滤。 - [x] 有 `entry_module:list:read` 才显示入口模块管理菜单。 ### 14.9 前端公文、上传、规则分组任务 - [x] 公文、上传、规则分组页面只补业务上下文和过滤逻辑,不重做页面视觉。 - [x] 修改 `legal-platform-frontend/components/govdoc-audit/audits.tsx`,从 URL 或入口上下文读取 `entryModuleId`。 - [x] 修改 `legal-platform-frontend/lib/api/govdoc-audit/api.ts`,请求参数带 `entry_module_id`。 - [x] 公文页面标题使用当前入口模块名称,不再写死“内部公文”。 - [x] 修改 `legal-platform-frontend/app/(audit)/files/upload/FilesUploadClient.tsx`,上传表单带 `entryModuleId`。 - [x] 上传页选择二级分组后传 `groupId`。 - [x] 上传页在存在多个二级分组时必须让用户明确选择。 - [x] 修改 `legal-platform-frontend/app/(audit)/rule-groups/RuleGroupsClient.tsx`,规则分组页读取当前入口模块。 - [x] 规则分组页只展示当前入口模块下的一级分组。 - [x] 规则绑定入口只出现在二级分组上。 ### 14.10 测试和验收任务 - [x] 验证入口模块名称不包含“合同”,但配置合同功能后侧边栏仍显示模板搜索和模板列表。 - [x] 验证入口模块名称不包含“公文”,但配置公文功能后仍进入公文列表。 - [x] 验证没有模板页面权限时,即使入口模块启用模板功能,侧边栏也不显示模板菜单。 - [x] 验证云浮租户只能看到分配给云浮或 `PUBLIC` 的入口模块。 - [x] 验证梅州租户只能看到分配给梅州或 `PUBLIC` 的入口模块。 - [x] 验证没有 `entry_module:create:write` 的用户不能创建入口模块。 - [x] 验证没有 `entry_module:update:write` 的用户不能编辑入口模块。 - [x] 验证没有 `entry_module:delete:delete` 的用户不能删除入口模块。 - [x] 验证普通文档列表按入口模块隔离。 - [x] 验证公文列表按入口模块隔离。 - [x] 验证上传文档写入 `entry_module_id/type_id/group_id/tenant_code`。 - [x] 验证规则绑定不能挂一级分组。 - [x] 验证规则绑定能挂二级分组。 - [x] 验证入口模块列表业务范围列展示真实绑定摘要,不再全部显示“待配置”。 - [x] 验证评查按文档二级分组命中正确规则集。 ### 14.10.1 执行记录 2026-05-23 - [x] 使用 Playwright 真实账号 `000/admin06111` 登录验证首页入口和侧边栏。 - [x] 验证点击 `合同评查` 后进入 `/documents/list?entryModuleId=1&documentTypeIds=...`。 - [x] 验证侧边栏顺序为:首页、文件上传、文档列表、规则管理。 - [x] 验证文档列表菜单不重复。 - [x] 验证上传文档按钮可见。 - [x] 使用 Playwright 跑优先项 `2/3/4/5`:`node /tmp/leaudit-playwright-priority-2-5-v3.js`,结果 `25 pass / 0 fail`。 - [x] 修复二级分组绑定规则集 500:`evaluationPointGroupServiceImpl._get_binding_row` SQL 中 `{access_filter}` 未注入。 - [x] 回归验证:二级分组绑定规则集返回 200。 - [x] 回归验证:`pytest -q tests/test_rule_group_binding_scope.py` 结果 `4 passed`。 - [x] 回归验证:`legal-platform-frontend` 下 `npx tsc --noEmit --pretty false` 通过。 - [x] 回归验证:目标 eslint 结果 `0 error / 6 warnings`,warning 为既有 ``、未使用变量类提示。 - [x] 使用 `./leaudit.sh status` 确认后端、前端、Worker、Beat 均运行。 - [x] 修复公文入口侧边栏仍使用旧“系统概览/文件管理”菜单的问题,统一为入口模块菜单:首页、公文列表、公文上传、规则分组。 - [x] 修复公文入口侧边栏混入普通“文件上传”的问题,避免和“公文上传”重复或语义冲突。 - [x] 使用 Playwright 真实账号 `000/admin06111` 跑页面入口 smoke:`node /tmp/leaudit-playwright-page-entry-suite.js`,结果 `31 pass / 0 fail`。 - [x] 页面入口 smoke 覆盖:合同入口名称不含“合同”仍显示模板搜索/模板列表;公文入口名称不含“公文”仍进入 `/govdoc/audits`;自定义入口只显示首页/文件上传/文档列表;三类入口刷新后不丢上下文。 - [x] 使用 Playwright 跑优先项 `2/3/4/5`:`node /tmp/leaudit-playwright-priority-2-5-v3.js`,结果 `25 pass / 0 fail`。 - [x] 优先项覆盖:上传写入 `entryModuleId/typeId/groupId/tenantCode`、入口 A/B 文档列表隔离、规则分组按入口过滤、一级分组拒绝绑定、二级分组可绑定、无入口管理权限接口返回 403。 - [x] 补充交叉评查入口模块配置能力:入口模块路由下拉支持 `/cross-checking`,菜单模板支持 `cross_checking`,功能清单支持 `cross_checking/cross_checking_upload/cross_checking_list`。 - [x] 修复首页交叉评查入口仍走旧“写死交叉评查卡片”的问题,改为按真实入口模块渲染,入口名称、入口 ID、功能清单全部来自 `leaudit_entry_modules`。 - [x] 修复 `/cross-checking` 入口点击不带入口上下文的问题,跳转统一进入 `/cross-checking/list?entryModuleId=...`。 - [x] 修复交叉评查入口侧边栏混入普通“文件上传”的问题,只显示 `创建任务/评查任务列表` 等交叉评查功能菜单。 - [x] 使用 Playwright 真实账号 `000/admin06111` 验证交叉评查入口模块:`node /tmp/leaudit-playwright-cross-entry.js`,结果 `8 pass / 0 fail`。 - [x] 新增数据库只读巡检脚本:`scripts/创建sql/verify_entry_module_menu_profile.sql`,覆盖字段、索引、非法菜单配置、文档入口归属、重复二级分组、未绑定入口模块一级分组。 - [x] 补充公文列表标题逻辑:有入口模块上下文时显示当前入口模块名称,例如 `入口模块-测试文档列表`;无上下文时才兜底 `内部公文文档列表`。 - [x] 补充入口模块管理菜单权限过滤:`/entry-modules` 设置子菜单必须带 `entry_module:list:read` 才显示。 - [x] 回归验证:`node --test --experimental-strip-types tests/govdoc-audit/govdoc-entry-title.test.mts` 结果 `2 pass / 0 fail`。 - [x] 回归验证:`node --test --experimental-strip-types tests/govdoc-audit/settings-menu-permission.test.mts` 结果 `1 pass / 0 fail`。 - [x] 修复 `Sidebar` 中无入口上下文时按 `/govdoc` 路径强行套公文菜单的问题;公文菜单只由入口模块 `menuProfile/features` 决定。 - [x] 使用 Playwright 真实账号 `000/admin06111` 跑剩余项验收:`node test-results/leaudit-playwright-entry-module-remaining-acceptance.js`,结果 `11 pass / 0 fail`。 - [x] 剩余项验收覆盖:名字不含“公文”的 govdoc 入口可进入;公文列表标题使用当前入口名称;侧栏只显示 `公文列表/公文上传/规则分组`;无 `entry_module:image:write` 的用户上传入口模块图标返回 403。 ### 14.10.2 下一步验收和开发安排 - [x] Playwright 页面级验收入口模块管理页:新建入口、编辑入口、功能勾选保存、功能回显、租户回显。 - [x] Playwright 页面级验收上传页:文件上传菜单排第二,真实表单选择文档类型和二级分组后提交。 - [x] Playwright 页面级验收规则分组页:入口 A/B 切换后页面树隔离,一级分组不出现绑定入口,二级分组绑定弹窗可用。 - [x] Playwright 页面级验收公文入口:入口名称不含“公文”但配置 `govdoc_audits/govdoc_upload` 后,公文列表和上传可进入。 - [x] Playwright 页面级验收公文详情页:确认不出现平台 Sidebar + 公文工作区 Sidebar 双侧栏。 - [x] 多租户真实账号验收:云浮、揭阳、梅州分别只看到已分配或 `PUBLIC` 入口模块。 - [x] 多租户权限验收:没有入口模块管理权限的租户用户不能在 UI 和接口层创建、编辑、删除入口模块。 - [x] 评查命中验收:上传带二级分组的文档后,启动评查,确认命中该二级分组绑定规则集。 ### 14.10.3 当前最新状态 2026-05-23 当前入口模块主链路已经完成: - [x] 入口模块不再靠名称包含“合同/公文”决定菜单。 - [x] 入口模块支持 `document_review/contract/govdoc/cross_checking/custom` 菜单模板。 - [x] 入口模块支持 `features` 控制首页、上传、文档列表、规则、规则分组、模板、公文、交叉评查菜单。 - [x] 首页点击入口后会保存完整入口上下文,并在 URL 带 `entryModuleId/documentTypeIds`。 - [x] 侧边栏菜单由入口模块 `menuProfile/features` 生成,再经过用户页面权限过滤。 - [x] 普通文档上传和列表已经接入 `entry_module_id/type_id/group_id/tenant_code`。 - [x] 公文列表接口已经支持 `entry_module_id`,页面标题已经改为当前入口模块名称。 - [x] 规则分组列表已经支持入口模块过滤,规则绑定只允许挂二级分组。 - [x] 入口模块管理权限不硬编码角色名,创建、编辑、删除、上传图标走 RBAC 权限点。 - [x] 数据库迁移脚本和只读验证 SQL 已补齐。 当前仍然需要验收或收口: - [x] 模板页面权限过滤:入口模块启用模板功能,但用户没有模板页面权限时,侧边栏不能显示模板菜单。 - [x] 多租户真实账号验收:云浮、揭阳、梅州只能看到自己租户或 `PUBLIC` 入口。 - [x] 公文列表数据隔离:同一租户下两个公文入口分别进入时,列表数据不能串入口。 - [x] 公文详情页侧栏:确认不出现平台 Sidebar 和公文工作区 Sidebar 双侧栏。 - [x] 入口模块管理页真实表单验收:新建、编辑、功能勾选、租户回显必须跑 Playwright。 - [x] 上传页真实表单验收:从入口进入后,选择文档类型和二级分组提交,后端落库字段正确。 - [x] 规则分组页真实页面验收:入口 A/B 切换后树隔离,一级分组无绑定入口,二级分组绑定弹窗可用。 - [x] 评查命中验收:上传带二级分组的文档后启动评查,确认按该二级分组命中规则集。 当前暂缓硬改: - [ ] `一级分组创建时必须有 entry_module_id` 仍保留兼容口径,避免旧全局分组或历史数据立即断链。下一轮只先补告警和数据巡检,不直接硬拦。 ### 14.10.4 执行记录 2026-05-24 - [x] 清理 Playwright 和手工验收产生的测试入口模块、测试业务大类、测试规则分组。 - [x] 测试数据清理采用软删除,未硬删历史文档,避免破坏审计记录和外键引用。 - [x] 已软删除测试入口模块 31 个:`37,39-68`。 - [x] 已软删除测试业务大类 24 个:`24-47`。 - [x] 已软删除测试规则分组 71 个:`52-122`。 - [x] 已软删除入口租户关联 34 条、规则分组绑定 3 条。 - [x] 反查验证:未删除测试入口模块 0 个、未删除测试业务大类 0 个、未删除测试规则分组 0 个、测试入口有效租户关联残留 0 条、测试分组有效规则绑定残留 0 条。 - [x] 修复入口模块列表“业务范围”列全部显示“待配置”的问题。 - [x] 根因确认:数据库中合同评查、案卷智能评查、内部公文已有业务范围绑定,但 `EntryModulesClient.tsx` 把业务范围列写死为“待配置”,后端入口模块列表也没有返回业务范围摘要。 - [x] 后端 `EntryModuleVO` 增加 `business_scope`,入口模块列表和详情返回业务大类数量、业务类型数量、大类名称列表。 - [x] `entryModuleAdminServiceImpl.py` 的 `business_scope` 查询兼容两种历史结构:通过 `leaudit_document_types.entry_module_id` 绑定业务大类,以及通过一级规则分组 `entry_module_id` 统计二级业务类型。 - [x] 前端 `EntryModule` 类型增加 `business_scope`。 - [x] 前端新增 `summarizeBusinessEntryScope()`,入口模块列表按真实业务范围展示,例如“建设工程合同、买卖合同、借款合同 等 10 类 / 覆盖 10 个业务类型”。 - [x] 真实数据库只读验证:合同评查 `10` 个业务大类、`10` 个业务类型;案卷智能评查 `10` 个业务大类、`10` 个业务类型;内部公文 `1` 个业务大类、`1` 个业务类型;智慧法务助手和交叉评查当前未配置业务范围,显示“待配置”符合数据事实。 - [x] 回归验证:`node --test --experimental-strip-types tests/govdoc-audit/business-entry-ui.test.mts` 结果 `7 pass / 0 fail`。 - [x] 回归验证:`legal-platform-frontend` 下 `npx tsc --noEmit --pretty false` 通过。 - [x] 回归验证:`.venv/bin/python -m py_compile fastapi_modules/fastapi_leaudit/domian/vo/entryModuleAdminVo.py fastapi_modules/fastapi_leaudit/services/impl/entryModuleAdminServiceImpl.py` 通过。 - [x] 回归验证:目标 eslint 通过,`0 error / 0 warning`。 - [x] RBAC 数据调整:删除 `省级管理员 / provincial_admin` 角色。 - [x] RBAC 删除级联清理:`user_role` 1 条、`role_permissions` 94 条、`role_route` 29 条。 - [x] 将 `000` 对应用户调整为系统超级管理员:当前库中该用户为 `sso_users.id=5, sub=000, username=admin`。 - [x] RBAC 反查验证:`provincial_admin` 剩余 0 个;`000` 用户当前只绑定 `super_admin / 系统超级管理员 / data_scope=ALL`。 - [x] 使用 Playwright 真实账号 `000/admin06111` 验收入口模块编辑页和上传页:`LEAUDIT_FRONTEND_URL=http://127.0.0.1:5193 node /tmp/leaudit-playwright-entry-upload-management-v1.js`,结果 `16 pass / 0 fail`。 - [x] 入口模块编辑页验收覆盖:4 步表单、菜单模板回显、功能勾选回显、PUBLIC 租户回显、业务范围回显。 - [x] 上传页验收覆盖:从入口上下文进入后按入口加载业务大类和业务类型,显示所属业务入口,上传接口和文档详情接口返回正确 `entryModuleId/typeId/groupId/tenantCode`。 - [x] 修复上传页单一业务类型时查不到二级分组的问题:`FilesUploadClient.tsx` 不再只在 `childDocumentTypeIds.length > 1` 时批量查询二级分组。 - [x] 后端文档上传、列表、详情 VO 返回 `entryModuleId`,方便前端和测试确认文档入口归属。 - [x] 修复公文详情页双侧栏风险:`/govdoc/detail/...` 和 `/govdoc-audit/detail/...` 不再渲染平台 Sidebar,只保留公文工作区布局。 - [x] 回归验证:`.venv/bin/python -m py_compile fastapi_modules/fastapi_leaudit/domian/vo/documentVo.py fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py` 通过。 - [x] 回归验证:`legal-platform-frontend` 下 `npx tsc --noEmit --pretty false` 通过。 - [x] 回归验证:目标 eslint 通过,`0 error / 1 warning`,warning 为 `files-upload.ts` 被 eslint ignore,非本次阻塞。 - [x] 本轮 Playwright 临时数据已清理:`PW入口编辑上传-pwui%`、`pw.root.pwui%`、`pw.child.pwui%`、`pw.type.pwui%`、`pw-ui-upload-pwui%` 有效残留均为 0。 - [x] 环境记录:`./leaudit.sh` 本轮出现 pidfile/后台进程状态不稳定,真实验收改用 TTY 手动启动后端 `run.py` 和前端 `npm run dev:dev`,Playwright 统一访问 `http://127.0.0.1:5193`。 - [x] 使用 Playwright 真实账号 `000/admin06111` 验收规则分组页:`LEAUDIT_FRONTEND_URL=http://127.0.0.1:5193 node /tmp/leaudit-playwright-rule-groups-scope-v1.js`,结果 `21 pass / 0 fail`。 - [x] 规则分组页验收覆盖:入口 A/B 树隔离、请求带 `entry_module_id`、一级分组不显示“配置规则”、后端拒绝一级分组绑定规则集、二级分组绑定弹窗可打开并保存。 - [x] 规则分组页验收发现一个非产品 BUG:临时脚本第一次按旧弹窗类名 `.modal-content/[role=dialog]` 断言失败;当前页面实际使用 `.rg-modal`,修正脚本后通过。 - [x] 规则分组 Playwright 临时数据已清理:`PW规则分组入口%`、`pw.rg.type.%`、`pw.rg.%`、`note like '%pwrg%'` 有效残留均为 0。 - [x] 使用 Playwright 真实账号 `000/admin06111` 验收公文入口深度链路:`LEAUDIT_FRONTEND_URL=http://127.0.0.1:5193 node /tmp/leaudit-playwright-govdoc-entry-scope-v1.js`,结果 `17 pass / 0 fail`。 - [x] 公文入口深度验收覆盖:创建 A/B 公文入口、创建 A/B 公文业务类型、A/B 公文列表请求带 `entry_module_id`、A/B 列表数据不串、标题使用当前入口模块名称、公文上传链接携带 `entryModuleId/documentTypeIds`、公文上传落库携带 `entry_module_id/type_id`、公文详情页不渲染平台 Sidebar。 - [x] 修正公文 Playwright 验收脚本:不再用 `request.postData()` 判断 multipart 表单,因为该方式对文件上传体不稳定;改为上传后解析新 `documentId` 并查询数据库验证 `leaudit_documents.entry_module_id/type_id`。 - [x] 公文 Playwright 临时数据已清理:`PW公文入口%`、`pw.govdoc.type.%`、`pw-govdoc-%` 有效文档残留均为 0。 - [x] 发现并记录一个非阻塞清理差异:公文删除接口会软删 `leaudit_documents`,但 `leaudit_document_files` 中历史测试文件记录仍保持 `deleted_at is null`;当前列表以文档主表删除态过滤,不影响入口隔离验收,后续如要严格清理可单独优化删除服务。 - [x] 使用 Playwright 真实账号验收多租户入口可见性和普通权限:`LEAUDIT_FRONTEND_URL=http://127.0.0.1:5193 node /tmp/leaudit-playwright-multitenant-entry-scope-v1.js`,结果 `37 pass / 0 fail`。 - [x] 多租户验收账号:`yf001/yfyc06111`、`jy001/jyyc06111`、`mz001/mzyc06111`、`001/gdyc06111`。 - [x] 多租户验收覆盖:云浮只看 `合同评查/案卷智能评查/内部公文`;揭阳只看 `合同评查/案卷智能评查/内部公文/交叉评查`;梅州地区管理员看 `合同评查/案卷智能评查/内部公文/智慧法务助手/交叉评查`;普通用户 `001` 看不到 `智慧法务助手`。 - [x] 权限验收覆盖:普通用户 `001` 首页不显示系统设置按钮,直调 `/api/v3/entry-modules` 返回 403。 - [x] 使用 Playwright APIRequestContext + 数据库只读查询验收评查命中链路:真实账号 `mz001/mzyc06111` 上传 `legal-platform-frontend/public/testWork/(最终版)智慧法务平台建设采购项目合同(1).docx`,传入 `typeId=1/groupId=2/entryModuleId=1/tenant_code=MZ/autoRun=true`。 - [x] 评查命中验收结果:上传生成 `documentId=232/runId=110`,`leaudit_documents` 写入 `type_id=1/group_id=2/entry_module_id=1/tenant_code=MZ/current_run_id=110`。 - [x] 规则集命中数据库证据:`leaudit_audit_runs.id=110` 写入 `rule_set_id=126/rule_version_id=500/group_id_snapshot=2/rule_binding_id_snapshot=101/tenant_code=MZ/scope_type_snapshot=TENANT`,符合 `leaudit_rule_group_bindings.group_id=2` 下梅州租户绑定。 - [x] 使用 Playwright 真实账号 `001/gdyc06111` 验收模板页面权限过滤:进入 `合同评查` 后 URL 为 `/documents/list?entryModuleId=1&documentTypeIds=...`,侧栏显示 `文件上传/文档列表`,不显示 `合同管理/模板搜索/模板列表`。 - [x] 模板权限验收补充证据:`001` 为 `common` 角色,无 `contract_template:*` 和 `entry_module:*` 权限;`/api/auth/session-data` 返回的合同入口 `features` 已被裁剪为 `home/documents/upload/rules/rule_groups`,`permissionMap` 不含 `/contract-template` 路由。 - [x] RBAC 权限数据收口:已按“入口模块只能系统超级管理员维护”口径移除 `admin / 地区管理员` 的 `entry_module:create/update/delete/image/list/detail` 权限和 `/entry-modules` 菜单。 - [x] 新增可重复执行 SQL:`scripts/创建sql/rbac_entry_module_super_admin_only.sql`,用于保证 `super_admin` 保留入口模块权限,同时移除 `admin/provincial_admin` 的入口模块权限和菜单。 - [x] 修复首页系统设置按钮跳转:不再固定跳 `/entry-modules`,而是跳当前用户有权限的第一个设置子菜单;没有任何设置子菜单时不显示系统设置按钮。 - [x] RBAC 收口真实验收:`mz001/mzyc06111` 登录后无 `entry_module:*` 权限,直调 `/api/v3/entry-modules` 返回 403,`settingsChildren` 不含 `/entry-modules`。 - [x] RBAC 收口真实验收:`000/admin06111` 登录后仍有入口模块 6 个权限,直调 `/api/v3/entry-modules` 返回 200,`settingsChildren` 包含 `/entry-modules`。 ### 14.10.5 当前最新状态 2026-05-24 当前已完成并验证: - [x] 入口模块列表业务范围列已经接真实后端摘要,不再统一显示“待配置”。 - [x] 合同评查、案卷智能评查、内部公文的业务范围摘要和当前数据库绑定一致。 - [x] 智慧法务助手、交叉评查当前没有绑定业务大类,所以继续显示“待配置”是正确状态。 - [x] 测试入口模块和测试业务大类已经清理,不会继续污染入口模块管理页和业务大类管理页。 - [x] `000/admin06111` 对应账号已变成系统超级管理员,后续 Playwright 验收使用该账号时具备系统级管理权限。 - [x] `provincial_admin` 已删除,后续计划和验收不要再依赖省级管理员这个角色。 - [x] 入口模块管理页和上传页已经完成真实 Playwright 验收,上传链路能落 `entry_module_id/type_id/group_id/tenant_code`。 - [x] 规则分组页已经完成真实 Playwright 验收,入口 A/B 不串树,一级不绑定规则,二级绑定弹窗可用。 - [x] 公文详情页双侧栏代码风险已修复,并已用真实公文详情页面 Playwright 确认。 - [x] 多租户真实账号验收已完成,云浮、揭阳、梅州入口可见性符合当前入口模块租户分配和 RBAC 菜单权限。 - [x] 模板页面权限过滤已完成真实 Playwright 验收,普通用户没有模板页面权限时,即使进入合同入口也不显示模板菜单。 - [x] 评查命中验收已完成,普通文档上传后按 `document.group_id -> leaudit_rule_group_bindings -> rule_set/current_version_id` 命中租户级规则集。 后续仍需继续验收: - [x] RBAC 权限数据收口:已从 `admin / 地区管理员` 移除入口模块管理权限,当前只有系统超级管理员可以维护入口模块。 ### 14.10.6 下一轮执行顺序 前置调整: ```text 真实前端 Playwright 全量验收前,先执行入口模块和文档类型的 UI/交互优化计划: docs/superpowers/plans/2026-05-23-business-entry-ux-optimization.md ``` 原因: ```text 当前“入口模块管理 + 文档类型管理 + 规则分组 + 上传”的技术语义学习成本过高。 如果先写真实前端测试,会把当前复杂交互固化下来。 应先把页面统一成“业务入口、业务大类、业务类型、规则配置”的用户语义,再做真实 Playwright 验收。 ``` 按下面顺序继续,不要先碰评查引擎: 1. P0:入口模块管理页 Playwright 验收。 验收内容: ```text 新建入口 -> 选择菜单模板 -> 勾选功能 -> 分配租户 -> 保存 -> 列表回显 -> 编辑回显 ``` 输出要求: ```text 已完成。继续维护时如果页面字段或回显错,先修前端表单和接口字段映射,不改运行时评查逻辑。 ``` 2. P1:上传页真实表单 Playwright 验收。 验收内容: ```text 从入口模块进入上传页 -> 文件上传菜单排第二 -> 选择文档类型 -> 选择二级分组 -> 提交 ``` 输出要求: ```text 已完成。验收确认数据库或接口返回里存在 entry_module_id/type_id/group_id/tenant_code。 ``` 3. P2:规则分组页 Playwright 验收。 验收内容: ```text 入口 A 只看到 A 的一级分组 入口 B 只看到 B 的一级分组 一级分组不出现绑定规则入口 二级分组可以打开绑定弹窗并保存 ``` 输出要求: ```text 已完成。验收确认前端透传 `entryModuleId`,后端列表过滤生效,一级/二级规则绑定边界正确。 ``` 4. P3:公文入口深度验收。 验收内容: ```text 公文列表按 entry_module_id 隔离 公文上传能从入口模块进入 公文详情页不出现双侧栏 ``` 输出要求: ```text 如果列表串数据,优先查 govdoc API 请求参数和 GovdocServiceImpl 查询过滤。 ``` 5. P4:多租户真实账号验收。 验收内容: ```text 云浮用户只看到云浮或 PUBLIC 入口 揭阳用户只看到揭阳或 PUBLIC 入口 梅州用户只看到梅州或 PUBLIC 入口 没有入口模块管理权限的租户用户 UI 不显示管理能力,接口返回 403 ``` 输出要求: ```text 如果缺少真实账号,先用现有 RBAC/租户数据造最小测试账号,并记录账号和租户。 ``` 6. P5:评查命中验收。 验收内容: ```text 上传带二级分组的文档 -> 绑定该二级分组规则集 -> 启动评查 -> 确认命中该规则集 ``` 输出要求: ```text 这里只验证 group_id -> leaudit_rule_group_bindings -> rule_set 主链路,不重构评查引擎。 ``` ### 14.10.7 下一轮可并行安排 可以拆成 3 个并行工作流。当前 3 个工作流均已完成,本节保留为后续复盘参考: - [x] 工作流 A:入口模块管理页 + 上传页 Playwright 验收,主要看表单和字段落库。 - [x] 工作流 B:规则分组页 + 公文入口深度验收,主要看入口上下文过滤和侧栏。 - [x] 工作流 C:多租户真实账号 + 模板权限过滤 + 评查命中验收,主要看权限、租户和运行时规则命中。 并行约束: - [x] 三个工作流都必须先执行 `./leaudit.sh status`,确认后端、前端、Worker、Beat 运行。 - [x] UI 验收必须使用 Playwright,账号优先使用 `000/admin06111`,多租户验收再补具体租户账号。 - [x] 每个工作流完成后都要回写本计划文档的执行记录。 - [x] 发现 BUG 先补最小复现脚本,再修代码,最后跑对应 Playwright 或 pytest 回归。 ### 14.11 建议提交顺序 1. 数据库迁移脚本。 2. 后端 DTO/VO 和入口模块服务。 3. 首页入口返回和前端入口上下文。 4. 侧边栏菜单生成逻辑。 5. 入口模块管理页菜单模板和功能勾选。 6. 文档上传和列表入口模块归属。 7. 公文列表入口模块过滤。 8. 规则分组和规则绑定校验。 9. 删除名称包含“合同/公文”的旧判断。 10. 跑完整验收清单。 ### 14.12 暂不执行的任务 - [ ] 暂不做同一个入口模块不同租户不同功能清单。 - [ ] 暂不删除旧地区字段。 - [ ] 暂不删除旧规则类型绑定表。 - [ ] 暂不重写评查运行引擎。 - [ ] 暂不把入口模块变成权限系统。 ### 14.13 下一阶段收口计划:entry_module_id 强一致性 #### 14.13.1 先把概念说清楚 `entry_module_id` 不是给用户看的新概念,它在前端应该叫“业务入口”或“工作台”。 它的作用只有一个:让系统知道一条数据属于哪个入口模块。 如果没有 `entry_module_id`,系统只能靠下面这些不稳定方式猜: ```text 入口名字里有没有“合同” 当前 URL 是不是 /govdoc 文档类型刚好属于哪个入口 规则分组刚好挂在哪个父节点下面 ``` 这些猜法会导致同一个租户下多个入口互相串数据,例如: ```text 合同入口能看到案卷文档 公文入口侧边栏混入普通文件上传 规则分组 A/B 入口互相出现 上传后评查命中错规则集 ``` 所以新链路必须明确落字段: ```text 入口模块 leaudit_entry_modules.id -> 业务大类/一级分组 leaudit_evaluation_point_groups.entry_module_id -> 业务类型/二级分组 group_id -> 文档 leaudit_documents.entry_module_id + group_id -> 评查按 group_id 命中规则绑定 ``` 用户理解口径: ```text 我从哪个业务入口进来,上传、列表、规则配置就只看这个入口下的数据。 ``` 开发理解口径: ```text entry_module_id 是入口隔离字段。 tenant_code 管租户边界。 group_id 管规则命中。 type_id 管文档分类。 features/menu_profile 管菜单显示。 ``` #### 14.13.2 当前不直接强拦的原因 当前代码里“一级业务大类必须有 `entry_module_id`”还没有完全强拦,是刻意保留兼容。 原因: ```text 老数据里可能还有全局一级分组 老文档可能只能从 type_id 或 group_id 推导入口 如果现在直接 400,可能会把旧规则配置、旧上传链路或历史列表打断 ``` 因此下一阶段不能一步到位硬改,必须按下面顺序推进: ```text 先巡检旧数据 再修复或回填孤儿数据 再加后端软提示/日志 最后才把新建和编辑一级业务大类改成必须有入口模块 ``` #### 14.13.3 实现顺序 1. 数据巡检。 执行只读巡检脚本: ```bash psql "$DATABASE_URL" -f scripts/创建sql/verify_entry_module_menu_profile.sql ``` 重点看这几类结果: ```text 文档 entry_module_id 为空 文档 entry_module_id 和 group/type 推导不一致 重复二级分组 一级分组 entry_module_id 为空 非法 menu_profile/features ``` 2. 旧数据处理。 如果一级分组没有 `entry_module_id`,先按可推导关系回填: ```text 一级分组有 document_type_id -> 用 leaudit_document_types.entry_module_id 回填 一级分组下二级分组有 document_type_id -> 用二级分组对应文档类型的 entry_module_id 回填 确实无法推导 -> 先列清单人工判断,不自动乱填 ``` 注意: ```text 不允许为了清零数据随便塞 entry_module_id=1 不允许把跨入口共用的旧分组直接强绑到某一个入口 ``` 3. 后端创建校验收紧。 文件: ```text fastapi_modules/fastapi_leaudit/services/impl/evaluationPointGroupServiceImpl.py ``` 目标: ```text 新建一级业务大类 pid=0 时,必须传 entry_module_id 新建一级业务大类不允许只靠 document_type_id 反推入口 新建二级业务类型 pid!=0 时,继承父级 entry_module_id 新建二级业务类型必须绑定 document_type_id 二级业务类型的 document_type_id 所属入口必须和父级入口一致 ``` 兼容策略: ```text 只强拦新建/编辑请求 不因为历史数据缺 entry_module_id 导致列表查询直接失败 ``` 4. 前端规则配置页补齐入口上下文。 文件: ```text legal-platform-frontend/app/(audit)/rule-groups/RuleGroupsClient.tsx ``` 目标: ```text 从业务入口进入规则配置页时,新增业务大类自动带当前 entryModuleId 页面顶部显示“当前业务入口:xxx” 如果没有入口上下文,不允许新增业务大类,只提示从首页业务入口进入 ``` 用户看到的是: ```text 当前业务入口:合同评查 新增业务大类时自动归属当前入口 ``` 用户不需要看到: ```text entry_module_id pid = 0 group_id ``` 5. 上传页保持现有两级选择。 文件: ```text legal-platform-frontend/app/(audit)/files/upload/FilesUploadClient.tsx ``` 目标不变: ```text 先选业务大类 再选业务类型 提交 entryModuleId/typeId/groupId/tenantCode ``` 这里不要做自动创建业务类型,也不要跳过业务类型选择。 6. 验收。 必须使用 Playwright 真实账号验收: ```text 000/admin06111:新增业务大类 -> 新增业务类型 -> 配置规则 -> 上传 -> 列表只看当前入口 mz001/mzyc06111:不能进入业务入口管理,能使用已分配入口 yf001/yfyc06111:只能看到云浮已分配入口 jy001/jyyc06111:只能看到揭阳已分配入口 ``` 数据库验收: ```text leaudit_evaluation_point_groups 一级分组有 entry_module_id leaudit_evaluation_point_groups 二级分组能通过父级拿到 entry_module_id leaudit_documents 上传后有 entry_module_id/type_id/group_id/tenant_code leaudit_audit_runs 按 group_id_snapshot 命中 rule_binding_id_snapshot ``` #### 14.13.4 推荐下一步直接做什么 下一步不先改代码,先做一次真实数据库巡检并记录结果。 执行顺序: ```text 1. 运行 verify_entry_module_menu_profile.sql 2. 把异常结果整理到本计划文档 3. 如果一级分组 entry_module_id 为空数量为 0,再开始后端强校验 4. 如果不为 0,先补一个只读清单和人工回填 SQL 草案 5. 回填完成后再改后端校验和前端规则配置页 ``` 这样做的原因: ```text entry_module_id 是隔离字段,一旦强校验加错,会直接影响上传、规则配置、列表过滤和评查命中。 先把数据状态摸清,再收紧代码,风险最低。 ``` #### 14.13.5 巡检记录 2026-05-24 已执行: ```bash PGPASSWORD='zhfw*123*' psql -h nas.7bm.co -p 54302 -U docauditai_admin -d leaudit_platform -f scripts/创建sql/verify_entry_module_menu_profile.sql ``` 同时修正巡检脚本字段错误: ```text leaudit_documents 没有 filename 字段,实际字段为 normalized_name。 已把脚本中的 d.filename 改为 d.normalized_name AS document_name。 ``` 巡检结论: ```text 必要字段存在: leaudit_entry_modules.menu_profile leaudit_entry_modules.features leaudit_documents.entry_module_id 必要索引存在: idx_leaudit_entry_modules_menu_profile idx_leaudit_documents_entry_module_id 非法 menu_profile:0 条 非法 feature 编码:0 条 重复二级分组:0 条 文档 entry_module_id 与 group/type 推导不一致:0 条 ``` 发现的问题: ```text 案卷智能评查 features 为空 智慧法务助手 features 为空 历史文档 entry_module_id 为空:68 条 其中 65 条可以通过 group/type 推导入口 其中 3 条公文示例没有 type_id/group_id,暂时无法自动推导入口 一级业务大类 entry_module_id 为空:14 条 ``` 68 条历史文档缺入口归属的推导结果: ```text 可推导为 合同评查 entry_module_id=1:47 条 可推导为 案卷智能评查 entry_module_id=2:14 条 可推导为 内部公文 entry_module_id=3:4 条 无法推导:3 条,均为旧公文示例,type_id/group_id 为空 ``` 14 条一级业务大类缺入口归属的来源: ```text 行政卷宗 root.casefile:可通过子业务类型推导为 案卷智能评查 entry_module_id=2 testmzceshi:测试残留,无子节点、无 document_type_id,需要人工确认是否删除 12 条 PW*/pw.* 测试业务大类:来自已软删除的 Playwright 临时入口,关联文档类型仍未软删 ``` 关键判断: ```text 当前正式 5 个入口本身没有结构性问题。 真正阻塞强校验的是历史数据和 Playwright 测试残留。 现在不能直接加“一级业务大类必须有 entry_module_id”的硬拦截,否则旧残留会继续污染巡检,也可能影响旧数据展示。 ``` 建议下一步: ```text 1. 先出只读清单 SQL,列出可自动回填的 65 条历史文档和 root.casefile。 2. 出数据修复 SQL 草案,但先不执行。 3. Playwright 测试残留建议统一软删:pw.* 文档类型、PW* 规则分组、对应临时入口关系。 4. testmzceshi 需要人工确认是否删除。 5. 3 条无 type_id/group_id 的旧公文示例不要自动回填,保留人工处理。 6. 数据修复完成后,再改后端强校验和前端规则配置页入口上下文。 ``` #### 14.13.6 数据修复执行记录 2026-05-24 已新增并执行数据修复脚本: ```text scripts/创建sql/repair_entry_module_scope_data_20260524.sql ``` 执行前做了事务回滚演练: ```text 可回填历史文档:65 条 可回填一级业务大类:1 条,root.casefile 行政卷宗 可继承父级入口的二级业务类型:19 条 可安全软删测试规则分组:25 条 可补默认功能的入口模块:1 条,案卷智能评查 ``` 正式执行结果: ```text UPDATE 65 UPDATE 1 UPDATE 19 UPDATE 25 UPDATE 1 COMMIT ``` 修复内容: ```text 65 条历史文档已按 group/type 推导回填 entry_module_id。 行政卷宗 root.casefile 已回填 entry_module_id=2,归属案卷智能评查。 行政卷宗及合同正式二级业务类型已继承父级 entry_module_id。 testmzceshi 和 PW*/pw.* 无引用测试规则分组已软删除。 案卷智能评查已补齐默认功能:home/documents/upload/rules/rule_groups。 ``` 修复后巡检结果: ```text 非法 menu_profile:0 条 非法 feature 编码:0 条 文档 entry_module_id 与 group/type 推导不一致:0 条 重复二级分组:0 条 一级业务大类 entry_module_id 为空:0 条 活跃 PW 测试文档类型:0 条 活跃测试规则分组残留:0 条 活跃文档类型指向已删除入口:0 条 ``` 剩余问题: ```text 仍有 3 条旧公文示例文档 entry_module_id 为空。 这 3 条同时没有 type_id 和 group_id,无法确定属于哪个入口模块。 本次没有自动回填,避免把历史公文示例错误塞进内部公文或其他入口。 ``` 剩余 3 条: ```text id=46 公文示例-瑕疵 id=47 公文示例-瑕疵 id=48 公文示例-合规-OOXML修正版 ``` 后续处理建议: ```text 如果确认它们是内部公文旧测试数据,可以单独回填 entry_module_id=3。 如果只是历史测试样例,也可以软删除。 在未确认前,不建议自动处理。 ``` #### 14.13.7 旧公文示例回填记录 2026-05-24 用户已确认 3 条旧公文示例可以回填。 已新增并执行: ```text scripts/创建sql/repair_govdoc_sample_entry_module_20260524.sql ``` 执行条件: ```text 只更新 id IN (46, 47, 48) 只更新 deleted_at IS NULL 只更新 entry_module_id IS NULL 只更新 engine_type='govdoc' 且 review_scope='govdoc' 只回填 entry_module_id=3,不伪造 type_id/group_id ``` 执行结果: ```text UPDATE 3 COMMIT ``` 回填后结果: ```text id=46 公文示例-瑕疵 -> entry_module_id=3 id=47 公文示例-瑕疵 -> entry_module_id=3 id=48 公文示例-合规-OOXML修正版 -> entry_module_id=3 ``` 最终巡检结果: ```text documents_without_entry_module = 0 文档 entry_module_id 与 group/type 推导不一致 = 0 一级业务大类 entry_module_id 为空 = 0 重复二级分组 = 0 非法 menu_profile = 0 非法 feature 编码 = 0 ```