先说结论
在 Odoo 采购里,下面三件事经常被混着讲,但源码语义并不一样:
- Bill Control Policy / purchase_method:决定采购行按“订购数量”还是“已收货数量”进入可开票逻辑
- qty_received / qty_received_method:决定系统如何计算“已收货”这个数字
- 3-Way Matching:决定供应商账单在会计控制上,是否要把收货事实一起纳入付款判断
所以更准确地说:
Bill Control Policy 管“能不能形成可开票数量”,qty_received 管“收了多少”,3-Way Matching 管“账单能不能在付款控制上顺利通过”。
把这三件事合成一句“按收货开票”是不够的。
这篇为什么不是已有“服务/耗材收货数量”文章的重复
站里已经有文章讲:
- 服务、耗材为什么可能手工维护
qty_received - 耗材在
purchase_stock下又如何被 stock move 接管
那篇重点是 收货数量怎么来。
这篇往前后各走一步,专门讲:
- 收货数量和
purchase_method怎么共同决定qty_to_invoice - 3-Way Matching 又是在更后面的会计控制层做什么
- 为什么你以为“启用 3-Way Matching 了”,但采购行本身还是可能已经可开票
也就是说,这篇讲的是 采购控制层次拆分,不是单纯解释某种产品类型的收货字段。
第一关键点:采购行是否可开票,核心看 purchase_method
在 /home/ubuntu/odoo-temp/addons/purchase/models/purchase_order_line.py 里,_compute_qty_invoiced() 的逻辑非常直接。
如果订单已经在 purchase 状态:
- 当
line.product_id.purchase_method == 'purchase' qty_to_invoice = product_qty - qty_invoiced- 否则
qty_to_invoice = qty_received - qty_invoiced
这就把很多现场争论一次性讲清了。
按订购数量控制
意思是:
- 采购一确认
- 即便货还没收
- 行也可能已经具备开票数量
按已收货数量控制
意思是:
- 不看你下了多少
- 只看系统认定你实际收了多少
- 没收货,就没有可开票数量
所以 Bill Control Policy 不是邮件提醒,不是审批权限,也不是付款校验。
它首先是 采购行数量进入开票链路的口径选择。
第二关键点:qty_received 不是所有产品都按同一套方式算
同一文件里,基础采购模块先给出默认规则:
serviceconsu
默认 qty_received_method = 'manual'
也就是:
- 服务和耗材在纯
purchase模块语义下,收货数量更像业务确认量 - 不是天然依赖库存 move 自动累加
但在 /home/ubuntu/odoo-temp/addons/purchase_stock/models/purchase_order_line.py 里,purchase_stock 又把 consu 改成:
qty_received_method = 'stock_moves'
这就是很多实施现场最容易看错的地方。
同样叫“耗材”:
- 没有库存接管时,更像手工确认收了多少
- 有库存接管时,会按采购相关
stock.move的完成、退货、回退边界来自动计算
也就是说,你说“我们已经收货了”,到底有没有反映到可开票数量,先得看这条采购行属于哪种收货计算机制。
第三关键点:库存型、耗材、服务三类产品的采购控制体验会明显不同
库存型产品
通常依赖库存 move。
业务感受是:
- 入库做没做
- 退货怎么冲减
- Dropship 回流、采购退货怎么处理
都会直接影响 qty_received。
耗材
在纯采购语义下可能是手工;装上 purchase_stock 后又会偏向库存 move 驱动。
所以耗材是最容易让人觉得“同一个产品今天能手工改、明天又被库存单据接管”的一类。
服务
默认仍然偏手工确认。
这也符合业务现实:
- 服务不是进仓
- 它更像交付事实或业务确认
所以如果服务行配置成按收货开票,你真正要关心的是:
- 谁在何时把
qty_received录进去
而不是仓库有没有做入库单。
第四关键点:3-Way Matching 不是“另一种 Bill Control Policy”
在 res.config.settings 里,采购模块暴露了:
module_account_3way_match
帮助文案写得很清楚:
- 启用后,供应商账单在付款前要结合采购与收货一起核对
这已经说明,3-Way Matching 的位置比采购行可开票更靠后。
它关心的是:
- 账单来没来
- 采购单有没有
- 收货事实够不够支撑付款
而不是直接改写 qty_to_invoice 的计算公式。
所以你完全可能遇到这种情况:
- 采购行因为按订购数量控制,已经可以生成账单
- 但公司又开启了 3-Way Matching
- 结果账单虽然生成了,付款控制或后续审核仍然会看收货状态
这两层控制并不冲突,它们只是落在不同环节。
为什么很多团队会误把两者当成一回事
因为从业务口语看,它们都像在表达:
- 没收货就别付钱
但在系统层,它们分别解决不同问题。
Bill Control Policy 解决的是
- 采购行是否形成“可开票数量”
3-Way Matching 解决的是
- 账单与采购、收货事实之间的会计一致性控制
前者更偏 数量口径。
后者更偏 账单控制。
如果把两者混起来,你就会出现这种常见误判:
- “为什么启用了 3-Way Matching,系统还让我建 Vendor Bill?”
答案常常不是配置错了,而是你把“可建账单”和“可顺利走完付款控制”当成同一个节点了。
第五关键点:可开票数量为 0 时,往往不是 3-Way Matching 的锅
源码和官方翻译里还有一个非常典型的报错语义:
- 如果产品按 received quantity 控制,请先确保已有收货数量
这句话背后的真实根因通常是:
purchase_method走的是按收货控制- 但
qty_received还没有被系统认定为非零
此时你该查的是:
- 产品的采购控制策略
- 当前采购行的
qty_received_method - 是否已经有对应收货动作或手工确认
- 退货/回退是否把已收货量抵掉了
而不是先怪 3-Way Matching。
实战排错顺序
如果采购账单流程表现异常,建议按这个顺序拆:
1. 先看产品是按 ordered 还是 received 开票
这是根口径。
2. 再看 qty_received 是怎么来的
- manual
- stock_moves
- 还是别的模块扩展
3. 再看当前收货事实有没有真正落到采购行
别只看仓库说“已经收了”,要看采购行上的数字有没有变。
4. 最后再看公司有没有启用 3-Way Matching
这决定账单在会计控制上的后续行为,而不是先天决定采购行能否出现开票数量。
最容易误解的 5 个点
1. 以为 3-Way Matching 就是“按收货开票”
不是。它更偏账单与付款控制。
2. 以为 Bill Control Policy 决定付款是否放行
它首先决定的是采购行的 qty_to_invoice 怎么算。
3. 以为服务采购不能按收货控制
可以,但它的“收货”通常是业务确认,不是仓库入库。
4. 以为耗材永远手工收货
装了 purchase_stock 后,耗材可能改由库存 move 自动计算。
5. 以为仓库做了单据,采购行就一定会出现可开票数量
还要看退货、回退、产品类型和控制策略是否把这个数量又抵掉了。
一句话记忆法
Bill Control Policy 决定采购行按“订购”还是“收货”形成可开票数量;3-Way Matching 决定账单在会计控制上是否还要把收货事实一起算进去。
DISCUSSION
评论区