Merge branch 'main' of http://192.168.1.22:3000/Microservice/cn-rdms
This commit is contained in:
355
docs/superpowers/specs/2026-05-22-ticket-design.md
Normal file
355
docs/superpowers/specs/2026-05-22-ticket-design.md
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
# 工单需求规格说明
|
||||||
|
|
||||||
|
日期:2026-05-22
|
||||||
|
|
||||||
|
## 1. 背景
|
||||||
|
|
||||||
|
`rdms-project` 当前承载项目、产品、需求、执行、任务等核心交付对象。现有代码中产品需求、项目需求已经具备 `sourceType` / `sourceBizId` 来源字段,可以承接来自工单的需求派生关系;执行和任务也已经形成“项目需求 -> 执行 -> 任务”的后续交付链路。
|
||||||
|
|
||||||
|
本需求新增内部工单能力。工单作为独立业务对象存在,不复用需求、执行或任务主表。工单用于记录内部用户提交的诉求,经工单负责人受理后,可按归属类型派生产品需求或项目需求,并通过现有需求链路继续流转到执行、任务。
|
||||||
|
|
||||||
|
## 2. 目标
|
||||||
|
|
||||||
|
1. 支持内部用户创建普通工单或父工单。
|
||||||
|
2. 支持父工单逐步拆分子工单,父工单只汇总,不直接处理。
|
||||||
|
3. 支持普通工单、子工单作为最小处理单位,由指定工单负责人受理、拒绝、处理和关闭。
|
||||||
|
4. 支持工单单归属到一个产品或一个项目。
|
||||||
|
5. 支持产品工单派生产品需求、项目工单派生项目需求。
|
||||||
|
6. 支持有派生需求的工单在全部需求完成后自动关闭。
|
||||||
|
7. 支持无派生需求的工单由工单负责人手动关闭。
|
||||||
|
|
||||||
|
## 3. 非目标
|
||||||
|
|
||||||
|
1. 本期不做外部客户工单,不保留 `sourceChannel`、`externalCustomerName`、`externalContact` 等外部来源字段。
|
||||||
|
2. 本期不做工单编号 `ticketNo`。
|
||||||
|
3. 工单不能直接派生执行或任务。
|
||||||
|
4. 父工单不能受理、拒绝、派生需求或手动关闭。
|
||||||
|
5. 本期不引入流程引擎,不做可配置审批流。
|
||||||
|
6. 本期不自动判断工单是否涉及多个产品/项目,也不自动判断归属产品或项目;这些由录入人员人工选择。
|
||||||
|
|
||||||
|
## 4. 核心概念
|
||||||
|
|
||||||
|
### 4.1 工单形态
|
||||||
|
|
||||||
|
使用 `ticketMode` 表达工单形态:
|
||||||
|
|
||||||
|
| 值 | 含义 | 是否可处理 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `single` | 普通工单 | 是 | 单归属工单,不挂父工单 |
|
||||||
|
| `parent` | 父工单 | 否 | 原始诉求汇总单,可持续拆分子工单 |
|
||||||
|
| `child` | 子工单 | 是 | 挂在父工单下的最小处理单位 |
|
||||||
|
|
||||||
|
不使用 `isParent`。`ticketMode` 比布尔字段更准确,可以区分普通工单、父工单和子工单,避免在父工单尚未创建子工单时无法识别其形态。
|
||||||
|
|
||||||
|
### 4.2 归属类型
|
||||||
|
|
||||||
|
普通工单和子工单必须单归属:
|
||||||
|
|
||||||
|
| `belongType` | 归属对象 | 可派生对象 | 后续链路 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `product` | 一个产品 | 产品需求 | 产品需求 -> 指派项目 -> 项目需求 -> 执行 -> 任务 |
|
||||||
|
| `project` | 一个项目 | 项目需求 | 项目需求 -> 执行 -> 任务 |
|
||||||
|
|
||||||
|
父工单不填写 `belongType`、`productId`、`projectId`。
|
||||||
|
|
||||||
|
## 5. 业务流程
|
||||||
|
|
||||||
|
### 5.1 总流程图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A([开始]) --> B[录入人员创建工单]
|
||||||
|
B --> C{ticketMode}
|
||||||
|
|
||||||
|
C -->|parent| P1[父工单: splitting]
|
||||||
|
P1 --> P2[列表操作列拆分子工单]
|
||||||
|
P2 --> C1[创建子工单: pending_accept]
|
||||||
|
P1 --> P3{是否存在子工单且全部为终态}
|
||||||
|
P3 -->|否| P1
|
||||||
|
P3 -->|是| P4[父工单: closed]
|
||||||
|
P4 --> Z([结束])
|
||||||
|
|
||||||
|
C -->|single| S1[普通工单: pending_accept]
|
||||||
|
C1 --> S2[工单负责人待处理]
|
||||||
|
S1 --> S2
|
||||||
|
S2 --> D{是否受理}
|
||||||
|
D -->|否| R[工单: rejected]
|
||||||
|
R --> Z
|
||||||
|
D -->|是| E[工单: processing]
|
||||||
|
|
||||||
|
E --> F{是否派生需求}
|
||||||
|
F -->|否| G[负责人填写处理结论并手动关闭]
|
||||||
|
G --> H[工单: closed]
|
||||||
|
H --> I[触发父工单汇总检查]
|
||||||
|
I --> Z
|
||||||
|
|
||||||
|
F -->|是| J{归属类型}
|
||||||
|
J -->|product| K[派生一个或多个产品需求]
|
||||||
|
J -->|project| L[派生一个或多个项目需求]
|
||||||
|
K --> M[等待派生需求全部完成]
|
||||||
|
L --> M
|
||||||
|
M --> N{全部派生需求完成}
|
||||||
|
N -->|否| E
|
||||||
|
N -->|是| O[系统自动关闭工单: closed]
|
||||||
|
O --> I
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 父工单流程
|
||||||
|
|
||||||
|
1. 录入人员创建父工单,状态为 `splitting`。
|
||||||
|
2. 父工单只记录原始诉求和附件,不进入处理队列。
|
||||||
|
3. 工单列表查询的操作列为父工单提供“拆分子工单”入口。
|
||||||
|
4. 录入人员可以持续新增子工单。
|
||||||
|
5. 父工单至少存在一个子工单,且所有子工单均进入终态后,系统自动关闭父工单。
|
||||||
|
6. 父工单关闭后不再作为处理对象,但可继续作为历史汇总查看。
|
||||||
|
|
||||||
|
### 5.3 普通工单 / 子工单流程
|
||||||
|
|
||||||
|
1. 创建后进入 `pending_accept`。
|
||||||
|
2. 工单负责人判断是否受理。
|
||||||
|
3. 不受理则进入 `rejected`,需要填写拒绝原因。
|
||||||
|
4. 受理后进入 `processing`。
|
||||||
|
5. 处理中可以派生需求,也可以在无派生需求时填写处理结论并手动关闭。
|
||||||
|
6. 一旦存在派生需求,工单不能手动关闭,必须等待全部派生需求完成后自动关闭。
|
||||||
|
|
||||||
|
## 6. 状态模型
|
||||||
|
|
||||||
|
### 6.1 父工单状态
|
||||||
|
|
||||||
|
| 状态 | 含义 | 进入方式 | 退出方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `splitting` | 拆分中 / 汇总中 | 创建父工单 | 所有子工单终态后自动关闭 |
|
||||||
|
| `closed` | 已关闭 | 系统自动关闭 | 终态 |
|
||||||
|
|
||||||
|
父工单不允许进入 `pending_accept`、`rejected`、`processing`。
|
||||||
|
|
||||||
|
### 6.2 普通工单 / 子工单状态
|
||||||
|
|
||||||
|
| 状态 | 含义 | 进入方式 | 退出方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `pending_accept` | 待受理 | 创建普通工单或子工单 | 受理或拒绝 |
|
||||||
|
| `rejected` | 已拒绝 | 工单负责人拒绝 | 终态 |
|
||||||
|
| `processing` | 处理中 | 工单负责人受理 | 手动关闭或自动关闭 |
|
||||||
|
| `closed` | 已关闭 | 手动关闭或派生需求全部完成后自动关闭 | 终态 |
|
||||||
|
|
||||||
|
终态包括 `rejected`、`closed`。
|
||||||
|
|
||||||
|
### 6.3 自动关闭规则
|
||||||
|
|
||||||
|
1. 普通工单 / 子工单存在派生需求时,只有全部派生需求完成后才自动关闭。
|
||||||
|
2. 普通工单 / 子工单不存在派生需求时,允许工单负责人手动关闭,必须填写 `closeResult`。
|
||||||
|
3. 父工单至少存在一个子工单,且所有子工单均为 `rejected` 或 `closed` 后,自动关闭。
|
||||||
|
4. 父工单不直接检查需求完成情况,只汇总子工单终态。
|
||||||
|
|
||||||
|
## 7. 数据模型
|
||||||
|
|
||||||
|
### 7.1 工单主表
|
||||||
|
|
||||||
|
建议表名:`rdms_ticket`
|
||||||
|
|
||||||
|
| 字段 | 类型建议 | 必填规则 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `id` | `bigint` | 是 | 主键 |
|
||||||
|
| `parent_id` | `bigint` | 否 | 父工单 ID;`child` 必填 |
|
||||||
|
| `ticket_mode` | `varchar(32)` | 是 | `single` / `parent` / `child` |
|
||||||
|
| `title` | `varchar(255)` | 是 | 工单标题 |
|
||||||
|
| `description` | `text` | 否 | 工单描述,支持富文本 |
|
||||||
|
| `ticket_type` | `varchar(32)` | 是 | 工单类型,字典 |
|
||||||
|
| `priority` | `varchar(32)` | 否 | 优先级,建议复用需求优先级字典 |
|
||||||
|
| `status_code` | `varchar(32)` | 是 | 工单状态 |
|
||||||
|
| `submitter_id` | `bigint` | 是 | 提交人用户 ID |
|
||||||
|
| `submitter_nickname` | `varchar(64)` | 否 | 提交人昵称快照 |
|
||||||
|
| `owner_id` | `bigint` | `single` / `child` 必填 | 工单负责人用户 ID |
|
||||||
|
| `owner_nickname` | `varchar(64)` | 否 | 工单负责人昵称快照 |
|
||||||
|
| `belong_type` | `varchar(32)` | `single` / `child` 必填 | `product` / `project` |
|
||||||
|
| `product_id` | `bigint` | 产品工单必填 | 归属产品 ID |
|
||||||
|
| `project_id` | `bigint` | 项目工单必填 | 归属项目 ID |
|
||||||
|
| `accept_time` | `datetime` | 否 | 受理时间 |
|
||||||
|
| `reject_reason` | `varchar(500)` | 拒绝时必填 | 拒绝原因 |
|
||||||
|
| `close_time` | `datetime` | 否 | 关闭时间 |
|
||||||
|
| `close_result` | `varchar(1000)` | 手动关闭时必填 | 处理结论 |
|
||||||
|
| `attachments` | `json` | 否 | 附件列表,沿用 `AttachmentItem` |
|
||||||
|
|
||||||
|
审计字段、逻辑删除字段复用现有 `BaseDO` 风格。
|
||||||
|
|
||||||
|
### 7.2 工单需求关联表
|
||||||
|
|
||||||
|
建议表名:`rdms_ticket_requirement_link`
|
||||||
|
|
||||||
|
| 字段 | 类型建议 | 必填规则 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `id` | `bigint` | 是 | 主键 |
|
||||||
|
| `ticket_id` | `bigint` | 是 | 工单 ID |
|
||||||
|
| `target_type` | `varchar(32)` | 是 | `product_requirement` / `project_requirement` |
|
||||||
|
| `target_id` | `bigint` | 是 | 需求 ID |
|
||||||
|
|
||||||
|
不设置 `relationType`。本期关联表只表达“该工单派生了哪些需求”。
|
||||||
|
|
||||||
|
建议唯一约束:`ticket_id + target_type + target_id`。
|
||||||
|
|
||||||
|
## 8. 需求派生规则
|
||||||
|
|
||||||
|
### 8.1 产品工单派生产品需求
|
||||||
|
|
||||||
|
适用条件:
|
||||||
|
|
||||||
|
1. `ticketMode` 为 `single` 或 `child`。
|
||||||
|
2. `belongType = product`。
|
||||||
|
3. 工单状态为 `processing`。
|
||||||
|
|
||||||
|
派生结果:
|
||||||
|
|
||||||
|
1. 创建 `ProductRequirementDO`。
|
||||||
|
2. `productId` 使用工单 `productId`。
|
||||||
|
3. `sourceType = "work_order"`。
|
||||||
|
4. `sourceBizId = ticketId`。
|
||||||
|
5. 标题、描述、优先级、附件、提出人等字段可从工单带入,并允许派生表单二次编辑。
|
||||||
|
6. 写入 `rdms_ticket_requirement_link`,`targetType = product_requirement`。
|
||||||
|
|
||||||
|
后续链路沿用现有产品需求分发到项目的能力。
|
||||||
|
|
||||||
|
### 8.2 项目工单派生项目需求
|
||||||
|
|
||||||
|
适用条件:
|
||||||
|
|
||||||
|
1. `ticketMode` 为 `single` 或 `child`。
|
||||||
|
2. `belongType = project`。
|
||||||
|
3. 工单状态为 `processing`。
|
||||||
|
|
||||||
|
派生结果:
|
||||||
|
|
||||||
|
1. 创建 `ProjectRequirementDO`。
|
||||||
|
2. `projectId` 使用工单 `projectId`。
|
||||||
|
3. `sourceType = "work_order"`。
|
||||||
|
4. `sourceBizId = ticketId`。
|
||||||
|
5. 标题、描述、优先级、附件、提出人等字段可从工单带入,并允许派生表单二次编辑。
|
||||||
|
6. 写入 `rdms_ticket_requirement_link`,`targetType = project_requirement`。
|
||||||
|
|
||||||
|
后续执行和任务沿用现有项目需求、执行、任务链路。
|
||||||
|
|
||||||
|
### 8.3 完成判定
|
||||||
|
|
||||||
|
1. 产品工单只检查由该工单派生的产品需求。
|
||||||
|
2. 项目工单只检查由该工单派生的项目需求。
|
||||||
|
3. 需求完成态应复用现有需求状态模型的终态配置,不在工单逻辑中写死具体状态码。
|
||||||
|
4. 派生需求数量大于 0 且全部完成时,系统自动关闭对应工单。
|
||||||
|
|
||||||
|
## 9. 页面与待办
|
||||||
|
|
||||||
|
### 9.1 我的提交
|
||||||
|
|
||||||
|
展示当前用户提交的工单,包括:
|
||||||
|
|
||||||
|
1. 父工单。
|
||||||
|
2. 普通工单。
|
||||||
|
3. 子工单。
|
||||||
|
|
||||||
|
建议支持按状态、工单类型、归属类型、归属对象、创建时间筛选。
|
||||||
|
|
||||||
|
### 9.2 我的待处理
|
||||||
|
|
||||||
|
展示当前用户负责的普通工单和子工单,不展示父工单。
|
||||||
|
|
||||||
|
筛选条件:
|
||||||
|
|
||||||
|
1. `ownerId = 当前用户`。
|
||||||
|
2. `ticketMode in (single, child)`。
|
||||||
|
3. `statusCode in (pending_accept, processing)`。
|
||||||
|
|
||||||
|
### 9.3 父工单列表操作
|
||||||
|
|
||||||
|
工单列表查询中,父工单行需要在操作列展示“拆分子工单”入口。该入口只对父工单提交人或具备工单管理权限的用户可见。
|
||||||
|
|
||||||
|
点击后进入新增子工单表单,表单需要携带父工单上下文,并要求录入人员填写子工单的归属类型、归属产品/项目、工单负责人、工单类型等处理字段。
|
||||||
|
|
||||||
|
### 9.4 父工单汇总展示
|
||||||
|
|
||||||
|
父工单汇总展示需要包含:
|
||||||
|
|
||||||
|
1. 原始诉求信息。
|
||||||
|
2. 子工单列表。
|
||||||
|
3. 子工单归属产品/项目。
|
||||||
|
4. 子工单负责人。
|
||||||
|
5. 子工单状态。
|
||||||
|
6. 子工单派生需求数量与完成数量。
|
||||||
|
7. 父工单自动关闭结果。
|
||||||
|
|
||||||
|
## 10. 权限规则
|
||||||
|
|
||||||
|
1. 创建工单走全域权限。
|
||||||
|
2. 父工单新增子工单:父工单提交人或具备工单管理权限的用户可操作。
|
||||||
|
3. 普通工单 / 子工单受理、拒绝、手动关闭:工单负责人或具备工单管理权限的用户可操作。
|
||||||
|
4. 派生产品需求时,需要满足产品对象权限。
|
||||||
|
5. 派生项目需求时,需要满足项目对象权限。
|
||||||
|
6. 父工单不能执行受理、拒绝、派生需求、手动关闭动作。
|
||||||
|
|
||||||
|
## 11. 接口建议
|
||||||
|
|
||||||
|
接口路径建议落在 `rdms-project` 模块:
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `POST` | `/project/tickets` | 创建普通工单或父工单 |
|
||||||
|
| `POST` | `/project/tickets/{parentId}/children` | 父工单新增子工单 |
|
||||||
|
| `GET` | `/project/tickets/my-submitted/page` | 我的提交 |
|
||||||
|
| `GET` | `/project/tickets/my-pending/page` | 我的待处理 |
|
||||||
|
| `GET` | `/project/tickets/{id}` | 工单详情 |
|
||||||
|
| `POST` | `/project/tickets/{id}/accept` | 受理 |
|
||||||
|
| `POST` | `/project/tickets/{id}/reject` | 拒绝 |
|
||||||
|
| `POST` | `/project/tickets/{id}/close` | 无派生需求时手动关闭 |
|
||||||
|
| `POST` | `/project/tickets/{id}/derive-product-requirement` | 派生产品需求 |
|
||||||
|
| `POST` | `/project/tickets/{id}/derive-project-requirement` | 派生项目需求 |
|
||||||
|
|
||||||
|
创建、更新类接口需要继续遵守仓库 HTTP 动词语义约定。部分动作使用语义化 `POST` 子动作接口,不引入 `PATCH`。
|
||||||
|
|
||||||
|
## 12. 校验规则
|
||||||
|
|
||||||
|
1. `ticketMode = parent` 时,`ownerId`、`belongType`、`productId`、`projectId` 必须为空。
|
||||||
|
2. `ticketMode = single` 时,`ownerId`、`belongType` 必填,且 `productId` / `projectId` 按归属类型二选一。
|
||||||
|
3. `ticketMode = child` 时,`parentId`、`ownerId`、`belongType` 必填,且父工单必须存在且 `ticketMode = parent`。
|
||||||
|
4. 产品工单必须填写 `productId`,不能填写 `projectId`。
|
||||||
|
5. 项目工单必须填写 `projectId`,不能填写 `productId`。
|
||||||
|
6. 只有 `pending_accept` 状态的普通工单 / 子工单可以受理或拒绝。
|
||||||
|
7. 只有 `processing` 状态的普通工单 / 子工单可以派生需求。
|
||||||
|
8. 有派生需求的工单不能手动关闭。
|
||||||
|
9. 无派生需求的 `processing` 工单可以手动关闭,必须填写 `closeResult`。
|
||||||
|
10. 父工单不能手动关闭。
|
||||||
|
11. 父工单至少有一个子工单,且全部子工单终态后才能自动关闭。
|
||||||
|
|
||||||
|
## 13. 错误处理
|
||||||
|
|
||||||
|
建议新增明确错误码覆盖以下场景:
|
||||||
|
|
||||||
|
1. 工单不存在。
|
||||||
|
2. 工单形态非法。
|
||||||
|
3. 父工单不能处理。
|
||||||
|
4. 子工单父级非法。
|
||||||
|
5. 工单归属类型非法。
|
||||||
|
6. 产品工单不能派生项目需求。
|
||||||
|
7. 项目工单不能派生产品需求。
|
||||||
|
8. 工单状态不允许当前动作。
|
||||||
|
9. 有派生需求的工单不能手动关闭。
|
||||||
|
10. 派生需求未全部完成,工单不能自动关闭。
|
||||||
|
|
||||||
|
## 14. 测试重点
|
||||||
|
|
||||||
|
1. 创建父工单后状态为 `splitting`,且不能受理、拒绝、派生需求、手动关闭。
|
||||||
|
2. 父工单可以持续新增多个子工单。
|
||||||
|
3. 子工单必须单归属产品或项目。
|
||||||
|
4. 普通工单 / 子工单创建后进入 `pending_accept`。
|
||||||
|
5. 工单负责人拒绝后进入 `rejected`。
|
||||||
|
6. 工单负责人受理后进入 `processing`。
|
||||||
|
7. 无派生需求的 `processing` 工单可手动关闭。
|
||||||
|
8. 有派生需求的工单不能手动关闭。
|
||||||
|
9. 产品工单只能派生产品需求。
|
||||||
|
10. 项目工单只能派生项目需求。
|
||||||
|
11. 派生需求全部完成后自动关闭工单。
|
||||||
|
12. 所有子工单终态后自动关闭父工单。
|
||||||
|
|
||||||
|
## 15. 风险与约束
|
||||||
|
|
||||||
|
1. 自动关闭依赖需求完成态判定,实施时必须和现有需求状态模型对齐。
|
||||||
|
2. 产品需求分发到项目后的项目需求、执行、任务链路不属于工单直接职责,工单只追踪自己直接派生的需求。
|
||||||
|
3. 父工单允许持续补子工单,会带来“父工单已关闭后是否允许继续补子单”的边界。本规格默认父工单关闭后不再补子单;如需重开父工单,需要单独设计重开动作。
|
||||||
|
4. 现有前端已有 `/ticket/my-submitted`、`/ticket/my-pending` 资源入口,后端接口落地时需要与前端路由和菜单权限同步。
|
||||||
|
|
||||||
Reference in New Issue
Block a user