# Statements 对账单 API

本文件专门描述客户与供应商对账单接口的请求与响应结构。所有接口均位于 `/api/v1/`，默认需要登录并具备员工身份，且只返回当前员工所属商户的数据。

---

## 1. 功能概览

- **客户对账单**：聚合指定客户的销售单、销售退货单、收款单，以及外部来源的 statement-only 业务依据，统一展示正负金额、当前余额以及累欠金额。
- **供应商对账单**：聚合指定供应商的采购单、采购退货单、付款单，展示待付金额的增减与余额。
- **排序规则**：固定按 `occurred_at -> recorded_at -> source_id` 倒序排列，不支持外部修改。
- **金额方向**：
  - 客户：销售单记入 `positive_amount`（增加应收），销售退货/收款记入 `negative_amount`（减少应收）。
  - 供应商：采购单记入 `positive_amount`（增加待付），采购退货/付款记入 `negative_amount`（减少待付）。
- **余额来源**：供应商对账单直接使用余额表；客户对账单在存在外部 statement-only 业务依据时，会基于余额表做一次“仅用于 statement 展示”的临时口径修正。

### 1.1 外部 statement-only 业务依据

当客户的历史 `sale` / `sale_return` 来自外部系统、且不希望落入核心 `SalesOrder` / `SalesReturnOrder` 时，系统会把它们存入：

- `business.ExternalCustomerStatementOrder`

这些记录：

- 只参与客户对账单计算
- 不参与库存流转
- 不参与核心销售单审批链
- 不修改持久化 `CustomerBalance`

在对账单响应中，它们会以以下 `source_type` 出现：

- `external_sales_order`
- `external_sales_return_order`

---

## 2. 接口列表

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

请求无需额外参数；分页暂不开放（按时间倒序返回全部记录）。单条记录查询则必须提供如下 Query 参数：

| 参数 | 说明 |
|------|------|
| `counterparty_type` | 业务主体类型，`customer` 或 `supplier` |
| `counterparty_id` | 对应客户或供应商 ID |
| `order_type` | 单据类型，取值与 `source_type` 一致，例如 `sales_order`、`purchase_order` 等 |
| `order_id` | 单据 ID |

> ⚠️ 由于底层仍需构建完整对账单后再筛选匹配记录，在大体量数据下应谨慎调用此接口。

---

## 3. 响应结构

```json
{
  "counterparty": 6,
  "counterparty_name": "杭州零售商",
  "records": [
    {
      "source_type": "sales_order",
      "source_label": "销售单",
      "source_id": 1024,
      "occurred_at": "2025-11-30",
      "recorded_at": "2025-12-01T03:26:18.815992Z",
      "status": 2,
      "status_label": "审批通过",
      "counterparty": 6,
      "counterparty_name": "杭州零售商",
      "positive_amount": "3200.00",
      "negative_amount": "0.00",
      "cumulative_amount": "0.00",
      "current_balance": "1850.00",
      "arrears_amount": "1850.00"
    },
    {
      "source_type": "receipt_order",
      "source_label": "收款单",
      "source_id": 2001,
      "occurred_at": "2025-12-05",
      "recorded_at": "2025-12-05T02:11:07.441982Z",
      "status": 2,
      "status_label": "审批通过",
      "counterparty": 6,
      "counterparty_name": "杭州零售商",
      "positive_amount": "0.00",
      "negative_amount": "1500.00",
      "cumulative_amount": "3200.00",
      "current_balance": "1850.00",
      "arrears_amount": "-335.00"
    }
  ],
  "summary": {
    "positive_total": "3200.00",
    "negative_total": "1500.00"
  }
}
```

### 3.1 单条记录查询示例

`GET /api/v1/statements/record/?counterparty_type=customer&counterparty_id=6&order_type=sales_order&order_id=1024`

返回结构与对账单列表保持一致，仅 `records` 数组只包含匹配的那一条记录，`summary` 会基于该数组重新计算。

