销售到库存链路

销售单确认后,Odoo 是怎样一路生成发货与补货动作的

从 sale.order._action_confirm 到 sale.order.line._action_launch_stock_rule,再到 stock.rule.run、_run_pull、_run_buy,讲清楚销售如何触发库存、采购与补货链。

Odoo 开发 库存 销售
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 17 阅读

先说结论

一张销售单点“确认”之后,Odoo 不是简单地“生成一个出库单”就完事了。

更准确地说,它会做三层事情:

  1. 销售层确认业务承诺
  2. 库存规则层决定该怎么 fulfil 这份需求
  3. 执行层生成 move / picking / 采购单 / 制造需求

所以你看到的“发货单出来了”“系统去采购了”“系统去拉下游库位了”,本质上都不是销售模块自己硬编码完成的,而是通过 procurement + stock.rule 这套调度机制分发出去的。


入口 1:sale.order._action_confirm()

sale_stock/models/sale_order.py 里,销售单确认时最关键的一句是:

self.order_line._action_launch_stock_rule()

也就是说:

  • 销售单确认
  • 不直接一股脑创建所有库存对象
  • 而是交给每条销售行自己去“发起库存规则”

这个设计很重要,因为真正决定后续动作的,不是订单头,而是每一条产品行的产品类型、路线、仓库、数量、公司、交货位置


入口 2:sale.order.line._action_launch_stock_rule()

这一步是整条链路的真正起点。

源码里的注释写得很直白:它会根据销售行的规则去触发:

  • _run_pull
  • _run_buy
  • _run_manufacture

也就是拉式补货、采购补货、制造补货。

这一步先做了哪些过滤

不是所有销售行都会触发库存逻辑。源码里先排掉这些情况:

  • 不是 sale 状态
  • 订单已锁定
  • 产品类型不需要库存链路
  • 已经做过相同数量的 procurement,不必重复跑

这意味着销售模块并不会盲目重复造 move。

它会先判断:

这条销售行还有没有“未被 fulfil 的差额数量”?

只有差额存在,才继续往下。


销售行在这一步准备了什么上下文

在真正调用 stock.rule 之前,销售行会准备一份 procurement values。

里面通常包含:

  • 计划日期
  • 目的地位置
  • 仓库
  • 路线
  • 客户 / 收货地址相关信息
  • 原始单据引用
  • 下游 move 关联线索

你可以把它理解成一张“任务说明书”:

我现在有一个来自销售的需求,请系统根据规则决定怎么满足它。

这里有个很容易忽略但很关键的点:

  • 销售行并不关心“最后一定是发货单还是采购单”
  • 它只负责把需求包装成 procurement
  • 真正的动作决策交给 stock.rule.run()

这就是 Odoo 这条链路可扩展的根源。


stock.rule.run():分发器,而不是执行器本体

当销售行把 procurements 收集完之后,会调用:

self.env['stock.rule'].run(procurements)

这一层最像一个调度中心。

它会根据命中的 route / rule 决定:

  • 是直接从上游库位拉货?
  • 还是需要采购?
  • 还是应该制造?
  • 还是应该做 push / pull 链条中的某一步?

所以很多人调试销售出库问题时,盯着 sale 模块看半天,其实只看到了 30%。

另外 70% 往往在:

  • stock.rule
  • stock.move
  • purchase_stock
  • mrp

_run_pull():最常见的“生成库存 move”路径

如果命中的规则是 pull,那么 Odoo 会走到 stock.rule._run_pull()

这一步的核心动作非常清晰:

  1. 校验 source location 是否存在
  2. 为每个 procurement 计算 move values
  3. 按公司分组批量创建 stock.move
  4. 对新 move 执行 _action_confirm()

源码里还有一个非常值得注意的细节:

moves = self.env['stock.move'].sudo().with_company(company_id).create(moves_values)

也就是说,Odoo 在这里是 sudo 创建 move 的。

为什么?

因为业务触发人可能是销售员,但销售员不一定具备完整库存对象创建权限。如果不提权,很多“销售触发库存”场景会因为权限而断链。

