先说结论
采购这条链路里,Odoo 不是“点了确认就直接建一张采购单”这么简单。
更准确地说,它走的是一条采购补货流水线:
- 补货需求先进入
stock.rule buy规则根据供应商、公司和采购域,把需求聚合成 RFQ- 采购单确认后,
purchase_stock再把它接回收货单和库存移动 - 这样采购、收货、后续库存动作就连成了一条链
所以如果你在排查“为什么没生成采购单”“为什么收货单和采购单不是一一对应”,第一件事不是盯界面,而是看这条链是不是被分支规则改写了。
第一步:buy 规则先把补货需求接进来
在 addons/purchase_stock/models/stock_rule.py 里,stock.rule 扩展了一个新的动作:buy。
这意味着同一个 stock.rule,既可以做 pull / push,也可以把需求交给采购。
run() 里还有一段很关键的逻辑:
- 如果采购相关的 routes 里包含
buy - Odoo 就把仓库的 reception route 一并并进来
这一步很重要,因为它保证了“买回来以后要怎么收货”也在同一条业务链里。
换句话说,buy 规则不只是“买”,它还顺带指定了“收到哪里去”。
第二步:_run_buy() 把需求分组,准备生成 RFQ
_run_buy() 是整条链路的核心。
它会对每个 procurement 做这些事:
- 找匹配的供应商
supplier - 如果没有供应商,按场景决定是报错、回退还是通知负责人
- 把
supplier和propagate_cancel写回procurement.values - 用
_make_po_get_domain()算出采购单分组域 - 按 domain 把 procurements 聚合到同一张 RFQ 或 PO 上
这一步的关键不是“写一张采购单”,而是“先算分组边界”。
也就是说,Odoo 会先判断:
- 这几个需求是不是应该落到同一个供应商
- 是不是同一公司
- 是不是应该共用同一张采购单
只有边界一致,才会合并。
没有供应商时,Odoo 也不是只有一种反应
这部分特别容易误解。
源码里对“找不到供应商”并不是只有一个统一错误,而是分场景处理:
- orderpoint 场景:会收集成
ProcurementException - 非 orderpoint 场景:可能把下游 move 改成 MTS,或者取消相关链路,再通知负责人
这说明 Odoo 不是机械地“找不到供应商就结束”,而是会考虑当前需求来自哪里、后续链条该怎么收尾。
这也是为什么自动采购问题经常看起来“有时报错,有时又继续跑”:不是 bug,一般是分支条件不同。
第三步:创建或复用采购单
在同一个 domain 下,Odoo 会先查是否已经有现成 PO:
- 有,就复用,并更新
origin/reference_ids - 没有,就用
_prepare_purchase_order()创建新单
这里有两个小细节很值得记:
1)origin 会拼接多个来源
所以一张 PO 可能来自多个 procurement 的来源单据。
2)reference_ids 也会被合并
这给后续追踪和报表提供了更清晰的链路。
这就是为什么采购单经常不是“单一来源”的,它本来就允许被补货和手工操作共同驱动。
第四步:采购单确认后,收货链路才真正接上
在 addons/purchase_stock/models/purchase_order.py 里,button_approve() 会在父类逻辑之后调用 _create_picking()。
这一步会:
- 复用已有的未完成收货单
- 如果没有,就调用
_prepare_picking()创建新的 incoming picking - 再把采购行转成对应的 stock move
- 然后确认、分配,并把后续受影响的 pickings 也一起推进
所以你可以把它理解为:
buy规则负责把需求变成采购单,purchase_stock负责把采购单变成“能收货的库存动作”。
如果只看采购单,不看 _create_picking(),你就会漏掉“采购确认后为什么库存还没动”的关键一步。
一个实用的排查顺序
遇到采购链路异常时,建议按这个顺序看:
stock.rule里有没有buy动作_get_matching_supplier()有没有找到供应商- 采购单是不是被正确按 domain 分组了
button_approve()之后有没有进入_create_picking()- 收货单有没有被复用,而不是新建
这样基本能把“为什么没建单”“为什么没收货”“为什么合单了”分清楚。
总结
Odoo 的采购链路本质上是:
buy规则负责把补货需求转换成采购请求purchase.order负责承接供应商和单据合并purchase_stock再把确认后的采购单接到收货单和库存移动
这条链真正的价值,不是“自动建单”四个字,而是把补货、采购、收货和库存动作串成了一个可追踪的闭环。
English sidecar
The short version
Odoo procurement does not simply “create a PO when you click confirm”. It runs a pipeline:
- The demand first enters
stock.rule - The
buyrule groups procurements into RFQs by supplier, company, and PO domain purchase_stockconnects the confirmed PO to incoming pickings and stock moves- Procurement, purchasing, and receiving become one traceable chain
Step 1: the buy rule catches demand
In addons/purchase_stock/models/stock_rule.py, Odoo adds a buy action to stock.rule.
That means a rule can send demand to purchasing instead of just pulling or pushing stock.
The run() override also injects the warehouse reception route when the routes include buy, so the receiving side is part of the same flow.
Step 2: _run_buy() groups procurements into RFQs
_run_buy() is the core of the flow.
For each procurement, it:
- Finds a matching vendor
- Handles the “no vendor found” case differently depending on the context
- Stores
supplierandpropagate_cancelback intoprocurement.values - Computes a PO grouping domain with
_make_po_get_domain() - Groups procurements into the same RFQ or PO when the domain matches
The key idea is not “create a PO now”, but “decide the grouping boundary first”.
No vendor is not always the same failure
The source handles missing vendors differently depending on where the demand came from.
- In orderpoint flows, it can raise aggregated procurement errors
- In other cases, it may fall back to MTS, cancel downstream moves, or notify the responsible user
That is why auto-purchasing sometimes fails loudly and sometimes continues with a fallback.
Step 3: create or reuse the purchase order
For each PO domain, Odoo looks for an existing order first.
- If it finds one, it reuses it and updates the origin / references
- If not, it calls
_prepare_purchase_order()and creates a new one
That is why a PO may have multiple origins and reference IDs.
Step 4: approval connects the receipt flow
In purchase_stock, button_approve() calls _create_picking() after the parent logic.
That step:
- reuses open receipts when possible
- creates a new incoming picking otherwise
- converts purchase lines into stock moves
- confirms and assigns the picking chain
So the buy rule creates the purchase request, and purchase_stock turns the confirmed PO into receiving operations.
Debugging order
When the flow breaks, check:
- Whether the rule really has
buy - Whether
_get_matching_supplier()finds a vendor - Whether procurements are grouped into the expected PO domain
- Whether
button_approve()reaches_create_picking() - Whether an existing receipt was reused instead of a new one being created
Takeaway
Odoo purchasing is not just PO creation. It is a full chain:
- buy rule = demand to purchase request
- purchase order = vendor-facing document
- purchase_stock = receipt and stock-move bridge
That is what makes the flow traceable from demand to receipt.
DISCUSSION
评论区