6.0 KiB
Business 模块已审核单据反向能力审计
审计日期:2026-06-12
结论
business 模块的正式单据在 status = APPROVED 后,没有发现可将原单据改回审批中、撤销审核、反审或直接红冲原单据的 service/API。
已审核单据不能作废这一点由统一作废逻辑实现:_cancel_order_impl() 在目标单据已审批时直接抛错。
但存在“通过新单据抵消业务影响”的能力:
- 采购单、销售单:可通过新建并审核采购退货单/销售退货单形成库存和应收/应付反向影响。
- 付款单、收款单:创建时允许负金额,用于退款场景,审核后形成与正向付款/收款相反的余额影响。
- 余额变动记录模型有冲抵字段,但当前 service 未使用,未形成可调用的冲抵能力。
审计范围
核心文件:
business/services.pybusiness/models.pyapi_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 层只暴露 approve 和 cancel 两类 action,没有发现 unapprove、reverse、red_flush 等动作。
已审核后的反向能力
| 原单据 | 是否可直接撤销/反审 | 是否有业务反向单据 | 说明 |
|---|---|---|---|
| 采购单 | 否 | 有,采购退货单 | 采购退货单审核后出库,并减少供应商应付。 |
| 销售单 | 否 | 有,销售退货单 | 销售退货单审核后入库,并减少客户应收。 |
| 采购退货单 | 否 | 未发现专用反向单据 | 只能再做新的采购单产生正向影响。 |
| 销售退货单 | 否 | 未发现专用反向单据 | 只能再做新的销售单产生正向影响。 |
| 付款单 | 否 | 无专用反向单据,但允许负金额 | create_payment_order() 允许 amount < 0,注释说明为供应商退款。 |
| 收款单 | 否 | 无专用反向单据,但允许负金额 | create_receipt_order() 允许 amount < 0,注释说明为退款给客户。 |
采购/销售退货单细节
create_purchase_return_order() 可传 purchase_order 或 purchase_order_id 关联原采购单。
当前校验:
- 原采购单存在且属于当前商户。
- 原采购单供应商必须与退货单供应商一致。
当前未校验:
- 原采购单必须是
APPROVED。 - 退货数量不能超过原采购单数量。
- 同一原采购单累计退货数量不能超过原单数量。
create_sales_return_order() 可传 sales_order 或 sales_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_tooffset_atoffset_idcancelledcancelled_at
但代码搜索只发现模型定义和创建余额变动记录,未发现 service/API 更新这些字段。因此当前没有可用的余额变动冲抵流程。
API 暴露情况
api_v1/views/business/*/views.py 的审核接口统一只接受:
action = approve | cancel
对应模块:
purchase/views.pysales/views.pypurchase_return/views.pysales_return/views.pypayment/views.pyreceipt/views.py
未发现 API 层绕过 service 暴露已审核撤销、反审或红冲原单的能力。
风险点
- 退货单可作为采购单/销售单的反向能力,但当前不要求原单已审核。
- 退货单未限制数量,可能产生超原单退货或重复累计退货。
- 负金额付款/收款具备资金反向效果,但没有原单关联,后续对账只能从业务含义判断,不能从数据结构追溯到被冲销单据。
BalanceChangeRecord的冲抵字段未被 service 使用,容易让维护者误以为已有冲抵流程。
建议
如果业务需求只是确认“已审核后是否有撤销/红冲能力”,当前答案是:没有直接撤销/反审/红冲原单的能力;有通过新单据产生反向影响的能力。
如果要把退货单作为正式红冲/撤销路径,建议补充:
- 原单必须
APPROVED。 - 按产品、颜色、规格、批次等维度限制退货数量。
- 校验同一原单累计已审核退货数量。
- 在退货单或余额变动记录中明确记录冲销关系。