# 销售单审批流程与红冲规划 ## 1. 背景 - 销售单在创建时同样进入 `PENDING` 状态,审批通过后才会触发出库流程。 - `business.services.review_sales_order` 统一处理审批通过 (`APPROVED`) 与作废 (`CANCELLED`) 的业务规则。 - `api_v1/views/business/sales/views.py` 暴露 `POST /api/v1/sales-orders//review/` 接口,供前端、自动化和第三方系统一致调用。 ## 2. 审批流程总览 | 步骤 | 说明 | |------|------| | 1 | 前端调用 `POST /api/v1/sales-orders//review/`, body: `{"action": "approve"}` 或 `{"action": "cancel"}` | | 2 | API 层校验员工身份、订单归属商户、`action` 可选项 | | 3 | API 调用 `review_sales_order`,根据 `action` 映射到 `SalesOrderStatusEnum.APPROVED` / `CANCELLED` | | 4 | Service 端根据状态调用 `_approve_sales_order` 或 `_cancel_sales_order`,并在必要时抛出业务异常(如重复审批、已有出入库记录等) | | 5 | API 返回最新的 `SalesOrderSerializer` 数据,供前端刷新详情 | ## 3. 审批通过触发的事务 1. `_approve_sales_order` 构建 `stock_flow_items`(结构与采购单一致)。 2. 在事务内将 `sales_order.status` 更新为 `APPROVED`。 3. 读取商户设置 `auto_create_stock_change_tasks`: - 未配置或为 `False` 时,仅更新状态。 - 为 `True` 时投递 `business.tasks.create_sales_order_stock_entries` Celery 任务。 4. Celery 任务执行 `StockFlowService.stock_out`: - 依据仓库模式创建出库 `StockChangeRecord` / `StockChangeDetail`。 - `stock.services.make_stock_change_completed` 会在自动完成开启时写入 `Inventory` 并生成 `StockSnapshot`。 5. 任务返回 `stock_change_record_id` 与明细信息,并写日志用于审计。 ## 4. 作废流程约束 1. 仍通过 `POST /api/v1/sales-orders//review/`, body: `{"action": "cancel"}`。 2. `_cancel_sales_order` 会检查 `source_type = SALES` 且 `source_id = 销售单ID` 的 `StockChangeRecord` 是否存在。 3. 若已经生成出库记录,则抛出 `ValueError('销售单已生成出入库记录,无法作废')`,API 返回 `400` 提示前端。 4. 若未生成记录,则在事务内将状态更新为 `CANCELLED`,且不会触发 Celery 任务。 ## 5. 常见异常与返回 - 缺少 `customer` 字段:`400` + `{"error": "缺少客户 ID"}`。 - 审批重复:保持幂等,直接返回当前状态。 - 已作废单据审批:`ValueError('作废状态的销售单无法再次审批')` -> `400`。 - 作废时已有出库记录:`ValueError('销售单已生成出入库记录,无法作废')` -> `400`。 - ID 不存在或不属于当前商户:`404`。 ## 6. 创建接口参数约定 销售单创建接口沿用采购单的参数格式,根据仓库模式填写 `items`: | 仓库模式 | 出库模式 | `items` 必填字段 | |----------|----------|------------------| | `UNRESTRICTED`(宽出) | 无需指定入库明细 | `product_id`, `quantity`, `num_of_rolls`, `price`, `unit` | | `RESTRICT_IN`(严进宽出) | 无需指定入库明细 | `product_id`, `numbers` (list[int]), `price`, `unit` | | `RESTRICT_IN_OUT`(严出) | 必须指定要消耗的入库明细 | `product_id`, `consume_detail_ids` (list[int]), `quantity`, `price`, `unit` | > 说明:在严出模式下,`consume_detail_ids` 列表中的 ID 必须为同仓库、同商户且未被消耗的入库明细 ID。系统会在审批通过时逐条扣减对应库存。 ## 6. 红冲(对冲)规划 与采购单一致,后续将通过库存红冲入口(`StockFlowService.offset_stock_change` 占位方法)实现销售单出库的反向抵销。关键原则: - 采用新增反向记录的方式,保留所有历史变动。 - 红冲记录需要指向原 `StockChangeRecord` / `StockSnapshot`,保证审计可追溯。 - 红冲需与业务对象绑定,避免孤立库存操作。 文档与测试应在红冲能力落地时同步更新,确保审批、出库、红冲形成闭环。