1
0
forked from erp-dev/erp
Files
erpnew/docs/api_v2_mission_api_2026-07-03.md
2026-07-05 13:53:17 +08:00

776 lines
19 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.
# Mission 任务模块 API 文档
更新日期2026-07-03
本文档面向前端,描述 `mission` 中立任务模块的对外 API。当前 mission API 仅在 `api_v2` 提供;已检查 `api_v1/urls.py``api_v1` 暂无 mission 相关接口。
## 1. 基本约定
- Base URL: `/api/v2`
- 认证:所有 mission 接口均需要登录。
- 当前员工:后端使用 `request.user.employee` 作为当前业务员工。
- 商户隔离:任务、任务分类、参与者、回应均按当前员工所属 `merchant` 隔离。
- 参与者对象:任务参与者是 `basic_info.Employee`,不是 Django `User`
- 删除任务:不提供物理删除任务接口;业务结束请使用取消接口。
- 普通创建/更新接口不允许直接修改状态字段,状态变化必须走独立状态接口。
普通创建/更新禁止提交字段:
| 字段 | 说明 |
|---|---|
| `is_urgent` | 是否紧急,使用 `set-urgent` 接口修改 |
| `is_completed` | 是否完成由结束回应、reopen、reject 流程维护 |
| `is_cancelled` | 是否取消,使用 `cancel` 接口修改 |
| `cancelled_by` | 取消人,由后端写入 |
| `cancelled_at` | 取消时间,由后端写入 |
| `rejected_by` | 回应撤销人,由后端写入 |
| `rejected_at` | 回应撤销时间,由后端写入 |
## 2. API v1 状态
已检查 `api_v1/urls.py``api_v1/views`,当前没有 mission 模块接口。
如前端需要任务模块,请使用本文档中的 `/api/v2/...` 接口。
## 3. 核心数据结构
### 3.1 EmployeePayload
任务模块中所有人员字段均使用员工对象摘要:
```json
{
"id": 21,
"name": "李四",
"merchant_id": 10
}
```
### 3.2 MissionCategory
```json
{
"id": 1,
"merchant": 10,
"name": "通用",
"payload_processor": "",
"speech_enabled": false,
"created_at": "2026-07-03T10:00:00+08:00",
"updated_at": "2026-07-03T10:00:00+08:00"
}
```
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | number | 任务分类 ID |
| `merchant` | number | 所属商户 ID |
| `name` | string | 分类名称,同商户内唯一 |
| `payload_processor` | string | payload 增强器,可为空;当前可选 `structured_description_v1` |
| `speech_enabled` | boolean | 是否启用任务播报 |
| `created_at` | string | 创建时间 |
| `updated_at` | string | 更新时间 |
### 3.3 Mission
```json
{
"id": 1,
"merchant": 10,
"description": "请跟进这张生产单",
"category": 1,
"category_name": "通用",
"is_urgent": false,
"is_completed": false,
"is_cancelled": false,
"notify_if_unreplied": true,
"unreplied_notify_interval_minutes": 30,
"unreplied_notify_max_count": 5,
"unreplied_notify_sent_count": 0,
"unreplied_last_notified_at": null,
"cancelled_at": null,
"creator": {
"id": 20,
"name": "张三",
"merchant_id": 10
},
"cancelled_by": null,
"participants": [
{
"id": 21,
"name": "李四",
"merchant_id": 10
}
],
"content_type": 33,
"content_type_label": "printing.printingorder",
"content_id": 123,
"extra": {
"source": "printing-order"
},
"has_ending_reply": false,
"can_reply": true,
"created_at": "2026-07-03T10:00:00+08:00",
"updated_at": "2026-07-03T10:00:00+08:00"
}
```
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | number | 任务 ID |
| `merchant` | number | 所属商户 ID |
| `description` | string | 任务描述 |
| `category` | number | MissionCategory.id |
| `category_name` | string | 分类名称 |
| `is_urgent` | boolean | 是否紧急 |
| `is_completed` | boolean | 是否完成 |
| `is_cancelled` | boolean | 是否取消 |
| `notify_if_unreplied` | boolean | 是否开启未回复提醒 |
| `unreplied_notify_interval_minutes` | number/null | 未回复提醒间隔,单位分钟 |
| `unreplied_notify_max_count` | number | 未回复最大提醒次数,默认 5 |
| `unreplied_notify_sent_count` | number | 已发送未回复提醒次数 |
| `unreplied_last_notified_at` | string/null | 上一次未回复提醒时间 |
| `cancelled_at` | string/null | 取消时间 |
| `creator` | EmployeePayload | 创建员工 |
| `cancelled_by` | EmployeePayload/null | 取消员工 |
| `participants` | EmployeePayload[] | 最终展开后的任务参与员工列表 |
| `content_type` | number/null | django_content_type.id |
| `content_type_label` | string/null | 可读模型标识,例如 `printing.printingorder` |
| `content_id` | number/null | 关联对象 ID |
| `extra` | object/null | 扩展字段 |
| `has_ending_reply` | boolean | 是否已有结束任务的有效回应 |
| `can_reply` | boolean | 当前是否允许继续回应 |
| `created_at` | string | 创建时间 |
| `updated_at` | string | 更新时间 |
### 3.4 MissionLite
`MissionLite` 用于轻量查询,字段与 `Mission` 基本一致,但不返回 `participants``has_ending_reply``can_reply`
### 3.5 MissionReply
```json
{
"id": 100,
"mission": 1,
"merchant": 10,
"responder": {
"id": 20,
"name": "张三",
"merchant_id": 10
},
"content": "已处理",
"extra": {
"attachment_ids": [1001, 1002]
},
"replied_at": "2026-07-03T10:10:00+08:00",
"ends_task": true,
"is_rejected": false,
"rejected_by": null,
"rejected_at": null,
"created_at": "2026-07-03T10:10:00+08:00",
"updated_at": "2026-07-03T10:10:00+08:00"
}
```
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | number | 回应 ID |
| `mission` | number | Mission.id |
| `merchant` | number | 所属商户 ID |
| `responder` | EmployeePayload | 回应员工 |
| `content` | string | 回应内容 |
| `extra` | object/null | 扩展字段 |
| `replied_at` | string | 回应时间 |
| `ends_task` | boolean | 是否结束任务 |
| `is_rejected` | boolean | 是否已被撤销 |
| `rejected_by` | EmployeePayload/null | 撤销员工 |
| `rejected_at` | string/null | 撤销时间 |
| `created_at` | string | 创建时间 |
| `updated_at` | string | 更新时间 |
### 3.6 MissionWithReplies
`Mission` 结构基础上额外返回 `replies`
```json
{
"id": 1,
"description": "请跟进这张生产单",
"participants": [],
"replies": [
{
"id": 100,
"mission": 1,
"content": "已处理",
"ends_task": true,
"is_rejected": false
}
]
}
```
## 4. 参与者参数规则
任务创建和任务更新支持两类参与者参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| `participant_ids` | number[] | 显式参与员工列表,传 `Employee.id` |
| `employee_type_ids` | number[] | 职位列表,传 `EmployeeType.id`,后端会展开为该职位下的在职员工 |
最终参与者计算规则:
```text
最终参与者 = participant_ids 中的员工 + employee_type_ids 对应职位下的在职员工
```
详细规则:
- `participant_ids` 传的是 `Employee.id`
- `employee_type_ids` 传的是 `EmployeeType.id`
- `employee_type_ids` 只允许传当前商户下的职位。
- 职位展开时只加入 `status=ACTIVE` 的员工。
- 重复员工会自动去重。
- 职位下没有员工不会报错。
- 传入不存在或跨商户的 `EmployeeType.id` 会返回 400。
- `PATCH /missions/{mission_id}/` 中,只要传了 `participant_ids``employee_type_ids`,即表示重新设置最终参与者列表,不是增量追加。
- `PATCH /missions/{mission_id}/` 中,如果两个参数都不传,则不修改原参与者。
清空参与者:
```json
{
"participant_ids": [],
"employee_type_ids": []
}
```
## 5. content_type 规则
`content_type``content_id` 用于把任务挂靠到业务对象上。
- `content_type``django_content_type.id`
- `content_id` 是对应业务对象主键。
- 两者必须同时提交或同时省略。
- 前端不应硬编码 `content_type`,不同环境的 ID 可能不同。
- 推荐通过 `GET /api/v2/content-types/` 获取可用值。
响应中的 `content_type_label` 是只读辅助字段,格式为:
```text
app_label.model
```
例如:
```text
printing.printingorder
business.salesorder
```
当前允许前端用于 mission 关联的业务对象:
| content_type_label | 中文说明 |
|---|---|
| `printing.plateorder` | 开版单 |
| `printing.printingorder` | 印刷单 |
| `printing.printingjob` | 印刷任务 |
| `business.purchaseorder` | 采购单 |
| `business.presalesorder` | 预销售单 |
| `business.salesorder` | 销售单 |
| `business.prepurchaseorder` | 预采购单 |
| `business.purchasereturnorder` | 采购退货单 |
| `business.salesreturnorder` | 销售退货单 |
| `business.paymentorder` | 付款单 |
| `business.receiptorder` | 收款单 |
| `stock.transferorder` | 调拨单 |
| `shipment.shipment` | 发货单 |
| `stateflow.process` | 工艺流程实例 |
## 6. 接口列表
### 6.1 查询可用 content_type
```http
GET /api/v2/content-types/
```
响应:`200 OK`
```json
[
{
"id": 33,
"app_label": "printing",
"model": "printingorder",
"label": "printing.printingorder",
"name": "印刷单"
}
]
```
### 6.2 查询任务分类列表
```http
GET /api/v2/mission-categories/
```
响应:`200 OK`
```json
[
{
"id": 1,
"merchant": 10,
"name": "通用",
"payload_processor": "",
"speech_enabled": false,
"created_at": "2026-07-03T10:00:00+08:00",
"updated_at": "2026-07-03T10:00:00+08:00"
}
]
```
### 6.3 创建任务分类
```http
POST /api/v2/mission-categories/
```
请求体:
```json
{
"name": "生产跟进",
"speech_enabled": true,
"payload_processor": "structured_description_v1"
}
```
请求字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `name` | string | 是 | 分类名称,同商户内唯一 |
| `speech_enabled` | boolean | 否 | 是否启用播报,默认 false |
| `payload_processor` | string | 否 | 可为空;可选 `structured_description_v1` |
响应:`201 Created`,返回 `MissionCategory`
常见错误:
```json
{"name": ["分类名称已存在"]}
```
### 6.4 查询任务分类详情
```http
GET /api/v2/mission-categories/{category_id}/
```
响应:`200 OK`,返回 `MissionCategory`
### 6.5 更新任务分类
```http
PATCH /api/v2/mission-categories/{category_id}/
```
请求体:
```json
{
"name": "售后跟进",
"speech_enabled": false,
"payload_processor": ""
}
```
响应:`200 OK`,返回 `MissionCategory`
### 6.6 删除任务分类
```http
DELETE /api/v2/mission-categories/{category_id}/
```
响应:
- `204 No Content`:删除成功。
- `400 Bad Request`:分类已被任务使用,不能删除。
错误示例:
```json
{"detail": "任务分类已被使用,不能删除"}
```
### 6.7 查询任务列表
```http
GET /api/v2/missions/
```
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `category` | number | 否 | 按任务分类 ID 过滤 |
| `is_urgent` | boolean | 否 | 按是否紧急过滤,支持 `1/0/true/false/yes/no` |
| `is_completed` | boolean | 否 | 按是否完成过滤 |
| `is_cancelled` | boolean | 否 | 按是否取消过滤 |
| `content_type` | number | 否 | 按 content_type ID 过滤 |
| `content_id` | number | 否 | 按关联对象 ID 过滤 |
响应:`200 OK`,返回 `Mission[]`
```json
[
{
"id": 1,
"description": "请跟进这张生产单",
"category": 1,
"category_name": "通用",
"participants": [],
"can_reply": true
}
]
```
### 6.8 创建任务
```http
POST /api/v2/missions/
```
请求体:
```json
{
"description": "请跟进这张生产单",
"category": 1,
"content_type": 33,
"content_id": 123,
"extra": {
"source": "printing-order"
},
"participant_ids": [101, 102],
"employee_type_ids": [3, 4],
"notify_if_unreplied": true,
"unreplied_notify_interval_minutes": 30,
"unreplied_notify_max_count": 5
}
```
请求字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `description` | string | 是 | 任务描述,不能为空 |
| `category` | number | 否 | MissionCategory.id不传则使用默认“通用”分类 |
| `content_type` | number/null | 否 | django_content_type.id`content_id` 必须同时提供或同时省略 |
| `content_id` | number/null | 否 | 关联业务对象 ID`content_type` 必须同时提供或同时省略 |
| `extra` | object/null | 否 | 扩展字段 |
| `participant_ids` | number[] | 否 | 显式参与员工 ID 列表,传 Employee.id |
| `employee_type_ids` | number[] | 否 | 参与职位 ID 列表,传 EmployeeType.id展开为在职员工 |
| `notify_if_unreplied` | boolean | 否 | 是否开启未回复提醒,默认 false |
| `unreplied_notify_interval_minutes` | number/null | 否 | 未回复提醒间隔;开启提醒时必填 |
| `unreplied_notify_max_count` | number | 否 | 最大提醒次数,默认 5 |
响应:`201 Created`,返回 `Mission`
常见错误:
```json
{"detail": "参与者不存在或不属于任务所属商户"}
```
```json
{"detail": "员工职位不存在或不属于任务所属商户"}
```
```json
{"unreplied_notify_interval_minutes": ["开启未回复提醒时必须设置提醒间隔"]}
```
### 6.9 查询任务详情
```http
GET /api/v2/missions/{mission_id}/
```
响应:`200 OK`,返回 `Mission`
### 6.10 更新任务
```http
PATCH /api/v2/missions/{mission_id}/
```
请求体示例:
```json
{
"description": "更新后的任务描述",
"category": 2,
"extra": {
"channel": "wechat"
},
"participant_ids": [101],
"employee_type_ids": [3],
"notify_if_unreplied": true,
"unreplied_notify_interval_minutes": 20,
"unreplied_notify_max_count": 8
}
```
可更新字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `description` | string | 任务描述,不能为空 |
| `category` | number | MissionCategory.id |
| `content_type` | number/null | 与 `content_id` 必须同时提交;可提交 null 清空关联 |
| `content_id` | number/null | 与 `content_type` 必须同时提交;可提交 null 清空关联 |
| `extra` | object/null | 扩展字段;提交 null 可清空 |
| `participant_ids` | number[] | 显式员工参与者列表 |
| `employee_type_ids` | number[] | 按职位展开的参与者列表 |
| `notify_if_unreplied` | boolean | 是否开启未回复提醒 |
| `unreplied_notify_interval_minutes` | number/null | 未回复提醒间隔 |
| `unreplied_notify_max_count` | number | 未回复最大提醒次数 |
响应:`200 OK`,返回 `Mission`
注意:
- `participant_ids``employee_type_ids` 表示重新设置最终参与者列表,不是增量追加。
- 如果两个字段都不传,则不修改参与者。
- 若提交 `content_type``content_id`,两者必须同时出现在请求体中。
- 状态字段不能通过该接口更新。
清空任务关联对象:
```json
{
"content_type": null,
"content_id": null
}
```
清空参与者:
```json
{
"participant_ids": [],
"employee_type_ids": []
}
```
### 6.11 删除任务
```http
DELETE /api/v2/missions/{mission_id}/
```
不支持。
响应:`405 Method Not Allowed`
```json
{"detail": "Mission 删除接口未提供,请使用 cancel 接口取消任务"}
```
### 6.12 按印刷单查询任务
```http
GET /api/v2/missions/by-printing-order/{printing_order_id}/
```
查询指定印刷单关联的任务。
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `category_ids` | number[]/string | 否 | 分类过滤。支持重复参数或逗号分隔,例如 `?category_ids=1,2``?category_ids=1&category_ids=2` |
| `include_details` | boolean | 否 | 是否返回详情。默认 truefalse 时返回 MissionLite[] |
响应:
- `include_details=true` 或未传:`200 OK`,返回 `MissionWithReplies[]`
- `include_details=false``200 OK`,返回 `MissionLite[]`
示例:
```http
GET /api/v2/missions/by-printing-order/123/?category_ids=1,2&include_details=true
```
常见错误:
```json
{"include_details": "必须是布尔值"}
```
```json
{"category_ids": "必须是整数 ID 列表"}
```
### 6.13 查询任务回应列表
```http
GET /api/v2/missions/{mission_id}/replies/
```
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `ends_task` | boolean | 否 | 按是否结束任务过滤 |
| `is_rejected` | boolean | 否 | 按是否已撤销过滤 |
响应:`200 OK`,返回 `MissionReply[]`
### 6.14 创建任务回应
```http
POST /api/v2/missions/{mission_id}/replies/
```
请求体:
```json
{
"content": "已处理完成",
"ends_task": true,
"extra": {
"attachment_ids": [1001, 1002]
}
}
```
请求字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `content` | string | 是 | 回应内容,不能为空 |
| `ends_task` | boolean | 否 | 是否结束任务,默认 false |
| `extra` | object/null | 否 | 扩展字段 |
响应:`201 Created`,返回 `MissionReply`
行为说明:
- 回应人固定为当前登录用户关联的 Employee。
- 如果 `ends_task=true`,任务会被标记为完成。
- 已取消任务或已有未撤销结束回应的任务不可继续回应。
### 6.15 重新打开任务
```http
POST /api/v2/missions/{mission_id}/reopen/
```
权限要求:当前用户需要 Django 权限 `mission.reopen_mission`
响应:`200 OK`,返回 `Mission`
行为说明:
- 用于重新打开已完成任务。
- 会撤销已有结束任务效果,使任务回到未完成状态。
常见错误:
```json
{"detail": "缺少重新打开任务权限"}
```
### 6.16 取消任务
```http
POST /api/v2/missions/{mission_id}/cancel/
```
响应:`200 OK`,返回 `Mission`
行为说明:
- 取消人固定为当前登录用户关联的 Employee。
- 取消后 `is_cancelled=true``cancelled_by``cancelled_at` 由后端写入。
### 6.17 设置任务紧急状态
```http
POST /api/v2/missions/{mission_id}/set-urgent/
```
请求体:
```json
{
"is_urgent": true
}
```
请求字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `is_urgent` | boolean | 是 | 是否紧急 |
响应:`200 OK`,返回 `Mission`
### 6.18 撤销任务回应
```http
POST /api/v2/mission-replies/{reply_id}/reject/
```
权限要求:当前用户需要 Django 权限 `mission.reject_mission_reply`
响应:`200 OK`,返回 `MissionReply`
行为说明:
- 撤销人固定为当前登录用户关联的 Employee。
- 如果撤销的是结束任务回应,任务可能会被重新打开。
常见错误:
```json
{"detail": "缺少撤销任务回应权限"}
```
## 7. 常见状态码
| 状态码 | 场景 |
|---|---|
| `200 OK` | 查询、更新、状态操作成功 |
| `201 Created` | 创建任务、分类、回应成功 |
| `204 No Content` | 删除任务分类成功 |
| `400 Bad Request` | 参数非法、跨商户、业务状态不允许 |
| `401 Unauthorized` | 未登录 |
| `403 Forbidden` | 缺少 `reopen_mission``reject_mission_reply` 等权限 |
| `404 Not Found` | 对象不存在或不属于当前商户 |
| `405 Method Not Allowed` | 不支持的删除任务操作 |
## 8. 前端接入建议
- 创建/更新任务时,`participant_ids` 使用 `Employee.id``employee_type_ids` 使用 `EmployeeType.id`
- 如果只想按职位添加参与者,可以只传 `employee_type_ids`
- 如果 PATCH 不想改变参与者,不要传 `participant_ids``employee_type_ids`
- 如果 PATCH 想清空参与者,两个字段都传空数组。
- `content_type` 不要硬编码,应从 `/api/v2/content-types/` 获取。
- 普通任务编辑页不要提交状态字段,紧急、取消、重新打开、撤销回应都走独立接口。
- 响应中的 `participants` 是后端最终展开后的员工列表,前端展示以该字段为准。
更新日期2026-07-03