先说结论
在 Odoo 里,“员工离职”不是一个按钮后面只做一件事。
源码里至少拆成了三条边界:
- Departure 语义:为什么离开、哪天离开
- 合同边界:合同是否在这一天结束
- 账号边界:相关
res.users要不要一起归档
所以把离职简单理解成“archive 员工”会丢掉很多业务含义。
为什么 archive 不等于 departure
action_archive() 只是把员工归档,并清理一些对已归档员工的引用,比如经理、教练等关系。
但当只归档单个员工且不是特殊上下文时,源码会弹出 hr.departure.wizard。
这已经很能说明问题:
系统认为“归档”只是技术状态变化,而“离职登记”才是业务事件。
如果两者完全等价,根本没必要再起一个向导。
Departure 向导到底补了什么业务语义
hr.departure.wizard 会让你填:
departure_reason_iddeparture_descriptiondeparture_date- 是否
remove_related_user - 是否
set_date_end
这表示 Odoo 想明确区分:
1. 人为什么离开
离职原因不是装饰字段,它能支持:
- 统计分析
- 审计追踪
- 后续流程分支
而且默认离职原因还有 Fired、Resigned、Retired 这些主数据。
2. 人什么时候离开
departure_date 不是 archive 时间戳,而是业务上的离开日期。
3. 合同要不要在这天结束
set_date_end 为真时,当前有效版本的 contract_date_end 会被写成离职日期。
这说明“人离开了”与“合同边界怎么处理”是相关但可分离的。
4. 关联用户要不要归档
remove_related_user 又是另一条边界。
不是每个离职员工都必须立刻删账号,也不是每个账号都只对应一个员工关系。
为什么相关用户不能想归档就归档
向导里有一段很容易被忽视但很关键的判断:
如果同一个 res.users 还关联着其他员工,而且这些员工不都在本次离职范围里,那么这个用户不会被归档。
这背后的逻辑很成熟:
- 一个用户可能关联多个 employee
- 不能因为离掉其中一个,就误伤其他仍活跃的关系
所以 Odoo 不是“有 user 就一起关”,而是先判断这个 user 是否还能被其他活跃员工合法使用。
为什么离职日期不能早于当前合同开始日
源码里会阻止这种情况:
- 当前有效版本有
contract_date_start - 但你填的
departure_date更早
原因很简单:
这样会让时间线自相矛盾。
如果合同都还没开始,怎么能先离职?
这和前面版本/合同时间线文章其实是同一思路:Odoo 很重视 HR 时间轴是否自洽。
归档后为什么还要保留 departure 字段
向导执行后,员工会写入:
departure_reason_iddeparture_descriptiondeparture_date
这说明 Odoo 想保留的是“离开事件的解释”,而不只是让记录消失在活跃列表里。
换句话说,archive 是可见性变化,departure 是历史语义沉淀。
为什么 unarchive 又会清掉 departure 信息
源码里 action_unarchive() 会把:
departure_reason_iddeparture_descriptiondeparture_date
清空。
这非常值得注意,因为它传达了一个设计假设:
如果你把员工恢复为活跃状态,那么系统倾向于认为之前那次离职事件已经不再成立。
这也提醒实施时要小心:如果企业想保留“曾离职后返聘”的完整历史,可能需要额外建模,而不能只靠 archive/unarchive。
这套设计的现实意义
1. 离职是业务事件,不只是技术状态
所以需要理由、日期、备注。
2. 合同结束与账号处理不应自动混成一步
它们可以联动,但不应被视作天然同义词。
3. 返聘与恢复活跃要谨慎理解
因为 unarchive 默认会抹掉上一轮 departure 语义。
一句话记忆
在 Odoo 里,员工离职至少包含三层:离职事件、合同边界、账号边界;archive 只是其中最表面的那一层。
DISCUSSION
评论区