1
0
forked from erp-dev/erp
This commit is contained in:
2026-07-05 13:53:17 +08:00
parent 4538e51ad5
commit e1cc6df122
24 changed files with 3030 additions and 495 deletions

View File

@@ -0,0 +1,332 @@
# /api/core/ 核心权限 API 文档
`/api/core/` 是系统核心权限管理 API 集合,用于管理 Django auth 层的 `User``Group``Permission`
这组接口权限极高,必须谨慎使用。一般业务开通用户、员工绑定、商户业务身份维护,不应该优先使用这里的用户创建接口,而应该使用业务侧 Employee / UserProfile / 员工开通流程。
## 访问控制
所有 `/api/core/` 接口都必须同时满足:
```text
1. 已登录
2. request.user.is_superuser == True
3. request.user.employee 存在
4. request.user.employee.merchant 存在
```
不满足时返回:
```text
未登录401
已登录但不是 superadmin或没有 employee/merchant403
```
认证方式:
```http
Authorization: Bearer <access_token>
```
登录接口仍为:
```http
POST /api/auth/login/
```
## Merchant 隔离
`users` 接口引入 merchant 隔离。
当前 merchant 来自:
```text
request.user.employee.merchant
```
`/api/core/users/` 只返回和操作当前 merchant 范围内的用户:
```text
User.employee.merchant == 当前 merchant
User.profile.merchant == 当前 merchant
```
`groups` 暂时是全局资源,不做 merchant 隔离。
`permissions` 是 Django 全局权限,只读,不做 merchant 隔离。
## Users
### 列表
```http
GET /api/core/users/
```
支持分页:
```http
GET /api/core/users/?limit=100&offset=0
```
支持搜索和排序:
```http
GET /api/core/users/?search=admin
GET /api/core/users/?ordering=username
GET /api/core/users/?ordering=-date_joined
```
返回字段包含:
```json
{
"id": 1,
"username": "admin",
"email": "admin@example.com",
"first_name": "",
"last_name": "",
"is_active": true,
"is_staff": true,
"is_superuser": true,
"last_login": "2026-07-03T10:00:00+08:00",
"date_joined": "2026-07-03T09:00:00+08:00",
"merchant_id": 1,
"employee_id": 10,
"employee_name": "管理员",
"groups": [1, 2],
"group_details": [
{"id": 1, "name": "管理员"}
],
"user_permissions": [101, 102],
"permission_details": []
}
```
### 详情
```http
GET /api/core/users/{id}/
```
如果目标用户不属于当前 merchant 范围,返回 `404`
### 创建
```http
POST /api/core/users/
```
示例:
```json
{
"username": "core_user",
"password": "strongPass123",
"email": "core@example.com",
"first_name": "Core",
"last_name": "User",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [1],
"user_permissions": [101]
}
```
创建行为:
```text
1. 创建 Django User
2. 创建 UserProfile并将 merchant 设置为当前 superadmin 的 merchant
3. 不创建 Employee
4. 不绑定业务身份
```
重要说明:
```text
/api/core/users/ 创建出来的是 core auth 用户,不是完整业务用户。
因为不会创建 Employee新用户通常不能直接登录业务系统。
一般情况下,不建议在这里创建业务用户。
```
### 更新
```http
PATCH /api/core/users/{id}/
PUT /api/core/users/{id}/
```
可更新字段包括:
```text
username
email
first_name
last_name
is_active
is_staff
is_superuser
groups
user_permissions
password
```
安全限制:
```text
不允许停用当前登录用户自己
不允许取消当前登录用户自己的 is_superuser
```
### 停用用户
用户不允许删除,只能停用:
```http
PATCH /api/core/users/{id}/
Content-Type: application/json
{
"is_active": false
}
```
### 删除用户
不允许:
```http
DELETE /api/core/users/{id}/
```
返回:
```text
405 Method Not Allowed
```
## Groups
`groups` 使用 Django `Group`,暂时是全局资源,不做 merchant 隔离。
但是所有 group API 仍然必须通过 `/api/core/` 的 superadmin 访问控制。
### 列表
```http
GET /api/core/groups/
```
### 详情
```http
GET /api/core/groups/{id}/
```
### 创建
```http
POST /api/core/groups/
```
```json
{
"name": "财务管理员",
"permissions": [101, 102, 103]
}
```
### 更新
```http
PATCH /api/core/groups/{id}/
PUT /api/core/groups/{id}/
```
设置 permissions 时传完整权限 id 列表:
```json
{
"permissions": [101, 102, 103]
}
```
### 删除
```http
DELETE /api/core/groups/{id}/
```
删除 group 会影响已分配该 group 的用户,请谨慎操作。
## Permissions
`permissions` 使用 Django `Permission`,只读。
权限通常来自 Django model 默认权限和代码中的 `Meta.permissions`,不应该由前端任意创建、修改或删除。
### 列表
```http
GET /api/core/permissions/
```
支持搜索:
```http
GET /api/core/permissions/?search=salesorder
GET /api/core/permissions/?search=business
```
返回字段:
```json
{
"id": 101,
"name": "Can view sales order",
"codename": "view_salesorder",
"content_type": 12,
"app_label": "business",
"model": "salesorder",
"full_code": "business.view_salesorder"
}
```
### 详情
```http
GET /api/core/permissions/{id}/
```
### 不允许写操作
以下操作不允许:
```http
POST /api/core/permissions/
PATCH /api/core/permissions/{id}/
PUT /api/core/permissions/{id}/
DELETE /api/core/permissions/{id}/
```
返回:
```text
405 Method Not Allowed
```
## 前端使用建议
```text
1. /api/core/ 只给最高权限管理界面使用。
2. 普通业务用户开通不要默认走 /api/core/users/。
3. 创建业务用户应优先走员工/业务身份流程。
4. 修改 is_superuser、is_staff、groups、user_permissions 时必须二次确认。
5. permissions 只作为可分配权限源,不允许前端创建权限。
6. group 暂时是全局资源,删除或改名会影响所有 merchant。
```
更新日期2026-07-03

