# Business 模块 API 汇总

本文件汇集 `business` 模块及其在 `api_v1` 下的所有接口，前端只需参考本页即可完成对接。所有接口均位于 `/api/v1/`，除明确说明外均需用户已登录且具备员工身份。

---

## 1. 通用约定

| 项 | 说明 |
|----|------|
| 认证 | Session / Token（与项目统一）。用户必须关联 `Employee` 且属于目标 `Merchant`。 |
| 多租户 | `request.user.employee.merchant` 自动限定数据范围，接口内部已校验，无需额外参数。 |
| 分页 | 列表接口使用 `limit` / `offset`，默认 `limit=20`，最大 `100`。 |
| 状态枚举 | `1=PENDING`、`2=APPROVED`、`3=CANCELLED`。审批接口通过 `action=approve/cancel` 修改状态。 |
| 金额字段 | 字符串形式的十进制数（例如 `"123.45"`），与后端 `Decimal` 精度一致。 |
| items 结构 | 受仓库模式影响：<br>• 严进/严进严出：`numbers: [int,…]` 表示条数明细<br>• 宽进宽出：`quantity` + `num_of_rolls`<br>• 严出：`consume_detail_ids: [detail_id,…]` |
| 审批副作用 | 采购/销售审批通过后根据商户设置触发 Celery 入/出库任务；付款/收款审批通过将同步写入余额表。 |
| 余额审计 | 审批成功后会同步写入 `BalanceChangeRecord`，记录来源单据、方向、前后余额与冲抵占位字段，供审计/红冲使用。 |

错误响应统一为 `{"error": "代码", "message": "描述"}`，字段可能因场景扩展（如 `record_id`、`fields` 等）。

---

## 2. 采购相关：采购单 + 采购退货单

| API | 方法 | 描述 |
|-----|------|------|
| `/purchase-orders/` | GET | 分页列表。 |
| `/purchase-orders/` | POST | 创建采购单。 |
| `/purchase-orders/<id>/` | PUT/PATCH | 编辑采购单（仅限审批中）。 |
| `/purchase-orders/<id>/review/` | POST | 审批或作废。 |

### 2.1 创建请求体

`items` 数组支持以下字段：

| 字段 | 必填 | 说明 |
|------|------|------|
| `product_id` | 是 | 产品 ID |
| `price` | 是 | 单价 |
| `unit` | 否 | 单位，默认使用产品单位 |
| `numbers` / `quantity` + `num_of_rolls` | 仓库模式相关 | 严进提供 `numbers`，宽进提供 `quantity`/`num_of_rolls` |
| `empty_diff_percent` | 否 | **空差百分比**，用于计算实际数量与空差（默认 `0`） |
| `color` / `spec` / `batch_number` / `remarks` | 否 | 可选信息 |

```json
{
  "supplier": 12,
  "warehouse": 8,
  "order_date": "2025-11-30",
  "items": [
    {
      "product_id": 1001,
      "numbers": [30, 25],
      "price": "12.50",
      "unit": "米"
    }
  ],
  "remarks": "可选"
}
```

宽进仓将 `numbers` 换为 `quantity` + `num_of_rolls`。至少 1 条明细，否则返回 400。

> 创建成功（201）返回精简体：`{"id", "human_id", "status", "message"}`，不包含 `items`、`total_amount` 等明细字段。需要完整结构请通过编辑（PUT/PATCH）或审批（`review`）接口获取，二者返回完整序列化。

### 2.2 审批

`POST /purchase-orders/<id>/review/`，请求体 `{"action": "approve"}` 或 `{"action": "cancel"}`。

- `approve`：在事务内将状态置为 `APPROVED`、写入供应商余额（正向金额），并在商户开启自动任务时触发 `create_purchase_order_stock_entries`。
- `cancel`：若已存在 `StockChangeRecord`（source=`PURCHASE`），返回 400；否则置为 `CANCELLED`。

成功返回最新的采购单序列化（含 `items`、`total_amount`、`status_name` 等）。

### 2.3 采购退货单（PurchaseReturnOrder）

| API | 方法 | 描述 |
|-----|------|------|
| `/purchase-return-orders/` | GET/POST | 与采购单相同的查询/创建接口，字段改为 `return_date`。 |
| `/purchase-return-orders/<id>/` | PUT/PATCH | 编辑采购退货单（仅限审批中）。 |
| `/purchase-return-orders/<id>/review/` | POST | 审批或作废，流程与采购单一致。 |

