Files
zkxw/docs/superpowers/specs/2026-09-20-oa-instance-comment-design.md
T
2026-09-24 09:59:26 +08:00

256 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 结构
数组元素:
```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`
响应概念:
```json
{
"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:
```json
{
"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`