这就是 Odoo 的典型思路:

  • 前台用户在业务对象上有权限
  • 底层履约对象由系统在安全边界内代为推进

move 创建后为什么还要 _action_confirm()

很多人以为创建 stock.move 之后事情就结束了,其实远没有。

stock.move._action_confirm() 才是下一层真正把链路继续向前推进的地方。

这一步会做几件关键事:

  • 把 move 从 draft 变成 confirmed / waiting
  • make_to_order move 再次生成 procurement 请求
  • 需要时继续触发下游规则
  • 按 picking 维度分组归并
  • 对符合条件的 move 尝试自动 assign

换句话说:

销售行触发的是第一跳 procurement,move confirm 则负责把这条供应链继续往前传。

这也是为什么复杂路线看起来像“自己会长出来”——因为每一步 confirm 都可能继续派生下一步。


_run_buy():什么时候会走采购

如果产品路线命中 Buy,或者仓库补货规则最终落到采购,stock.rule.run() 会转去采购扩展模块的逻辑。

理解上不用把它想复杂:

  • 销售先提出需求
  • 规则发现本仓不该自己供货
  • 那就把需求转成采购需求
  • 采购模块再负责合并 / 选供应商 / 生成 PO

所以销售并不是“直接建采购单”,而是:

销售需求 → procurement → buy rule → purchase.order line / RFQ

这个分层非常利于扩展:

  • 你可以换供应商选择逻辑
  • 可以加 MOQ、采购提前期
  • 可以按公司、仓库、路线、供应商策略做定制

而无需改销售确认入口。


销售确认后为什么还会去 picking.action_confirm()

sale.order.line._action_launch_stock_rule() 末尾,Odoo 还会把订单相关的未完成 picking 拿出来,执行:

pickings_to_confirm.action_confirm()

源码注释已经说明原因:

当前 scheduler trigger 是在 picking confirm 上触发的,而不是 stock.move confirm。

这句话很值钱。

它告诉你一件事实:

  • 销售跑完 procurement 后,系统还会补一脚 picking confirm
  • 目的是让调度、可用量检查、后续 reservation 流程接上

所以如果你看到“销售确认了但 picking 状态不对”,不要只查 sale order line,也要查 picking confirm 有没有被正常跑到。


用业务语言重新翻译整条链

如果不用源码术语,这条链可以翻译成:

  1. 客户下单,销售确认承诺
  2. 每条产品行问系统:“这件货该怎么 fulfil?”
  3. 规则引擎查看仓库路线
  4. 能内部拉货就生成库存 move
  5. 需要外采就转成采购需求
  6. 需要生产就转成制造需求
  7. move / picking 在确认后继续触发下一跳
  8. 最终形成可执行的发货、收货、调拨、采购或制造动作

这也是 Odoo 强的地方:

它不是写死“销售 -> 出库”,而是让路线系统决定“销售 -> 什么动作组合”。


实战调试时最该看的 6 个点

如果你遇到“销售单确认后没出库”“没采购”“路线怪怪的”,优先看这几个点:

1. 产品类型和发票/库存属性

产品本身是否应该进入库存履约链。

2. 销售行路线 / 仓库 / 公司

很多异常不是代码 bug,而是上下文命中了错误规则。

3. sale.order.line._action_launch_stock_rule() 是否真正执行

比如是否被 skip_procurement、状态过滤或数量差额判断提前跳过。

4. stock.rule.run() 命中了哪条 rule

这是分叉口。

5. stock.move._action_confirm() 后 move 进了什么状态

confirmedwaiting,还是继续生成了新的 procurement。

6. picking confirm / assign 有没有继续推进

很多“单生成了但走不动”的问题卡在这里。


一句话记忆法

把整条链记成一句话:

销售模块提出需求,库存规则决定路径,move / picking / purchase / mrp 负责把路径执行出来。

理解这一句之后,你再看 Odoo 销售、库存、采购、制造之间的关系,脑子会清楚很多。

DISCUSSION

评论区

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