先说结论
Odoo 自动采购不是只有一个“合单开关”。
从需求出来到最后收货,至少有四层不同的合并/拆分判断:
- procurement 先不先合并
- 这些 procurement 会不会进同一张 PO / RFQ
- 进了同一张 PO 后,会不会合并到同一条 PO line
- 后续 stock move 会不会继续被合并
所以现场看到“为什么这次合了、那次没合”,不要只盯 group_id 一个字段。
真正的答案通常是:
procurement group 只是链路标识之一,真正决定采购合并/拆分的,是多层 domain、candidate 与 distinct fields 一起作用。
这篇为什么不是已有“procurement group explained”重复
站里已有文章已经讲过 procurement group 的基本概念,以及它如何把需求链串起来。
这篇改讲一个更落地的问题:
- 为什么相同产品不会总是进同一张 RFQ
- 为什么已经进了同一张 RFQ,也不一定并成一行
- 为什么收货 move 有时还会再次合并或保持分离
也就是说,这篇聚焦的是合单/拆单行为边界,不是 procurement group 的入门定义。
第一层:在 _run_buy() 之前,procurement 自己就可能先被合并
在 addons/purchase_stock/models/stock_rule.py 里,Odoo 先调用:
_get_procurements_to_merge()_merge_procurements()
而 _get_procurements_to_merge_groupby() 用来决定哪些 procurement 可以先归为一组。
这里看的关键因素有:
product_idproduct_uompropagate_cancelproduct_description_variants- 某些 orderpoint 条件
特别值得注意的是这句注释:
- 不要把不同 orderpoint 的 procurement 混在一起
原因包括:
quantity_in_progress会依赖 orderpoint- 生成 move 的目标位置也可能依赖 orderpoint
这就说明:
- 还没到 PO 层,需求本身就可能先被拆开
- 而且拆开的原因可能不是供应商,而是补货规则语义不同
第二层:能不能进同一张 PO,看的是 PO domain,不只看产品相同
在 _run_buy() 里,每个 procurement 会先找 supplier,然后调用:
_make_po_get_domain(company_id, procurement.values, partner)
这些 domain 相同的 procurement,才会进入同一组 procurements_by_po_domain。
也就是说,能不能进入同一张 RFQ/PO,首先取决于:
- 公司
- 供应商
- 地址/目的地
- 协议
- 其他 domain 维度
这就是为什么“同一个产品”不等于“同一张采购单”。
产品只是 line 层条件,不是 order 层条件。
第三层:进了同一张 PO 后,还要再看能不能并进同一条 PO line
就算 procurement 已经被放进同一张 PO,Odoo 还会继续做 line 级判断。
源码会先按产品拿到候选 po_lines,然后用:
_find_candidate(*procurement)
去判断是否能并入现有行。
如果找到 candidate:
- 调
_update_purchase_order_line(...) - 直接写回已有行数量和数据
如果找不到:
- 新建一条采购行
这说明“同一张单里同产品只有一行”也不是绝对规则。
只要 candidate 规则不满足,就会拆成多行。
而站里已有《purchase line merge boundaries》那篇更偏订单修改/补货线合并,这篇则是从 _run_buy() 的自动采购入口往上看。
第四层:后续 stock move 还会再做一次自己的合并判断
很多人以为 PO line 定完以后,后面就不会再有“合并/拆分”了。
其实不对。
在 addons/purchase_stock/models/stock_move.py 里,_prepare_merge_moves_distinct_fields() 额外把这些字段加入了 distinct 条件:
purchase_line_idcreated_purchase_line_ids
这意味着 stock move 的合并边界会受到采购行关联影响。
也就是说,即使前面看起来像同一批需求,只要它们落在不同的 purchase_line_id 或不同创建来源上,move 合并就可能被阻止。
反过来,某些情况下 move 又能在后续流程中被并起来。
所以你在收货层看到的“为什么只有一个 move”或“为什么拆成多个 move”,不能直接反推前面 PO 一定是怎样建出来的。
第五层:group_id 很重要,但它不是唯一决定因素
purchase_stock 里有很多测试和逻辑都围绕 group_id、move_dest_ids、orderpoint_id 这些字段展开。
这会让很多人形成一个过度简化的理解:
- 只要 procurement group 一样,就应该合单
- 只要 procurement group 不一样,就一定拆单
源码并不支持这么简单的结论。
group_id 的作用更像:
- 标记需求链来源
- 帮助下游 move 追溯上下游文档
- 参与 route / move 组织
但真正的采购合并至少还要再经过:
- supplier matching
- PO domain grouping
- line candidate matching
- move distinct fields
所以 group 是重要线索,但不是唯一真相。
最容易踩坑的 5 个误区
1)以为自动采购合单只看供应商
不对。PO domain 远不止供应商一个条件。
2)以为进了同一张 RFQ 就一定只剩一条采购行
不对。还要过 line candidate 匹配。
3)以为 procurement group 相同就一定能全程合并
不对。不同层级的合并条件不一样。
4)以为 stock move 是否合并能直接代表 PO 是否合并
不对。move 有自己的 distinct fields 规则。
5)以为 orderpoint 只是补货建议,不影响合并边界
源码明确把某些 orderpoint 情况当成 procurement 拆分依据。
排错建议:看到“没合单”,先问自己卡在哪一层
建议按四层去查:
- procurement 有没有先被
_merge_procurements()合并 _make_po_get_domain()产出的 domain 是否一致- 同 PO 内
_find_candidate()为什么没找到现有行 - 收货阶段 move distinct fields 为什么阻止了后续合并
这样排错会比盯一个 group_id 高效得多。
一句话记忆
Odoo 自动采购的合并/拆分不是单点判断,而是 procurement、PO domain、PO line candidate 和 stock move 四层规则叠加出来的结果。
DISCUSSION
评论区