# Stock Management API Documentation This document provides detailed documentation for the stock management module APIs, focusing on the three main stock change creation modes: Full, Relaxed, and Restricted. ## Table of Contents 1. [Overview](#overview) 2. [Stock Models](#stock-models) 3. [Stock Change Modes](#stock-change-modes) 4. [API Endpoints](#api-endpoints) 5. [Examples](#examples) 6. [Error Handling](#error-handling) ## Overview The stock management system handles all inventory movement operations including stock-in (入库) and stock-out (出库) transactions. The system supports three different modes of operation: 1. **Full Mode**: Standard operation with explicit quantity lists 2. **Relaxed Mode**: Automatically splits quantities based on total and unit count 3. **Restricted Mode**: Strict "restricted-in/out" mode for warehouse operations ## Stock Models ### StockChangeRecord Represents a stock change record (either stock-in or stock-out). **Fields**: - `id`: Record ID - `type`: Change type (1=Stock-in, 2=Stock-out) - `warehouse`: Associated warehouse - `source_type`: Source of the change (1=Purchase, 6=Sales, etc.) - `source_id`: ID of the source document (optional) - `created_by`: User who created the record - `is_finished`: Whether the stock change is completed - `finished_at`: When the record was completed - `remarks`: Additional notes ### StockChangeDetail Represents detailed items within a stock change record. **Fields**: - `id`: Detail ID - `product`: Associated product - `stock_change_record`: Parent stock change record - `quantity`: Quantity of the product - `unit`: Unit of measurement - `is_consumed`: Whether this detail has been consumed (for restricted mode) - `consume_with`: Reference to the inbound detail being consumed (for restricted mode) ### Inventory Represents current inventory levels for a product in a warehouse. **Fields**: - `id`: Inventory ID - `product`: Associated product - `warehouse`: Warehouse location - `quantity`: Current quantity - `num_of_rolls`: Number of rolls - `spec`: Product specification - `description`: Additional notes ## Stock Change Modes ### Source Types The system supports various source types for stock changes: **Incoming (入库) Sources**: - `1`: Purchase (采购) - `2`: Sales Return (销退) - `3`: Transport In (调入) - `4`: Recheck Addition (盘盈) - `5`: Combine (合并) - `11`: Offset (红冲,反向抵销入库) **Outgoing (出库) Sources**: - `6`: Sales (销售) - `7`: Purchase Return (采购退货) - `8`: Transport Out (调出) - `9`: Recheck Removal (盘亏) - `10`: Explode (拆卷) - `11`: Offset (红冲,反向抵销出库) ### Warehouse Modes Warehouses can operate in different modes: - `1`: Restricted In (严进) - Strict control on stock-in - `2`: Restricted In/Out (严进严出) - Strict control on both stock-in and stock-out - `3`: Unrestricted (宽进宽出) - Relaxed controls for both directions ## API Endpoints ### 1. Full Mode Stock Change **URL**: `POST /api/v1/stock-change/` **Description**: Creates a stock change record with explicit quantity lists for each product. **Request Parameters**: ```json { "type": 1, // 1=入库, 2=出库 "warehouse": 1, // 仓库ID "source_type": 1, // 来源类型,见上文说明 "source_id": 123, // 可选,来源单据ID "products": [ { "product": 1, // 产品ID "quantity": [85.5, 75.2, 90.0] // 数量列表,每个值对应一条明细 }, { "product": 2, "quantity": [120.0] } ] } ``` **Response**: ```json { "stock_change_record": { "id": 15, "type": 1, "warehouse": 1, "source_type": 1, "source_id": 123, "is_finished": false, "created_at": "2025-11-25T10:30:00Z", "updated_at": "2025-11-25T10:30:00Z" }, "details": [ { "id": 45, "product": 1, "product_name": "纯棉印花布", "quantity": 85.5, "unit": 1, "unit_display": "米", "stock_change_record": 15, "is_consumed": false, "consume_with_id": null }, { "id": 46, "product": 1, "product_name": "纯棉印花布", "quantity": 75.2, "unit": 1, "unit_display": "米", "stock_change_record": 15, "is_consumed": false, "consume_with_id": null } // ... 更多明细 ], "message": "成功创建库存变动记录及 3 条明细", "created_details_count": 3 } ``` ### 2. Relaxed Mode Stock Change **URL**: `POST /api/v1/stock-change/relaxed/` **Description**: Creates a stock change record using total quantity and unit count to automatically split into details. **Request Parameters**: ```json { "type": 1, // 1=入库, 2=出库 "warehouse": 1, // 仓库ID "source_type": 1, // 来源类型 "source_id": 123, // 可选,来源单据ID "products": [ { "product": 1, // 产品ID "quantity": { "value": 250.8, // 总数量 "unit_count": 2.5 // 单条数量,默认为1 } } ] } ``` **Response**: ```json { "stock_change_record": { "id": 16, "type": 1, "warehouse": 1, "source_type": 1, "source_id": 123, "is_finished": false, "created_at": "2025-11-25T11:00:00Z", "updated_at": "2025-11-25T11:00:00Z" }, "details": [ { "id": 48, "product": 1, "product_name": "纯棉印花布", "quantity": 100.0, // 自动拆分 "unit": 1, "unit_display": "米", "stock_change_record": 16, "is_consumed": false, "consume_with_id": null }, { "id": 49, "product": 1, "product_name": "纯棉印花布", "quantity": 100.0, // 自动拆分 "unit": 1, "unit_display": "米", "stock_change_record": 16, "is_consumed": false, "consume_with_id": null }, { "id": 50, "product": 1, "product_name": "纯棉印花布", "quantity": 50.8, // 剩余数量 "unit": 1, "unit_display": "米", "stock_change_record": 16, "is_consumed": false, "consume_with_id": null } ], "message": "成功创建库存变动记录及 3 条明细", "created_details_count": 3 } ``` ### 3. Restricted Mode Stock Change **URL**: `POST /api/v1/stock-change/restrict/` **Description**: Creates a stock change record in "restricted-in-out" mode where outbound details must reference existing inbound details. **Request Parameters**: ```json { "type": 2, // 1=入库, 2=出库 "warehouse": 1, // 仓库ID,必须为严进严出模式 "source_type": 6, // 来源类型 "source_id": 123, // 可选,来源单据ID "products": [ { "product": 1, // 产品ID "quantity": [150.5], // 数量列表 "consume_with": [23, 24] // 必须指定消耗的入库明细ID列表 } ] } ``` **Response**: ```json { "stock_change_record": { "id": 17, "type": 2, "warehouse": 1, "source_type": 6, "source_id": 123, "is_finished": false, "created_at": "2025-11-25T11:15:00Z", "updated_at": "2025-11-25T11:15:00Z" }, "details": [ { "id": 51, "product": 1, "product_name": "纯棉印花布", "quantity": 75.25, "unit": 1, "unit_display": "米", "stock_change_record": 17, "is_consumed": false, "consume_with_id": 23 // 消耗的入库明细ID }, { "id": 52, "product": 1, "product_name": "纯棉印花布", "quantity": 75.25, "unit": 1, "unit_display": "米", "stock_change_record": 17, "is_consumed": false, "consume_with_id": 24 // 消耗的入库明细ID } ], "message": "成功创建库存变动记录及 2 条明细", "created_details_count": 2 } ``` ### 4. Stock Transfer (调拨) **URL**: `POST /api/v1/stock-change/transfer/` **Description**: Performs an in-transaction transfer between two warehouses that share the same warehouse **mode** (可以跨类型,例如整件仓 → 散件仓)。一次调用会同步创建调出与调入的 `StockChangeRecord` 并立即完成库存扣加,确保库存一致性。 **Business Constraints**: - `from_warehouse` 与 `to_warehouse` 必须属于当前商户,且不可相同。 - 两个仓库的 `mode` 必须一致(RESTRICT_IN、UNRESTRICTED 均可)。目前不支持 `RESTRICT_IN_OUT`(严进严出)的调拨。 - 请求在数据库事务内完成,不能依赖异步任务;支持 `request_id` 幂等重试。 - `products` 列表沿用原出入库的 payload 结构,允许: - `quantities`: 严谨/严进模式的显式数量数组; - `value + unit_count/num_of_rolls`: 宽松模式的拆分定义; - `consume_detail_ids`: 若未来开放严进严出调拨,可在此补充(当前会拒绝)。 **Request Parameters**: ```json { "from_warehouse": 3, "to_warehouse": 5, "transfer_date": "2025-12-02", // 可选,默认当天 "remarks": "整件仓调拨到散件仓", // 可选 "request_id": "transfer-20251202-001", // 可选幂等键 "products": [ { "product_id": 12, "quantities": ["10.50", "5.25"] }, { "product_id": 27, "value": "120.0", "unit_count": "30" } ] } ``` **Response**: ```json { "id": 88, "merchant": 6, "from_warehouse": 3, "from_warehouse_name": "整件一号仓", "to_warehouse": 5, "to_warehouse_name": "散件贵宾仓", "mode": 1, "mode_display": "严进宽出", "operator": 24, "transfer_date": "2025-12-02", "status": 2, "status_display": "已完成", "remarks": "整件仓调拨到散件仓", "request_id": "transfer-20251202-001", "outgoing_record_id": 512, "incoming_record_id": 513, "items": [ { "id": 143, "product": 12, "product_name": "贡缎白坯布", "total_quantity": "15.75", "unit": 1, "unit_display": "米", "num_of_rolls": 2 }, { "id": 144, "product": 27, "product_name": "彩纱混纺", "total_quantity": "120.00", "unit": 1, "unit_display": "米", "num_of_rolls": 4 } ] } ``` **Error Cases**: | HTTP | Payload 示例 | 说明 | |------|--------------|------| | 400 | `{ "error": "调拨要求调出仓与调入仓的出入库模式一致" }` | 两仓 mode 不同 | | 400 | `{ "error": "调入仓与调出仓不能相同" }` | 相同仓库 | | 400 | `{ "error": "调出仓不存在或不属于当前商户" }` | 仓库不属于当前商户 | | 400 | `{ "error": "调拨产品明细不能为空" }` | products 为空 | | 409 | `{ "error": "请求重复" }` *(未来可扩展)* | 同一 `request_id` 重复提交(当前直接返回已存在记录) | > **提示**:响应中附带 `outgoing_record_id` 与 `incoming_record_id`,可继续使用既有的 `GET /api/v1/stock-change//` 接口查看具体的明细与快照。 ### Other Related Endpoints #### List Stock Changes **URL**: `GET /api/v1/stock-changes/` **Response**: List of stock change records with pagination ```json { "count": 100, "next": "http://example.com/api/v1/stock-changes/?page=2", "previous": null, "results": [ { "id": 15, "type": 1, "type_display": "入库", "warehouse": 1, "source_type": 1, "source_type_display": "采购", "is_finished": true, "finished_at": "2025-11-25T12:00:00Z", "created_at": "2025-11-25T10:30:00Z" } ] } ``` #### Get Stock Change Details **URL**: `GET /api/v1/stock-change//` **Response**: Detailed view of a specific stock change record with its details ```json { "stock_change_record": { "id": 15, "type": 1, "type_display": "入库", "warehouse": 1, "source_type": 1, "source_type_display": "采购", "is_finished": true, "finished_at": "2025-11-25T12:00:00Z", "created_at": "2025-11-25T10:30:00Z" }, "details": [ { "id": 45, "product": 1, "product_name": "纯棉印花布", "quantity": 85.5, "unit": 1, "unit_display": "米", "is_consumed": false, "consume_with_id": null } ] } ``` ### 4. 库存红冲(Stock Change Offset) - **URL**: `POST /api/v1/stock-change//offset/` - **权限**:登录员工,且仓库必须属于当前商户。 - **描述**:针对已完成的库存变动记录生成反向库存记录,恢复库存数量并标记原快照 `cancelled=true`。 **Request Body** ```json { "reason": "采购单作废,冲销库存", "request_id": "rcf-20251201-0001", // 可选,幂等键 "extra_meta": { "source": "purchase_order", "operator": 18 } } ``` > **注意**:当前版本仅支持“全量红冲”,`items` 字段请留空;未来版本会按需要开放部分明细红冲能力。 **Response** ```json { "status": "success", "message": "红冲记录已创建", "stock_change_record": { "id": 602, "type": 2, "source_type": 11, "source_id": 498, "warehouse": 3, "is_finished": true }, "details": [ { "id": 1880, "product": 15, "quantity": "100.00", "unit": 1 } ], "created_details_count": 1 } ``` **错误返回** | HTTP | 状态 | 说明 | |------|------|------| | 400 | `{"error": "仅允许对已完成的库存变动执行红冲"}` | 原记录未完成或仍在处理中 | | 400 | `{"error": "该库存变动记录已执行红冲"}` | 阻止重复红冲 | | 403 | `{"error": "无权访问该库存变动记录"}` | 仓库不属于当前商户 | | 404 | `{"error": "库存变动记录ID xxx 不存在"}` | 记录不存在 | 红冲成功后,会生成 `source_type=OFFSET` 的库存记录,`source_id` 指向原库存记录 ID;原 `StockSnapshot` 会设置 `cancelled=true`、`offset_id=<新快照ID>`,便于审计追踪。 --- ### 5. Stock Snapshot List - **URL**: `GET /api/v1/stock-snapshots/` - **Description**: Read-only list of `StockSnapshot` entries. Supports `limit/offset` pagination,搜索与过滤。 - **Query Parameters**: | 参数 | 说明 | |------|------| | `product` / `product_name` / `product_code` | 指定产品或模糊搜索 | | `warehouse` / `warehouse_name` | 指定仓库或模糊搜索 | | `stock_change_record` | 关联的出入库记录 ID | | `unit` | 单位过滤 | | `cancelled` | `true/false`,是否被标记取消 | | `has_offset` | `true/false`,筛选是否存在冲抵记录 | | `date_from` / `date_to` | 创建时间范围(`YYYY-MM-DD`) | | `search` | 针对产品名、产品编码、仓库名的全文搜索 | | `ordering` | 排序字段,默认 `-created_at` | **Response 示例**: ```json { "count": 2, "results": [ { "id": 901, "product": 15, "product_name": "纯棉印花布", "warehouse": 3, "warehouse_name": "严进仓", "delta": "120.00", "quantity_before": "80.00", "quantity_after": "200.00", "stock_change_record": 456, "stock_change_record_type": "入库", "stock_change_record_source": "采购", "unit": 1, "unit_display": "米", "num_of_rolls": 3, "offset_to": null, "cancelled": false, "created_at": "2025-11-25T12:05:00Z" } ] } ``` ### 5. Merchant Auto Complete Setting - **URL**: `POST /api/v1/set-merchant-auto-complete-stock-change/` - **Description**: 为当前商户开启 / 关闭“创建出入库记录后自动完成”功能。开启后,`create_*` 接口在事务内会立即调用 `stock.services.make_stock_change_completed`,自动写入库存与快照。 - **Request**: ```json { "auto_complete": true } ``` - **Response**: ```json { "status": "success" } ``` 只有具备员工身份的登录用户才可调用该接口。 ### 6. Stock Change Completion Behavior - **自动完成**:当商户开启 `auto_complete_stock_change` 时,任意模式的创建接口都会立即调用 `make_stock_change_completed`,更新 `Inventory` 并生成 `StockSnapshot`。 - **手动完成**:当需要补处理或未开启自动完成时,可调用 `POST /api/v1/stock-change//finish/` 手动触发 `make_stock_change_completed`,也可直接在后台脚本里调用服务函数。 - **状态字段**:`StockChangeRecord.is_finished` / `finished_at` 会在完成时标记,用于列表和详情接口判断记录是否已经生效。 ### 7. Finish Stock Change Record (Manual) - **URL**: `POST /api/v1/stock-change//finish/` - **Description**: 手动触发指定出入库记录的库存扣减与快照写入,适用于未开启自动完成或需要补处理的场景。仅当前商户下且仓库可见的员工可调用。 - **Request**: 无需请求体,只需保证 URL 中的 `` 为目标 `StockChangeRecord` 的 ID。 - **Response**: ```json { "status": "success", "message": "库存变动记录已标记为完成", "stock_change_record": { "id": 17, "type": 1, "warehouse": 1, "warehouse_name": "严谨仓", "source_type": 1, "source_type_display": "采购", "source_id": 123, "is_finished": true, "finished_at": "2025-11-25T12:30:00Z", "created_at": "2025-11-25T11:50:00Z", "created_by": "stock_admin", "remarks": null } } ``` ### 8. Stock Change Offset (Red Flush Placeholder) - **URL**: `POST /api/v1/stock-change//offset/` - **Description**: 面向未来的库存红冲入口。当前仅提供占位实现,用于锁定最终接口形态;实际红冲逻辑尚未上线,因此调用会返回 `501 Not Implemented`,方便前端或上游业务在流程编排中预留节点。 - **Request**: ```json { "reason": "审批错误,需要冲销", "items": [ { "product_id": 1, "quantities": ["50.00"] } ], "request_id": "po-123-offset", "extra_meta": { "source": "purchase_order", "operator": "warehouse_admin" } } ``` - **Response(当前占位行为)**: ```json { "error": "stock_offset_not_ready", "message": "库存红冲功能尚未实现", "record_id": 25 } ``` ## Examples ### Example 1: Creating a Stock-in Record (Full Mode) ```bash curl -X POST http://example.com/api/v1/stock-change/ \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your_token" \ -d '{ "type": 1, "warehouse": 1, "source_type": 1, "products": [ { "product": 1, "quantity": [100.5, 75.3, 120.0] } ] }' ``` ### Example 2: Creating a Stock-out Record (Relaxed Mode) ```bash curl -X POST http://example.com/api/v1/stock-change/relaxed/ \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your_token" \ -d '{ "type": 2, "warehouse": 1, "source_type": 6, "products": [ { "product": 2, "quantity": { "value": 250.8, "unit_count": 2.5 } } ] }' ``` ### Example 3: Creating a Stock-out Record (Restricted Mode) ```bash curl -X POST http://example.com/api/v1/stock-change/restrict/ \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your_token" \ -d '{ "type": 2, "warehouse": 1, "source_type": 6, "products": [ { "product": 3, "quantity": [100.0, 50.0], "consume_with": [45, 46] } ] }' ``` ### Example 4: Finish a Stock Change Record Manually ```bash curl -X POST http://example.com/api/v1/stock-change/17/finish/ \ -H "Authorization: Bearer your_token" ``` ## Error Handling ### Common Error Responses #### Authentication Error ```json { "error": "无权限访问" } ``` #### Missing Required Parameters ```json { "error": "缺少必要参数", "message": "请提供 type, warehouse, source_type" } ``` #### Validation Error ```json { "error": "产品数据验证失败", "details": { "products": [ "产品列表中存在重复的产品ID" ] } } ``` #### Warehouse/Product Visibility Error ```json { "error": "仓库ID 1 对当前用户不可见" } ``` #### Business Logic Error ```json { "error": "仓库出入库模式为【严进严出】,该模式暂未支持当前操作" } ``` ## Business Rules 1. **Warehouse Mode Restrictions**: - Full mode works with all warehouse modes - Relaxed mode requires "unrestricted" (宽进宽出) or "restricted-in" (严进) warehouses - Restricted mode requires "restricted-in-out" (严进严出) warehouses 2. **Stock Change Validation**: - Stock-in can only use incoming source types (1, 2, 3, 4, 5) - Stock-out can only use outgoing source types (6, 7, 8, 9, 10) 3. **Restricted Mode Specific Rules**: - Outbound details must reference existing inbound details - Referenced details must belong to the same warehouse - Referenced details must not already be consumed 4. **Permission Checks**: - Users can only access warehouses they have permission to see - Users can only access products they have permission to see - All stock change operations require proper authentication