价目表命中逻辑

Odoo 价目表为什么总感觉“命中错了”:pricelist rule priority、formula 与 country group 选价逻辑讲透

很多人在 Odoo 里排查价目表时,只盯着公式本身,却没先搞清规则优先级、适用范围和国家组匹配。官方源码显示,真正决定“为什么选到这条价目表、为什么命中这条规则”的,是 pricelist 选择与 rule 搜索顺序的组合。本文把这套逻辑讲透。

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

先说结论

在 Odoo 里,“价格不对”通常不是公式算错,而是你以为系统命中了 A,实际上系统先选了另一张 pricelist,或先停在了另一条规则上

标准逻辑至少分两层:

  • 先选哪张价目表
  • 再在这张价目表里命中哪条规则

而这两层都不是拍脑袋决定的。

如果你只看某条 formula rule 的 price_discountprice_roundprice_surcharge,但没有同时看:

  • pricelist 自身 sequence
  • country_group_ids
  • rule 的 applied_on
  • min_quantity
  • product / template / category 适配范围

那你就很容易把“命中错了”误判成“公式错了”。


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

核心参考文件包括:

  • /home/ubuntu/odoo-temp/addons/product/models/product_pricelist.py
  • /home/ubuntu/odoo-temp/addons/product/models/product_pricelist_item.py

最关键的源码信号有:

  • product.pricelist_order = "sequence, id, name"
  • _get_country_pricelist_multi() 会先找匹配 country_group_ids 的价目表,再考虑 fallback
  • _compute_price_rule() 先调用 _get_applicable_rules() 拉出候选规则,再逐条找第一条适用规则
  • product.pricelist.item_order = "applied_on, min_quantity desc, categ_id desc, id desc"
  • formula 规则的计算顺序是:base -> discount/markup -> rounding -> surcharge -> min/max margin

这些点决定了“选哪张表”和“命中哪条 rule”的完整路径。


第一层:先别急着看规则,系统可能连价目表都没选到你以为的那张

很多排查习惯一上来就打开 pricelist line。

product.pricelist 先要解决的是:

当前客户 / 国家 / 公司 / 默认属性,应该落到哪张 pricelist?

_get_country_pricelist_multi() 的逻辑就说明,国家组在这一步已经参与决策。

它会优先考虑:

  • 当前上下文国家
  • 给定国家列表
  • country_group_ids 匹配的 pricelist
  • 没有 country group 的 fallback pricelist
  • partner 默认 pricelist 属性或公司默认配置

这意味着一个典型误区是:

  • 你以为客户用了“海外批发价目表”
  • 实际上因为国家组、默认属性或 fallback 逻辑,系统拿到的是另一张表

如果第一层就看错,第二层再精细地研究公式也没用。


第二层:规则优先级不是“谁写在上面谁先算”,而是按模型排序和搜索结果来

product.pricelist.item_order 明确是:

  • applied_on
  • min_quantity desc
  • categ_id desc
  • id desc

再配合 _compute_price_rule() 的循环逻辑:

  • 系统先 search 出候选规则
  • 再按排序结果,从前到后找第一条 _is_applicable_for(...) 为真的规则
  • 找到就停

这说明 rule priority 不是“所有命中的规则一起算,再挑最优价”,而是:

按既定顺序找到第一条适用规则,然后停止。

这是很多人理解错的核心原因。

Odoo 的 pricelist 不是一个“并行比较器”,更像一个“有序命中器”。


第三层:为什么 variant、product、category、global 看起来像同级,实际不是同级

applied_on 取值包括:

  • 0_product_variant
  • 1_product
  • 2_product_category
  • 3_global

注意它们前面的数字不是装饰。

因为 _order 第一位就是 applied_on,所以排序天然表达出一层优先级语义:

  • 变体级规则优先于模板级
  • 模板级优先于品类级
  • 品类级优先于全局级

这很合理。

因为系统假设:

  • 越具体的规则,越应该先尝试
  • 越泛化的规则,越应该做兜底

所以当你发现“全局规则没生效”,很多时候不是它坏了,而是前面已经有一条更具体的规则先把你截住了。