View File

@@ -0,0 +1,137 @@
# API Docs 与 Schema 使用说明
更新日期2026-07-03
本文档说明 `/api/schema/``/api/docs/` 的用途、登录方式和前端审查使用流程。
## 访问保护
当前 API 文档端点已启用 JWT 登录保护:
- 未登录访问 `/api/schema/` 会返回 401。
- 未登录访问 `/api/docs/` 会返回 401。
- 只要是有效登录用户即可访问,不额外要求管理员权限。
## 两个端点的区别
### `/api/schema/`
机器可读的 OpenAPI schema。
用途:
- 给 Apifox、Postman、Swagger Editor 导入。
- 给前端生成接口类型或客户端代码。
- 用于审查当前后端实际暴露的 API 路径、请求参数、响应结构和认证方式。
返回格式通常是 OpenAPI JSON。
### `/api/docs/`
浏览器可读的 Swagger UI 页面。
用途:
- 在浏览器里查看接口文档。
- 在页面中通过 Authorize 填入 JWT 后调试接口。
注意Swagger UI 页面本身也需要登录保护。浏览器地址栏不能直接携带 `Authorization` header因此最顺手的方式通常是先用工具拿到 token再在 Swagger UI 的 Authorize 里填入 token。
## 登录获取 JWT
登录接口:
```http
POST /api/auth/login/
```
请求体:
```json
{
"username": "frontend-reviewer",
"password": "your-password"
}
```
成功响应:
```json
{
"refresh": "refresh-token",
"access": "access-token"
}
```
说明:
- 登录用户必须绑定 `Employee`
- 未绑定员工身份的用户会登录失败。
- 当前 access token 有效期由后端 `SIMPLE_JWT` 配置控制。
## 访问 `/api/schema/`
请求:
```http
GET /api/schema/
Authorization: Bearer {access-token}
```
curl 示例:
```bash
curl -H "Authorization: Bearer ${ACCESS_TOKEN}" https://your-domain/api/schema/
```
Apifox/Postman 使用方式:
1. 先调用 `/api/auth/login/` 获取 `access`
2. 导入 OpenAPI URL`https://your-domain/api/schema/`
3. 给导入请求添加 Header
```http
Authorization: Bearer {access-token}
```
## 访问 `/api/docs/`
浏览器直接访问:
```text
https://your-domain/api/docs/
```
如果浏览器没有携带 JWT会返回 401。
推荐审查流程:
1. 调用 `/api/auth/login/` 获取 `access`
2. 打开 `/api/docs/`
3. 点击 Swagger UI 页面右上角 `Authorize`
4. 输入:
```text
Bearer {access-token}
```
5. 之后即可在 Swagger UI 中查看并调试接口。
## 前端审查建议
- 如果目标是审查接口结构,优先使用 `/api/schema/` 导入 Apifox/Postman。
- 如果目标是临时浏览和手动调试,使用 `/api/docs/`
- 不要把 `/api/schema/` 的内容提交到前端仓库作为长期静态副本schema 会随着后端代码变化而变化。
- 审查前请确认使用的是目标环境的域名,因为 `content_type` 等 ID 在不同环境可能不同。
## 当前抽检重点
本次配置变更后需要确认:
- 匿名访问 `/api/schema/` 返回 401。
- 匿名访问 `/api/docs/` 返回 401。
- 登录后访问 `/api/schema/` 返回 200。
- 登录后访问 `/api/docs/` 返回 200。
- `/api/schema/` 中包含最新 mission API 变更,例如 `employee_type_ids`
更新日期2026-07-03

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,775 @@
# 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

