先说结论
在 Odoo 里,员工档案不是“一张万能主表”。
官方源码把数据拆成三层:
- 员工对象上的 private 字段:私人电话、私人邮箱、生日、证件、银行、紧急联系人
- 工作联系信息:
work_phone、work_email、work_contact_id - 外部协作对象
res.partner:会被消息、会计、附件、审批等别的模块继续消费
所以它的核心原则不是“一个人就该只有一条资料”,而是:
同一个员工会有不同用途的数据面,HR 面、协作面和对外联系面必须分层。
这也是为什么很多人一改员工邮箱,就发现联系人、报销伙伴、银行账户显示跟着变化;再一改私人邮箱,又发现前台同事根本看不到。不是系统混乱,而是它本来就在保护边界。
第一层:private_* 字段本来就不是给全员协作链路用的
在 /home/ubuntu/odoo-temp/addons/hr/models/hr_employee.py 里,private_phone、private_email、生日、证件、签证、紧急联系人等字段都挂在 HR 访问组下。
这说明 Odoo 的默认判断很明确:
- 这些数据属于 人事管理
- 不属于日常协作通讯录
- 更不该默认暴露给所有员工或业务模块
所以如果你希望“员工自己可见、同事也可搜到、还能给报销/发票直接带出”,那往往就不该写到 private 字段,而该判断是不是应该放在工作联系人这一侧。
第二层:work_contact_id 不是装饰字段,而是协作与会计的桥
同一文件里有三个关键字段:
work_phonework_emailwork_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 认定工资支付、财务识别这类数据,应该挂在 工作联系实体 上,而不是挂在私人邮箱、私人住址那层。
这样做有两个好处:
- 财务和 HR 不是完全割裂的数据岛
- 又不会把私人字段直接暴露给业务对象
这也是很多实施里会踩的坑:
- 以为银行账户是纯 HR 私有数据
- 结果 payroll / expense / accounting 链路都找不到稳定 partner
第五层:用户、员工、联系人三者不是永远一一对应
同一个模型里还处理了 user_id、work_contact_id、_remove_work_contact_id() 等逻辑。
这说明官方知道现实里经常发生:
- 员工换账号
- 一个用户重新关联到另一个员工
- 旧联系人不能继续假装属于原来那个人
所以系统会在某些关联变更时主动清理 work_contact_id,避免“旧用户 partner 继续挂在错误员工名下”。
这一步看起来麻烦,但它解决的是历史数据串线问题。
最容易误解的 4 件事
1)把 private email 当公司通讯录邮箱
这是最常见的误建模。private email 是 HR 面,不是协作面。
2)把 work contact 当冗余字段
实际上它是很多外部流程继续认人的锚点。
3)让多个员工共用同一个联系人,再期待字段自动双向同步
源码已经明确在防这个坑。
4)把员工、用户、联系人强行做成永远一一对应
真正上线后,兼职、返聘、账号调整、跨公司迁移都会打破这个假设。
排错顺序:为什么我改了员工信息,别的地方没跟着变
建议按这个顺序查:
- 先看改的是
private_*还是work_* - 再看员工是否存在
work_contact_id - 看这个
work_contact_id是否被多个员工共用 - 看相关下游模块到底读的是 employee 字段,还是 partner 字段
- 最后再查用户绑定、公司维度和银行账户 partner 归属
很多“同步失效”并不是 bug,而是你改错了那一层。
最后一句话
Odoo 把员工私人信息和工作联系人拆开,不是为了把表单做复杂,而是为了让 HR 隐私、协作通讯和财务链路能同时成立。真正该做的不是强行合并,而是先分清你要维护的是哪一层真相。
DISCUSSION
评论区