- **创建字段**：`supplier`、`warehouse`、`return_date`、`items`、可选 `purchase_order`。`items` 结构沿用采购单；当仓库 `mode=RESTRICT_IN_OUT` 时必须提供 `consume_detail_ids`，指明要冲销的入库明细。
- **审批逻辑**：
  - `approve`：在事务内锁单、构建 `stock_flow_items`，状态置为 `APPROVED`，调用 `StockFlowService.stock_out`（source=`PURCHASE_RETURN`），并向 `BalanceService` 写入 **负值** 以减少供应商应付。
  - `cancel`：仅允许 `PENDING` 且尚未生成 `StockChangeRecord` 的单据；审批完成后禁止作废。

---

## 3. 销售相关：销售单 + 销售退货单

| API | 方法 | 描述 |
|-----|------|------|
| `/sales-orders/` | GET | 分页列表。 |
| `/sales-orders/` | POST | 创建销售单。 |
| `/sales-orders/<id>/` | PUT/PATCH | 编辑销售单（仅限审批中）。 |
| `/sales-orders/<id>/review/` | POST | 审批或作废。 |

### 3.1 创建请求体

销售单 `items` 字段同样支持 `empty_diff_percent`、`color`、`spec`、`batch_number`、`remarks` 等信息，用途与采购单一致；仓库模式决定使用 `numbers`、`quantity + num_of_rolls` 或 `consume_detail_ids`。

顶层可选字段 `kind`（销售单类型）：`1=大货`、`2=样板`。不传或传空时默认 `1`（大货）；传入非法枚举值返回 400。

```json
{
  "customer": 6,
  "warehouse": 3,
  "kind": 1,
  "order_date": "2025-11-30",

  "items": [
    {
      "product_id": 1001,
      "numbers": [20, 18],
      "price": "18.80",
      "unit": "米"
    }
  ],
  "remarks": ""
}
```

仓库为严出（`RESTRICT_IN_OUT`）时必须改用：

```json
{
  "product_id": 1001,
  "consume_detail_ids": [321, 322],
  "quantity": 200,
  "price": "20",
  "unit": "米"
}
```

> 创建成功（201）返回精简体：`{"id", "human_id", "status", "message"}`，不包含 `items`、`total_amount` 等明细字段。需要完整结构请通过编辑（PUT/PATCH）或审批（`review`）接口获取，二者返回完整序列化。

### 3.2 审批

与采购单一致，但方向为出库：

- `approve`：写入客户余额（正向欠款），若商户配置自动出库则触发 `create_sales_order_stock_entries`。
- `cancel`：如果已存在 `StockChangeRecord`（source=`SALES`）则拒绝。

### 3.3 销售退货单（SalesReturnOrder）

| API | 方法 | 描述 |
|-----|------|------|
| `/sales-return-orders/` | GET/POST | 创建 / 列表接口。 |
| `/sales-return-orders/<id>/` | PUT/PATCH | 编辑销售退货单（仅限审批中）。 |
| `/sales-return-orders/<id>/review/` | POST | 审批或作废。 |

- **创建字段**：`customer`、`warehouse`、`return_date`、`items`、可选 `sales_order`。因退货为入库动作，严出仓不再需要 `consume_detail_ids`。
- **审批逻辑**：
  - `approve`：状态置为 `APPROVED`，调用 `StockFlowService.stock_in`（source=`SALES_RETURN`），并向 `BalanceService` 写入 **负值** 以冲减客户欠款，同时写 `BalanceChangeRecord`。
  - `cancel`：仅允许 `PENDING` 且未生成 `StockChangeRecord` 的单据；审批通过后不可作废。

---

## 4. 付款单（PaymentOrder）

| API | 方法 | 描述 |
|-----|------|------|
| `/payment-orders/` | GET | 分页列表。 |
| `/payment-orders/` | POST | 创建付款单。 |
| `/payment-orders/<id>/review/` | POST | 审批或作废。 |

### 4.1 创建请求体

```json
{
  "supplier": 12,
  "bank_account": 3,
  "payment_date": "2025-11-30",
  "amount": "5000.00",
  "discount_amount": "120.00",
  "remarks": "",
  "markup": "审批备注"
}
```

