772 lines
14 KiB
Markdown
772 lines
14 KiB
Markdown
# 新项目交叉评查详细业务逻辑定稿
|
|
|
|
> 目标:把新项目交叉评查的业务逻辑收敛成一份“可直接指导实现”的定稿,重点避免实现阶段把任务状态、评分语义、权限边界、版本逻辑和完成条件做错。
|
|
|
|
## 1. 定位
|
|
|
|
交叉评查在新项目中必须被视为:
|
|
|
|
- 一层独立的“协作复核业务”
|
|
- 建立在 `leaudit` 已有文档、评查运行、规则结果之上
|
|
- 不负责重新生成底层规则结果
|
|
- 只负责任务协作、提案、投票、完成确认和展示分数叠加
|
|
|
|
因此,交叉评查不是评查引擎的一部分,而是评查结果之上的二次协作层。
|
|
|
|
## 2. 核心业务对象
|
|
|
|
新项目中交叉评查至少包含五类核心对象:
|
|
|
|
### 2.1 任务
|
|
|
|
任务是交叉评查的协作容器,负责绑定:
|
|
|
|
- 一组参与人
|
|
- 一组主要负责人
|
|
- 一批文档
|
|
- 一个文档类型语义
|
|
- 一个任务状态
|
|
|
|
任务是交叉评查权限判断的顶层边界。
|
|
|
|
### 2.2 任务成员
|
|
|
|
任务成员分为两类:
|
|
|
|
- `participant`:普通参与人
|
|
- `principal`:主要负责人
|
|
|
|
另有一个特殊角色:
|
|
|
|
- `assigner`:任务创建者
|
|
|
|
实现上建议:
|
|
|
|
- `assigner` 保存在任务主表
|
|
- `participant/principal` 保存在成员表
|
|
|
|
### 2.3 任务文档
|
|
|
|
任务文档表示:
|
|
|
|
- 哪些文档进入了交叉评查任务
|
|
- 每个文档在该任务内的完成状态
|
|
|
|
注意:
|
|
|
|
- 任务文档状态是“任务内状态”
|
|
- 不等于文档全局状态
|
|
- 不等于底层评查运行状态
|
|
|
|
### 2.4 提案
|
|
|
|
提案表示:
|
|
|
|
- 某个参与人针对某个“规则结果”提出的分数调整建议
|
|
|
|
提案的本质是:
|
|
|
|
- 绑定到具体 `rule_result`
|
|
- 以“增量分值”表达加分或扣分
|
|
|
|
### 2.5 投票
|
|
|
|
投票表示:
|
|
|
|
- 任务成员对某条提案的同意或反对
|
|
|
|
投票只作用于提案状态流转,不直接改文档状态。
|
|
|
|
## 3. 不可搞错的几个真相源
|
|
|
|
实现阶段最容易尴尬的就是“到底哪张表是真相源”。
|
|
|
|
这里必须定死:
|
|
|
|
### 3.1 文档真相源
|
|
|
|
- `leaudit_documents`
|
|
|
|
### 3.2 文档文件与版本真相源
|
|
|
|
- `leaudit_document_files`
|
|
- `leaudit_documents.versionGroupKey`
|
|
- `leaudit_documents.versionNo`
|
|
- `leaudit_documents.previousVersionId`
|
|
- `leaudit_documents.rootVersionId`
|
|
- `leaudit_documents.isLatestVersion`
|
|
|
|
### 3.3 机器评查结果真相源
|
|
|
|
- `leaudit_rule_results`
|
|
|
|
### 3.4 人工审核覆盖真相源
|
|
|
|
- `leaudit_review_point_audits`
|
|
|
|
### 3.5 交叉评查协作真相源
|
|
|
|
- `leaudit_cross_review_tasks`
|
|
- `leaudit_cross_review_task_members`
|
|
- `leaudit_cross_review_task_documents`
|
|
- `leaudit_cross_review_proposals`
|
|
- `leaudit_cross_review_votes`
|
|
|
|
## 4. 明确禁止的实现方式
|
|
|
|
以下做法不要再用:
|
|
|
|
### 4.1 不要直接修改 `leaudit_rule_results` 原始分数
|
|
|
|
原因:
|
|
|
|
- 会污染机器评查原始结果
|
|
- 会让重新跑批、调试、审计对账变得混乱
|
|
|
|
### 4.2 不要把交叉评查完成状态写回文档全局状态
|
|
|
|
原因:
|
|
|
|
- 交叉评查完成只是任务内完成
|
|
- 同一文档可能同时出现在多个任务或多个业务视角下
|
|
|
|
### 4.3 不要靠前端自行拼表判断权限
|
|
|
|
原因:
|
|
|
|
- 字段口径容易漂移
|
|
- 任务成员、负责人和创建者语义容易搞混
|
|
- 前端无法作为权限真相源
|
|
|
|
### 4.4 不要再用“同名同类型猜版本”作为主逻辑
|
|
|
|
原因:
|
|
|
|
- 新平台已经有正式版本字段
|
|
- 同名同类型只能作为兼容或兜底
|
|
|
|
## 5. 任务业务逻辑定稿
|
|
|
|
## 5.1 创建任务
|
|
|
|
### 输入
|
|
|
|
- 文档 ID 列表
|
|
- 参与人 ID 列表
|
|
- 主要负责人 ID 列表
|
|
- 任务名称
|
|
- 任务类型
|
|
- 文档类型信息
|
|
|
|
### 必须规则
|
|
|
|
1. `assigner_id` 必须自动加入任务成员集合
|
|
2. 成员列表必须去重
|
|
3. 主要负责人必须同时是任务成员
|
|
4. 所有文档必须真实存在
|
|
5. 所有文档必须可被当前用户纳入交叉评查
|
|
6. 初始状态必须是 `in_progress`
|
|
|
|
### 输出
|
|
|
|
- 任务主记录
|
|
- 任务成员记录
|
|
- 任务文档记录
|
|
|
|
### 注意
|
|
|
|
- 创建任务不等于重新跑评查
|
|
- 默认不触发新的机器评查
|
|
- 如果文档上传接口内包含自动评查,是上传链路行为,不是任务创建行为
|
|
|
|
## 5.2 任务列表
|
|
|
|
任务列表只返回当前用户参与的任务。
|
|
|
|
### 当前用户可见任务的判定
|
|
|
|
用户满足以下任一条件即可见:
|
|
|
|
- 是 `assigner`
|
|
- 是成员表里的 `participant`
|
|
- 是成员表里的 `principal`
|
|
|
|
### 返回字段必须稳定
|
|
|
|
- `task_id`
|
|
- `task_name`
|
|
- `task_status`
|
|
- `doc_type`
|
|
- `task_type`
|
|
- `task_created_at`
|
|
- `progress`
|
|
- `total_documents`
|
|
- `evaluation_region`
|
|
|
|
### 进度定义
|
|
|
|
`progress = 已完成任务文档数 / 任务文档总数 * 100`
|
|
|
|
这里的“已完成”只看:
|
|
|
|
- `leaudit_cross_review_task_documents.audit_status`
|
|
|
|
不看:
|
|
|
|
- `leaudit_documents.audit_status`
|
|
- `leaudit_audit_runs.status`
|
|
- `leaudit_rule_results`
|
|
|
|
## 5.3 任务状态
|
|
|
|
任务状态建议保留三态:
|
|
|
|
- `pending`
|
|
- `in_progress`
|
|
- `completed`
|
|
|
|
但如果当前产品没有明确“待启动”态,实际上可以只使用:
|
|
|
|
- `in_progress`
|
|
- `completed`
|
|
|
|
### 状态流转规则
|
|
|
|
- 创建任务 -> `in_progress`
|
|
- 所有任务内文档完成 -> `completed`
|
|
- 只要还有未完成文档 -> 保持 `in_progress`
|
|
|
|
### 禁止的隐式流转
|
|
|
|
不要因为:
|
|
|
|
- 提案全部投完
|
|
- 文档所有规则点都看过
|
|
- 有人上传了附件
|
|
|
|
就自动把任务置完成。
|
|
|
|
任务完成只能由“所有任务内文档被确认完成”推导出来。
|
|
|
|
## 6. 任务文档业务逻辑定稿
|
|
|
|
## 6.1 文档进入任务
|
|
|
|
文档进入任务只有两种方式:
|
|
|
|
- 创建任务时挂入
|
|
- 向已有任务追加文档
|
|
|
|
### 文档进入任务后默认状态
|
|
|
|
- `audit_status = 0`
|
|
|
|
即:
|
|
|
|
- 新纳入任务的文档,默认都还未完成交叉评查
|
|
|
|
## 6.2 文档完成的真正含义
|
|
|
|
任务内文档“完成”表示:
|
|
|
|
- 负责人或创建者确认:该文档在这个任务内的交叉评查工作结束
|
|
|
|
它不表示:
|
|
|
|
- 文档机器评查已完成
|
|
- 所有人都看过
|
|
- 所有提案都被通过
|
|
- 文档在全局业务上彻底完结
|
|
|
|
## 6.3 谁可以确认文档完成
|
|
|
|
只有:
|
|
|
|
- 任务创建者 `assigner`
|
|
- 主要负责人 `principal`
|
|
|
|
普通 `participant` 不可确认完成。
|
|
|
|
## 6.4 确认完成前的业务建议
|
|
|
|
建议在后端允许确认前做提醒型校验,而不是强阻断:
|
|
|
|
- 文档下是否还有 `pending` 提案
|
|
- 是否存在未投票成员
|
|
|
|
推荐策略:
|
|
|
|
- 默认允许负责人确认完成
|
|
- 如果仍有 `pending` 提案,返回告警信息给前端二次确认
|
|
|
|
这样更贴近真实业务,不会因为个别滞后投票把流程卡死。
|
|
|
|
## 7. 提案业务逻辑定稿
|
|
|
|
## 7.1 提案的最小绑定单位
|
|
|
|
提案必须绑定到:
|
|
|
|
- 一个任务
|
|
- 一个文档
|
|
- 一个规则结果 `rule_result_id`
|
|
|
|
不要只绑定:
|
|
|
|
- 文档 + 评查点名称
|
|
- 文档 + 规则名
|
|
|
|
因为这些都不够稳定。
|
|
|
|
## 7.2 谁可以提案
|
|
|
|
只有该任务成员可以提案。
|
|
|
|
### 必须满足
|
|
|
|
1. 用户是任务成员
|
|
2. 文档属于该任务
|
|
3. `rule_result_id` 属于该文档
|
|
4. 当前提案目标不是已删除/无效结果
|
|
|
|
## 7.3 提案唯一性规则
|
|
|
|
建议规则:
|
|
|
|
- 同一用户对同一任务下同一文档的同一 `rule_result_id`
|
|
- 同时只能存在一条“有效提案”
|
|
|
|
有效提案指:
|
|
|
|
- `pending`
|
|
- `approved`
|
|
- `rejected`
|
|
|
|
如果需要允许历史重提,建议前提是:
|
|
|
|
- 原提案已 `cancelled`
|
|
- 或被软删
|
|
|
|
## 7.4 提案分值规则
|
|
|
|
### 必须禁止
|
|
|
|
- `0` 分提案
|
|
|
|
### 必须校验
|
|
|
|
1. 当前分值已为 `0` 时,不允许继续扣分
|
|
2. 当前分值已达该规则满分时,不允许继续加分
|
|
3. 提案增量应用后,理论结果不能越界
|
|
|
|
边界是:
|
|
|
|
- 最低 `0`
|
|
- 最高该规则满分
|
|
|
|
## 7.5 提案创建后的自动动作
|
|
|
|
提案创建成功后:
|
|
|
|
1. 自动写入一条提案人 `agree` 投票
|
|
2. 立刻触发提案状态判定
|
|
|
|
## 8. 投票业务逻辑定稿
|
|
|
|
## 8.1 谁可以投票
|
|
|
|
只有该任务成员可以投票。
|
|
|
|
### 额外限制
|
|
|
|
- 非成员不能投
|
|
- 已删除提案不能投
|
|
- 已结束提案不能投
|
|
|
|
## 8.2 提案人能否投票
|
|
|
|
建议保留老逻辑:
|
|
|
|
- 提案创建后自动算提案人一票同意
|
|
|
|
这意味着:
|
|
|
|
- 提案人不需要再手动投一次
|
|
- 后续若要改票,只能通过显式撤销/重投逻辑支持
|
|
|
|
## 8.3 投票值
|
|
|
|
推荐只保留:
|
|
|
|
- `agree`
|
|
- `disagree`
|
|
|
|
“取消投票”不作为状态值保存,而是:
|
|
|
|
- 将该投票软删
|
|
|
|
## 8.4 一人一票
|
|
|
|
同一提案、同一成员:
|
|
|
|
- 同一时刻只能存在一条有效投票
|
|
|
|
如重复投票:
|
|
|
|
- 覆盖旧投票类型
|
|
|
|
## 9. 提案状态机定稿
|
|
|
|
提案状态建议固定为:
|
|
|
|
- `pending`
|
|
- `approved`
|
|
- `rejected`
|
|
- `cancelled`
|
|
|
|
## 9.1 创建后的初始状态
|
|
|
|
- `pending`
|
|
|
|
## 9.2 通过规则
|
|
|
|
设任务有效成员数为 `n`
|
|
|
|
通过阈值:
|
|
|
|
- `threshold = floor(n / 2) + 1`
|
|
|
|
若:
|
|
|
|
- `agree >= threshold`
|
|
|
|
则:
|
|
|
|
- `proposal.status = approved`
|
|
|
|
## 9.3 否决规则
|
|
|
|
若:
|
|
|
|
- `disagree >= threshold`
|
|
|
|
则:
|
|
|
|
- `proposal.status = rejected`
|
|
|
|
## 9.4 提前否决规则
|
|
|
|
若:
|
|
|
|
- 即使所有剩余未投票者都改投同意,`agree` 也达不到 `threshold`
|
|
|
|
则:
|
|
|
|
- `proposal.status = rejected`
|
|
|
|
## 9.5 撤销规则
|
|
|
|
只有提案人可撤销,且只允许撤销:
|
|
|
|
- `pending`
|
|
|
|
撤销后:
|
|
|
|
- `status = cancelled`
|
|
- 同时软删或停用关联投票
|
|
|
|
## 9.6 提案终态不可再操作
|
|
|
|
对于:
|
|
|
|
- `approved`
|
|
- `rejected`
|
|
- `cancelled`
|
|
|
|
不允许再继续投票或修改状态。
|
|
|
|
## 10. 分数语义定稿
|
|
|
|
这是实现中最容易“做着做着又回到老系统”的地方,必须说清楚。
|
|
|
|
## 10.1 三层分数
|
|
|
|
新项目详情页涉及三层分数:
|
|
|
|
### 机器分
|
|
|
|
来自:
|
|
|
|
- `leaudit_rule_results`
|
|
|
|
含义:
|
|
|
|
- 规则执行后机器给出的原始结果分值
|
|
|
|
### 人工审核分
|
|
|
|
来自:
|
|
|
|
- `leaudit_review_point_audits`
|
|
|
|
含义:
|
|
|
|
- 人工对规则结果的覆盖性判断
|
|
|
|
### 交叉评查调整分
|
|
|
|
来自:
|
|
|
|
- 所有已通过的 `leaudit_cross_review_proposals`
|
|
|
|
含义:
|
|
|
|
- 协作层加减分累计值
|
|
|
|
## 10.2 推荐最终展示分数计算
|
|
|
|
建议按以下顺序:
|
|
|
|
1. 先计算基础分 `baseScore`
|
|
2. 再叠加交叉评查增量 `crossDelta`
|
|
3. 再做边界裁剪
|
|
|
|
### `baseScore` 计算建议
|
|
|
|
若存在人工审核覆盖:
|
|
|
|
- 按人工审核后的通过/不通过结果计算基础分
|
|
|
|
否则:
|
|
|
|
- 按机器结果计算基础分
|
|
|
|
### `crossDelta`
|
|
|
|
取该 `rule_result_id` 下所有:
|
|
|
|
- `status = approved`
|
|
- `deleted_at is null`
|
|
|
|
提案的 `proposed_score_delta` 累加值
|
|
|
|
### `finalScore`
|
|
|
|
`finalScore = clamp(baseScore + crossDelta, 0, ruleMaxScore)`
|
|
|
|
## 10.3 绝对禁止
|
|
|
|
不要:
|
|
|
|
- 直接更新 `leaudit_rule_results.score`
|
|
- 直接把聚合后的最终分落回机器结果表
|
|
|
|
## 11. 详情页聚合逻辑定稿
|
|
|
|
当前详情页主入口已经是:
|
|
|
|
- `DocumentServiceImpl.GetReviewPoints()`
|
|
|
|
这条主链路可以保留。
|
|
|
|
## 11.1 详情页返回内容应包含
|
|
|
|
- 规则点列表
|
|
- 单点机器分
|
|
- 单点人工审核覆盖结果
|
|
- 单点最终展示分
|
|
- 交叉评查提案列表
|
|
- 文档级统计信息
|
|
|
|
## 11.2 提案列表读取规则
|
|
|
|
提案列表优先从:
|
|
|
|
- `leaudit_cross_review_proposals`
|
|
|
|
读取。
|
|
|
|
如果需要历史兼容,可短期允许:
|
|
|
|
- 新表优先
|
|
- 旧 `cross_scoring_proposals` 兜底
|
|
|
|
但这必须是过渡方案,不是长期方案。
|
|
|
|
## 11.3 文档总分统计
|
|
|
|
文档总分建议由每个规则点的最终展示分累加得到。
|
|
|
|
即:
|
|
|
|
- 总分不是单独存储字段
|
|
- 而是聚合计算结果
|
|
|
|
## 12. 版本归纳逻辑定稿
|
|
|
|
## 12.1 主规则
|
|
|
|
任务文档列表必须优先基于:
|
|
|
|
- `versionGroupKey`
|
|
|
|
归组。
|
|
|
|
### 当前版本判定
|
|
|
|
优先使用:
|
|
|
|
- `isLatestVersion = true`
|
|
|
|
### 历史版本排序
|
|
|
|
按:
|
|
|
|
- `versionNo desc`
|
|
|
|
或时间倒序
|
|
|
|
## 12.2 兜底规则
|
|
|
|
只有在极端兼容场景下,才允许用:
|
|
|
|
- `normalizedName + typeId`
|
|
|
|
做辅助兜底。
|
|
|
|
## 12.3 追加附件后的语义
|
|
|
|
追加附件如果产生新文档,则该新文档:
|
|
|
|
- 应进入同一版本链
|
|
- 自动挂到当前任务
|
|
- 默认 `audit_status = 0`
|
|
|
|
原因:
|
|
|
|
- 新版本进入任务后,需要重新做交叉评查确认
|
|
|
|
## 13. 权限逻辑定稿
|
|
|
|
## 13.1 权限判断分两层
|
|
|
|
### 第一层:RBAC 权限点
|
|
|
|
例如:
|
|
|
|
- `cross_review:task:read`
|
|
- `cross_review:task:create`
|
|
- `cross_review:proposal:create`
|
|
- `cross_review:proposal:vote`
|
|
- `cross_review:document:complete`
|
|
|
|
### 第二层:业务归属校验
|
|
|
|
例如:
|
|
|
|
- 是否是该任务成员
|
|
- 是否是负责人
|
|
- 文档是否属于该任务
|
|
- 规则结果是否属于该文档
|
|
|
|
两层都通过,才允许操作。
|
|
|
|
## 13.2 读权限
|
|
|
|
用户可读某任务,当且仅当:
|
|
|
|
- 是 `assigner`
|
|
- 或是任务成员
|
|
|
|
## 13.3 写权限
|
|
|
|
### 创建提案
|
|
|
|
- 任务成员即可
|
|
|
|
### 投票
|
|
|
|
- 任务成员即可
|
|
|
|
### 撤销提案
|
|
|
|
- 仅提案人
|
|
|
|
### 确认文档完成
|
|
|
|
- 仅 `assigner` 或 `principal`
|
|
|
|
### 上传任务文档 / 追加附件
|
|
|
|
- 建议仅 `assigner` 或 `principal`
|
|
|
|
## 14. 实施时的接口行为定稿
|
|
|
|
## 14.1 后端必须兜住这些错误
|
|
|
|
- 文档不存在
|
|
- 文档不属于任务
|
|
- 规则结果不属于文档
|
|
- 用户不是任务成员
|
|
- 用户不是负责人
|
|
- 提案重复
|
|
- 提案已结束
|
|
- 分值越界
|
|
|
|
## 14.2 后端返回建议
|
|
|
|
每个关键接口尽量返回:
|
|
|
|
- `success`
|
|
- `message`
|
|
- 必要结果对象
|
|
- 必要告警信息
|
|
|
|
例如确认完成时可以带:
|
|
|
|
- `has_pending_proposals`
|
|
- `pending_proposal_count`
|
|
- `has_pending_votes`
|
|
|
|
## 15. 推荐开发顺序
|
|
|
|
为了避免做到一半逻辑漂移,建议严格按以下顺序:
|
|
|
|
### 第一步
|
|
|
|
先落表结构和状态定义。
|
|
|
|
### 第二步
|
|
|
|
实现 service 层纯业务逻辑:
|
|
|
|
- 任务
|
|
- 提案
|
|
- 投票
|
|
- 完成确认
|
|
|
|
### 第三步
|
|
|
|
实现 controller 接口。
|
|
|
|
### 第四步
|
|
|
|
把详情页聚合接到新提案表。
|
|
|
|
### 第五步
|
|
|
|
再做上传、附件、版本链整合。
|
|
|
|
## 16. 最终一句话定稿
|
|
|
|
新项目交叉评查的正确实现方式应该是:
|
|
|
|
- 用 `leaudit_*` 作为文档与评查结果底座
|
|
- 用 `leaudit_cross_review_*` 承接协作层
|
|
- 用聚合计算得到最终展示分
|
|
- 用任务内状态管理完成进度
|
|
- 用后端统一收口权限和状态流转
|
|
|
|
只要这五点不偏,整体实现就不会跑歪。
|
|
|