256 lines
9.9 KiB
Markdown
256 lines
9.9 KiB
Markdown
# 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`
|