字段说明：

- `bank_account`：可选，引用 `basic_info.BankAccount`，用于记录具体的付款账户。
- `markup`：可选字符串，用于记录票据附言；与 `remarks`（内部备注）区分。
- `discount_amount`：可选，默认 0，允许大于 `amount`（表示折扣大于实付）；必须 ≥ 0。
- 列表/详情响应额外包含 `is_external_source` 与 `external_source_id`，用于标识是否来自外部系统同步以及对应外部记录 ID。
- `settlement_amount = amount + discount_amount`，所有余额、对账及汇总均基于结算金额。
- `amount` 必须大于 0。返回 201 + 创建的记录，响应中会包含 `discount_amount` 与 `settlement_amount`。

### 4.2 审批逻辑

- `approve`：在事务内锁定单据，防止重复审批；状态改为 `APPROVED`，并将供应商余额 **减少** 对应金额。
- `cancel`：仅允许从 `PENDING` 作废，且若已审批则返回 400。

---

## 5. 收款单（ReceiptOrder）

| API | 方法 | 描述 |
|-----|------|------|
| `/receipt-orders/` | GET | 列表。 |
| `/receipt-orders/` | POST | 创建收款单。 |
| `/receipt-orders/<id>/review/` | POST | 审批或作废。 |

### 5.1 创建

```json
{
  "customer": 6,
  "bank_account": 3,
  "receipt_date": "2025-11-30",
  "amount": "3200.00",
  "discount_amount": "50.00",
  "remarks": "",
  "markup": "回单附言"
}
```

字段说明：

- `bank_account`：可选，引用到账银行账户。
- `discount_amount`：可选，默认 0，可大于 `amount`，但必须 ≥ 0。
- `markup`：可选，记录回单附言，默认留空。
- 列表/详情响应额外包含 `is_external_source` 与 `external_source_id`，用于外部收款/退款同步关联与审计。
- `settlement_amount` 为响应只读字段（`amount + discount_amount`），对账及余额只会计算结算金额。

### 5.2 审批

- `approve`：状态改为 `APPROVED`，客户余额 **减少** 对应金额（冲减欠款）。若已取消则拒绝再次审批。
- `cancel`：仅允许从 `PENDING` 作废；当单据已审批时返回 400。

---

## 6. 余额表 / 对账接口

余额数据来自 `SupplierBalance` / `CustomerBalance` 表，所有审批通过的单据均在事务内写入，保证与业务状态一致。

### 6.1 客户余额

| API | 方法 | 描述 |
|-----|------|------|
| `/customers/<id>/balance/` | GET | 查询指定客户应收余额。 |

响应示例：

```json
{
  "customer": 6,
  "customer_name": "杭州零售商",
  "balance": "3200.00"
}
```

- 正数表示客户仍欠款；负数表示已收超额。
- 若客户无记录返回 `"0"`。

### 6.2 供应商余额

| API | 方法 | 描述 |
|-----|------|------|
| `/suppliers/<id>/balance/` | GET | 查询指定供应商待付余额。 |

响应示例：

```json
{
  "supplier": 3,
  "supplier_name": "桐乡面料商",
  "balance": "1850.00"
}
```

- 正数表示仍需支付给供应商的金额；负数表示预付或多付。
- 若供应商无记录返回 `"0"`。

### 6.3 余额变动记录（BalanceChangeRecord）

- **写入时机**：仅在审批通过瞬间写入；审批成功后禁止作废，若需冲销必须通过红冲/对冲流程生成反向记录。
- **字段概要**：
  - `target_type`：供应商 / 客户
  - `source_type` + `source_id`：关联具体业务对象（采购/销售/付款/收款）
  - `delta / balance_before / balance_after / direction`：记录本次增减与余额快照
  - `offset_to / offset_id`：预留冲抵链路，与库存 `StockSnapshot` 设计一致
  - `request_id / extra_meta`：用于幂等和记录审批上下文（操作者、触发渠道等）
- **用途**：对账、审计和红冲追踪。业务单据红冲会生成反向 `BalanceChangeRecord`，并通过 `offset_to / offset_id / red_flush_id` 与原记录关联。目前余额变动记录仍未开放对外查询 API，可在内部管理端或报表服务中直接访问；若后续开放，请提供分页、时间范围与 `source_type` 过滤能力。

