forked from erp-dev/erp
4.9 KiB
4.9 KiB
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: 红冲时间,可空。
覆盖单据:
PurchaseOrderSalesOrderPurchaseReturnOrderSalesReturnOrderPaymentOrderReceiptOrder
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=Truered_flush_id=<本次 UUID>red_flushed_at=<当前时间>
原余额变动记录:
cancelled=Truecancelled_at=<当前时间>offset_id=<反向余额记录 ID>red_flush_id=<本次 UUID>
反向余额变动记录:
offset_to=<原余额记录 ID>offset_at=<当前时间>red_flush_id=<本次 UUID>
原库存记录:
red_flush_id=<本次 UUID>
反向库存记录:
source_type=OFFSETsource_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:
docker compose exec -T -e DB_HOST=postgres -e DB_PORT=5432 web \
uv run python manage.py test business.tests --keepdb --noinput
覆盖率:
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
docker compose exec -T web uv run coverage report -m