先说结论
Odoo 的批量请假,不是“给多人一键补一段假期”。
源码里它先后处理的是:
- 这段日期在公司日历时区里到底对应哪个 UTC 区间
- 已有请假是否冲突,冲突能不能自动化处理
- 新建假单时,哪些 context 必须打开,避免日历同步和日期重算互相打架
也就是说,批量请假管理的不是“批量创建”,而是在不弄乱现有假单和时区语义的前提下批量创建。
为什么先做时区换算,而不是直接拿日期写 date_from/date_to
action_generate_time_off() 先取:
- 公司日历的时区
- 没有则退回用户时区
- 再没有则 UTC
然后把 date_from 和 date_to 组合成当天最早/最晚时间,再转成 UTC naive datetime。
这一步非常关键。
因为批量请假面对的是“日期型业务输入”,但 hr.leave 真正落库的核心字段是 date_from/date_to datetime。如果不先把公司/业务时区解释清楚,就会出现这些典型问题:
- 本地看是 3 月 1 日到 3 月 3 日,数据库里却跨到了前一天晚上
- 多公司、多地区时,请假归属日错位
- 冲突检查按错区间跑,导致漏判或误判
所以 Odoo 先统一时间语义,再处理业务逻辑,这是对的。
_get_work_days_data_batch() 暗示:批量假期仍然是按员工算工日
在 _prepare_employees_holiday_values() 里,系统会先调用:
employees.sudo()._get_work_days_data_batch(date_from_tz, date_to_tz)
然后按每个员工取:
work_days_data[employee.id]['days']
这说明即使是一次性批量生成,Odoo 也没有偷懒把所有人都按同一个天数处理。
因为:
- 不同员工日历不同
- 同一时间段内工作日数可能不同
- 一些员工可能这段时间本来就没有工作日
因此,批量请假本质上仍然是“一批逐人计算过的请假记录”,只是由一个向导集中发起。
冲突检查为什么先拦“小时假”
action_generate_time_off() 在真正 create 之前,会先搜索冲突中的现有 hr.leave:
- 时间区间有重叠
- 状态不是 cancel / refuse
- 员工在目标名单内
如果找到冲突,它先筛:
leave_type_request_unit == 'hour'
然后直接报错。
这个选择特别有代表性。
Odoo 没有冒险去自动拆小时假,因为小时假通常带更细的开始/结束时间语义。自动拆错一次,后面就是考勤、工时、审批一起乱。
所以官方在这里选的是:
能自动处理的才自动处理;小时级复杂冲突,宁可拒绝,也不假装智能。
这是很成熟的产品边界感。
为什么整天假可以拒绝或拆分
对于冲突中的非小时假,Odoo 分了两类:
1)一天内的单天假
如果 request_date_from == request_date_to,系统会直接:
action_refuse()
2)多天假
则会调用:
_split_leaves(self.date_from, self.date_to + timedelta(days=1))
这背后的思路是:
- 单天假没什么可切的,直接拒绝最清晰
- 多天假有机会把冲突区间“让出来”,保留前后剩余段
也就是说,Odoo 不是把旧假一律删掉重建,而是在尽可能保留原有业务事实的剩余部分。
这和“全删重来”相比,审计和员工感知都要稳得多。
create 时为什么要带这么多 context
批量创建时,向导用了这些 context:
tracking_disable=Truemail_activity_automation_skip=Trueleave_fast_create=Trueno_calendar_sync=Trueleave_skip_state_check=Trueleave_compute_date_from_to=True
这几项放在一起非常能说明问题。
它不是为了“偷偷绕规则”,而是为了在批量场景下避免几个系统层互相抢活:
- tracking / activity 太多会造成噪音
- calendar sync 会带来额外副作用
- 日期字段若按普通 onchange 流程重算,可能和批量预先算好的 number_of_days 打架
尤其注释里已经明确说了:
如果这里再走 _compute_date_from_to 常规路径,会触发 _compute_number_of_days,而不同员工时区/日历混在一起时,批量结果就可能互相冲突。
这就是典型的“批量向导必须显式关掉一部分默认行为”的例子。
为什么创建后还要 _validate_leave_request()
向导 create 完 leaves 后,最后会统一:
leaves._validate_leave_request()
这表示官方不想让批量请假停在“草稿导入”层面,而是希望它成为一个可直接进入业务状态的生成动作。
但注意,它前面又没有粗暴绕过所有约束,而是先做了冲突清理和日期准备。顺序很重要:
- 先修边界
- 再批量创建
- 最后统一验证
这比“先塞进去再看看谁报错”稳得多。
这套设计最值得学的地方
如果你自己做过 HR 批量工具,会知道最难的从来不是 create 多条记录,而是:
- 时间语义会不会错
- 旧记录怎么处理
- 批量逻辑会不会破坏模型默认计算
Odoo 这套向导给出的答案是很务实的:
- 先统一时区
- 拒绝无法安全拆分的小时冲突
- 对可处理的整天冲突做局部拒绝或拆分
- 创建时用专用 context 压住副作用
这比很多“看上去一键化”的工具成熟得多。
一句话记忆
Odoo 批量请假不是多人一键建单,而是先做时区解释、冲突治理和旧假拆分,再在受控 context 下批量创建并验证。
DISCUSSION
评论区