1
0
forked from erp-dev/erp
Files
erpnew/docs/api_v1_shipment_api_2026-07-08.md

827 lines
22 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.
# Shipment 模块 API v1 文档
面向前端对接,基于当前代码生成。接口统一前缀为 `/api/v1/`
## 通用说明
- 认证:所有接口均要求登录认证。
- 商户隔离:普通用户只能访问自己员工所属商户的数据;超级管理员部分接口可跨商户,具体见接口说明。
- 分页:列表接口使用 `limit``offset`,分页响应一般为 `{ "count": 0, "next": null, "previous": null, "results": [] }`
- 错误:业务校验失败通常返回 `400 { "detail": "错误原因" }`;不存在通常返回 `404`
## 枚举
### 出货单状态 `ShipmentStatus`
| 值 | 含义 |
| --- | --- |
| `1` | 草稿(未发布) |
| `2` | 已发布 |
| `3` | 已取消 |
| `4` | 已驳回 |
| `5` | 已审核 |
### 送货单状态 `ShipmentDeliveryStatus`
| 值 | 含义 |
| --- | --- |
| `1` | 待送货 |
| `2` | 送货中 |
| `3` | 已送达 |
### 销售品单位 `UnitChoices`
| 值 | 含义 |
| --- | --- |
| `1` | 米 |
| `2` | 件 |
| `3` | 码 |
| `4` | 个 |
## 公共响应结构
### Shipment
```json
{
"id": 1,
"merchant_id": 1,
"merchant_name": "测试印花厂",
"customer": 10,
"customer_name": "客户A",
"address_id": 5,
"fabric": "面料信息",
"order_description": "订单描述",
"shipment_date": "2026-01-14",
"address": "杭州市测试路 1 号",
"contact_name": "张三",
"contact_phone": "13800138000",
"area": "华东",
"coordinates": "120.1551,30.2741",
"remark": "备注",
"status": 1,
"status_display": "草稿(未发布)",
"external_id": null,
"geo_coordinates": null,
"extra": {"source": "customer_address"},
"delivery_id": null,
"status_modified_at": null,
"cancelled_by_id": null,
"cancelled_by_name": null,
"approved_by_id": null,
"approved_by_name": null,
"items_count": 2,
"created_by_id": 1,
"created_by_name": "张三",
"external_finished_products_count": 0,
"sales_items": [],
"external_finished_products": [],
"created_at": "2026-01-14T10:00:00Z",
"updated_at": "2026-01-14T10:00:00Z"
}
```
字段说明:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `address_id` | integer/null | 关联客户地址 ID只表示关联关系 |
| `address` | string | 出货单地址快照,由请求体写入 |
| `contact_name` | string | 联系人快照,由请求体写入 |
| `contact_phone` | string | 联系电话快照,由请求体写入 |
| `area` | string | 地区快照,最大 100 字符 |
| `coordinates` | string/null | 经纬度字符串,最大 100 字符 |
| `geo_coordinates` | object/array/null | 历史 Geo 字段,保留兼容老数据 |
| `extra` | object/array/null | 扩展 JSON |
重要行为:传 `address_id` 时,后端只校验并保存关联,不会用 `CustomerAddress` 自动覆盖 `address/contact_name/contact_phone/area/coordinates/extra`。这些快照字段仍以请求体为准。
### SalesItem
```json
{
"id": 1,
"name": "销售品A",
"quantity": "100.00",
"unit": 1,
"unit_display": "米",
"position": "A1-01",
"remark": "备注",
"printing_job_id": 123,
"printing_order_id": 456,
"external_order_id": "KD20441758",
"customer_id": 10,
"customer_name": "客户A",
"shipment_id": null,
"shipment_date": null,
"merge_remark": null,
"created_at": "2026-01-14T10:00:00Z",
"created_by_id": 1,
"created_by_name": "张三"
}
```
详情接口额外包含:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `product_image_url` | string/null | 关联生产任务产品主图 URL |
### ShipmentDelivery
```json
{
"id": 1,
"merchant_id": 1,
"merchant_name": "测试印花厂",
"driver_name": "司机A",
"vehicle_trip": "浙A12345",
"contact_phone": "13800138000",
"vehicle_capacity": "4.2米",
"remark": "备注",
"internal_remark": "内部备注",
"shipment_order_ids": [1, 2, 3],
"status": 1,
"status_display": "待送货",
"started_at": null,
"delivered_at": null,
"cancelled_at": null,
"shipments_count": 2,
"shipments": [],
"created_by_id": 1,
"created_by_name": "张三",
"operator_id": null,
"operator_name": null,
"cancelled_by_id": null,
"cancelled_by_name": null,
"created_at": "2026-01-14T10:00:00Z",
"updated_at": "2026-01-14T10:00:00Z"
}
```
`shipments` 内元素为送货单内出货单摘要:
```json
{
"id": 1,
"customer": 10,
"customer_name": "客户A",
"fabric": "面料信息",
"order_description": "订单描述",
"shipment_date": "2026-01-14",
"status": 5,
"status_display": "已审核",
"external_id": null,
"delivery_id": 1
}
```
## 出货单接口
### 查询出货单列表
`GET /api/v1/shipment/shipments/`
查询参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `customer` | integer | 否 | 客户 ID |
| `status` | integer | 否 | 出货单状态 |
| `delivery_id` | integer | 否 | 送货单 ID空字符串、`null``none` 不触发过滤 |
| `delivery_isnull` | boolean | 否 | `true/1/yes` 查未绑定送货单,`false/0/no` 查已绑定送货单 |
| `external_id` | string | 否 | 外部订单号精确匹配 |
| `shipment_date_from` | date | 否 | 出货日期起始,`YYYY-MM-DD` |
| `shipment_date_to` | date | 否 | 出货日期结束,`YYYY-MM-DD` |
| `only_address_null` | boolean | 否 | `true` 查地址为空字符串,`false` 查地址非空 |
| `limit` | integer | 否 | 分页大小 |
| `offset` | integer | 否 | 分页偏移 |
响应:分页 `Shipment[]`
### 创建出货单
`POST /api/v1/shipment/shipments/`
请求体:
```json
{
"customer": 10,
"address_id": 5,
"shipment_date": "2026-01-14",
"address": "杭州市测试路 1 号",
"contact_name": "张三",
"contact_phone": "13800138000",
"area": "华东",
"coordinates": "120.1551,30.2741",
"extra": {"source": "customer_address"},
"remark": "备注",
"sales_items": [1, 2, 3]
}
```
请求字段:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `customer` | integer | 是 | 客户 ID |
| `address_id` | integer/null | 否 | 客户地址 ID仅校验和关联 |
| `shipment_date` | date | 是 | 出货日期 |
| `address` | string | 否 | 地址快照,默认 `""`,最大 255 |
| `contact_name` | string | 否 | 联系人快照,默认 `""`,最大 100 |
| `contact_phone` | string | 否 | 联系电话快照,默认 `""`,最大 50 |
| `area` | string | 否 | 地区快照,默认 `""`,最大 100 |
| `coordinates` | string/null | 否 | 经纬度字符串,最大 100 |
| `extra` | JSON/null | 否 | 扩展信息 |
| `remark` | string | 否 | 出货单备注,默认 `""` |
| `sales_items` | integer[] | 否 | 要关联的销售品 ID 列表,默认 `[]`,后端会去重 |
业务规则:
- 用户必须关联商户。
- 客户必须属于当前商户。
- `address_id` 存在时,客户地址必须存在、未软删除、属于当前商户、属于请求中的 `customer`
- `sales_items` 非空时,销售品必须存在、未删除、未绑定其他出货单、属于当前商户,并且都来自同一个生产订单。
响应:`201 Shipment`
### 创建外部出货单
`POST /api/v1/shipment/shipments/external/`
用于没有内部销售品、但需要记录外部成品表的出货单。
请求体:
```json
{
"customer": 10,
"shipment_date": "2026-01-14",
"address": "杭州市测试路 1 号",
"contact_name": "张三",
"contact_phone": "13800138000",
"area": "华东",
"coordinates": "120.1551,30.2741",
"extra": {"source": "external"},
"remark": "备注",
"external_id": "EXT-001",
"external_finished_products": [
{
"style_name": "款式A",
"num_of_rolls": 10,
"remark": "成品备注"
}
]
}
```
请求字段:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `customer` | integer | 是 | 客户 ID |
| `shipment_date` | date | 是 | 出货日期 |
| `address` | string | 否 | 地址,最大 255 |
| `contact_name` | string | 否 | 联系人,最大 100 |
| `contact_phone` | string | 否 | 联系电话,最大 50 |
| `area` | string | 否 | 地区,最大 100 |
| `coordinates` | string/null | 否 | 经纬度字符串,最大 100 |
| `extra` | JSON/null | 否 | 扩展信息 |
| `remark` | string | 否 | 备注 |
| `external_id` | string | 是 | 外部订单号,不能为空,最大 120 |
| `external_finished_products` | array | 是 | 外部成品表,不能为空 |
`external_finished_products` 元素:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `style_name` | string | 是 | 款式名,最大 200 |
| `num_of_rolls` | integer | 是 | 卷数,最小 0 |
| `remark` | string/null | 否 | 备注,最大 200 |
响应:`201 Shipment`
### 获取出货单详情
`GET /api/v1/shipment/shipments/{id}/`
查询参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `status` | integer | 否 | 可选状态过滤;不匹配则返回 404 |
响应:`200 Shipment`
### 更新出货单
`PATCH /api/v1/shipment/shipments/{id}/`
`PUT /api/v1/shipment/shipments/{id}/`
当前 `PUT` 行为等同 `PATCH`,允许只传部分字段。
请求体:
```json
{
"customer": 10,
"shipment_date": "2026-01-15",
"address": "新地址",
"contact_name": "李四",
"contact_phone": "13900139000",
"area": "华南",
"coordinates": null,
"extra": null,
"remark": "新备注",
"external_id": "EXT-002",
"geo_coordinates": {"lng": 120.1, "lat": 30.2}
}
```
可更新字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `customer` | integer | 客户 ID |
| `shipment_date` | date | 出货日期 |
| `address` | string | 地址,最大 255 |
| `contact_name` | string | 联系人,最大 100 |
| `contact_phone` | string | 联系电话,最大 50 |
| `area` | string | 地区,最大 100 |
| `coordinates` | string/null | 经纬度字符串;传 `null` 可清空 |
| `extra` | JSON/null | 扩展信息;传 `null` 可清空 |
| `remark` | string | 备注 |
| `external_id` | string/null | 外部订单号;空字符串或 `null` 会清空 |
| `geo_coordinates` | JSON/null | 当前 serializer 接收该字段,但业务更新服务未保存它,前端不建议使用 |
业务规则:
- 已关联送货单的出货单不允许修改业务数据。
- 修改客户时,新客户必须存在且属于同一商户。
响应:`200 Shipment`
### 修改出货单状态
`PATCH /api/v1/shipment/shipments/{id}/status/`
`PUT /api/v1/shipment/shipments/{id}/status/`
请求体:
```json
{
"status": 5
}
```
响应:`200 Shipment`
## 送货单接口
### 查询送货单列表
`GET /api/v1/shipment/deliveries/`
查询参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `status` | integer | 否 | 送货单状态 |
| `driver_name` | string | 否 | 司机名模糊匹配 |
| `vehicle_trip` | string | 否 | 车次模糊匹配 |
| `limit` | integer | 否 | 分页大小 |
| `offset` | integer | 否 | 分页偏移 |
响应:分页 `ShipmentDelivery[]`
### 创建送货单
`POST /api/v1/shipment/deliveries/`
请求体:
```json
{
"driver_name": "司机A",
"vehicle_trip": "浙A12345",
"contact_phone": "13800138000",
"vehicle_capacity": "4.2米",
"remark": "备注",
"internal_remark": "内部备注",
"shipment_order_ids": [3, 2, 1],
"shipments": [1, 2, 3]
}
```
请求字段:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `driver_name` | string | 是 | 司机名,最大 100 |
| `vehicle_trip` | string | 是 | 车次,最大 100 |
| `contact_phone` | string | 否 | 联系电话,默认 `""`,最大 50 |
| `vehicle_capacity` | string | 否 | 车辆容量,默认 `""`,最大 100 |
| `remark` | string | 否 | 备注,默认 `""`,最大 200 |
| `internal_remark` | string | 否 | 内部备注,默认 `""`,最大 200 |
| `shipment_order_ids` | array/null | 否 | 前端自管出货单排序 ID 数组,可为 `null` |
| `shipments` | integer[] | 否 | 要关联的出货单 ID 列表,默认 `[]`,后端会去重 |
响应:`201 ShipmentDelivery`
### 按生产订单查询送货单
`GET /api/v1/shipment/deliveries/by-printing-order/{printing_order_id}/`
路径参数:
- `printing_order_id` 支持内部 `PrintingOrder.id``external_order_id`
- 如果 `external_order_id` 匹配多个生产订单,返回 `500 { "message": "...请改用内部ID查询" }`
查询参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `status` | integer | 否 | 送货单状态 |
| `driver_name` | string | 否 | 模糊匹配 |
| `vehicle_trip` | string | 否 | 模糊匹配 |
| `contact_phone` | string | 否 | 模糊匹配 |
| `vehicle_capacity` | string | 否 | 模糊匹配 |
| `remark` | string | 否 | 模糊匹配 |
| `internal_remark` | string | 否 | 模糊匹配 |
| `started_at_from` | date/datetime | 否 | 开始送货时间起 |
| `started_at_to` | date/datetime | 否 | 开始送货时间止 |
| `delivered_at_from` | date/datetime | 否 | 送达时间起 |
| `delivered_at_to` | date/datetime | 否 | 送达时间止 |
| `cancelled_at_from` | date/datetime | 否 | 取消时间起 |
| `cancelled_at_to` | date/datetime | 否 | 取消时间止 |
| `created_at_from` | date/datetime | 否 | 创建时间起 |
| `created_at_to` | date/datetime | 否 | 创建时间止 |
| `shipment_date_from` | date | 否 | 关联出货日期起 |
| `shipment_date_to` | date | 否 | 关联出货日期止 |
| `ordering` | string | 否 | 可选 `id/created_at/updated_at/started_at/delivered_at/cancelled_at/status`,支持前缀 `-` |
| `limit` | integer | 否 | 分页大小 |
| `offset` | integer | 否 | 分页偏移 |
响应:分页 `ShipmentDelivery[]`,字段使用专用 DTO不包含 `cancelled_by_id/cancelled_by_name`
### 获取送货单详情
`GET /api/v1/shipment/deliveries/{id}/`
响应:`200 ShipmentDelivery`
### 更新送货单
`PATCH /api/v1/shipment/deliveries/{id}/`
`PUT /api/v1/shipment/deliveries/{id}/`
当前 `PUT` 行为等同 `PATCH`
请求体:
```json
{
"driver_name": "司机B",
"vehicle_trip": "浙B12345",
"contact_phone": "13900139000",
"vehicle_capacity": "9.6米",
"remark": "新备注",
"internal_remark": "新内部备注",
"shipment_order_ids": [2, 1],
"shipments": [1, 2]
}
```
可更新字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `driver_name` | string | 司机名,最大 100不允许空字符串 |
| `vehicle_trip` | string | 车次,最大 100不允许空字符串 |
| `contact_phone` | string | 联系电话,最大 50 |
| `vehicle_capacity` | string | 车辆容量,最大 100 |
| `remark` | string | 备注,最大 200 |
| `internal_remark` | string | 内部备注,最大 200 |
| `shipment_order_ids` | array/null | 前端自管排序 ID 数组,传入即更新 |
| `shipments` | integer[] | 传入即替换当前绑定出货单 |
响应:`200 ShipmentDelivery`
### 删除送货单
`DELETE /api/v1/shipment/deliveries/{id}/`
响应:`204 No Content`
### 修改送货单状态
`POST /api/v1/shipment/deliveries/{id}/status/`
请求体:
```json
{
"status": 2
}
```
允许状态:`1``2``3`
响应:`200 ShipmentDelivery`
### 取消送货单
`POST /api/v1/shipment/deliveries/{id}/cancel/`
权限:需要 `shipment.cancel_shipmentdelivery`
响应:`200 ShipmentDelivery`
### 追加绑定出货单到送货单
`POST /api/v1/shipment/deliveries/{id}/bind-shipments/`
请求体:
```json
{
"shipments": [4, 5]
}
```
请求字段:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `shipments` | integer[] | 是 | 要追加绑定的出货单 ID不能为空后端会去重 |
响应:`200 ShipmentDelivery`
## 销售品接口
### 查询当前可出货客户
`GET /api/v1/shipment/sales-items/customers/`
从未出货销售品反推出客户列表。
查询参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `merchant` | integer | 超级管理员必填 | 超级管理员必须传商户 ID普通用户忽略 |
| `customer_name` | string | 否 | 客户名称模糊匹配 |
| `limit` | integer | 否 | 分页大小 |
| `offset` | integer | 否 | 分页偏移 |
响应:
```json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"customer_id": 10,
"customer_name": "客户A",
"mobile": "13900139000",
"area": "杭州",
"unshipped_sales_items_count": 3
}
]
}
```
### 按客户查询销售品
`GET /api/v1/shipment/sales-items/by-customer/{customer_id}/`
查询参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `include_already_has_shipment` | boolean | 否 | 是否包含已关联出货单销售品,默认 `false`;只有字符串 `true` 会视为真 |
| `external_order_id` | string | 否 | 按生产订单外部订单号过滤 |
| `limit` | integer | 否 | 分页大小 |
| `offset` | integer | 否 | 分页偏移 |
响应:分页 `SalesItem[]`
### 按客户查询销售品分组
`GET /api/v1/shipment/sales-items/by-customer/{customer_id}/group/`
第一层按 `external_order_id` 分组并分页,第二层按 `name` 分组。
查询参数同“按客户查询销售品”。
响应:
```json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"external_order_id": "KD20441758",
"count": 2,
"name_groups": [
{
"name": "销售品A",
"count": 2,
"items": []
}
]
}
]
}
```
排序:`external_order_id = null` 排最后。
### 按生产订单查询销售品
`GET /api/v1/shipment/sales-items/by-printing-order/{printing_order_id}/`
路径参数:
- `printing_order_id` 支持内部 `PrintingOrder.id``external_order_id`
- 如果 `external_order_id` 匹配多个生产订单,此接口返回 `400 { "detail": "...请改用内部ID查询" }`
查询参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `include_already_has_shipment` | boolean | 否 | 是否包含已关联出货单销售品,默认 `false`;只有字符串 `true` 会视为真 |
响应:
```json
{
"count": 2,
"results": []
}
```
`results``SalesItem[]`,该接口不使用标准分页。
### 按生产订单查询销售品分组
`GET /api/v1/shipment/sales-items/by-printing-order/{printing_order_id}/group/`
`printing_job_id` 分组并分页。
查询参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `include_already_has_shipment` | boolean | 否 | 是否包含已关联出货单销售品,默认 `false` |
| `limit` | integer | 否 | 分页大小,作用于分组数量 |
| `offset` | integer | 否 | 分页偏移,作用于分组数量 |
响应:
```json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"printing_job_id": 123,
"count": 2,
"items": []
}
]
}
```
### 手动创建销售品
`POST /api/v1/shipment/sales-items/`
请求体:
```json
{
"printing_job_id": 123,
"name": "销售品A",
"quantity": "100.50",
"unit": 1,
"customer_id": 10,
"remark": "备注",
"position": "A1-01",
"merge_remark": {
"merge_type": "merge",
"jobs": [1, 2],
"main_job": 1,
"quantity": "100.50",
"unit": "米",
"job_count": 2
}
}
```
请求字段:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `printing_job_id` | integer | 是 | 生产任务 ID最小 1 |
| `name` | string | 是 | 销售品名称,最大 200 |
| `quantity` | string | 是 | 数量,最大 20支持小数 |
| `unit` | integer | 是 | 单位:`1/2/3/4` |
| `customer_id` | integer/null | 否 | 客户 ID不传时默认从生产订单获取 |
| `remark` | string | 否 | 备注,最大 200 |
| `position` | string | 否 | 货位,最大 200 |
| `merge_remark` | object/null | 否 | 合卷备注 JSON结构严格校验 |
`merge_remark` 必须只包含这些字段:`merge_type``jobs``main_job``quantity``unit``job_count`
响应:`201 SalesItem`
### 获取销售品详情
`GET /api/v1/shipment/sales-items/{id}/`
响应:`200 SalesItem`,包含 `product_image_url`
### 更新销售品
`PATCH /api/v1/shipment/sales-items/{id}/`
请求体只能包含以下字段:
```json
{
"quantity": "80.00",
"remark": "新备注",
"position": "B2-01"
}
```
可更新字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `quantity` | string | 数量,支持小数 |
| `remark` | string | 备注,最大 200 |
| `position` | string | 货位,最大 200 |
如果请求包含其他字段,返回 `400`
响应:`200 SalesItem`,包含 `product_image_url`
### 删除销售品
`DELETE /api/v1/shipment/sales-items/{id}/`
权限:需要 `shipment.soft_delete_salesitem`
响应:
```json
{
"detail": "销售品已标记为删除"
}
```
### 重建销售品
`POST /api/v1/shipment/sales-items/{id}/rebuild/`
仅销售品创建者可执行。
请求体:
```json
{
"new_printing_job_id": 456,
"quantity": "50.00"
}
```
请求字段:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `new_printing_job_id` | integer | 是 | 新生产任务 ID最小 1 |
| `quantity` | string | 否 | 新数量;不传则沿用原销售品数量 |
响应:`200 SalesItem`
## 前端重点注意
- 创建出货单时,`address_id` 不会自动填充地址快照。前端若选择客户地址,需要同时把地址表中的 `address/contact_name/contact_phone/area/coordinates/extra` 放入创建请求。
- `coordinates` 是新的字符串字段,前端不要再把新数据写到 `geo_coordinates`
- `geo_coordinates` 是历史兼容字段,读取时可能存在旧值,但更新接口目前不保存它。
- `area` 现在最大 100 字符,可以直接承载客户地址的 `area`
- `extra` 可用于承载客户地址扩展信息,允许对象、数组或 `null`
- 更新出货单时,传 `coordinates: null``extra: null` 可以清空对应字段;不传则不修改。
- 按生产订单查送货单的 `external_order_id` 多匹配错误返回 `500 + message`;按生产订单查销售品的同类错误返回 `400 + detail`