释放数量

Odoo Blanket Order 的数量为什么不会自动“扣合同余额”:释放数量、已下单统计与 supplierinfo 回写边界讲透

很多人把 Odoo Blanket Order 想成“合同总量逐笔释放、每下一个 PO 就自动扣减余额”。源码并不是这么严。它会统计已下单数量,会把价格回写到 supplierinfo,也会在协议结束时清理这些数据,但不会把 Blanket Order 做成强制余额控制引擎。

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

先说结论

Odoo 的 Blanket Order 会做三件很重要的事:

  1. 确认协议时要求每行必须有价格和数量
  2. 把协议行写入 product.supplierinfo
  3. 统计同一协议下、同一产品已经落成 purchase 状态的已下单数量

但它不会天然把自己做成一个“合同余额强锁系统”。

更准确地说:

Odoo 会告诉你协议数量、已采购数量和供应商条件,但不会默认阻止你超出协议数量继续下单。

这正是很多实施团队对 Blanket Order 最容易产生期待错位的地方。


这篇为什么不是已有“Blanket Order 与 supplierinfo 边界”重写

站里已有文章已经讲过:

  • Blanket Order 会在确认时创建 supplierinfo
  • PO 通过 requisition_id 参与协议上下文过滤
  • 协议结束时相关 supplierinfo 会被清理

那篇重点在协议与供应商主数据的边界

这篇改讲一个现场更常见的问题:

  • 协议数量到底是不是“余额池”
  • qty_ordered 到底在统计什么
  • 改价时 supplierinfo 会被回写到什么程度
  • 为什么这条链能辅助释放,但不等于严格合同执行

第一关键点:Blanket Order 确认前就要求数量和价格都完整

addons/purchase_requisition/models/purchase_requisition.pyaction_confirm() 里,blanket_order 会逐行检查:

  • price_unit > 0
  • product_qty > 0

缺一不可。

这一步很有意思。

它说明官方并不把 Blanket Order 仅仅当成一个“价格意向”文档,而是认为:

  • 协议行必须达到足以支撑后续采购引用的完整度

也就是说,数量从一开始就有意义。

但这个“有意义”,不代表“强制余额锁单”。


第二关键点:系统会统计已下单数量,但统计口径是“已采购”,不是“剩余额度控制”

同一文件里的 purchase.requisition.line 定义了:

  • qty_ordered

它在 _compute_ordered_qty() 里按这条逻辑计算:

  • 只统计当前 agreement 下 state == 'purchase' 的采购单
  • 只累计同产品的采购行数量
  • 必要时做 UoM 换算
  • 同一 requisition 里同产品只让一条协议行显示累计值,避免重复展示

这意味着什么?

意味着 Odoo 非常关心“这份协议下,这个产品已经真正下了多少采购”。

但源码并没有在确认 PO 或新增 PO 行时写出类似:

  • 如果 qty_ordered + 本次数量 > agreement_qty 就禁止

也没有天然生成“remaining_qty”强约束链路。

所以 qty_ordered 更像:

  • 释放监控指标
  • 执行进度提示
  • 协议消费观察窗

而不是合同余额闸门。


第三关键点:supplierinfo 的回写边界,是“协议价同步”,不是“完整释放台账”

Blanket Order 的 write() 里有个很重要的实现:

  • 如果改了 price_unit
  • 且是已确认、未关闭/未取消的 blanket order
  • 系统会把对应 supplier_info_idsprice 同步更新

这就是典型的“writeback”。

但你仔细看会发现,回写内容非常克制,主要围绕:

  • 价格
  • 供应商
  • 产品
  • UoM
  • 币种
  • 协议行关联

它没有把 Blanket Order 做成完整释放台账,例如:

  • 每次释放一张 PO 就写剩余额
  • 每次改 PO 数量就重算合同可用余额并阻断超量
  • 每次分批执行都记录 release schedule 明细

所以这条 writeback 链的本质是:

让协议价格持续影响采购主数据,而不是让协议数量变成严格会计账。


第四关键点:协议结束或取消时,相关 supplierinfo 会被清理

action_done()action_cancel() 都会把协议行相关的 supplier_info_ids 删除。

这意味着 Blanket Order 注入的供应商条件是:

  • 有生命周期的
  • 与当前协议绑定的
  • 不是永久静态主数据

这也再次说明,Odoo 的重点是“协议在有效期内参与采购引擎”,而不是“永久维护一份合同余额总账”。

如果它真想把 Blanket Order 做成刚性合同控制器,通常会更强调:

  • 可追溯释放记录
  • 剩余额冻结
  • 超量审批
  • 合同偏离日志

但原生实现并没有把力度放在这里。


第五关键点:新加行、改价、删行都围绕 supplierinfo 生命周期,而不是 release governance

create()write()unlink() 这几段代码的行为很能说明问题:

  • 已确认的 Blanket Order 新增行,必要时会创建 supplierinfo
  • 已确认状态改价,会同步更新相关 supplierinfo.price
  • 删除有效协议行,会把关联 supplierinfo 一起删掉

整条链都在围绕一个中心:

  • 协议行如何映射为可消费的供应商条件,并在生命周期内保持同步

而不是:

  • 每一次 release 如何被严格审计和强制扣减

这就是 Odoo Blanket Order 的真正边界。


现场最容易误解的 5 个点

1)以为 agreement quantity 就是系统自动管控的合同余额

源码只统计 qty_ordered,没有默认把它做成硬阻断。

2)以为每次下 PO 都会自动回写 release 余额

原生没有这套完整余额台账。

3)以为 supplierinfo writeback 代表“合同正在被精确执行”

它更多代表价格条件同步,不代表完整合同治理。

4)以为关闭协议只是改状态

实际上还会清理掉这份协议注入的 supplierinfo。

5)以为 Blanket Order 天然等于采购合同模块

它更像“带数量参考的采购框架 + 供应商条件生命周期管理”。


什么时候需要自定义

如果你的业务真的要求:

  • 每个协议行必须有剩余额
  • PO 超量必须阻断或审批
  • 分批释放要留 release 明细
  • 按月/按期次消耗协议量

那就不要把责任强压给原生 Blanket Order。

更合适的做法通常是:

  1. 在协议行上增加 remaining_qty
  2. 在 PO 确认或行保存时校验超量
  3. 单独做 release log 或 release schedule
  4. 把超量、改价、越期释放纳入审批链

这样你的扩展会顺着 Odoo 的原始边界走,而不是误解它已经提供了完整合同释放系统。


一句话记忆

Odoo Blanket Order 会统计已下单数量、回写 supplierinfo、管理协议生命周期,但不会原生把协议数量做成严格的合同余额扣减引擎。

DISCUSSION

评论区

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