# business 模块结构与设计说明

本文档记录业务模块当前的结构、关键约定以及未来演进需要遵循的决策背景，防止在迭代中遗失设计意图。

## 1. 模块职责

`business` 负责“业务单据”领域，目前包含采购单（PurchaseOrder）与销售单（SalesOrder），同时保留可扩展至退货单等更多对象的通用框架。核心职责：

- 聚合与校验业务明细数据
- 通过服务层触发库存（stock）、财务等下游模块
- 统一处理审批、作废等状态流转

## 2. 模型抽象

### 2.1 OrderItemsAggregationMixin
封装订单明细聚合逻辑，默认访问 `items` 关联，可通过 `order_items_accessor` 指定其他关联名。提供：

- `get_total_amount()`
- `get_total_quantity()`
- `get_total_diff_quantity()`

这些方法仅关心明细结构，不关心订单方向。

### 2.2 OrderDirectionMixin
用于获取带方向的金额，约定：

- `get_direction()` 返回 `1`（正向/入库）或 `-1`（负向/出库）
- `get_signed_total_amount()` 在内部调用 `get_total_amount()` 并乘以方向

任何需要“正负金额”的业务（财务统计、库存红冲）都应依赖此能力，而不是重复编写正负逻辑。

### 2.3 OrderCounterpartyMixin
对外暴露统一的 `get_counterparty()` 接口，通过 `get_counterparty_field_name()`（或 `counterparty_field_name` 属性）确定具体业务主体字段。这样采购单/销售单分别返回 `Supplier` 与 `Customer`，但调用方只需面对一个接口。

### 2.4 订单类型
当前业务对象覆盖：

- **PurchaseOrder**（采购单）：`direction=1`，`counterparty=supplier`，含库存明细。
- **SalesOrder**（销售单）：`direction=-1`，`counterparty=customer`，含库存明细；严进严出模式需记录 `consume_detail_ids`。
- **PurchaseReturnOrder**（采购退货单）：`direction=-1`，`counterparty=supplier`，仓库出库、供应商欠款减少，可选关联原采购单。
- **SalesReturnOrder**（销售退货单）：`direction=1`，`counterparty=customer`，仓库入库、客户欠款减少，可选关联原销售单。
- **PaymentOrder**（付款单）：`direction=-1`，`counterparty=supplier`，仅金额字段，不触发库存。
- **ReceiptOrder**（收款单）：`direction=-1`，`counterparty=customer`，仅金额字段，不触发库存。
- **SupplierBalance / CustomerBalance**：实时维护供应商应付、客户应收余额，所有审批通过的带金额单据都会写入，供查询接口和报表使用。
- **BalanceChangeRecord**（余额变动记录）：仿照 `stock.StockSnapshot` 的“流水 + 快照”模式，记录每一次余额写入的来源、方向、前后余额与可能的冲抵关系，为财务审计与未来的红冲能力提供依据。

通过 mixin，所有单据都具备：
- 统一的金额聚合与方向计算（资金/库存可共用 `get_signed_total_amount()`）
- 一致的业务主体接口（供应商/客户）
- 可扩展的服务与序列化模式（有无库存由具体模型决定）

## 3. 服务层约定

- `business.services` 负责 orchestration，与 API 解耦。审批、作废、触发库存等流程必须先在服务层实现，再暴露给 API。
- **作废统一实现**：`_cancel_order_impl()` 是所有单据作废的单一入口，通过参数化 `order_model_cls` / `approved_status` / `cancelled_status` / `stock_source_type` 适配不同模型，将 APPROVED 检查统一在 `select_for_update` 锁内完成。
- 任何涉及库存的逻辑都必须通过 `stock.services.StockFlowService`，不得直接操作库存模型，保持模块边界清晰。
- 红冲/对冲等高级动作应由业务模块提供入口（例如 `PurchaseOrder` 红冲），但最终仍调用库存服务完成实际库存变动。

## 4. 未来演进建议

1. **新增单据**：若未来出现调拨单、退货单或更多资金类单据，优先复用 `OrderDirectionMixin + OrderCounterpartyMixin`（如有明细再叠加 `OrderItemsAggregationMixin`），仅通过 `get_direction()` / `get_counterparty_field_name()` 区别方向与主体，减少重复实现。
2. **审批/状态机**：作废逻辑已通过 `_cancel_order_impl()` 统一实现，新增单据的作废只需委托该函数并传入对应参数，无需复制逻辑。
3. **统计与报表**：财务/库存统计应依赖 `get_signed_total_amount()` / `get_direction()`，确保采购/销售、退货/正向都能通过统一接口处理。
4. **余额审计**：任何会写入 Supplier/CustomerBalance 的流程必须通过 `BalanceService`，以便自动生成 `BalanceChangeRecord`。审批通过后禁止作废，若未来需要冲销，必须新建红冲记录并维护 `offset_to/offset_id` 链路。
5. **文档同步**：新增单据或服务时必须更新本文件，描述新增模型如何复用 mixin、如何影响下游模块，保持设计透明。

以上约定的目标是：**保持各类业务单据的独立性，同时通过 mixin/服务层抽象复用绝大多数公共逻辑**。如需变更此架构，请在评估后更新本文件，说明原因与迁移方案。

