# 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//` - Method: `GET` 成功响应:`MissionCategory` ## 更新任务分类 - URL: `/api/v2/mission-categories//` - Method: `PATCH` 允许更新: | 参数 | 类型 | 说明 | |------|------|------| | `name` | string | 分类名称,同商户下唯一 | 成功响应:`MissionCategory` ## 删除任务分类 - URL: `/api/v2/mission-categories//` - 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//` - Method: `GET` 成功响应:`Mission` 跨商户访问返回 `404` ## 更新任务 - URL: `/api/v2/missions//` - 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//` - Method: `DELETE` 当前不提供删除能力,固定返回: ```json { "detail": "Mission 删除接口未提供,请使用 cancel 接口取消任务" } ``` HTTP 状态码:`405 Method Not Allowed` ## 获取任务回应列表 - URL: `/api/v2/missions//replies/` - Method: `GET` 查询参数: | 参数 | 类型 | 说明 | |------|------|------| | `ends_task` | boolean | `true` / `false`,筛选是否为结束回应 | | `is_rejected` | boolean | `true` / `false`,筛选是否已被撤销 | 响应:`MissionReply[]`,按 `replied_at` 升序排列 ## 创建任务回应 - URL: `/api/v2/missions//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//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//cancel/` - Method: `POST` 请求体可为空: ```json {} ``` 执行效果: - `Mission.is_cancelled=true` - `Mission.cancelled_by=当前员工` - `Mission.cancelled_at=当前时间` - 不强行修改 `Mission.is_completed` 成功响应:`Mission` ## 设置紧急状态 - URL: `/api/v2/missions//set-urgent/` - Method: `POST` 请求参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `is_urgent` | boolean | 是 | 新的紧急状态 | 请求示例: ```json { "is_urgent": true } ``` 成功响应:`Mission` ## 撤销任务回应 - URL: `/api/v2/mission-replies//reject/` - Method: `POST` - 额外权限:`mission.reject_mission_reply` 请求体可为空: ```json {} ``` 执行效果: - 当前回应标记为 `is_rejected=true` - 当前回应的 `rejected_by`、`rejected_at` 由后端写入 - 如果当前回应原本是唯一有效的结束回应,则对应任务会被重新置为未完成 成功响应:`MissionReply` 无权限响应:`403 Forbidden`