forked from erp-dev/erp
6.6 KiB
6.6 KiB
采购单审批流程与红冲规划
1. 背景
- 采购单在创建时默认进入
PENDING(审批中)状态,不再立即创建出入库记录。 business.services.review_purchase_order统一处理审批通过 (APPROVED) 与作废 (CANCELLED) 的业务规则。api_v1/views/business/purchase/views.py暴露POST /api/v1/purchase-orders/<id>/review/接口作为唯一入口,便于前端、自动化流程和第三方系统统一调用。
2. 审批流程总览
| 步骤 | 说明 |
|---|---|
| 1 | 前端在采购单详情页或审批面板触发 POST /api/v1/purchase-orders/<id>/review/,请求体只包含 {"action": "approve"} 或 {"action": "cancel"} |
| 2 | API 层验证:当前用户必须具备员工身份、采购单必须属于当前商户;action 字段必须是 approve/cancel 之一 |
| 3 | API 调用 business.services.review_purchase_order,根据 action 映射到 PurchaseOrderStatusEnum.APPROVED 或 CANCELLED |
| 4 | Service 内部基于状态决定调用 _approve_purchase_order 或 _cancel_purchase_order,并在必要时抛出业务错误(例如重复审批、作废时已有出入库记录等) |
| 5 | API 返回最新的 PurchaseOrderSerializer 数据(包含总金额、总数量、明细与状态等)供前端刷新页面 |
3. 审批通过触发的事务
_approve_purchase_order先构建stock_flow_items:- 宽进仓:
items=[{'product_id', 'value', 'num_of_rolls'}] - 严进/严进严出:
items=[{'product_id', 'quantities': [...] }]
- 宽进仓:
- 在数据库事务中,将
purchase_order.status置为APPROVED并保存,保证状态更新原子性。 - 读取商户设置
auto_create_stock_change_tasks:- 若未配置或为
False,审批仅改变状态,不会触发后续任务。 - 若为
True,投递business.tasks.create_purchase_order_stock_entriesCelery 任务。
- 若未配置或为
- Celery 任务执行
StockFlowService.stock_in:- 依据仓库模式创建入库
StockChangeRecord及StockChangeDetail。 - 后续在
stock.services.make_stock_change_completed中写入Inventory并生成StockSnapshot。
- 依据仓库模式创建入库
- 任务返回
stock_change_record_id、明细数量等信息,并写日志用于追踪审计。
4. 作废流程约束
- 同样通过
POST /api/v1/purchase-orders/<id>/review/,但{"action": "cancel"}。 - Service 端调用
_cancel_purchase_order,首先查询是否存在source_type = PURCHASE且source_id = 采购单ID的StockChangeRecord。 - 若已存在入库记录,直接抛出错误“采购单已生成出入库记录,无法作废”;API 返回
400并提示原因。 - 若未生成记录,则在事务内将状态更新为
CANCELLED。不会触发 Celery 任务。
5. 常见异常与返回
- 缺少
action字段:400+ DRF 序列化错误。 - 审批已是目标状态:直接返回现状(幂等)。
- 已作废的单据请求审批:
ValueError('作废状态的采购单无法再次审批')-> API 返回400。 - 作废时已有出入库记录:
ValueError('采购单已生成出入库记录,无法作废')-> API 返回400。 - ID 不存在或不属于当前商户:API 返回
404。
6. 采购退货(采退)流程补充
- API:
POST /api/v1/purchase-return-orders/创建,POST /api/v1/purchase-return-orders/<id>/review/审批。 - 创建阶段与采购单一致,只是字段改为
return_date,并允许传入purchase_order以关联原单。 - 审批通过:
- 在事务内锁单、生成
stock_flow_items。 - 状态置为
APPROVED。 - 调用
BalanceService.adjust_supplier_balance(delta=-总金额,source=PURCHASE_RETURN_ORDER),写入BalanceChangeRecord。 - 触发
create_purchase_return_order_stock_entries,底层调用StockFlowService.stock_out(source=PURCHASE_RETURN),受仓库 mode 约束:- 宽进仓:传递
value + num_of_rolls - 严进仓:传递
numbers - 严进严出:必须提供
consume_detail_ids
- 宽进仓:传递
- 在事务内锁单、生成
- 作废逻辑沿用采购单:只有
PENDING且未产生StockChangeRecord时才允许取消,一旦审批成功便禁止。
7. 下一阶段:库存红冲(对冲)方案规划
6.1 目标与原则
- 目标:允许在采购单及其衍生的出入库记录完成后,通过“红冲”方式撤销或抵消错误的库存变动,同时保持库存快照与主库存的可追溯性。
- 原则:
- 采用“新增反向记录”方式对冲,而非直接修改原记录;保留每一次实际发生的库存变更。
- 红冲记录需要指向原
StockChangeRecord/StockSnapshot(如offset_to、offset_id字段),便于审计。 - 红冲动作必须与业务流程挂钩(例如采购单作废或红冲),避免孤立的库存操作。
6.2 行动步骤
-
业务入口确定
- 定义哪些业务对象可以触发红冲(采购单、销售单等)。
- 明确触发条件:如采购单审批通过后,允许“创建红冲请求”并指向原采购单。
-
库存服务层改造
- 在
stock.services.StockFlowService或相关函数中增加创建红冲记录的能力:- 根据原
StockChangeRecord生成反向StockChangeRecord和明细。 - 更新
StockSnapshot的offset_to/offset_id/cancelled字段,确保链路闭环。
- 根据原
- 确保
Inventory调整遵循相同的原子逻辑(事务 + 快照)。
- 在
-
业务服务与模型扩展
- 在
business.services中引入“红冲采购单”或“撤销审批”的新方法:- 校验:原单是否允许红冲、是否存在未处理的红冲记录。
- 调用库存红冲服务并记录关联。
- 必要时在
PurchaseOrder或新模型中记录红冲状态/引用。
- 在
-
API 与文档更新
- 设计新的红冲 API(例如
/api/v1/purchase-orders/<id>/offset/),请求体注明原因、操作者。 - 文档中要说明红冲流程与限制(只能对已审批单据、需管理员权限等)。
- 设计新的红冲 API(例如
-
测试与审计
- 单元测试:业务 Service、库存 Service、API 都要覆盖正常流程与错误场景。
- 集成测试:验证红冲后库存数量恢复、快照关系正确、消息/通知链路是否需要补充。
- 如有需要,增加日志或审计表,记录红冲操作人、时间、关联记录。
通过以上步骤,审批流程与红冲机制可以衔接:审批负责“正向入库”与任务分发,红冲负责在业务需要时生成配对的反向库存记录,保持库存账实一致,同时具备可回溯、可审计的业务闭环。