# 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 | 否 | 是否返回详情。默认 true;false 时返回 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