销售可编辑边界

Odoo 销售确认后为什么有的能改、有的不能改:lock、editable 与 confirmed order 的真实边界

很多人把销售确认后的改单理解成“解锁就都能改”。但从官方源码看,Odoo 真正控制的是不同字段、不同订单行、不同下游状态下的可编辑边界:有些字段只是锁单后受限,有些字段只要已经交付或开票就不能再改。本文把 confirmed order 的 editability 讲透。

销售
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

在 Odoo 里,销售单确认后不是简单分成“能改”和“不能改”两类。

标准逻辑其实分了三层:

  • 订单层:订单是否锁定,决定你还能不能继续做很多后续动作。
  • 字段层:有些字段确认后还能改,有些字段会被明确拦住。
  • 订单行层:哪怕整张单已经解锁,某些订单行只要出现已交付或已开票,就会变成不可随意编辑。

所以真正该问的不是:

销售确认后到底能不能改?

而是:

哪一层对象、哪个字段、处在什么下游状态下还能改?


这篇文章主要参考哪些源码

核心参考文件包括:

  • /home/ubuntu/odoo-temp/addons/sale/models/sale_order.py
  • /home/ubuntu/odoo-temp/addons/sale/models/sale_order_line.py
  • /home/ubuntu/odoo-temp/addons/sale/wizard/res_config_settings.py

最关键的源码信号有:

  • group_auto_done_setting 对应 “Lock Confirmed Sales” 设置
  • action_confirm() 在确认后会根据 _should_be_locked() 决定是否自动锁单
  • write() 明确禁止修改已确认订单的 pricelist_id
  • sale.order.line_compute_product_updatable() 会根据 lockedqty_deliveredqty_invoiced 决定产品是否还能改
  • product_uom_readonlysale / cancel 状态下会被设成只读

这些点放在一起,才能看清 Odoo 对“确认后改单”的真实设计。


第一层:锁单不是确认本身,而是确认后的第二道闸门

res.config.settings 里,group_auto_done_setting 就是销售设置中的 Lock Confirmed Sales

action_confirm() 完成后,会继续调用:

  • _should_be_locked()
  • action_lock()

也就是说,标准 Odoo 的意思不是“确认必然锁单”,而是:

  • 先确认订单进入 sale
  • 如果启用了自动锁单特性,再把 locked 设成 True

这就解释了为什么很多项目现场会出现两种完全不同的感受:

  • 有的公司觉得“确认后几乎什么都改不了”
  • 有的公司觉得“确认后仍然还能继续调整”

差异往往不在于你是不是进入了 sale,而在于有没有启用自动锁单,以及有没有产生更深的下游事实。


第二层:解锁不是恢复草稿,而是局部恢复修改能力

action_unlock() 的实现非常短:

  • 只是把 locked = False

这意味着它并不会:

  • 把订单状态从 sale 改回 draft
  • 自动撤销拣货
  • 自动撤销发票
  • 自动让所有字段重新完全可编辑

它真正做的是:

把订单从“锁定的确认单”变回“未锁定的确认单”。

注意这个差别非常关键。

很多顾问把 unlock 当成“回到还能像报价一样编辑”。 但源码根本不是这个语义。 它只是在订单层放开一道门,后面还要继续接受字段层和订单行层的限制。


第三层:已确认订单里,价目表就是被明确禁止修改的

sale.order.write() 里有一条非常直接的限制:

  • 只要 vals 里包含 pricelist_id
  • 并且当前订单里存在 state == 'sale'
  • 就抛错:不能修改已确认订单的价目表

这条规则很有代表性。

因为它说明 Odoo 并没有采用“确认后全部字段冻结”的粗暴方案,而是采用:

  • 某些字段仍可继续调整
  • 某些字段一旦确认就不允许再变

为什么价目表被单独保护?

因为 pricelist 不是普通展示字段。它会影响:

  • 新增行如何定价
  • 折扣如何展示
  • 后续重算价格的基准
  • 税前后价格解释口径

如果确认后还能随便切 pricelist,整个报价承诺的价格基础就会漂移。

所以 Odoo 在模型层直接把它拦住,而不是只靠界面隐藏按钮。


第四层:订单行的“可编辑”比订单本身更严格

sale.order.line 里有个特别关键的计算字段:product_updatable

它默认是 True,但以下场景会被设成 False:

  • 行是 down payment
  • 订单状态是 cancel
  • 订单状态是 sale,且满足以下任一条件:
  • 订单已锁定
  • 行已经有 qty_invoiced > 0
  • 行已经有 qty_delivered > 0

