forked from erp-dev/erp
159 lines
6.0 KiB
Markdown
159 lines
6.0 KiB
Markdown
# 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()`:
|
||
|
||
```python
|
||
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,不禁止负数,并有明确注释:
|
||
|
||
```python
|
||
# 金额类字段要求非零(允许负数用于处理退款场景)
|
||
# 付款单负金额 = 供应商退款,收款单负金额 = 退款给客户
|
||
```
|
||
|
||
影响:
|
||
|
||
- 负数付款单审核后,`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` 的审核接口统一只接受:
|
||
|
||
```python
|
||
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`。
|
||
- 按产品、颜色、规格、批次等维度限制退货数量。
|
||
- 校验同一原单累计已审核退货数量。
|
||
- 在退货单或余额变动记录中明确记录冲销关系。
|