# Shipment API 文档 出货管理模块 API 文档,包含出货单和销售品相关接口。 ## 目录 - [查询出货单](#查询出货单) - [创建出货单](#创建出货单) - [查询有待出货销售品的客户](#查询有待出货销售品的客户) - [按客户查询销售品](#按客户查询销售品) - [查询销售品详情](#查询销售品详情) - [通过生产订单查询销售品](#通过生产订单查询销售品) --- ## 查询出货单 ### 出货单列表 - **URL**: `/api/v1/shipment/shipments/` - **Method**: `GET` - **认证**: 需要登录(JWT Token) #### 查询参数(可选) | 参数 | 类型 | 说明 | |------|------|------| | limit | int | 分页大小(LimitOffsetPagination) | | offset | int | 偏移量 | | customer | int | 客户ID | | status | int | 状态(1=草稿(未发布), 2=已发布, 3=已取消, 4=已驳回, 5=已审核) | | external_id | string | 外部订单号(精确匹配) | | shipment_date_from | string | 出货日期起始(YYYY-MM-DD) | | shipment_date_to | string | 出货日期结束(YYYY-MM-DD) | #### 响应格式 使用 LimitOffsetPagination: ```json { "count": 2, "next": null, "previous": null, "results": [ { "id": 1, "merchant_id": 1, "merchant_name": "测试印花厂", "customer": 1, "customer_name": "客户A", "shipment_date": "2026-01-14", "status": 1, "status_display": "草稿(未发布)", "status_modified_at": null, "cancelled_by_id": null, "cancelled_by_name": null, "approved_by_id": null, "approved_by_name": null, "remark": "备注信息", "items_count": 3, "sales_items": [], "external_finished_products": [], "created_by_id": 1, "created_by_name": "张三", "created_at": "2026-01-14T10:00:00Z", "updated_at": "2026-01-14T10:00:00Z" } ] } ``` ### 出货单详情 - **URL**: `/api/v1/shipment/shipments//` - **Method**: `GET` - **认证**: 需要登录(JWT Token) #### 查询参数(可选) | 参数 | 类型 | 说明 | |------|------|------| | status | int | 可选状态过滤;取值为 1=草稿(未发布), 2=已发布, 3=已取消, 4=已驳回, 5=已审核。传入后仅当该出货单状态匹配时才返回详情 | --- ## 创建出货单 创建出货单并关联销售品。 业务规则补充: - 当 `sales_items` 非空时,所有销售品都必须关联有效的 `printing_job` - 并且这些销售品必须全部来自同一个 `printing_order` - 如果存在缺少生产任务、生产任务不存在、或跨生产订单混装,接口会拒绝创建 - 新创建的出货单默认状态为 `草稿(未发布)` ### 接口信息 - **URL**: `/api/v1/shipment/shipments/` - **Method**: `POST` - **认证**: 需要登录(JWT Token) ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | customer | int | 是 | 客户ID | | shipment_date | string | 是 | 出货日期(YYYY-MM-DD) | | area | string | 否 | 出货地区(可空字符串,长度<=30) | | remark | string | 否 | 备注 | | sales_items | array[int] | 否 | 要关联的销售品ID列表 | ### 请求示例 ```json { "customer": 1, "shipment_date": "2026-01-14", "area": "华东", "remark": "备注信息", "sales_items": [1, 2, 3] } ``` ### 响应格式 ```json { "id": 1, "merchant_id": 1, "merchant_name": "测试印花厂", "customer": 1, "customer_name": "客户A", "shipment_date": "2026-01-14", "area": "华东", "remark": "备注信息", "status": 1, "status_display": "草稿(未发布)", "status_modified_at": null, "cancelled_by_id": null, "cancelled_by_name": null, "approved_by_id": null, "approved_by_name": null, "items_count": 3, "sales_items": [], "external_finished_products": [], "created_by_id": 1, "created_by_name": "张三", "created_at": "2026-01-14T10:00:00Z", "updated_at": "2026-01-14T10:00:00Z" } ``` ### 响应字段说明 | 字段 | 类型 | 说明 | |------|------|------| | id | int | 出货单ID | | merchant_id | int | 所属商户ID(从当前登录用户推导) | | merchant_name | string | 所属商户名称(从当前登录用户推导) | | customer | int | 客户ID | | customer_name | string | 客户名称 | | shipment_date | string | 出货日期 | | area | string | 出货地区 | | remark | string | 备注 | | status | int | 状态枚举(1=草稿(未发布), 2=已发布, 3=已取消, 4=已驳回, 5=已审核) | | status_display | string | 状态显示名称 | | status_modified_at | string/null | 最后一次状态修改时间 | | cancelled_by_id | int/null | 取消人 ID,仅当进入已取消时可能有值 | | cancelled_by_name | string/null | 取消人名称,仅当进入已取消时可能有值 | | approved_by_id | int/null | 审核人 ID,仅当进入已审核时可能有值 | | approved_by_name | string/null | 审核人名称,仅当进入已审核时可能有值 | | items_count | int | 关联的销售品数量 | | created_by_id | int | 创建人ID | | created_by_name | string | 创建人名称 | | created_at | string | 创建时间 | | updated_at | string | 更新时间 | ### 状态流转规则 - `草稿(未发布)` 只能流转到 `已发布` - `已发布` 可以流转到 `已审核` / `已驳回` / `已取消` - `已驳回` 可以流转到 `已审核` / `已取消` - `已审核` 可以流转到 `已取消` - `已取消` 不可再流转到其他状态 - 重复设置同一状态保持幂等,不报错 - 进入 `已审核` 时必须提供审核人 ### 错误响应 #### 400 Bad Request - 客户不存在 ```json { "detail": "客户 999 不存在" } ``` #### 400 Bad Request - 销售品不存在 ```json { "detail": "以下销售品不存在: [999]" } ``` #### 400 Bad Request - 销售品已关联其他出货单 ```json { "detail": "以下销售品已关联到其他出货单: [1, 2]" } ``` #### 400 Bad Request - 销售品缺少关联生产任务 ```json { "detail": "以下销售品缺少关联生产任务,无法创建出货单: [1]" } ``` #### 400 Bad Request - 销售品关联的生产任务不存在 ```json { "detail": "以下销售品关联的生产任务不存在,无法创建出货单: [1]" } ``` #### 400 Bad Request - 销售品来自不同生产订单 ```json { "detail": "出货单中的销售品必须来自同一个生产订单" } ``` --- ## 创建出货单(external 版) 创建出货单但**不绑定任何销售品**,同时写入并关联“外部成品表”(用于兼容外部遗留系统)。 ### 接口信息 - **URL**: `/api/v1/shipment/shipments/external/` - **Method**: `POST` - **认证**: 需要登录(JWT Token) ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | customer | int | 是 | 客户ID | | shipment_date | string | 是 | 出货日期(YYYY-MM-DD) | | area | string | 否 | 出货地区(可空字符串,长度<=30) | | remark | string | 否 | 备注 | | external_id | string | 是 | 外部订单号(长度<=120) | | external_finished_products | array[object] | 是 | 外部成品表结构数组(至少 1 条) | `external_finished_products` 每项结构: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | style_name | string | 是 | 款式名称 | | num_of_rolls | int | 是 | 卷数 | | remark | string | 否 | 备注(可空) | ### 请求示例 ```json { "customer": 1, "shipment_date": "2026-01-14", "area": "华南", "remark": "external 备注(可选)", "external_id": "EXT-ORDER-001", "external_finished_products": [ {"style_name": "款式A", "num_of_rolls": 2, "remark": "A备注"}, {"style_name": "款式B", "num_of_rolls": 5} ] } ``` ### 响应示例 ```json { "id": 1, "merchant_id": 1, "merchant_name": "测试印花厂", "customer": 1, "customer_name": "客户A", "shipment_date": "2026-01-14", "area": "华南", "remark": "external 备注(可选)", "status": 1, "status_display": "草稿(未发布)", "external_id": "EXT-ORDER-001", "status_modified_at": null, "cancelled_by_id": null, "cancelled_by_name": null, "approved_by_id": null, "approved_by_name": null, "items_count": 0, "external_finished_products_count": 2, "created_by_id": 1, "created_by_name": "张三", "created_at": "2026-01-14T10:00:00Z", "updated_at": "2026-01-14T10:00:00Z" } ``` ### 错误响应 #### 400 Bad Request - external_id 为空 ```json { "external_id": ["external_id 不能为空"] } ``` #### 400 Bad Request - external_finished_products 为空 ```json { "external_finished_products": ["external_finished_products 不能为空"] } ``` --- ## 查询有待出货销售品的客户 查询当前商户下“存在未出货销售品”的客户列表。 ### 接口信息 - **URL**: `/api/v1/shipment/sales-items/customers/` - **Method**: `GET` - **认证**: 需要登录(JWT Token) ### 查询参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | limit | int | 否 | 分页大小(LimitOffsetPagination) | | offset | int | 否 | 偏移量 | ### 业务逻辑 1. 仅统计当前商户下的销售品 2. 只统计 `shipment` 为空的销售品(未出货) 3. 只返回至少拥有 1 条未出货销售品的客户 4. 返回客户基础信息及未出货销售品数量 ### 响应格式 ```json { "count": 2, "next": null, "previous": null, "results": [ { "customer_id": 12, "customer_name": "客户A", "mobile": "13800000000", "area": "杭州", "unshipped_sales_items_count": 18 } ] } ``` ### 响应字段说明 | 字段 | 类型 | 说明 | |------|------|------| | count | int | 结果总数 | | next | string/null | 下一页链接 | | previous | string/null | 上一页链接 | | results[].customer_id | int | 客户ID | | results[].customer_name | string | 客户名称 | | results[].mobile | string/null | 客户手机号 | | results[].area | string/null | 客户地区 | | results[].unshipped_sales_items_count | int | 该客户未出货销售品数量 | --- ## 按客户查询销售品 查询指定客户下的销售品列表,默认仅返回未出货销售品。 ### 接口信息 - **URL**: `/api/v1/shipment/sales-items/by-customer//` - **Method**: `GET` - **认证**: 需要登录(JWT Token) ### 路径参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | customer_id | int | 是 | 客户ID | ### 查询参数 | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | include_already_has_shipment | bool | 否 | false | 是否包含已关联出货单的销售品 | | external_order_id | string | 否 | - | 按生产订单外部订单号精确筛选 | | limit | int | 否 | 800 | 分页大小 | | offset | int | 否 | 0 | 偏移量 | ### 业务逻辑 1. 仅允许查询当前商户下的客户 2. 仅查询当前商户下、`customer_id` 匹配的销售品 3. 默认只返回 `shipment` 为空的销售品(待出货) 4. 可通过 `include_already_has_shipment=true` 包含已出货销售品 5. 可通过 `external_order_id` 按关联生产订单的外部订单号精确筛选 ### 响应格式 ```json { "count": 2, "next": null, "previous": null, "results": [ { "id": 101, "name": "赛扬 190g", "quantity": "1200.00", "unit": 1, "unit_display": "米", "position": "A1-01", "remark": "", "printing_job_id": 88, "printing_order_id": 23, "external_order_id": "KD20135142", "customer_id": 12, "customer_name": "客户A", "shipment_id": null, "shipment_date": null, "created_at": "2026-03-28T10:00:00Z", "created_by_id": 1, "created_by_name": "张三" } ] } ``` ### 错误响应 #### 404 Not Found - 客户不存在或无权限 ```json { "detail": "客户 999 不存在" } ``` --- ## 查询销售品详情 查询单个销售品详情,返回列表接口中的全部字段,并额外补充关联生产任务产品图片。 ### 接口信息 - **URL**: `/api/v1/shipment/sales-items//` - **Method**: `GET` - **认证**: 需要登录(JWT Token) ### 路径参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | int | 是 | 销售品ID | ### 业务逻辑 1. 仅允许查询当前商户下的销售品 2. 返回销售品基础信息、关联客户信息、关联出货信息 3. 额外返回关联 `PrintingJob` 产品图 URL:`product_image_url` 4. 若无关联图片,则 `product_image_url` 返回 `null` ### 响应格式 ```json { "id": 101, "name": "赛扬 190g", "quantity": "1200.00", "unit": 1, "unit_display": "米", "position": "A1-01", "remark": "", "printing_job_id": 88, "printing_order_id": 23, "external_order_id": "KD20135142", "customer_id": 12, "customer_name": "客户A", "shipment_id": null, "shipment_date": null, "created_at": "2026-03-28T10:00:00Z", "created_by_id": 1, "created_by_name": "张三", "product_image_url": "https://cdn.example.com/products/88/main.jpg" } ``` ### 响应字段说明 | 字段 | 类型 | 说明 | |------|------|------| | id | int | 销售品ID | | name | string | 销售品名称 | | quantity | string | 数量(Decimal,保留2位小数) | | unit | int | 单位编码(1=米, 2=件, 3=码, 4=个) | | unit_display | string | 单位显示名称 | | position | string | 货位(可能为空) | | remark | string | 备注(可能为空) | | printing_job_id | int/null | 关联的生产任务ID | | printing_order_id | int/null | 关联的生产订单ID | | external_order_id | string/null | 关联生产订单的外部订单号 | | customer_id | int/null | 销售品关联客户ID | | customer_name | string/null | 销售品关联客户名称 | | shipment_id | int/null | 关联的出货单ID,null 表示未出货 | | shipment_date | string/null | 出货日期(YYYY-MM-DD),null 表示未出货 | | created_at | string | 创建时间(ISO 8601) | | created_by_id | int/null | 创建人ID | | created_by_name | string/null | 创建人名称 | | product_image_url | string/null | 关联产品主图 URL,无图时为 `null` | ### 错误响应 #### 404 Not Found - 销售品不存在或无权限 ```json { "detail": "销售品 999 不存在" } ``` ### 使用示例 ```bash curl -X GET \ 'https://api.example.com/api/v1/shipment/sales-items/101/' \ -H 'Authorization: Bearer ' ``` --- ## 通过生产订单查询销售品 查询与指定生产订单(PrintingOrder)关联的所有销售品(SalesItem)。 ### 接口信息 - **URL**: `/api/v1/shipment/sales-items/by-printing-order//` - **Method**: `GET` - **认证**: 需要登录(JWT Token) ### 路径参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | printing_order_id | string | 是 | 生产订单内部ID,或 `external_order_id` | ### 查询参数 | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | include_already_has_shipment | bool | 否 | false | 是否包含已关联出货单的销售品 | ### 业务逻辑 1. 先尝试将路径参数按内部生产订单ID查询 2. 若内部ID未命中,则按 `external_order_id` 查询生产订单 3. 若 `external_order_id` 命中多条生产订单,则返回 `400 Bad Request` 4. 获取该生产订单下所有 `PrintingJob` 的 ID 5. 查询 `SalesItem`,过滤 `printing_job_id` 在这些 job ID 中的记录 6. 根据 `include_already_has_shipment` 参数决定是否过滤已关联出货单的销售品: - `false`(默认):只返回 `shipment` 为空的销售品(待出货) - `true`:返回所有销售品(包含已出货的) ### 响应格式 ```json { "count": 2, "results": [ { "id": 1, "name": "产品A - 红色", "quantity": "100.00", "unit": 1, "unit_display": "米", "position": "A1-01", "remark": "加急处理", "printing_job_id": 123, "printing_order_id": 456, "customer_id": null, "customer_name": null, "shipment_id": null, "shipment_date": null, "created_at": "2026-01-14T10:00:00Z", "created_by_id": 1, "created_by_name": "张三" }, { "id": 2, "name": "产品B - 蓝色", "quantity": "50.50", "unit": 1, "unit_display": "米", "position": "", "remark": "", "printing_job_id": 124, "printing_order_id": 456, "customer_id": 10, "customer_name": "客户A", "shipment_id": 5, "shipment_date": "2026-01-13", "created_at": "2026-01-13T15:30:00Z", "created_by_id": 2, "created_by_name": "李四" } ] } ``` ### 响应字段说明 | 字段 | 类型 | 说明 | |------|------|------| | count | int | 结果总数 | | results | array | 销售品列表 | | results[].id | int | 销售品ID | | results[].name | string | 销售品名称 | | results[].quantity | string | 数量(Decimal,保留2位小数) | | results[].unit | int | 单位编码(1=米, 2=件, 3=码, 4=个) | | results[].unit_display | string | 单位显示名称 | | results[].position | string | 货位(可能为空) | | results[].remark | string | 备注(可能为空) | | results[].printing_job_id | int/null | 关联的生产任务ID | | results[].printing_order_id | int/null | 关联的生产订单ID | | results[].customer_id | int/null | 销售品级别的客户ID | | results[].customer_name | string/null | 销售品关联客户名称 | | results[].shipment_id | int/null | 关联的出货单ID,null 表示未出货 | | results[].shipment_date | string/null | 出货日期(YYYY-MM-DD),null 表示未出货 | | results[].created_at | string | 创建时间(ISO 8601) | | results[].created_by_id | int/null | 创建人ID | | results[].created_by_name | string/null | 创建人名称 | ### 错误响应 #### 404 Not Found - 生产订单不存在 ```json { "detail": "生产订单 999 不存在" } ``` #### 400 Bad Request - external_order_id 匹配多条生产订单 ```json { "detail": "external_order_id KD20135142 匹配到多个生产订单,请改用内部ID查询" } ``` #### 401 Unauthorized - 未登录 ```json { "detail": "Authentication credentials were not provided." } ``` ### 使用示例 #### 查询待出货的销售品(默认) ```bash curl -X GET \ 'https://api.example.com/api/v1/shipment/sales-items/by-printing-order/123/' \ -H 'Authorization: Bearer ' ``` #### 使用 external_order_id 查询 ```bash curl -X GET \ 'https://api.example.com/api/v1/shipment/sales-items/by-printing-order/KD20135142/' \ -H 'Authorization: Bearer ' ``` #### 查询所有销售品(包含已出货) ```bash curl -X GET \ 'https://api.example.com/api/v1/shipment/sales-items/by-printing-order/123/?include_already_has_shipment=true' \ -H 'Authorization: Bearer ' ``` --- ## 单位编码对照表 | 编码 | 名称 | |------|------| | 1 | 米 | | 2 | 件 | | 3 | 码 | | 4 | 个 | --- ## 相关模块 - `shipment/services.py`: 业务逻辑层 - `api_v1/views/shipment/`: API 视图层 - `shipment/models.py`: 数据模型(SalesItem, Shipment) --- ## 外部系统兼容(数据模型说明) 为兼容外部遗留系统,Shipment 模块新增了“外部成品表”模型: - **关系**:`Shipment` 1 → N `ExternalFinishedProduct` - 外键在 `ExternalFinishedProduct.shipment`(可空) - **Shipment 外部字段**: - `external_id`:外部订单号(可空)