第四层:min_quantity 的优先级往往比很多人想的更高

同一类适用范围下,min_quantity desc 排在前面。

这意味着对同一产品来说:

  • 满足 100 件门槛的规则
  • 会先于满足 10 件门槛的规则被尝试

这不是后来比较出来的“最优折扣”,而是搜索顺序直接决定的。

所以排查批量折扣异常时,最应该先看的是:

  • 当前数量是否已经跨过更高门槛
  • 某条高门槛规则是否比你以为的那条先被命中

很多实施现场会把这类问题解释成:

  • 系统没取最优惠价格

但 Odoo 标准逻辑本来就不是“自动替你挑最低价算法”,而是“按规则顺序命中第一条适用项”。


第五层:formula 真正的计算顺序,和很多人口头理解的不一样

product.pricelist.item 里,formula 相关字段包括:

  • base
  • price_discount
  • price_round
  • price_surcharge
  • price_min_margin
  • price_max_margin

源码注释与 rule tip 的说明都指向同一件事:

  • 先拿 base
  • 再做 discount / markup
  • 再做 rounding
  • 再加 surcharge
  • 最后受 min/max margin 约束

所以几个常见误解要先丢掉:

误解 1:extra fee 先加再四舍五入

标准逻辑恰好相反,rounding 在 surcharge 之前。

误解 2:formula 总是基于销售价

不一定。base 还可以来自:

  • list_price
  • standard_price
  • 另一张 pricelist

误解 3:markup 和 discount 是两个独立世界

如果 base 是 standard_price,显示语义更像 markup;否则更像 discount。

这就是为什么同样一个公式界面,业务解释会完全不同。


第六层:country group 决定“用哪张表”,不是 rule 里的局部附加条件

很多人会把 country group 想成 pricelist item 上的额外过滤。

标准源码不是这么做的。

country_group_ids 在 pricelist 头上,它首先参与的是:

  • 哪张 pricelist 可被当前国家命中

这意味着国家组的正确理解应该是:

先把客户导向一张更合适的价目表,再在那张表里走规则匹配。

它不是“在一张全球通用表里顺便再加一个国家条件”。

这点特别重要,因为很多实施设计错的根源就是:

  • 想用一张超级大表覆盖所有国家、币种、规则层级

最后排查时就会极度痛苦。

对于跨国家定价,很多时候更可维护的设计是:

  • 先用 country groups 拆表
  • 再在表内做 product/category/quantity 规则

新手最容易误解的 6 件事

1. 价目表问题先看公式

不对,先确认系统到底选了哪张 pricelist。

2. 所有命中规则会一起比较,系统自动选最好的一条

不是,标准逻辑是按顺序命中第一条适用规则。

3. global rule 失效就是 bug

很多时候只是被更具体的 rule 提前拦截。

4. min_quantity 只是附带条件,不影响优先级

不对,在同层规则里它直接影响排序。

5. country group 是 rule 条件

标准上它首先是 pricelist 选择条件。

6. formula 里 surcharge、rounding、discount 顺序无所谓

非常有所谓,顺序错了金额就会偏。


实战里我建议这样排查价目表

第一步:先确认是哪张 pricelist

看:

  • partner 默认属性
  • company 默认值
  • country group 是否命中
  • 当前上下文是否带国家代码

第二步:再看候选规则排序

重点看:

  • applied_on 的具体程度
  • min_quantity 门槛
  • category / product / variant 覆盖范围

第三步:最后才看 formula

确认:

  • base 来自哪里
  • discount / markup 是正还是负
  • rounding 与 surcharge 顺序是否符合预期
  • margin 限制有没有截断结果

这个顺序反过来做,通常会把自己绕晕。


最后一句话

Odoo 的 pricelist 不是“写了一堆折扣公式,系统帮你自动找最优解”。

它更像:

先把客户分配到一张表,再在这张表里按有序规则命中第一条可用规则。

理解了这句话,你排查价格问题时就不会只盯公式,而会先看:

  • 表选对没有
  • 规则顺序对没有
  • 公式只是最后一步有没有算对

DISCUSSION

评论区

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