### 字段说明

| 字段 | 说明 |
|------|------|
| `source_type` / `source_label` | 业务来源及可读名称（`sales_order`、`payment_order` 等）。 |
| `source_id` | 业务单据 ID。 |
| `occurred_at` | 业务日期（如 `sales_date`、`return_date`、`receipt_date` 等）。 |
| `recorded_at` | 系统记录时间（`created_at`）。 |
| `status` / `status_label` | 业务单据当前状态（仅返回 `APPROVED` 的记录）。 |
| `counterparty` / `counterparty_name` | 客户或供应商信息。 |
| `positive_amount` / `negative_amount` | 金额正负值（字符串形式的 Decimal）。 |
| `cumulative_amount` | 当前记录输出前累计的对账金额（正负抵消）。 |
| `current_balance` | 余额表的最新应收/待付款快照；每条记录重复提供，便于表格展示。 |
| `arrears_amount` | 累欠金额，等于 `current_balance - cumulative_amount`，表示该记录时刻的实时欠款/待付款。 |
| `remarks` | 统一备注字段。所有 record 都会返回；无备注时为空字符串。 |
| `extra` | 预留字段，后续可扩展批次、仓库等信息。 |
| `summary` | 记录集中正负金额的求和，供前端快速展示。 |

对于外部 statement-only 业务依据，`extra` 当前会额外包含：

- `external_source_id`
- `external_customer_id`
- `settlement_method`

对客户对账单来说，当前还可能出现两种新的 `source_type`：

- `external_sales_order`
- `external_sales_return_order`

`remarks` 的来源统一如下：

- 本地采购/销售/退货/收付款单：直接取对应单据的 `remarks`
- 外部 statement-only 业务依据：取 `ExternalCustomerStatementOrder.remarks`，其中会包含同步时整理过的 `BeiZhu` / `BeiZhuC` / `BeiZhuD` / `MeoD` 文本

---

## 4. 业务规则

1. **审批依赖**：只有审批通过的单据才会出现在对账单中；审批完成后若需冲销则需走红冲流程。
2. **数据一致性**：在纯本地业务场景下，对账单直接基于审批后的业务单据和余额表计算，因此账面数据与业务状态保持一致。
3. **幂等保障**：`BalanceService` 在审批中使用行级锁和 `select_for_update`，避免重复写入；对账单查询为只读操作，不影响事务。
4. **扩展字段**：如需在对账单中加入汇总、备注、仓库信息，可在视图构建 `extra` 字段或通过 serializer context 注入新的统计字段。

### 4.1 客户余额口径边界

这是本次修正最重要的边界说明。

当客户存在 `ExternalCustomerStatementOrder` 时：

- `GET /customers/<id>/balance/` 仍返回数据库中持久化的 `CustomerBalance.balance`
- `GET /customers/<id>/statements/` 中每条记录的 `current_balance` / `arrears_amount`，会基于“本地余额 + 外部业务来源净额”做一次临时口径修正

因此，在存在外部 statement-only 业务依据的客户上：

- 余额接口和对账单接口的余额展示，可能暂时不完全一致

这是当前设计的有意结果，因为：

- 我们需要让对账单拥有完整的“应收业务依据”
- 但又不希望把外部历史业务单写入核心销售链，进而污染库存与审批语义

换句话说：

- `CustomerBalance` 代表 ERP 内部正式余额账
- 客户对账单在此场景下代表“兼容外部历史业务依据后的展示口径”

---

## 5. 场景示例

### 5.1 客户账龄视图

前端可使用 `cumulative_amount`、`arrears_amount` 直接绘制折线或柱状图；由于排序固定为最新在前，可反向遍历展示账龄。

### 5.2 供应商对账

供应商 API 返回结构与客户完全一致，只是正负金额对应采购维度，方便在采购结算或付款审批前快速核对余款。

---

如需拓展分页、过滤、导出等能力，请在提交需求时同步更新本文件，保持文档的单一来源。


