1
0
forked from erp-dev/erp
Files
erpnew/docs/stock_api_overview.md
2026-06-13 11:44:15 +08:00

20 KiB
Raw Blame History

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
  2. Stock Models
  3. Stock Change Modes
  4. API Endpoints
  5. Examples
  6. 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:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

{
  "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_warehouseto_warehouse 必须属于当前商户,且不可相同。
  • 两个仓库的 mode 必须一致RESTRICT_IN、UNRESTRICTED 均可)。目前不支持 RESTRICT_IN_OUT(严进严出)的调拨。
  • 请求在数据库事务内完成,不能依赖异步任务;支持 request_id 幂等重试。
  • products 列表沿用原出入库的 payload 结构,允许:
    • quantities: 严谨/严进模式的显式数量数组;
    • value + unit_count/num_of_rolls: 宽松模式的拆分定义;
    • consume_detail_ids: 若未来开放严进严出调拨,可在此补充(当前会拒绝)。

Request Parameters:

{
  "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:

{
  "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_idincoming_record_id,可继续使用既有的 GET /api/v1/stock-change/<id>/ 接口查看具体的明细与快照。

List Stock Changes

URL: GET /api/v1/stock-changes/

Response: List of stock change records with pagination

{
  "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/<id>/

Response: Detailed view of a specific stock change record with its details

{
  "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/<id>/offset/
  • 权限:登录员工,且仓库必须属于当前商户。
  • 描述:针对已完成的库存变动记录生成反向库存记录,恢复库存数量并标记原快照 cancelled=true

Request Body

{
  "reason": "采购单作废,冲销库存",
  "request_id": "rcf-20251201-0001",   // 可选,幂等键
  "extra_meta": { "source": "purchase_order", "operator": 18 }
}

注意:当前版本仅支持“全量红冲”,items 字段请留空;未来版本会按需要开放部分明细红冲能力。

Response

{
  "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 指向原库存记录 IDStockSnapshot 会设置 cancelled=trueoffset_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 示例

{
  "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:
{
  "auto_complete": true
}
  • Response:
{
  "status": "success"
}

只有具备员工身份的登录用户才可调用该接口。

6. Stock Change Completion Behavior

  • 自动完成:当商户开启 auto_complete_stock_change 时,任意模式的创建接口都会立即调用 make_stock_change_completed,更新 Inventory 并生成 StockSnapshot
  • 手动完成:当需要补处理或未开启自动完成时,可调用 POST /api/v1/stock-change/<id>/finish/ 手动触发 make_stock_change_completed,也可直接在后台脚本里调用服务函数。
  • 状态字段StockChangeRecord.is_finished / finished_at 会在完成时标记,用于列表和详情接口判断记录是否已经生效。

7. Finish Stock Change Record (Manual)

  • URL: POST /api/v1/stock-change/<id>/finish/
  • Description: 手动触发指定出入库记录的库存扣减与快照写入,适用于未开启自动完成或需要补处理的场景。仅当前商户下且仓库可见的员工可调用。
  • Request: 无需请求体,只需保证 URL 中的 <id> 为目标 StockChangeRecord 的 ID。
  • Response:
{
  "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 与业务红冲

POST /api/v1/stock-change/<id>/offset/ 已在上文“库存红冲Stock Change Offset”说明。业务单据红冲会通过 business service 调用同一库存对冲能力,并把同一个 red_flush_id 写入原库存记录和反向库存记录。

面向前端或业务系统的正式业务红冲入口请优先使用 business 单据 API例如

  • POST /api/v1/purchase-orders/<id>/red-flush/
  • POST /api/v1/sales-orders/<id>/red-flush/
  • POST /api/v1/purchase-return-orders/<id>/red-flush/
  • POST /api/v1/sales-return-orders/<id>/red-flush/

完整业务 API 见 docs/2026-06-12_business_red_flush_api.md

Examples

Example 1: Creating a Stock-in Record (Full Mode)

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)

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)

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

curl -X POST http://example.com/api/v1/stock-change/17/finish/ \
  -H "Authorization: Bearer your_token"

Error Handling

Common Error Responses

Authentication Error

{
  "error": "无权限访问"
}

Missing Required Parameters

{
  "error": "缺少必要参数",
  "message": "请提供 type, warehouse, source_type"
}

Validation Error

{
  "error": "产品数据验证失败",
  "details": {
    "products": [
      "产品列表中存在重复的产品ID"
    ]
  }
}

Warehouse/Product Visibility Error

{
  "error": "仓库ID 1 对当前用户不可见"
}

Business Logic Error

{
  "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