先说结论
Odoo 采购单确认后,并不是只有两种世界:
- 完全不能改
- 完全随便改
源码里的真实设计更像一套分层控制:
- 公司策略 决定确认后是否自动锁单
- 锁单状态 决定你能不能继续编辑、取消
- 已确认订单行 即便没锁,也不是想删就删
- 数量等关键字段变更 会在 chatter 留痕
- 重发邮件 和 供应商 acknowledged 是协同确认链,不等于锁单
所以更准确地说:
Odoo 把“确认后修改”拆成了流程状态、锁单策略、字段留痕、供应商沟通四个层面,而不是一句“确认后可改/不可改”。
这篇为什么不是已有“receipt reminder / acknowledged”文章的重复
站里已有一篇重点讲:
- 催交提醒什么时候发
acknowledged为什么会影响提醒继续与否
那篇关注的是:
- 供应商有没有确认收到采购单
这篇换成另一个现场更常问的问题:
- 采购单确认后到底还能怎么改
- 自动锁单和手工解锁的边界是什么
- 为什么有时可以重发,有时又不该把重发当成“重新走审批”
也就是说,这篇主问题是 采购单确认后的生命周期管理,而不是提醒邮件节奏。
第一关键点:是否自动锁单,先看公司策略
在 /home/ubuntu/odoo-temp/addons/purchase/models/res_company.py 和 res_config_settings.py 里,采购公司策略里有:
po_lockeditlock
文案也很明确:
- confirmed purchase orders are not editable
在 purchase.order.button_approve() 里,订单进入 purchase 后,如果公司策略是 lock,系统会写入:
locked = True
这说明“确认后能不能改”首先不是用户个人习惯,而是公司级采购治理策略。
第二关键点:锁单不是状态机主状态,但它会实实在在阻止后续动作
purchase.order 上有个独立字段:
locked
它不是 state 本身,但影响非常真实。
例如 button_cancel() 里,源码明确先检查:
- 如果订单已锁,直接报错:必须先 unlock
也就是说,锁单不是视觉装饰,而是取消动作的前置门槛。
这很符合真实采购管理:
- 已确认且锁定的采购单,不应该无痕撤销
- 至少要先显式解除保护,再进入取消流程
所以 “locked” 可以理解成:
- 对已确认采购单施加的额外编辑/撤销保护层
第三关键点:就算没锁,已确认采购单也不是任你改删
很多人看到公司策略是 edit,就会以为:
- 那确认后就是完全自由编辑
也不对。
在 purchase_order_line.py 里,_unlink_except_purchase() 明确规定:
- 如果订单行所在订单已在
purchase - 且这行不是 section/note 之类展示行
- 就不能直接删除该采购行
这说明即便没锁,Odoo 也不希望你把已确认的真实业务行像草稿一样随手删掉。
它表达的不是“绝对不能改”,而是:
- 确认后的修改要保留业务连续性,不应该把已承诺事实抹掉。
第四关键点:数量变更会留痕,不是静默覆盖
purchase_order_line.write() 里还有一个很有业务味的设计:
- 如果订单已在
purchase - 且
product_qty发生变化 - 系统会
message_post_with_source(...)
也就是说,确认后改数量不是纯字段更新,而是会在 chatter 里留下轨迹。
这解决的是采购现实里的一个关键问题:
- 订单改过,但谁也不知道改了什么
Odoo 并没有把这种变更强行变成版本管理系统,但至少要求:
- 数量类核心调整要有留痕
所以“PO amendment” 在标准 Odoo 里更像:
- 有条件地继续编辑 + 留痕
而不是单独一个“修订单”对象。
第五关键点:新增行、改单价、改日期,还会继续影响后续库存与估值
在 purchase_stock 扩展里,已确认订单行的修改还会进一步影响:
- move 的 deadline
- 关联 picking 的更新
- 已估值 move 的价值重算
比如:
- 改
date_planned会更新相关 move 的截止日期 - 改
product_qty可能触发_create_or_update_picking() - 改
price_unit还可能影响 stock move 的价格与估值
这说明确认后的修改不是“界面补记一下备注”,而是会继续波及执行链路。
因此实施上更应该把“确认后允许修改”理解成:
- 在受控前提下继续修正执行单
而不是:
- 把确认状态当草稿继续用
第六关键点:重发邮件不是重新确认审批,而是重新发起沟通
action_rfq_send() 这条链也很容易被误读。
源码里它会根据状态选择:
- 草稿/已发送时,展示为 RFQ 语义
- 已确认后,展示为 Purchase Order 语义
并且 message_post() 在带 mark_rfq_as_sent 上下文时,会把草稿订单写成:
state = 'sent'
这说明“发邮件/重发邮件”主要解决的是:
- 把采购单内容再发给供应商
- 保持沟通状态与门户入口正确
它不是“重新走审批流”的按钮,也不是“重发后就自动取消旧版本”的版本机制。
所以现场里如果采购员说“我重发了一次给供应商”,更准确的系统理解是:
- 同一张 PO 又发起了一轮邮件沟通
而不是:
- 系统自动生成了新的修订版采购单
第七关键点:供应商 acknowledged 是协同确认,不是锁单确认
purchase.order 上的:
acknowledged
以及 action_acknowledge() 做的事非常简单:
self.acknowledged = True
这点特别值得强调。
它说明 acknowledged 的业务含义是:
- 供应商确认已经收到采购单
它不等于:
- 采购单进入不可编辑状态
- 采购单完成法务签约
- 采购单自动冻结所有字段
因此这几个概念不能混:
- 确认(purchase state):内部采购流程通过
- 锁单(locked):系统对已确认订单施加修改保护
- 供应商确认(acknowledged):外部协同方确认已收到单据
三者相关,但不是同一个开关。
最容易误解的 5 个点
1. 以为确认后是否能改,只看订单状态
不够。还要看公司锁单策略与当前 locked 状态。
2. 以为没锁就等于完全自由修改
不对。已确认订单行仍有删除限制,关键字段变更也会留痕。
3. 以为重发邮件等于重新发起审批
它本质上是重新沟通,不是重建版本。
4. 以为供应商点了确认,采购单就等于锁死
acknowledged 只是外部确认收到单据。
5. 以为确认后改数量只影响界面显示
它还可能继续影响 picking、move deadline、库存估值等执行链路。
实战建议:确认后的变更怎么管更稳
如果团队经常要在确认后改 PO,建议先分清三类动作:
第一类:只是再次发送给供应商
- 用 resend / send mail 思路理解
- 重点是沟通,不是版本管理
第二类:需要微调数量、日期、价格
- 确认公司是否允许不锁单或允许先解锁
- 让采购员知道改动会留痕,并可能影响后续库存链
第三类:需要撤销或大改
- 先看是否已锁
- 再看是否已有 vendor bill
- 再决定是解锁后取消、还是保留原单追加调整
这样比笼统说“确认后别改”更符合标准 Odoo 的真实边界。
一句话记忆法
Odoo 采购单确认后不是简单“能改或不能改”,而是由公司锁单策略、已确认行限制、变更留痕,以及邮件/acknowledged 协同链共同决定后续边界。
DISCUSSION
评论区