### 6.4 业务单据红冲（Red Flush）

正式业务单据已开放整单红冲入口，详细说明请参阅独立文档 `docs/2026-06-12_business_red_flush_api.md`。

| API | 方法 | 描述 |
|-----|------|------|
| `/purchase-orders/<id>/red-flush/` | POST | 红冲已审批采购单，反向余额和库存影响 |
| `/sales-orders/<id>/red-flush/` | POST | 红冲已审批销售单；反向余额，若存在销售出库记录则同步反向库存 |
| `/purchase-return-orders/<id>/red-flush/` | POST | 红冲已审批采购退货单，反向余额和库存影响 |
| `/sales-return-orders/<id>/red-flush/` | POST | 红冲已审批销售退货单，反向余额和库存影响 |
| `/payment-orders/<id>/red-flush/` | POST | 红冲已审批付款单，反向供应商余额 |
| `/receipt-orders/<id>/red-flush/` | POST | 红冲已审批收款单，反向客户余额 |

请求体：

```json
{
  "reason": "录入错误，需要红冲"
}
```

`reason` 必填且不能为空。红冲成功后原单据状态保持已审批，并返回 `is_red_flushed=true`、`red_flush_id`、`red_flushed_at`。销售单没有对应库存记录时只反向余额，不执行库存红冲。外部来源付款/收款单不允许红冲。

## 7. 对账单（Statements）

客户/供应商对账单提供统一的金额视图与余额快照，用于销售/采购结算场景。完整说明（含响应示例、字段定义与业务规则）请参阅 `docs/statements.md`。

| API | 方法 | 描述 |
|-----|------|------|
| `/customers/<id>/statements/` | GET | 指定客户的销售 / 销退 / 收款对账单 |
| `/suppliers/<id>/statements/` | GET | 指定供应商的采购 / 采退 / 付款对账单 |
| `/statements/record/` | GET | 通过主体与单据参数获取单条对账记录 |

关键特性：

- 固定按 `occurred_at -> recorded_at -> source_id` 倒序输出，不提供排序参数。
- `positive_amount` / `negative_amount` 统一表示余额增减；`cumulative_amount`、`current_balance`、`arrears_amount` 均冗余在每条记录中，前端可直接使用。
- 客户对账单在存在外部 statement-only 业务依据时，可能出现 `external_sales_order` / `external_sales_return_order` 两种新的 `source_type`。
- 客户对账单在存在外部 statement-only 业务依据时，`current_balance` / `arrears_amount` 会基于“本地余额 + 外部业务来源净额”做临时展示口径修正；余额接口本身仍返回持久化 `CustomerBalance.balance`。
- 单条查询接口需提供 `counterparty_type`、`counterparty_id`、`order_type`、`order_id` 四个 Query 参数，返回 schema 与列表一致，仅 `records` 中包含匹配记录。

---

## 8. 错误码与常见响应

| 场景 | HTTP | 返回 |
|------|------|------|
| 未登录 / 认证失败 | 401 | `{"detail": "Authentication credentials were not provided."}` |
| 非本商户数据 | 403 | `{"error": "forbidden", "message": "无权限访问"}` |
| 单据不存在 | 404 | `{"error": "purchase_order_not_found"}` 等 |
| 审批非法状态 | 400 | 例如 `{"error": "purchase_order_has_stock_records"}`、`{"error": "receipt_order_already_approved"}` |
| 红冲非法状态 | 400 | 例如未审批、重复红冲、要求库存闭环的单据缺少可红冲库存记录、缺少可红冲余额记录、外部来源单据不允许红冲 |

---

## 9. 参考文档

- `docs/2026-06-12_business_red_flush_api.md`：业务单据红冲独立 API 文档。
- `docs/2026-06-12_business_red_flush_design.md`：业务单据红冲 service/API 设计备查。
- `docs/purchase_order_approval_and_red_flush.md`：采购单审批与红冲背景。
- `docs/sales_order_approval_and_red_flush.md`：销售单审批与严出模式说明。
- `docs/payment_receipt_workflow.md`：资金类单据与余额表写入逻辑。

本汇总会随着业务扩展同步更新，如需新增接口请补充到此文件以保持前端“单文档”体验。***

