# API v1：预销售单 / 预采购单（前端对接）

本文档面向前端，描述预销售单（PreSalesOrder）与预采购单（PrePurchaseOrder）的 API、字段与参数。

- API 前缀：`/api/v1/`
- 认证：接口均要求登录（`IsAuthenticated`）。
- 权限：要求当前用户绑定员工（`request.user.employee`），否则返回 `403`。

## 分页（Limit/Offset）

预销售单列表、预采购单列表均使用 DRF `LimitOffsetPagination`：

- `limit`：返回条数，默认 `20`，最大 `100`
- `offset`：偏移量，默认 `0`

分页响应结构：

```json
{
  "count": 123,
  "next": "http://.../api/v1/pre-sales-orders/?limit=20&offset=20",
  "previous": null,
  "results": [
    { "id": 1, "human_id": "YS...", "items": [] }
  ]
}
```

## 列表查询参数（已支持）

预销售单列表 `GET /pre-sales-orders/`：

- `limit` / `offset`
- `customer` / `customer_id`（int）
- `warehouse` / `warehouse_id`（int）
- `kind`（int）
- `human_id__icontains`（string）
- `created_at_from`（datetime 或 date）
- `created_at_to`（datetime 或 date）

预采购单列表 `GET /pre-purchase-orders/`：

- `limit` / `offset`
- `supplier` / `supplier_id`（int）
- `warehouse` / `warehouse_id`（int）
- `kind`（int）
- `human_id__icontains`（string）
- `created_at_from`（datetime 或 date）
- `created_at_to`（datetime 或 date）

---


---

# 预销售单 PreSalesOrder

## 1) 列表

- `GET /api/v1/pre-sales-orders/?limit=20&offset=0`
- 查询参数：
  - `limit`（可选，int，默认 20，最大 100）
  - `offset`（可选，int，默认 0）
- 响应：`200 OK`，分页结构，`results` 为预销售单数组

## 2) 创建

- `POST /api/v1/pre-sales-orders/`

请求体参数（JSON）：

- `customer` / `customer_id`（必填，int）：客户 ID
- `warehouse` / `warehouse_id`（必填，int）：仓库 ID
- `kind`（可选，int）：预销售单类型
  - `1`：大货
  - `2`：样板
  - 默认 `1`
- `remarks`（可选，string）
- `items`（必填，array，非空）：明细列表

`items[]` 字段：

- `product_id` / `product`（必填，int）：产品 ID
- `quantity`（必填，string 或 number）：数量（会被转换为 Decimal）
- `unit`（可选，string）：单位；为空时会尝试使用产品默认单位
- `product_name`（可选，string）：产品名称（弱关联承载）
- `color`（可选，string）
- `spec`（可选，string）
- `quantity_of_rolls`（可选，string）：各条数数量（原样保存）
- `num_of_rolls`（可选，int）：条数，必须 > 0，默认 `1`
- `order_quantity`（可选，int）：下单数量，必须为非负整数
- `remarks`（可选，string）：明细备注

响应：`201 CREATED`，返回预销售单详情（含 `items`）。

示例请求：

```json
{
  "customer": 1,
  "warehouse": 2,
  "kind": 1,
  "remarks": "预销售单备注",
  "items": [
    {
      "product_id": 10,
      "quantity": "12.5",
      "unit": "米",
      "order_quantity": 7,
      "remarks": "明细备注"
    }
  ]
}
```

## 3) 详情

- `GET /api/v1/pre-sales-orders/{id}/`
- 响应：`200 OK`，返回预销售单详情

## 4) 更新（全量/部分）

- `PUT /api/v1/pre-sales-orders/{id}/`
- `PATCH /api/v1/pre-sales-orders/{id}/`

请求体参数：

- `customer` / `customer_id`（可选，int）
- `warehouse` / `warehouse_id`（可选，int）
- `kind`（可选，int）
- `remarks`（可选，string）
- `items`（必填，array，非空）：更新时必须提供，否则会返回 `400`

响应：`200 OK`，返回更新后的详情。

## 5) 删除

- `DELETE /api/v1/pre-sales-orders/{id}/`
- 响应：`204 NO CONTENT`

## 6) 转换为销售单（仅严进严出仓库）

- `POST /api/v1/pre-sales-orders/{id}/convert-to-sales/`

约束：

- 仅支持仓库模式为 **严进严出（RESTRICT_IN_OUT）** 的预销售单
- 若不是严进严出，返回 `403`
- 需要已存在有效配货记录（用于生成销售单明细的 `consume_detail_ids`）

响应：`201 CREATED`

```json
{
  "id": 1001,
  "human_id": "XS20260202000001",
  "status": 1,
  "message": "预销售单已转换为销售单，等待审批"
}
```

错误示例（403）：

```json
{
  "error": "仅支持严进严出仓库模式"
}
```

错误示例（400）：

