先纠正一个常见误会
很多人把采购里的 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 会先判断产品类型:
consu和service走manual- 其他产品走自动收货逻辑
_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_received 和 qty_to_invoice,最后才去查界面。
DISCUSSION
评论区