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

6.0 KiB
Raw Blame History

Business 模块已审核单据反向能力审计

审计日期2026-06-12

结论

business 模块的正式单据在 status = APPROVED 后,没有发现可将原单据改回审批中、撤销审核、反审或直接红冲原单据的 service/API。

已审核单据不能作废这一点由统一作废逻辑实现:_cancel_order_impl() 在目标单据已审批时直接抛错。

但存在“通过新单据抵消业务影响”的能力:

  • 采购单、销售单:可通过新建并审核采购退货单/销售退货单形成库存和应收/应付反向影响。
  • 付款单、收款单:创建时允许负金额,用于退款场景,审核后形成与正向付款/收款相反的余额影响。
  • 余额变动记录模型有冲抵字段,但当前 service 未使用,未形成可调用的冲抵能力。

审计范围

核心文件:

  • business/services.py
  • business/models.py
  • api_v1/views/business/*/views.py

正式单据类型:

  • 采购单 PurchaseOrder
  • 销售单 SalesOrder
  • 采购退货单 PurchaseReturnOrder
  • 销售退货单 SalesReturnOrder
  • 付款单 PaymentOrder
  • 收款单 ReceiptOrder

已审核后禁止作废

所有正式单据的作废最终进入 business/services.py::_cancel_order_impl()

if locked.status == approved_status:
    raise ValueError(f'已审批的{error_label}无法作废')

覆盖的入口:

  • review_purchase_order(..., target_status=CANCELLED)
  • review_sales_order(..., target_status=CANCELLED)
  • review_purchase_return_order(..., target_status=CANCELLED)
  • review_sales_return_order(..., target_status=CANCELLED)
  • review_payment_order(..., target_status=CANCELLED)
  • review_receipt_order(..., target_status=CANCELLED)

API 层只暴露 approvecancel 两类 action没有发现 unapprovereversered_flush 等动作。

已审核后的反向能力

原单据 是否可直接撤销/反审 是否有业务反向单据 说明
采购单 有,采购退货单 采购退货单审核后出库,并减少供应商应付。
销售单 有,销售退货单 销售退货单审核后入库,并减少客户应收。
采购退货单 未发现专用反向单据 只能再做新的采购单产生正向影响。
销售退货单 未发现专用反向单据 只能再做新的销售单产生正向影响。
付款单 无专用反向单据,但允许负金额 create_payment_order() 允许 amount < 0,注释说明为供应商退款。
收款单 无专用反向单据,但允许负金额 create_receipt_order() 允许 amount < 0,注释说明为退款给客户。

采购/销售退货单细节

create_purchase_return_order() 可传 purchase_orderpurchase_order_id 关联原采购单。

当前校验:

  • 原采购单存在且属于当前商户。
  • 原采购单供应商必须与退货单供应商一致。

当前未校验:

  • 原采购单必须是 APPROVED
  • 退货数量不能超过原采购单数量。
  • 同一原采购单累计退货数量不能超过原单数量。

create_sales_return_order() 可传 sales_ordersales_order_id 关联原销售单。

当前校验:

  • 原销售单存在且属于当前商户。
  • 原销售单客户必须与退货单客户一致。

当前未校验:

  • 原销售单必须是 APPROVED
  • 退货数量不能超过原销售单数量。
  • 同一原销售单累计退货数量不能超过原单数量。

因此,退货单属于“新单据反向业务影响”,不是“撤销原单据”;它不会改变原采购单/销售单的 status,也不会标记原单已被红冲。

付款/收款负金额细节

_ensure_non_zero_amount() 只禁止金额为 0不禁止负数并有明确注释

# 金额类字段要求非零(允许负数用于处理退款场景)
# 付款单负金额 = 供应商退款,收款单负金额 = 退款给客户

影响:

  • 负数付款单审核后,review_payment_order() 使用 delta=-locked.settlement_amount,当 settlement_amount < 0 时会增加供应商应付余额。
  • 负数收款单审核后,review_receipt_order() 使用 delta=-locked.settlement_amount,当 settlement_amount < 0 时会增加客户应收余额。

这是一种资金侧反向能力,但没有和原付款/收款单建立关联,也没有把原单据标记为红冲或冲销。

余额冲抵字段

BalanceChangeRecord 存在以下字段:

  • offset_to
  • offset_at
  • offset_id
  • cancelled
  • cancelled_at

但代码搜索只发现模型定义和创建余额变动记录,未发现 service/API 更新这些字段。因此当前没有可用的余额变动冲抵流程。

API 暴露情况

api_v1/views/business/*/views.py 的审核接口统一只接受:

action = approve | cancel

对应模块:

  • purchase/views.py
  • sales/views.py
  • purchase_return/views.py
  • sales_return/views.py
  • payment/views.py
  • receipt/views.py

未发现 API 层绕过 service 暴露已审核撤销、反审或红冲原单的能力。

风险点

  1. 退货单可作为采购单/销售单的反向能力,但当前不要求原单已审核。
  2. 退货单未限制数量,可能产生超原单退货或重复累计退货。
  3. 负金额付款/收款具备资金反向效果,但没有原单关联,后续对账只能从业务含义判断,不能从数据结构追溯到被冲销单据。
  4. BalanceChangeRecord 的冲抵字段未被 service 使用,容易让维护者误以为已有冲抵流程。

建议

如果业务需求只是确认“已审核后是否有撤销/红冲能力”,当前答案是:没有直接撤销/反审/红冲原单的能力;有通过新单据产生反向影响的能力。

如果要把退货单作为正式红冲/撤销路径,建议补充:

  • 原单必须 APPROVED
  • 按产品、颜色、规格、批次等维度限制退货数量。
  • 校验同一原单累计已审核退货数量。
  • 在退货单或余额变动记录中明确记录冲销关系。