人力资源

Odoo 员工档案为什么要分“私人信息”和“工作联系人”:看懂 private 字段、work contact 与同步边界

很多团队把员工档案当成一个大表,电话、邮箱、银行、住址都往里堆。Odoo 源码其实刻意拆开了私人信息、工作联系信息和 partner 对象,还限制谁能看、谁能同步、什么时候自动建联系人。它要解决的不是“字段多不多”,而是“哪些数据应该进 HR,哪些应该进协作和会计链路”。

人力资源
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

在 Odoo 里,员工档案不是“一张万能主表”。

官方源码把数据拆成三层:

  • 员工对象上的 private 字段:私人电话、私人邮箱、生日、证件、银行、紧急联系人
  • 工作联系信息work_phonework_emailwork_contact_id
  • 外部协作对象 res.partner:会被消息、会计、附件、审批等别的模块继续消费

所以它的核心原则不是“一个人就该只有一条资料”,而是:

同一个员工会有不同用途的数据面,HR 面、协作面和对外联系面必须分层。

这也是为什么很多人一改员工邮箱,就发现联系人、报销伙伴、银行账户显示跟着变化;再一改私人邮箱,又发现前台同事根本看不到。不是系统混乱,而是它本来就在保护边界。

第一层:private_* 字段本来就不是给全员协作链路用的

/home/ubuntu/odoo-temp/addons/hr/models/hr_employee.py 里,private_phoneprivate_email、生日、证件、签证、紧急联系人等字段都挂在 HR 访问组下。

这说明 Odoo 的默认判断很明确:

  • 这些数据属于 人事管理
  • 不属于日常协作通讯录
  • 更不该默认暴露给所有员工或业务模块

所以如果你希望“员工自己可见、同事也可搜到、还能给报销/发票直接带出”,那往往就不该写到 private 字段,而该判断是不是应该放在工作联系人这一侧。

第二层:work_contact_id 不是装饰字段,而是协作与会计的桥

同一文件里有三个关键字段:

  • work_phone
  • work_email
  • work_contact_id

再看 _create_work_contacts()_compute_work_contact_details()_inverse_work_contact_details(),你会发现官方真正的设计是:

  • 员工如果没有工作联系人,可以自动创建一个 partner
  • 员工页上的工作电话、工作邮箱,会和这个 partner 做双向同步
  • 但同步不是无条件的,而是只在这个 partner 没被多个员工共用时才放开

这一步很关键。Odoo 不是在说“员工一定等于联系人”,而是在说:

当员工需要进入沟通、收款、审批、报销、附件这些外部链路时,需要一个可复用的 work contact 作为桥。

第三层:为什么同步逻辑要特意检查“是不是唯一员工”

_compute_work_contact_details()_inverse_work_contact_details() 里都有一个很容易被忽略的条件:

  • len(employee.work_contact_id.employee_ids) <= 1

这意味着如果一个 partner 被多个员工共用,Odoo 就不会再把员工页上的工作邮箱、工作电话当成绝对真相来回写。

背后的原因非常现实:

  • 共用联系人时,partner 已经不再只是某一个员工的私有工作档案
  • 再做双向同步,容易把别人的联系方式覆盖掉
  • 一旦 partner 已被会计、银行、审批或消息系统引用,误写的代价会更大

所以官方宁可保守一点,也不愿让“员工表单看起来顺手”破坏联系人主数据。

第四层:银行账户为什么跟 work_contact_id 绑,而不直接跟 private email 绑

员工银行账户字段的 domain 直接要求 partner_id = work_contact_id,并且还要受公司维度约束。

这说明 Odoo 认定工资支付、财务识别这类数据,应该挂在 工作联系实体 上,而不是挂在私人邮箱、私人住址那层。

这样做有两个好处:

  1. 财务和 HR 不是完全割裂的数据岛
  2. 又不会把私人字段直接暴露给业务对象

这也是很多实施里会踩的坑:

  • 以为银行账户是纯 HR 私有数据
  • 结果 payroll / expense / accounting 链路都找不到稳定 partner

第五层:用户、员工、联系人三者不是永远一一对应

同一个模型里还处理了 user_idwork_contact_id_remove_work_contact_id() 等逻辑。

这说明官方知道现实里经常发生:

  • 员工换账号
  • 一个用户重新关联到另一个员工
  • 旧联系人不能继续假装属于原来那个人

所以系统会在某些关联变更时主动清理 work_contact_id,避免“旧用户 partner 继续挂在错误员工名下”。

这一步看起来麻烦,但它解决的是历史数据串线问题。

最容易误解的 4 件事

1)把 private email 当公司通讯录邮箱

这是最常见的误建模。private email 是 HR 面,不是协作面。

2)把 work contact 当冗余字段

实际上它是很多外部流程继续认人的锚点。

3)让多个员工共用同一个联系人,再期待字段自动双向同步

源码已经明确在防这个坑。

4)把员工、用户、联系人强行做成永远一一对应

真正上线后,兼职、返聘、账号调整、跨公司迁移都会打破这个假设。

排错顺序:为什么我改了员工信息,别的地方没跟着变

建议按这个顺序查:

  1. 先看改的是 private_* 还是 work_*
  2. 再看员工是否存在 work_contact_id
  3. 看这个 work_contact_id 是否被多个员工共用
  4. 看相关下游模块到底读的是 employee 字段,还是 partner 字段
  5. 最后再查用户绑定、公司维度和银行账户 partner 归属

很多“同步失效”并不是 bug,而是你改错了那一层。

最后一句话

Odoo 把员工私人信息和工作联系人拆开,不是为了把表单做复杂,而是为了让 HR 隐私、协作通讯和财务链路能同时成立。真正该做的不是强行合并,而是先分清你要维护的是哪一层真相。

DISCUSSION

评论区

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