Files
leaudit-platform-backend/docs/交叉评查/新项目交叉评查详细业务逻辑定稿.md
T

14 KiB

新项目交叉评查详细业务逻辑定稿

目标:把新项目交叉评查的业务逻辑收敛成一份“可直接指导实现”的定稿,重点避免实现阶段把任务状态、评分语义、权限边界、版本逻辑和完成条件做错。

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 写权限

创建提案

  • 任务成员即可

投票

  • 任务成员即可

撤销提案

  • 仅提案人

确认文档完成

  • assignerprincipal

上传任务文档 / 追加附件

  • 建议仅 assignerprincipal

14. 实施时的接口行为定稿

14.1 后端必须兜住这些错误

  • 文档不存在
  • 文档不属于任务
  • 规则结果不属于文档
  • 用户不是任务成员
  • 用户不是负责人
  • 提案重复
  • 提案已结束
  • 分值越界

14.2 后端返回建议

每个关键接口尽量返回:

  • success
  • message
  • 必要结果对象
  • 必要告警信息

例如确认完成时可以带:

  • has_pending_proposals
  • pending_proposal_count
  • has_pending_votes

15. 推荐开发顺序

为了避免做到一半逻辑漂移,建议严格按以下顺序:

第一步

先落表结构和状态定义。

第二步

实现 service 层纯业务逻辑:

  • 任务
  • 提案
  • 投票
  • 完成确认

第三步

实现 controller 接口。

第四步

把详情页聚合接到新提案表。

第五步

再做上传、附件、版本链整合。

16. 最终一句话定稿

新项目交叉评查的正确实现方式应该是:

  • leaudit_* 作为文档与评查结果底座
  • leaudit_cross_review_* 承接协作层
  • 用聚合计算得到最终展示分
  • 用任务内状态管理完成进度
  • 用后端统一收口权限和状态流转

只要这五点不偏,整体实现就不会跑歪。