人力资源

Odoo 批量请假为什么不会粗暴覆盖原记录:冲突拒绝、拆分旧假与时区换算讲透

很多团队以为批量生成 time off 就是选一批员工、选一个区间、点创建。Odoo 的 hr.leave.generate.multi.wizard 源码明显更谨慎:它会先按公司日历时区把日期转成 UTC 区间,检查并拒绝无法自动拆分的小时假,对整天冲突假单做拒绝或拆分,再带着一组特殊 context 批量创建新假单并验证。

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

先说结论

Odoo 的批量请假,不是“给多人一键补一段假期”。

源码里它先后处理的是:

  1. 这段日期在公司日历时区里到底对应哪个 UTC 区间
  2. 已有请假是否冲突,冲突能不能自动化处理
  3. 新建假单时,哪些 context 必须打开,避免日历同步和日期重算互相打架

也就是说,批量请假管理的不是“批量创建”,而是在不弄乱现有假单和时区语义的前提下批量创建


为什么先做时区换算,而不是直接拿日期写 date_from/date_to

action_generate_time_off() 先取:

  • 公司日历的时区
  • 没有则退回用户时区
  • 再没有则 UTC

然后把 date_fromdate_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=True
  • mail_activity_automation_skip=True
  • leave_fast_create=True
  • no_calendar_sync=True
  • leave_skip_state_check=True
  • leave_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()

这表示官方不想让批量请假停在“草稿导入”层面,而是希望它成为一个可直接进入业务状态的生成动作

但注意,它前面又没有粗暴绕过所有约束,而是先做了冲突清理和日期准备。顺序很重要:

  1. 先修边界
  2. 再批量创建
  3. 最后统一验证

这比“先塞进去再看看谁报错”稳得多。


这套设计最值得学的地方

如果你自己做过 HR 批量工具,会知道最难的从来不是 create 多条记录,而是:

  • 时间语义会不会错
  • 旧记录怎么处理
  • 批量逻辑会不会破坏模型默认计算

Odoo 这套向导给出的答案是很务实的:

  • 先统一时区
  • 拒绝无法安全拆分的小时冲突
  • 对可处理的整天冲突做局部拒绝或拆分
  • 创建时用专用 context 压住副作用

这比很多“看上去一键化”的工具成熟得多。


一句话记忆

Odoo 批量请假不是多人一键建单,而是先做时区解释、冲突治理和旧假拆分,再在受控 context 下批量创建并验证。

DISCUSSION

评论区

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