Files
cn-rdms/docs/superpowers/specs/2026-05-22-ticket-design.md
2026-05-22 13:46:01 +08:00

356 lines
15 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.

# 工单需求规格说明
日期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` 资源入口,后端接口落地时需要与前端路由和菜单权限同步。