1
0
forked from erp-dev/erp
Files
erpnew/docs/api_v2_mission_api.md
2026-04-13 12:30:33 +08:00

438 lines
11 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.
# API v2 Mission 任务模块接口文档
本文档面向前端,描述 `mission` 中立任务模块的 API。
## 基本约定
- Base URL: `/api/v2`
- 认证:所有接口都需要登录。
- 人员身份:后端使用 `request.user.employee` 作为当前员工身份。
- 多商户隔离:所有任务、任务分类、参与者、回应都只允许访问当前员工所属商户的数据。
- 删除能力:不提供任务删除接口;需要结束业务时使用 `cancel` 取消任务。
- 状态字段:普通创建/更新接口不允许修改状态字段,状态变化统一走独立接口。
普通 CRUD 禁止提交这些字段:
| 字段 | 说明 |
|------|------|
| `is_urgent` | 是否紧急,使用 `set-urgent` 接口修改 |
| `is_completed` | 是否完成,由结束回应或 reopen/reject 流程维护 |
| `is_cancelled` | 是否取消,使用 `cancel` 接口修改 |
| `cancelled_by` | 取消人,由后端写入 |
| `cancelled_at` | 取消时间,由后端写入 |
| `rejected_by` | 撤销人,由后端写入 |
| `rejected_at` | 撤销时间,由后端写入 |
## content_type 说明
`content_type``content_id` 是可选的"关联业务对象"字段,用于把任务挂靠到系统中的某个具体业务单据或对象上。
### content_type 是什么
`content_type` 是一个整数,对应后端数据库 `django_content_type` 表的主键(`id`)。
该表记录了系统中所有 Django 模型的元信息,每一行代表一个模型,格式为 `app_label` + `model`
**前端不应硬编码 content_type 整数 ID**,因为这个 ID 在不同部署环境中可能不同。
正确做法:调用 `/api/v2/content-types/` 接口(见下方"查询可用 content_type"一节)获取当前环境的实际 ID。
### content_type_label 字段
响应体中的 `content_type_label` 字段是人类可读的模型标识,格式为 `"app_label.model"`,例如:
- `"printing.plateorder"` — 开版单
- `"printing.printingjob"` — 印刷任务
- `"business.purchaseorder"` — 采购单
该字段**只读**,用于前端展示或调试,不作为提交 content_type 时的值。
### 当前系统中可关联的主要业务对象
| `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` | 工艺流程实例 |
> 上述列表是"有意义的"业务关联对象;系统本身不限制可关联的模型类型,技术上任何有效 ContentType ID 都被接受。
### 查询可用 content_type
- URL: `/api/v2/content-types/`
- Method: `GET`
响应示例:
```json
[
{"id": 7, "app_label": "business", "model": "paymentorder", "label": "business.paymentorder", "name": "付款单"},
{"id": 8, "app_label": "business", "model": "presalesorder", "label": "business.presalesorder", "name": "预销售单"},
...
]
```
前端应在运行时调用此接口获取 `id`,不应硬编码,因为不同部署环境的 ID 可能不同。
## 数据结构
### MissionCategory
```json
{
"id": 1,
"merchant": 10,
"name": "通用",
"created_at": "2026-04-10T12:00:00+08:00",
"updated_at": "2026-04-10T12:00:00+08:00"
}
```
### Mission
```json
{
"id": 1,
"merchant": 10,
"description": "跟进客户问题",
"category": 1,
"category_name": "通用",
"is_urgent": false,
"is_completed": false,
"is_cancelled": false,
"cancelled_at": null,
"creator": {
"id": 20,
"name": "张三",
"merchant_id": 10
},
"cancelled_by": null,
"participants": [
{
"id": 21,
"name": "李四",
"merchant_id": 10
}
],
"content_type": null,
"content_type_label": null,
"content_id": null,
"has_ending_reply": false,
"can_reply": true,
"created_at": "2026-04-10T12:00:00+08:00",
"updated_at": "2026-04-10T12:00:00+08:00"
}
```
### MissionReply
```json
{
"id": 100,
"mission": 1,
"merchant": 10,
"responder": {
"id": 20,
"name": "张三",
"merchant_id": 10
},
"content": "已处理",
"replied_at": "2026-04-10T12:10:00+08:00",
"ends_task": true,
"is_rejected": false,
"rejected_by": null,
"rejected_at": null,
"created_at": "2026-04-10T12:10:00+08:00",
"updated_at": "2026-04-10T12:10:00+08:00"
}
```
## 任务分类列表
- URL: `/api/v2/mission-categories/`
- Method: `GET`
响应:`MissionCategory[]`
## 创建任务分类
- URL: `/api/v2/mission-categories/`
- Method: `POST`
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `name` | string | 是 | 分类名称,同商户下唯一 |
请求示例:
```json
{
"name": "售后"
}
```
成功响应:`201 Created`,返回 `MissionCategory`
## 任务分类详情
- URL: `/api/v2/mission-categories/<category_id>/`
- Method: `GET`
成功响应:`MissionCategory`
## 更新任务分类
- URL: `/api/v2/mission-categories/<category_id>/`
- Method: `PATCH`
允许更新:
| 参数 | 类型 | 说明 |
|------|------|------|
| `name` | string | 分类名称,同商户下唯一 |
成功响应:`MissionCategory`
## 删除任务分类
- URL: `/api/v2/mission-categories/<category_id>/`
- Method: `DELETE`
说明:
- 如果该分类已经被任务使用,则删除会被拒绝,返回 `400`
成功响应:`204 No Content`
## 任务列表
- URL: `/api/v2/missions/`
- Method: `GET`
查询参数:
| 参数 | 类型 | 说明 |
|------|------|------|
| `category` | int | 任务分类 ID |
| `is_urgent` | boolean | `true` / `false` |
| `is_completed` | boolean | `true` / `false` |
| `is_cancelled` | boolean | `true` / `false` |
| `content_type` | int | Django ContentType ID |
| `content_id` | int | 关联业务对象 ID |
响应:`Mission[]`
## 创建任务
- URL: `/api/v2/missions/`
- Method: `POST`
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `description` | string | 是 | 任务描述 |
| `category` | int | 否 | 任务分类 ID不传时默认使用当前商户下名称为“通用”的分类不存在则自动创建 |
| `content_type` | int/null | 否 | Django ContentType ID必须与 `content_id` 同时提供或同时省略 |
| `content_id` | int/null | 否 | 关联业务对象 ID必须与 `content_type` 同时提供或同时省略 |
| `participant_ids` | int[] | 否 | 参与者员工 ID 列表,必须属于当前商户 |
说明:
- `creator``merchant` 由当前登录用户的 employee 自动写入
- `category_name` 为只读字段,由后端根据分类表返回
- `is_urgent``is_completed``is_cancelled` 均按默认值创建,不接受请求参数
- 若关联对象存在 `merchant_id` 字段,后端会校验它必须属于当前商户
请求示例:
```json
{
"description": "跟进客户问题",
"category": 1,
"participant_ids": [21, 22]
}
```
成功响应:`201 Created`,返回 `Mission`
## 任务详情
- URL: `/api/v2/missions/<mission_id>/`
- Method: `GET`
成功响应:`Mission`
跨商户访问返回 `404`
## 更新任务
- URL: `/api/v2/missions/<mission_id>/`
- Method: `PATCH`
允许更新:
| 参数 | 类型 | 说明 |
|------|------|------|
| `description` | string | 任务描述 |
| `category` | int | 任务分类 ID |
| `content_type` | int/null | 关联对象类型;必须与 `content_id` 同时提供 |
| `content_id` | int/null | 关联对象 ID必须与 `content_type` 同时提供 |
| `participant_ids` | int[] | 重置参与者列表 |
禁止更新状态字段,见“基本约定”
成功响应:`Mission`
## 删除任务
- URL: `/api/v2/missions/<mission_id>/`
- Method: `DELETE`
当前不提供删除能力,固定返回:
```json
{
"detail": "Mission 删除接口未提供,请使用 cancel 接口取消任务"
}
```
HTTP 状态码:`405 Method Not Allowed`
## 获取任务回应列表
- URL: `/api/v2/missions/<mission_id>/replies/`
- Method: `GET`
查询参数:
| 参数 | 类型 | 说明 |
|------|------|------|
| `ends_task` | boolean | `true` / `false`,筛选是否为结束回应 |
| `is_rejected` | boolean | `true` / `false`,筛选是否已被撤销 |
响应:`MissionReply[]`,按 `replied_at` 升序排列
## 创建任务回应
- URL: `/api/v2/missions/<mission_id>/replies/`
- Method: `POST`
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `content` | string | 是 | 回应内容 |
| `ends_task` | boolean | 否 | 是否结束任务,默认 `false` |
说明:
- `responder` 使用当前登录用户的 employee
- 如果 `ends_task=true`,后端会同步设置 `Mission.is_completed=true`
- 已取消任务、或已有有效结束回应的任务不允许继续回应
成功响应:`201 Created`,返回 `MissionReply`
## 重新打开任务
- URL: `/api/v2/missions/<mission_id>/reopen/`
- Method: `POST`
- 额外权限:`mission.reopen_mission`
请求体可为空:
```json
{}
```
前置条件:
- 当前用户必须具备 `mission.reopen_mission`
- 任务未取消
- 任务已完成
- 任务存在有效的 `ends_task=true` 回应
执行效果:
- `Mission.is_completed=false`
- 相关结束回应被标记为 `ends_task=false`
- 相关结束回应记录 `is_rejected=true``rejected_by=当前员工``rejected_at=当前时间`
成功响应:`Mission`
无权限响应:`403 Forbidden`
## 取消任务
- URL: `/api/v2/missions/<mission_id>/cancel/`
- Method: `POST`
请求体可为空:
```json
{}
```
执行效果:
- `Mission.is_cancelled=true`
- `Mission.cancelled_by=当前员工`
- `Mission.cancelled_at=当前时间`
- 不强行修改 `Mission.is_completed`
成功响应:`Mission`
## 设置紧急状态
- URL: `/api/v2/missions/<mission_id>/set-urgent/`
- Method: `POST`
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `is_urgent` | boolean | 是 | 新的紧急状态 |
请求示例:
```json
{
"is_urgent": true
}
```
成功响应:`Mission`
## 撤销任务回应
- URL: `/api/v2/mission-replies/<reply_id>/reject/`
- Method: `POST`
- 额外权限:`mission.reject_mission_reply`
请求体可为空:
```json
{}
```
执行效果:
- 当前回应标记为 `is_rejected=true`
- 当前回应的 `rejected_by``rejected_at` 由后端写入
- 如果当前回应原本是唯一有效的结束回应,则对应任务会被重新置为未完成
成功响应:`MissionReply`
无权限响应:`403 Forbidden`