2026-09-20-oa-instance-comment-design.md 9.9 KB

OA 审批实例评论(Web 先)设计文档

日期: 2026-09-20
状态: 已批准
关联: docs/superpowers/specs/2026-07-14-oa-approval-dingtalk-design.md(审批意见 ≠ 本能力)
交付策略: Web 管理端先实现 → 同一套 API 再推广到微信小程序 / 移动端


1. 目标与范围

1.1 目标

在 OA 审批实例详情中提供独立「讨论评论」能力:参与人可发表文字评论、引用流程表单中已有附件,并可撤回自己的评论。评论不改变审批结果、不推进流程。

1.2 V1 包含(Web)

能力 说明
发表评论 纯文字,必填,最长 1024 字
引用表单附件 从当前实例 form_data_json 中 upload 类字段勾选已有文件;存引用快照,不复制对象存储文件
撤回自己的评论 仅作者;撤回后时间线保留「已撤回」痕迹
评论列表 详情页独立评论区展示;含正文、引用附件、人、时间
状态门禁 仅实例 status=running 可发表 / 撤回;结束后只读
权限 与「能打开该实例详情」同一套可见性;租户隔离

1.3 V1 明确不做

  • 一键通知审批人 / 抄送人 / 参与人
  • @ 某人
  • 快捷回复词、表情面板
  • 评论区新上传图片 / 附件(仅引用表单已有附件)
  • 楼中楼回复、编辑已发评论
  • 将讨论评论混入审批进度链(OaProgressChain)中的办理意见
  • 本期不交付小程序 UI(接口按双端预留)

1.4 与「审批意见」的边界(SHALL)

审批评论(本能力) 审批意见(现有)
存储 oa_instance_comment oa_task.comment_text / oa_instance_event.comment_text
时机 详情评论区随时(running) 同意 / 拒绝 / 打回 / 加签 / 转交
是否推进流程 否 是
UI 独立「评论」区块 办理弹窗 / 进度链「意见」

禁止把讨论评论写入 OaTask.comment_text。


2. 方案决策

曾对比:独立表 / 复用 oa_instance_event / 塞入实例 JSON。

选定:独立表 oa_instance_comment。

理由:发表 / 撤回 / 列表语义清晰;Web 与小程序共用同一 API;不污染办理事件模型。


3. 数据模型

3.1 表 oa_instance_comment

字段顺序:id → 业务字段 → 公共字段(tenant_no … del_flag),与工程 database-conventions 一致。

字段 类型 说明
id VARCHAR(64) PK UUID
instance_id VARCHAR(64) NOT NULL 所属 oa_instance.id
content VARCHAR(1024) NOT NULL 评论文本(撤回后仍保留原文,便于审计;前端对 revoked 不展示正文或仅展示占位)
attachment_refs_json TEXT NULL 引用快照 JSON 数组,见 §3.2
status VARCHAR(16) NOT NULL normal | revoked,默认 normal
revoked_time DATETIME NULL 撤回时间
tenant_no … 公共
order_num … 公共
remark … 公共
create_by … 评论作者 user_id(与现网一致用 create_by)
create_dept_id … 公共
create_time … 发表时间
update_by / update_time … 撤回时更新
del_flag … 默认 0;列表默认排除硬删;撤回用 status=revoked,不靠 del_flag 表示撤回

索引建议:(tenant_no, instance_id, create_time)。

SQL:新增迁移脚本 docs/sql/migrations/2026-09-20-oa-instance-comment.sql,并按仓库惯例评估是否同步全量 docs/sql/zkxw.sql。

3.2 attachment_refs_json 结构

数组元素:

{
  "fieldKey": "protocolFiles",
  "fieldLabel": "协议附件",
  "url": "https://minio.example/.../xx.pdf",
  "fileName": "协议.pdf"
}
  • 发表时服务端根据实例 form_schema_json + form_data_json 校验:每个 url 必须出现在该实例某一 upload 类字段的当前值中(规范化逻辑对齐 oa_related_approval._extract_upload_urls)。
  • 校验失败 → 400,中文错误(如「引用的附件不存在或不属于本表单」)。
  • 快照在发表时刻固化;之后表单数据变更不影响已发表评论中的展示名 / url(若对象存储文件被删导致 404,属存储层问题,本期不处理)。

Upload 类字段类型(与现网一致):upload / fcUpload / image / oaAttachment。


4. API 设计

前缀:现有 /web/admin/oa-approval(与 backend/app/api/oa_approval.py 一致)。

权限:调用方须通过与 GET /instances/{instance_id} 相同的实例可见性校验;所有查询 / 写入强制 tenant_no = 当前用户租户。

4.1 可引用附件列表

GET /instances/{instance_id}/form-attachments

响应概念:

{
  "items": [
    {
      "fieldKey": "protocolFiles",
      "fieldLabel": "协议附件",
      "files": [
        { "url": "...", "fileName": "协议.pdf" }
      ]
    }
  ]
}

无附件时 items: []。

4.2 评论列表