View File

@@ -0,0 +1,214 @@
# GET /api/v2/missions/my/
获取当前登录用户相关的任务列表。
## Authentication
需要 JWT 登录。
Authorization: Bearer <access_token>
当前用户必须绑定 Employee
request.user.employee 必须存在
## Query Params
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---:|---|---|
| `limit` | number | 否 | 800 | 分页条数,最大 1000 |
| `offset` | number | 否 | 0 | 分页偏移 |
| `created_by_me` | boolean | 否 | false | 是否只返回我创建的任务。`false` 表示不限定创建者,仍返回“我创建或我参与”的任务 |
| `is_completed` | boolean | 否 | 不限制 | 是否完成 |
| `is_cancelled` | boolean | 否 | 不限制 | 是否取消 |
| `is_urgent` | boolean | 否 | 不限制 | 是否紧急 |
| `created_at_from` | string | 否 | 不限制 | 创建时间开始,支持 `YYYY-MM-DD` 或日期时间 |
| `created_at_to` | string | 否 | 不限制 | 创建时间结束,支持 `YYYY-MM-DD` 或日期时间;传纯日期时包含整天 |
boolean 参数支持:
true 值:`true` / `1` / `yes` / `y` / `on`
false 值:`false` / `0` / `no` / `n` / `off`
## Filter Logic
接口会根据当前 JWT 用户找到:
current_employee = request.user.employee
current_merchant = current_employee.merchant
基础范围:
mission.merchant == current_merchant
默认返回“与我相关”的任务,即满足以下任一条件:
mission.creator == current_employee
mission.participants 包含 current_employee
`created_by_me=true` 时,只返回:
mission.creator == current_employee
状态筛选只有在显式传参时才生效:
is_completed=false
=> 只返回未完成任务
is_cancelled=false
=> 只返回未取消任务
is_urgent=true
=> 只返回紧急任务
## Date Filter
created_at_from=2026-07-03
=> created_at >= 2026-07-03 00:00:00
created_at_to=2026-07-03
=> created_at < 2026-07-04 00:00:00
如果传日期时间,则按传入的具体时间筛选:
created_at_from=2026-07-03T08:30:00+08:00
=> created_at >= 2026-07-03 08:30:00+08:00
created_at_to=2026-07-03T18:00:00+08:00
=> created_at <= 2026-07-03 18:00:00+08:00
## Common Examples
获取我相关的所有任务:
GET /api/v2/missions/my/
获取我相关的未完成、未取消任务:
GET /api/v2/missions/my/?is_completed=false&is_cancelled=false
获取我创建的未完成、未取消任务:
GET /api/v2/missions/my/?created_by_me=true&is_completed=false&is_cancelled=false
获取我相关的紧急任务:
GET /api/v2/missions/my/?is_urgent=true
获取我相关的某天创建的任务:
GET /api/v2/missions/my/?created_at_from=2026-07-03&created_at_to=2026-07-03
分页:
GET /api/v2/missions/my/?limit=20&offset=0
## Response
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"merchant": 1,
"description": "跟进客户问题",
"category": 1,
"category_name": "通用",
"is_urgent": false,
"is_completed": false,
"is_cancelled": false,
"notify_if_unreplied": false,
"unreplied_notify_interval_minutes": null,
"unreplied_notify_max_count": 5,
"unreplied_notify_sent_count": 0,
"unreplied_last_notified_at": null,
"cancelled_at": null,
"creator": {
"id": 10,
"name": "张三",
"merchant_id": 1
},
"cancelled_by": null,
"participants": [
{
"id": 11,
"name": "李四",
"merchant_id": 1
}
],
"content_type": 23,
"content_type_label": "printing.printingorder",
"content_id": 1001,
"extra": {
"source": "printing-order"
},
"has_ending_reply": false,
"can_reply": true,
"created_at": "2026-07-03T15:40:00+08:00",
"updated_at": "2026-07-03T15:40:00+08:00"
}
]
}
## Empty Response
{
"count": 0,
"next": null,
"previous": null,
"results": []
}
## Error Responses
### 未登录
401 Unauthorized
### 当前用户未绑定 Employee
{
"non_field_errors": [
"当前用户未关联员工"
]
}
### boolean 参数格式错误
{
"is_completed": "必须是布尔值"
}
或:
{
"is_cancelled": "必须是布尔值"
}
或:
{
"is_urgent": "必须是布尔值"
}
或:
{
"created_by_me": "必须是布尔值"
}
### created_at_from 格式错误
{
"created_at_from": "必须是日期或日期时间"
}
### created_at_to 格式错误
{
"created_at_to": "必须是日期或日期时间"
}