这条逻辑揭示了一个核心原则:

确认后的订单行能不能改,不只看订单是否解锁,还要看这条行是否已经开始兑现。

换句话说:

  • 没交付、没开票、未锁定的确认行,仍可能可改
  • 已交付或已开票的确认行,即便订单已经解锁,也不该继续改产品本身

这才符合业务现实。

因为一旦某行已经参与履约或结算,改产品不再是“修正文案”,而是在改已经发生过的商业事实。


第五层:产品可改,不代表单位与数量也能无限制改

在同一个 sale.order.line 里,product_uom_readonly 会在记录已保存且状态处于:

  • sale
  • cancel

时变成只读。

这说明即使某些情况下产品仍可更新,单位字段也不等价于完全自由。

原因很简单:

  • 单位直接影响数量解释
  • 数量影响交付数量、待开票数量、金额换算
  • 一旦确认后履约链开始运转,单位变化往往比改备注危险得多

所以 Odoo 的限制不是随机的,而是在保护计量口径的一致性。


第六层:确认后的“可编辑性”本质上和 qty_delivered / qty_invoiced 绑定

很多新手会把 editability 只理解成前端表单状态。

但在源码里,最硬的边界来自两个数量:

  • qty_delivered
  • qty_invoiced

只要其中任一个大于 0,行级可编辑性就会明显收紧。

这背后的业务逻辑非常清楚:

已交付

说明这条销售承诺已经开始对应物流、工时或费用事实。

已开票

说明这条销售承诺已经开始对应会计与收款语义。

所以一条确认行在“尚未兑现”与“已经兑现一部分”之间,系统给出的可改边界完全不同。

这也是为什么现场经常会出现一种错觉:

  • 同一张订单,有的行还能改,有的行已经灰掉

这不是系统不一致,而是系统在按履约深度做差异化保护。


第七层:确认后为什么不是所有东西都要冻结

标准 Odoo 没有把确认后的订单做成彻底只读,是有意为之。

因为真实业务里,确认后仍然常常需要调整:

  • 客户参考号
  • 交期承诺
  • 内部备注
  • 某些尚未交付的新加行
  • 某些价格重算后的说明动作

如果把确认后完全冻结,很多实施场景会非常僵硬。

所以 Odoo 采用的是一种更实务化的策略:

  • 允许一定范围内的后确认调整
  • 但把会破坏价格承诺、履约事实、开票追踪的修改点收紧

这是一种“允许业务继续推进,但不允许历史失真”的设计。


新手最容易误解的 5 件事

1. 解锁后就等于恢复成报价单

不是。解锁只移除 locked,不回退订单生命周期。

2. 只要订单没锁,订单行就一定能随便改

不是。qty_deliveredqty_invoiced 也会让行不可改。

3. 已确认订单可以通过换 pricelist 整体重算

标准源码明确禁止这么做。

4. 可编辑性只是前端视图控制

不是。很多关键边界在模型层就直接拦住了。

5. 订单是否能改只取决于订单头

不是。Odoo 明显区分订单头与订单行,而且行级限制更细。


实战里我建议这样判断确认后改单

我通常按下面顺序判断:

第一问:订单有没有锁

  • 锁了,先判断是否应该解锁
  • 没锁,再继续看更细的边界

第二问:要改的是哪个字段

  • 若是 pricelist_id 这种模型层明确禁改字段,就别想着走标准修改
  • 若是备注、参考号、承诺日期,再看项目场景是否允许

第三问:要改的是哪条行

  • 还没交付、没开票、未锁定的行,通常更有调整空间
  • 已交付或已开票的行,要优先考虑逆向业务处理,而不是直接改原行

第四问:改动会不会让下游失真

如果会影响:

  • 出库口径
  • 开票追踪
  • 毛利分析
  • 客户已确认价格

那就不要把“可编辑”误当成“应该编辑”。


最后一句话

Odoo 对 confirmed order 的 editability 设计,其实非常务实:

确认后不是一刀切锁死,而是按锁单状态、字段风险、履约深度来分层收紧。

所以真正成熟的实施方式,不是追问“为什么这里灰了”,而是先搞清楚:

  • 这是订单层限制,
  • 还是字段层限制,
  • 还是订单行已经开始兑现后的保护机制。

只有这样,你才知道下一步应该是解锁、补充新行、走逆向流程,还是干脆不要改原单。

DISCUSSION

评论区

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