收货控制

Odoo 采购收货控制为什么经常被误解成“开票开关”:Bill Control Policy、收货数量与 3-Way Matching 边界讲透

采购现场最常见的误会之一,就是把 Bill Control Policy 和 3-Way Matching 当成同一件事。其实前者决定“采购行什么时候变得可开票”,后者决定“账单在付款审批上是否还要看收货事实”。再叠加服务、耗材、库存型产品不同的 qty_received 计算方式,误判就更多了。

采购
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

在 Odoo 采购里,下面三件事经常被混着讲,但源码语义并不一样:

  1. Bill Control Policy / purchase_method:决定采购行按“订购数量”还是“已收货数量”进入可开票逻辑
  2. qty_received / qty_received_method:决定系统如何计算“已收货”这个数字
  3. 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 不是所有产品都按同一套方式算

同一文件里,基础采购模块先给出默认规则:

  • service
  • consu

默认 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 还没有被系统认定为非零

此时你该查的是:

  1. 产品的采购控制策略
  2. 当前采购行的 qty_received_method
  3. 是否已经有对应收货动作或手工确认
  4. 退货/回退是否把已收货量抵掉了

而不是先怪 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

评论区

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