多公司字段

Odoo 的 property 字段为什么不在业务表里:ir.property 存储、公司回退与搜索翻译链

`company_dependent=True` 看起来像给字段加了个多公司开关,但它背后不是普通列值覆盖,而是由 ir.property、默认回退和上下文公司共同决定读写结果。理解这条链,才能解释为什么同一字段在不同公司下会变、为什么搜索也不像普通字段那样直译成简单 SQL。

Odoo 开发
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 7 阅读

很多人第一次接触 company_dependent=True 时,会把它理解成:

  • 这个字段在不同公司有不同值
  • 所以数据库里大概就是一列加点公司过滤

这个理解抓到了表面现象,但没抓到 Odoo 真正的设计。

如果你去看 /home/ubuntu/odoo-temp/odoo/orm/fields.pyaddons/base/models/ir_property.py,再对照 test_fields.py 里的多公司测试,会发现它背后的核心不是“普通字段按公司分片”,而是:

字段值由 property 存储、字段默认回退、当前公司上下文以及读取/搜索翻译逻辑共同决定。

这也是为什么这类字段总让人觉得“像普通字段,但又不完全像”。

一、company_dependent=True 不是简单列覆盖

从语义上看,它表达的是:

  • 同一个业务字段
  • 在不同公司下允许拥有不同值

但 Odoo 并没有把这件事做成“在原表多放几列”。

因为这样会马上遇到几个问题:

  • 公司数不固定
  • 默认值与记录级值怎么合并
  • 多公司切换时怎样统一读写接口
  • 搜索域怎样表达“当前公司值 + 回退值”

所以框架采用的是 property 机制,而不是暴力扩列。

二、真正的值入口不只一个:当前公司值 + fallback

fields.py 相关逻辑里,company dependent 字段并不是永远要求当前公司一定有一条专属值。

Odoo 还会考虑 fallback。

这个 fallback 可能来自:

  • 字段自身默认
  • 属性默认
  • 更通用层级的属性值

这意味着读取 company dependent 字段时,系统脑子里想的不是:

  • 当前公司有没有一条值?

而是:

  • 当前公司有没有专属值?
  • 没有的话,该往哪一级回退?

所以你在多公司现场看到:

  • A 公司改了后只影响 A
  • B 公司没配时却还能读到一个“像默认值”的结果

这不是偶然,而是回退链设计的一部分。

三、with_company() 改的不只是展示公司,而是读取语义

很多开发者把 with_company() 只当成一种“切换当前公司”的语法糖。

对 property 字段来说,这个动作非常实质。

因为 company dependent 字段的取值,本来就和当前公司上下文绑定。

所以:

  • 同一条记录
  • 同一个字段
  • record.with_company(company_a)
  • 和在 record.with_company(company_b)

完全可以合法地读出不同结果。

这里最容易误解的一点是:

变的不是记录本身,而是读取这条记录的公司视角。

因此这类字段问题,常常不是“数据写错了”,而是“你在错误的公司上下文里读对了数据”。

四、为什么搜索也不像普通字段那样简单

普通字段搜索,很多人脑中默认模型是:

  • 域条件翻成 SQL where
  • 直接查业务表列

company dependent 字段没这么直接。

因为你要搜索的不是一列裸值,而是:

  • 当前公司是否有 property 值
  • 没有时 fallback 是否命中
  • 某些情况下默认值是否也应算匹配

也就是说,搜索语义必须先把“字段条件”翻译成“属性解析语义”,再落到查询层。

这也是为什么这类字段出问题时,现象往往很怪:

  • 表单里看着是这个值
  • 搜索结果却不像你想的那样筛
  • 换公司后又像恢复正常

根因不是 UI,而是搜索本来就不是在查一列简单字段。

五、为什么 Odoo 非要这样设计

因为它解决的是一个很现实的问题:

  • 字段接口要像普通字段一样用
  • 业务上又允许公司间差异
  • 同时还要保留默认值 / 回退值语义

如果全交给业务模块自己维护,你就会在每个模型里重复写:

  • 当前公司取值
  • 默认回退
  • 搜索翻译
  • 写入覆盖
  • 多公司切换

这不仅难统一,还极易出 bug。

所以 Odoo 把它抽成 property 机制,本质上是在用框架换一致性。

六、最常见的误区

误区 1:以为它就是“多公司版普通字段”

表面用法像,底层解析链完全不同。

误区 2:看到不同公司不同值,就以为数据重复写进了业务表

很多时候真正起作用的是 property 存储,不是原表列本身。

误区 3:以为 with_company() 只是界面上下文切换

对 company dependent 字段,它直接改变取值语义。

误区 4:以为搜索不准就是索引问题

很多时候是你把 property 字段按普通列的脑回路去理解了。

七、实战排错顺序

如果你想查“为什么这个字段在不同公司下不一样 / 搜索结果不对”,建议按这个顺序:

  1. 确认字段是否 company_dependent=True
  2. 确认当前代码运行在哪个公司上下文
  3. 检查当前公司有没有专属 property 值
  4. 没有的话,实际命中的 fallback 是什么
  5. 如果是搜索问题,别只盯业务表列,要看属性解析语义

这个顺序通常比“直接查表看值”更接近真相。

结语

company_dependent 真正值得理解的,不是“一个字段可以因公司不同而不同”这么一句表层结论,而是它背后的设计选择:

  • 不把问题粗暴塞进业务表结构
  • ir.property 承接公司特定值
  • 用 fallback 保证未配置公司也有合理结果
  • 用上下文公司决定当前读取视角
  • 用额外翻译逻辑把搜索语义补完整

所以这类字段从来不是“多公司版普通列”,而是:

一条由 property 存储、公司上下文与回退机制共同完成的字段解析链。

看懂这条链后,多公司字段的大部分“诡异行为”其实都不诡异了。

DISCUSSION

评论区

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