采购开票

Odoo 采购 Bill Control Policy 不是一个小开关:按订购数量还是按收货数量,会把整条开票逻辑改掉

从 product.template.purchase_method、qty_received、qty_to_invoice 和 invoice_status,拆开看采购开票到底按谁算。

会计 库存 采购
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先纠正一个常见误会

很多人把采购里的 Bill Control Policy 当成一个“发票开关”。 其实它真正控制的是:采购单上的可开票数量,到底按订购数量算,还是按已收货数量算。

这件事并不小,因为一旦口径变了,整条链路都会跟着变:

  • 采购行的 qty_to_invoice 怎么算
  • 订单的 invoice_status 怎么判断
  • 服务类和实物类商品为什么表现不同
  • 收货单和 vendor bill 之间到底是谁驱动谁

所以这不是一个“界面按钮”,而是一个业务规则开关。

purchase_method 才是控制政策的核心字段

product.template 里,purchase_method 的名字是 Control Policy。 源码里只有两种值:

  • purchase:按订购数量控制开票
  • receive:按收货数量控制开票

_compute_purchase_method() 还会做一件很重要的事:

  • 服务类产品默认走 purchase
  • 其他产品继承系统默认值,通常是 receive

这就解释了为什么“服务采购”经常看起来不太一样。 服务没有真实收货动作,按收货开票就没有意义,所以它默认更偏向按订购数量控制。

qty_to_invoice 是真正的分水岭

purchase.order.line._compute_qty_invoiced() 里,逻辑非常直接:

  • 如果订单已经进入 purchase 状态
  • 且产品控制政策是 purchase
  • 那么 qty_to_invoice = product_qty - qty_invoiced

否则:

  • qty_to_invoice = qty_received - qty_invoiced

这就是核心差异。

也就是说:

  • 按订购数量时,看的是“下单了多少,已经开了多少”
  • 按收货数量时,看的是“收了多少,已经开了多少”

这也是 3-way matching 的本质: 系统不是只看订单,也不是只看发票,而是要把订单、收货和账单对齐。

qty_received 也不是一个单一来源

采购行的 qty_received_method 会先判断产品类型:

  • consuservicemanual
  • 其他产品走自动收货逻辑

_compute_qty_received() 再根据这个方法决定 qty_received 从哪里来。 对服务和耗材来说,它更像一个手工维护的业务数字; 对实物商品来说,它和库存收货动作更紧密。

这就形成了一个很实用的边界:

有库存动作的商品,收货量可以从 stock move 回流;没有真实收货动作的服务,则由人工或业务规则维护。

订单状态为什么也会跟着变

purchase.order._get_invoiced() 会汇总每一行的 qty_to_invoice

  • 只要还有一行没开完票,invoice_status 就会是 to invoice
  • 所有行都结清并且已经有发票时,才会变成 invoiced
  • 还没进入 purchase 状态时,状态是 no

所以订单的发票状态不是独立算出来的,它是采购行数量逻辑的总和。

这也是为什么你改了产品控制政策后,订单状态表现会变。 不是状态机坏了,而是输入规则变了。

为什么这和收货单链路绑在一起

purchase_stock 里的 _create_picking() 会在采购单确认后创建收货相关的 stock picking 和 stock move。 也就是说,收货数量不是凭空出现的,它来自库存链路。

当你把开票控制切换到 receive 时,采购发票其实就被绑定到了库存执行结果:

  • 收货没做完,发票可开数量就可能不够
  • 收货做多/做少,会直接影响可开票数量
  • 退货、冲销、返工都会回写这条链路

这也是采购里最容易被误解的点: 你以为自己在调一个“发票规则”,其实你在决定采购、库存、会计三条线谁来驱动谁。

一句话记忆法

  • 按订购数量:订单说了算
  • 按收货数量:库存说了算

如果你在实施里经常遇到“为什么这张 PO 还不能开票”,先看产品的 purchase_method,再看 qty_receivedqty_to_invoice,最后才去查界面。

DISCUSSION

评论区

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