1
0
forked from erp-dev/erp
Files
erpnew/docs/2026-06-12_business_red_flush_design.md
2026-06-12 13:57:21 +08:00

160 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Business 单据红冲服务设计备查
日期2026-06-12
## 目标
`business` 模块为已审核正式单据增加整单红冲能力。第一阶段只实现 service 和测试,不新增 API。
## 已确认口径
- 红冲后原单据 `status` 保持 `APPROVED`,不回退、不作废。
- 原单据增加“已红冲”标记,用于列表查询提速。
- 每次红冲由 service 自动生成一个 UUID 类型的 `red_flush_id`
- `red_flush_id` 用于跨表、跨记录追踪同一次红冲涉及的所有数据,是审计关联批次 ID。
- 不新增 `red_flush_no`。该编号只适合人工展示,目前无需求。
- 第一版只支持整单红冲,不支持部分红冲。
- API 在 service 与测试完成、覆盖率达标后再增加API 层红冲原因必填。
- API 权限复用现有审批/作废权限,不新增独立红冲权限。
- 多商户隔离必须下沉到 service。红冲 service 调用方必须传入当前 `merchant`service 在解析单据后校验单据所属商户。
## 字段方案
6 类正式业务单据增加:
- `is_red_flushed`: 是否已红冲,布尔值,建索引。
- `red_flush_id`: 红冲批次 UUID可空建索引。
- `red_flushed_at`: 红冲时间,可空。
覆盖单据:
- `PurchaseOrder`
- `SalesOrder`
- `PurchaseReturnOrder`
- `SalesReturnOrder`
- `PaymentOrder`
- `ReceiptOrder`
`BalanceChangeRecord` 增加:
- `red_flush_id`: 红冲批次 UUID可空建索引。
`StockChangeRecord` 增加:
- `red_flush_id`: 红冲批次 UUID可空建索引。
暂不在 `StockSnapshot` 增加 `red_flush_id`,因为快照已有 `offset_to/offset_id/cancelled` 精确匹配关系,且可以通过 `StockChangeRecord.red_flush_id` 查到相关快照。
## 关系标记规则
一次红冲生成一个 `red_flush_id`,并在同一个事务中写入相关数据。
原业务单据:
- `is_red_flushed=True`
- `red_flush_id=<本次 UUID>`
- `red_flushed_at=<当前时间>`
原余额变动记录:
- `cancelled=True`
- `cancelled_at=<当前时间>`
- `offset_id=<反向余额记录 ID>`
- `red_flush_id=<本次 UUID>`
反向余额变动记录:
- `offset_to=<原余额记录 ID>`
- `offset_at=<当前时间>`
- `red_flush_id=<本次 UUID>`
原库存记录:
- `red_flush_id=<本次 UUID>`
反向库存记录:
- `source_type=OFFSET`
- `source_id=<原库存记录 ID>`
- `red_flush_id=<本次 UUID>`
库存快照继续复用现有关系:
- 原快照:`cancelled=True``cancelled_at``offset_id=<反向快照 ID>`
- 反向快照:`offset_to=<原快照 ID>``offset_at`
## Service 方案
新增业务红冲入口:
- `red_flush_purchase_order(...)`
- `red_flush_sales_order(...)`
- `red_flush_purchase_return_order(...)`
- `red_flush_sales_return_order(...)`
- `red_flush_payment_order(...)`
- `red_flush_receipt_order(...)`
内部复用统一实现,避免 6 类单据逻辑分叉。
校验规则:
- 必须提供当前 `merchant`
- 单据必须属于当前 `merchant`
- 只允许 `APPROVED` 单据红冲。
- `is_red_flushed=True` 的单据不能重复红冲。
- 原始余额变动记录必须存在且未被冲抵。
- 有库存影响的单据必须找到对应库存记录,库存记录必须已完成且未被红冲。
- 资金和库存任一红冲失败,整体事务回滚。
资金侧实现:
- 基于原 `BalanceChangeRecord.delta` 创建反向余额变动,`delta=-original.delta`
- 使用现有 `BalanceService.adjust_supplier_balance()` / `adjust_customer_balance()` 写余额与记录。
- 调整 `BalanceService` 让它返回新创建的 `BalanceChangeRecord`,方便写 `offset_to/offset_id/red_flush_id`
库存侧实现:
- 复用 `StockFlowService.offset_stock_change()`
- 扩展其参数 `red_flush_id=None`
- 该方法负责把 `red_flush_id` 写入原库存记录和反向库存记录。
## 测试要求
先跑现有 `business.tests` baseline。
新增 service 测试覆盖:
- 采购单整单红冲:余额反向、库存反向、单据标记、批次 ID 写入。
- 销售单整单红冲:余额反向、库存反向、单据标记、批次 ID 写入。
- 采购退货单整单红冲。
- 销售退货单整单红冲。
- 付款单整单红冲。
- 收款单整单红冲。
- 非已审核单据不能红冲。
- 已红冲单据不能重复红冲。
- 缺少原始余额记录时报错。
- 库存记录未完成或已红冲时报错。
- 事务回滚场景。
目标:`business` 模块覆盖率 90%+。
## 测试命令
开发环境在容器内运行测试,并绕过 PgBouncer
```bash
docker compose exec -T -e DB_HOST=postgres -e DB_PORT=5432 web \
uv run python manage.py test business.tests --keepdb --noinput
```
覆盖率:
```bash
docker compose exec -T -e DB_HOST=postgres -e DB_PORT=5432 web \
uv run coverage run --source=business manage.py test business.tests --keepdb --noinput
```
```bash
docker compose exec -T web uv run coverage report -m
```