多公司字段

Odoo 的 company_dependent 字段到底存在哪:JSONB 存值与 ir.default 回退链路

现在的 Odoo 里,company_dependent 字段已经不是很多人印象中的 ir.property 老路子了。它更像“字段本体存 JSONB,多公司值按公司 id 取,缺值时再走 ir.default 回退”。

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

先纠正一个很常见的旧印象

很多人一提到 company_dependent=True,脑子里马上冒出来的还是:

  • 这类值不在业务表里
  • 系统主要靠 ir.property
  • 读值时再从属性表里按公司找

但如果你对着现在这份 /home/ubuntu/odoo-temp 里的源码看,会发现主逻辑已经不是这套直觉了。

现在的重点更像是:

  • 字段本体就是模型上的字段
  • 数据库列会变成 jsonb
  • 不同公司的值按公司 id 存在 JSON 结构里
  • 如果当前公司没有专属值,再去走 ir.default 回退

所以它不该再被理解成“属性表外挂字段”,而应该理解成:

同一个字段,带着多公司维度一起存。


第一层:company_dependent 为什么会让列类型变成 jsonb

odoo/orm/fields.py 里,column_type 这段逻辑很直接:

  • 普通字段走自己的 _column_type
  • 但只要 self.company_dependent 为真,返回的就是 ('jsonb', 'jsonb')

这意味着数据库层不是给每个公司多开一列,也不是单独切表。 它是把“公司 → 值”的映射塞进一个 JSONB 列里。

这样设计有几个现实好处:

  1. 字段定义仍然统一:开发者写的还是一个字段,不需要为每个公司单独建模。
  2. 查询层还能继续走 ORM:不是把值扔到完全独立的配置系统里。
  3. 同字段的多公司值天然聚合:对框架来说更容易表达“当前公司值 + 回退值”。

所以从数据模型角度看,company_dependent 不是“变成另外一种字段系统”,而是:

这个字段仍然是字段,只是它的存储列不再是标量,而是带公司维度的 JSONB。


第二层:读值时不是“随便取一段 JSON”,而是先取当前公司键

同一个文件里的 to_sql() 更能说明问题。

源码会在 SQL 层做一个类似这样的思路:

  • 先从 JSONB 列里取当前 env.company.id 对应的值
  • 如果没有,再把 fallback 值补进来
  • 布尔、整数、浮点这类字段还会再做类型转换

也就是说,Odoo 不是把整坨 JSON 交给上层自己解释。 它在字段转 SQL 的时候,就已经把“当前公司视角”编码进去了。

这点特别重要,因为它解释了两件事:

1)为什么搜索、排序、分组还能继续工作

如果 company-dependent 值只是 Python 层拼装,很多 SQL 能力都会变弱。 但现在它在 SQL 生成阶段就能拿到“当前公司下的有效值”,于是:

  • domain 搜索还能成立
  • 某些排序仍然能落到 SQL
  • read_group 也还有机会基于这个有效值继续算

2)为什么切换当前公司后,同一字段看起来像“瞬间换了一个值”

因为你并不是在看一列固定标量。 你看到的是:

  • 这列 JSONB 中当前公司键对应的值
  • 或者这个公司没有专属值时的 fallback

换公司,本质上就是换读取视角。


第三层:fallback 不是玄学,是 ir.default

fields.py 里的 get_company_dependent_fallback() 也很关键。

它会:

  • env['ir.default']
  • 切到超级用户
  • 切到当前公司
  • _get_model_defaults(records._name)
  • 再从返回字典里拿当前字段名对应的默认值

这说明当某公司没有专属值时,默认回退并不是“读一条神秘属性记录”,而是:

回到这个模型在当前公司语义下的默认值系统。

再看 odoo/addons/base/models/ir_default.py_get_model_defaults() 会按:

  • model
  • user
  • company
  • condition

去找可用默认值,并按优先级选中每个字段最高优先的一条。

所以 company-dependent 字段的回退,不是“没有值就空着”,而是“没有公司专属值时,继续走默认值机制”。


这套设计到底解决了什么问题

它解决的不是“怎么把一个值存下来”这么简单。 真正解决的是:

问题 1:同一业务对象,不同公司需要不同配置

例如同一个 partner、同一个产品、同一个模板,在不同公司下需要看到不同默认字段值。

问题 2:但又不希望为了这个需求把模型拆裂

如果每家公司都复制一份对象,主数据维护会非常痛苦。

问题 3:还得保留“没配专属值时有默认行为”

这就是 JSONB 专属值 + ir.default 回退能同时成立的原因。

它把问题分成两层:

  • 有专属值时:按当前公司读 JSONB 对应键
  • 没专属值时:退到默认值系统

这个模型比“全有或全无”要柔和得多,也更适合真实业务。


新手最容易误解的 4 件事

误解 1:company_dependent 还是老式 ir.property

就当前源码看,核心存储重点已经是字段列本身的 JSONB 化,而不是把值主存放在另一张属性表里。

误解 2:切公司只是前端显示切换

不是。SQL 生成本身就会按当前公司取值,所以这是一层 ORM/查询语义,不只是界面渲染。

误解 3:没配置当前公司值就一定得到 False

不一定。它还会尝试走 ir.default 的模型默认值回退。

误解 4:它和普通字段一样容易直接写 SQL 排查

因为列是 JSONB,你手写 SQL 时如果还按普通标量字段理解,就很容易看错。 你必须先想清楚:

  • 当前公司是谁
  • 这个公司键有没有值
  • 没值时 fallback 是什么

实战开发时最该注意什么

1)别把“字段在表里”理解成“值就是普通列值”

字段确实在表里,但表里的内容可能是多公司 JSON 结构,不是单值。

2)查异常时,要区分“专属值缺失”还是“fallback 生效”

很多“怎么切公司后数据不对”的问题,根本不是写错了,而是某公司其实没配专属值,系统退回了默认值。

3)自己写 SQL/报表时,要先确认读取口径

ORM 已经帮你做了公司键选择和类型转换;你一旦绕开 ORM,就得自己承担这层复杂度。

4)做多公司配置时,要知道它更像“按公司覆盖”,不是“每公司独立记录”

这能帮你更准确地设计字段语义,也避免把 company-dependent 滥用成“大号配置中心”。


一句话总结

现在的 Odoo 里,company_dependent 更应该这样理解:

字段值主存于模型字段自己的 JSONB 列,读取时按当前公司取键;如果当前公司没有专属值,再回退到 ir.default 提供的默认值。

把这条主线看清后,你就不会再把它误判成“神秘属性系统”,而会把它当成一套非常明确的多公司字段存储与回退机制。

DISCUSSION

评论区

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