GET /instances/{instance_id}/comments

  • 返回该实例全部评论(含 revoked),按 create_time 升序。
  • 每条含:id、content、attachmentRefs、status、revokedTime、createBy、createByName(若详情已有用户名投影则对齐)、createTime、canRevoke(当前用户且 status=normal 且实例 running)。

另:GET /instances/{instance_id} 详情响应 SHALL 增加字段 comments(结构同列表项数组),Web 首屏少一次请求;独立列表接口供刷新 / 小程序复用。

4.3 发表评论

POST /instances/{instance_id}/comments

Body:

{
  "content": "请补充第二页签字页",
  "attachmentRefs": [
    { "fieldKey": "protocolFiles", "url": "...", "fileName": "协议.pdf" }
  ]
}

规则:

  • content trim 后非空,长度 ≤ 1024。
  • attachmentRefs 可选;有则服务端补全 / 校验 fieldLabel,拒绝非法 url。
  • 仅 oa_instance.status == running;否则 400「仅审批进行中可评论」。
  • 成功返回新建评论对象(同列表项)。

4.4 撤回评论

POST /instances/{instance_id}/comments/{comment_id}/revoke

规则:

  • 评论存在、未删、属于该实例与租户。
  • 仅 create_by == 当前用户。
  • 仅 status == normal;已撤回 → 幂等成功或 400(实现选一种,建议 幂等成功)。
  • 仅实例 running;结束后不可撤回。
  • 置 status=revoked、revoked_time=now,更新 update_by / update_time。
  • 不物理删除,不清空 content / attachment_refs_json(审计保留;UI 对 revoked 不展示正文与附件)。

5. 后端实现要点

位置 说明
backend/app/models/models.py 新增 OaInstanceComment
backend/app/schemas/ CreateBody / 列表项 Pydantic(camelCase 对外)
backend/app/crud/ 或 services/ 列表、发表校验、撤回;附件枚举可放 oa_related_approval 旁或 oa_instance_comment 服务模块
backend/app/api/oa_approval.py 注册上述 4 个路由;详情组装注入 comments
字段一致性 _row_to_dict 为可读字段真相源;CreateBody 与之对齐可写字段

6. Web 管理端 UI

6.1 页面

  • 主入口:web-admin/src/views/oa-approval/instances/detail.vue
  • 若存在详情弹窗复用同一详情数据源:OaInstanceDetailDialog.vue 等亦需挂载同一面板(与现网详情入口对齐,避免漏入口)。

6.2 组件

新建 web-admin/src/components/oa-approval/OaCommentPanel.vue(建议):

  1. 列表区:升序;normal 展示头像/姓名、时间、正文、引用附件芯片(预览/下载对齐现有附件交互);revoked 展示灰条「{姓名} 撤回了一条评论」+ 撤回时间。
  2. 操作:本人 + normal + 实例 running → 「撤回」按钮(需确认框)。
  3. 输入区(仅 running):el-input textarea;「引用附件」打开多选(数据来自 form-attachments);「发送」。
  4. 按钮带图标(ui-conventions);遮罩无 blur;文案中文。

视觉:Fresh Mint;独立 section,不要并入 OaProgressChain。

2026-09-21 更新(钉钉对齐):展示已改为并入审批进度时间线,见 docs/superpowers/specs/2026-09-21-oa-comment-in-progress-timeline-design.md。OaCommentPanel 仅保留底部输入;列表/撤回在 OaProgressChain(kind=comment)。

6.3 API 模块

web-admin/src/api/oaApproval.ts 增加对应 fetch 方法。


7. 移动端 / 小程序(二期,本期仅约束)

  • V1 不实现小程序页面。
  • API 不得依赖仅 Web 才有的请求头或 Element 专用结构;响应字段稳定、camelCase。
  • 二期在微信小程序审批详情复用:form-attachments / comments CRUD;UI 遵循 docs/miniprogram-ui-spec-sanqiang.md。
  • 现有 mobile-oa-h5 若与小程序并行,二期可择一或双端同接,不在本设计展开。

8. 验收标准

  1. 有详情可见权限的用户,在 running 实例详情可发表非空文字评论。
  2. 可多选引用表单附件;非法 url 被拒绝;评论区可预览/下载引用文件。
  3. 作者可撤回自己的 normal 评论;他人不可撤;撤回后见「已撤回」占位,正文与附件对用户隐藏。
  4. 非 running(completed / rejected / cancelled / returned 等)不可发、不可撤,仍可读历史评论。
  5. 同意/拒绝意见与进度链行为与改前一致。
  6. 租户隔离:不可读写他租户实例评论。

9. 非目标与后续

  • 评论通知、@、新上传附件、楼中楼 → 另开变更。
  • 小程序 UI → V2。
  • 是否把评论摘要打进打印 PDF → 不在 V1。

10. 实现顺序建议

  1. SQL + Model + Schema
  2. 服务:附件枚举、发表校验、撤回、列表
  3. API 路由 + 详情注入 comments
  4. 前端 API + OaCommentPanel + 挂到详情页
  5. 手工验收(Web)
  6. (可选)./scripts/update-graph.sh backend / frontend