```json
{
  "error": "明细 123 缺少配货库存明细"
}

## 7) 配货记录（Allocations）

### 7.1 创建配货记录

- `POST /api/v1/pre-sales-order-items/{item_id}/allocations/`

请求体参数（JSON）：

```json
{
  "stock_ids": [1, 2, 3],
  "quantity": "10.00",
  "unit": "KG",
  "remarks": "备注信息"
}
```

响应：`201 CREATED`，返回配货记录详情。

### 7.2 撤销配货记录（含解冻库存）

- `DELETE /api/v1/pre-sales-order-items/{item_id}/allocations/{pk}/`

行为说明：

- 将配货记录状态置为撤销（status=2）
- **同时解冻**该配货记录创建时冻结的库存（StockFreeze.status=取消）

响应：`200 OK`，返回配货记录详情。
```

## 预销售单字段说明（响应）

预销售单对象字段（`results[]` 与详情一致）：

- `id`（int）：主键
- `human_id`（string）：人类可读编号（如 `YSYYYYMMDD000001`）
- `merchant`（int）：所属商户 ID
- `merchant`（int）：商户 ID（调试字段）
- `customer`（int）：客户 ID
- `customer_name`（string，只读）
- `warehouse`（int）：仓库 ID
- `warehouse_name`（string，只读）
- `operator`（int|null）：经办人（员工）ID（创建时由后端从登录用户推导）
- `operator_name`（string，只读）
- `kind`（int）：类型（1/2）
- `remarks`（string|null）
- `created_at`（datetime string）
- `items`（array）：明细（只读）

`items[]` 字段（响应）：

- `id`（int）
- `product_id`（int|null）
- `product_name`（string）
- `color`（string|null）
- `quantity`（string|null，通常为两位小数，如 `"20.00"`）
- `unit`（string）
- `spec`（string|null）
- `remarks`（string|null）

---

# 预采购单 PrePurchaseOrder

## 1) 列表

- `GET /api/v1/pre-purchase-orders/?limit=20&offset=0`
- 查询参数：
  - `limit`（可选，int，默认 20，最大 100）
  - `offset`（可选，int，默认 0）
- 响应：`200 OK`，分页结构

## 2) 创建

- `POST /api/v1/pre-purchase-orders/`

请求体参数（JSON）：

- `supplier` / `supplier_id`（必填，int）：供应商 ID
- `warehouse` / `warehouse_id`（必填，int）：仓库 ID
- `kind`（可选，int）：预采购单类型
  - `1`：大货
  - `2`：样板
  - 默认 `1`
- `remarks`（可选，string）
- `items`（必填，array，非空）：明细列表（字段同预销售明细）

响应：`201 CREATED`，返回预采购单详情（含 `items`）。

示例请求：

```json
{
  "supplier": 1,
  "warehouse": 2,
  "kind": 1,
  "remarks": "预采购单备注",
  "items": [
    {
      "product_id": 10,
      "quantity": "12.5",
      "unit": "米",
      "order_quantity": 7,
      "remarks": "明细备注"
    }
  ]
}
```

## 3) 详情

- `GET /api/v1/pre-purchase-orders/{id}/`
- 响应：`200 OK`

## 4) 更新（全量/部分）

- `PUT /api/v1/pre-purchase-orders/{id}/`
- `PATCH /api/v1/pre-purchase-orders/{id}/`

请求体参数：

- `supplier` / `supplier_id`（可选，int）
- `warehouse` / `warehouse_id`（可选，int）
- `kind`（可选，int）
- `remarks`（可选，string）
- `items`（必填，array，非空）：更新时必须提供，否则会返回 `400`

响应：`200 OK`

## 5) 删除

- `DELETE /api/v1/pre-purchase-orders/{id}/`
- 响应：`204 NO CONTENT`

## 预采购单字段说明（响应）

预采购单对象字段（`results[]` 与详情一致）：

- `id`（int）
- `human_id`（string）：人类可读编号（如 `YCYYYYMMDD000001`）
- `merchant`（int）
- `merchant`（int）：商户 ID（调试字段）
- `supplier`（int）
- `supplier_name`（string，只读）
- `warehouse`（int）
- `warehouse_name`（string，只读）
- `operator`（int|null）
- `operator_name`（string，只读）
- `kind`（int，1/2）
- `remarks`（string|null）
- `created_at`
- `items`（array，只读）

`items[]` 字段同预销售明细响应。

---

# 错误响应约定（两类接口通用）

- `400 BAD REQUEST`：参数校验失败
  - 示例：`{"error": "items 需要为非空数组"}`
- `401 UNAUTHORIZED`：未登录
- `403 FORBIDDEN`：无员工信息/无权限
  - 示例：`{"error": "无权限访问"}`
- `404 NOT FOUND`：订单不存在
  - 示例：`{"error": "预销售单不存在"}` / `{"error": "预采购单不存在"}`
