# Shipment Delivery API 本文档说明 `shipment` 模块中的“送货单”模型及相关 API。 ## 业务说明 送货单用于表示一次具体的送货行为。 特点: - 一个送货单可关联多个出货单 - 一个出货单最多属于一个送货单 - 送货单与出货单关系为一对多 - 送货单使用独立状态流转,不复用出货单状态 ## 状态枚举 送货单状态定义如下: - `1 = 待送货` - `2 = 送货中` - `3 = 已送达` - `4 = 已取消` 状态机规则: - `待送货 -> 送货中` - `送货中 -> 已送达` - 不允许回退 - 重复设置同一状态时保持幂等,直接返回当前对象 - `已取消` 为终态 时间字段规则: - 进入 `送货中` 时写入 `started_at` - 进入 `已送达` 时写入 `delivered_at` ## 模型字段 送货单模型包含以下核心字段: - `id` - `merchant` - `driver_name` - `vehicle_trip` - `contact_phone` - `vehicle_capacity` - `remark` - `internal_remark` - `shipment_order_ids` - `status` - `started_at` - `delivered_at` - `created_by` - `operator` - `cancelled_at` - `cancelled_by` - `created_at` - `updated_at` 说明: - `driver_name` 已加索引 - `vehicle_trip` 已加索引 - `status` 已加索引 - `created_by` 为系统用户 `User` - `operator` 为业务员工 `Employee` - `merchant` 用于多商户隔离 - 创建、修改、状态流转时,会根据 `request.user.employee` 自动写入 `operator` - 取消送货单时会写入 `cancelled_at` 与 `cancelled_by` - `shipment_order_ids` 是前端自管的出货单顺序 JSON 字段;后端只保存和返回,不据此自动排序 `shipments` ## API 列表 ### 1. 查询送货单列表 - `GET /api/v1/shipment/deliveries/` 支持查询参数: - `status` - `driver_name` - `vehicle_trip` - `limit` - `offset` 查询说明: - `status` 为精确匹配 - `driver_name` 为包含匹配 - `vehicle_trip` 为包含匹配 示例: ```http GET /api/v1/shipment/deliveries/?status=2&driver_name=张&vehicle_trip=001&limit=20&offset=0 ``` 返回示例: ```json { "count": 1, "next": null, "previous": null, "results": [ { "id": 1, "merchant_id": 1, "merchant_name": "测试印花厂", "driver_name": "张司机", "vehicle_trip": "KD-001", "contact_phone": "13800138000", "vehicle_capacity": "9.6米厢车", "remark": "先装车", "internal_remark": "注意对账", "shipment_order_ids": [11, 10], "status": 1, "status_display": "待送货", "started_at": null, "delivered_at": null, "cancelled_at": null, "shipments_count": 2, "shipments": [ { "id": 10, "customer": 5, "customer_name": "客户A", "order_description": null, "shipment_date": "2026-04-03", "status": 2, "status_display": "已发布", "external_id": null } ], "created_by_id": 8, "created_by_name": "测试员工", "operator_id": 3, "operator_name": "测试员工", "cancelled_by_id": null, "cancelled_by_name": null, "created_at": "2026-04-03T10:00:00+08:00", "updated_at": "2026-04-03T10:00:00+08:00" } ] } ``` ### 2. 创建送货单 - `POST /api/v1/shipment/deliveries/` 请求体: ```json { "driver_name": "张司机", "vehicle_trip": "KD-001", "contact_phone": "13800138000", "vehicle_capacity": "9.6米厢车", "remark": "先装车", "internal_remark": "注意对账", "shipment_order_ids": [11, 10], "shipments": [10, 11] } ``` 字段说明: - `driver_name`: 必填,司机名 - `vehicle_trip`: 必填,车次 - `contact_phone`: 可选,联系电话 - `vehicle_capacity`: 可选,车辆容量 - `remark`: 可选,备注 - `internal_remark`: 可选,内部备注 - `shipment_order_ids`: 可选,前端自管的出货单顺序 JSON 数组;可传 `null`;未传时默认 `[]` - `shipments`: 可选,出货单 ID 列表 创建规则: - 只能绑定当前用户所属商户的出货单 - 只能绑定状态为 `已审核` 的出货单 - 已绑定到其他送货单的出货单不能重复绑定 - 创建接口不支持直接传入 `status` ### 3. 查询送货单详情 - `GET /api/v1/shipment/deliveries/{id}/` 返回字段与列表单项一致,但会返回完整 `shipments` 摘要数组。 ### 4. 修改送货单 - `PATCH /api/v1/shipment/deliveries/{id}/` - `PUT /api/v1/shipment/deliveries/{id}/` 请求体示例: ```json { "driver_name": "李司机", "vehicle_trip": "KD-001-B", "contact_phone": "13700137000", "vehicle_capacity": "13米高栏", "remark": "改派车辆", "internal_remark": "已电话确认", "shipment_order_ids": [11, 12], "shipments": [11, 12] } ``` 修改规则: - `driver_name`、`vehicle_trip`、`contact_phone`、`vehicle_capacity`、`remark`、`internal_remark` 可单独修改 - `shipment_order_ids` 按普通字段处理,后端仅保存与返回;值必须是数组或 `null` - `shipments` 如果传入,则视为“整体替换当前绑定的出货单集合” - 替换绑定时,出货单仍然必须满足“当前商户、已审核、未绑定到其他送货单” - 更新接口不支持直接修改 `status` ### 5. 删除送货单 - `DELETE /api/v1/shipment/deliveries/{id}/` 行为说明: - 删除送货单时,会先解除与其关联的出货单绑定 - 删除成功返回 `204 No Content` ### 6. 修改送货单状态 - `POST /api/v1/shipment/deliveries/{id}/status/` 请求体: ```json { "status": 2 } ``` 状态取值: - `1 = 待送货` - `2 = 送货中` - `3 = 已送达` 规则说明: - 只允许按状态机顺序流转 - 不允许回退 - 相同状态重复提交保持幂等 - 成功改到 `送货中` 时写入 `started_at` - 成功改到 `已送达` 时写入 `delivered_at` - 该接口不用于取消送货单 ### 7. 取消送货单 - `POST /api/v1/shipment/deliveries/{id}/cancel/` 权限要求: - 需要 Django 权限:`shipment.cancel_shipmentdelivery` 请求体: ```json {} ``` 行为说明: - 将送货单状态改为 `已取消` - 写入 `cancelled_at` - 写入 `cancelled_by` - 同时会根据 `request.user.employee` 更新 `operator` - 取消不会释放或解绑当前已关联的出货单;如需解绑,需显式删除送货单或通过修改接口改绑 - 已取消的送货单再次取消保持幂等 ### 8. 追加绑定出货单到已有送货单 - `POST /api/v1/shipment/deliveries/{id}/bind-shipments/` 请求体: ```json { "shipments": [12, 13] } ``` 规则说明: - 该接口是“追加绑定”,不会替换当前已有绑定 - 只允许绑定当前用户所属商户的出货单 - 出货单必须处于 `已审核` 状态 - 已绑定到其他送货单的出货单不能再次绑定 - 成功后会根据 `request.user.employee` 更新 `operator` ## Service 设计 送货单的业务写入逻辑统一放在 `shipment/services.py` 中: - `create_shipment_delivery(...)` - `update_shipment_delivery(...)` - `modify_shipment_delivery_status(...)` - `cancel_shipment_delivery(...)` - `delete_shipment_delivery(...)` 说明: - list / detail 属于简单只读查询,直接由 API 查询集处理 - create / update / delete / status modify 均通过 service 完成 ## 测试覆盖 已补充以下测试方向: - 创建送货单并绑定多个出货单 - 绑定已属于其他送货单的出货单时报错 - 绑定非当前商户出货单时报错 - 列表支持 `status / driver_name / vehicle_trip` 查询 - 详情返回关联出货单摘要 - 更新送货单并替换绑定出货单 - 创建、修改、状态流转会写入 `operator` - 取消送货单需要权限校验,并写入 `cancelled_at / cancelled_by` - 状态流转写入 `started_at / delivered_at` - 非法状态流转报错 - 删除送货单后解除出货单绑定