销售履约链

Odoo 销售确认后为什么会分成“补货链”和“开票链”

销售单确认只做一件事:把订单推进到可履约状态。真正的履约分成补货/出库和开票两条链,分别由 sale_stock 和开票逻辑接管。

会计 库存 销售
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

很多人第一次读销售单源码时,都会默认以为:

“销售单确认 = 出库 + 开票 + 所有后续动作一起做完。”

Odoo 不是这么设计的。

从源码看,销售确认更像是把订单推进到一个“可履约”的状态,然后把后续工作拆成两条不同的链:

  1. 补货 / 出库链:负责把商品真正送出去;
  2. 开票链:负责根据开票政策把可开票数量变成发票。

这两条链经常同时存在,但它们并不是同一条逻辑。

一、action_confirm() 只负责把订单推进到“已销售”阶段

addons/sale/models/sale_order.py 里,action_confirm() 的职责很明确:

  • 先检查订单是否允许确认;
  • 再写入确认值,把 state 推进到 sale
  • 然后调用 _action_confirm() 这个扩展钩子;
  • 最后按条件锁单、发确认邮件。

也就是说,核心销售模块本身并不在这里把所有下游文档一次性做完。 它只是把订单状态推进到“可以继续处理”的阶段。

二、真正的履约动作,很多都挂在 sale_stock

sale_stock 里,_action_confirm() 会继续调用订单行的 _action_launch_stock_rule()

这一段非常关键,因为它说明了一个事实:

销售确认后,并不是“订单本身”去创建库存动作, 而是订单行根据自己的补货需求,去启动 stock rule。

订单行在 _prepare_procurement_values() 里会把一堆关键上下文准备好:

  • origin:来源单据;
  • date_planned / date_deadline:计划和截止时间;
  • warehouse_idroute_ids:走哪条路线;
  • partner_id:送货对象;
  • location_final_id:最终目的地;
  • sequence 等追踪信息。

这些值随后会被包装成 procurement,再交给 stock.rule

三、stock.rule._run_pull() 才是真正创建库存移动的地方

_run_pull() 会把 procurement 转成 move values,然后用 sudo 创建 stock.move,最后 _action_confirm()

这个设计说明了两件事:

  1. 库存补货链是一个独立的规则系统;
  2. 它不依赖“销售单本身会不会直接写库存表”,而是依赖 route / rule 的配置。

所以有时候你看到销售确认后没出库,不是销售模块没跑,而是:

  • 路线没配;
  • 规则没匹配;
  • 补货条件还没满足;
  • 或者这个产品根本不走这条补货链。

四、开票链是另一套判断:看的是“可开票数量”

开票不看库存规则,它看的是另外一组变量:

  • invoice_status
  • qty_to_invoice
  • invoice_policy
  • 以及订单行是否是 down payment、note、section 这类特殊行。

sale.order._get_invoiceable_lines() 里,Odoo 会先筛掉不该开票的行,再把要开的行按 section / subsection / down payment 重新组织。

然后 _create_invoices() 负责真正创建 account.move

这意味着:

一个订单可以已经确认、已经生成出库单, 但仍然暂时没有可开票行; 反过来,一个订单也可能先有预付款发票,后面才真正出库。

五、最容易混淆的点:确认、交付、开票不是同一时刻

新手最常误解的是把这三件事混成一个按钮:

  • 确认:订单状态推进;
  • 交付:库存链路执行;
  • 开票:会计链路执行。

它们可以在业务上连续发生,但在源码里是不同层次、不同模块、不同判断条件。

这也是 Odoo 好用的原因:

  • 纯数字服务不需要库存链;
  • 先收款后交付的场景可以单独处理;
  • 部分交付、部分开票也能表达;
  • 复杂销售流程不会被塞进一个巨大的“确认函数”里。

结论

销售确认后之所以会分成“补货链”和“开票链”,是因为 Odoo 把履约拆成了两个独立维度:

  • 货怎么走,由 stock rule 负责;
  • 钱怎么开,由 invoice policy 和 invoiceable lines 负责。

理解这一点之后,你再看销售单源码,就不会再期待“确认按钮”顺手替你做完所有业务动作了。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。