人力资源

排班为什么不能只填上下班时间:Odoo 工时日历里的双周制、弹性工时与 FTE 计算

很多团队把工作日历理解成每周一到周五几点到几点。Odoo 的 resource_calendar 源码却明显把它当成人力制度底座:双周制、弹性工时、公共休假复制、小时/天换算和 FTE 比例都从这里长出来。

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

先说结论

很多人配置工作日历时,脑子里只有一个问题:

  • 周一到周五几点上班,几点下班?

但 Odoo 的 resource_calendar 源码告诉你,工作日历远不只是时间表。

它其实是很多 HR 计算的底座,至少会影响:

  • 一周到底算几小时
  • 一天平均算几小时
  • 是否全职
  • 请假按天还是按小时怎么换算
  • 双周制排班怎么表达
  • 弹性工时下“工作时段”到底如何理解
  • 公司公共休假怎么传播到具体日历

所以如果把 calendar 只看成“上下班时间模板”,后面很多 HR 结果都会解释不清。


为什么 schedule_type 是关键,不只是一个显示选项

源码里 resource.calendar 有:

  • schedule_type
  • flexible
  • fully_fixed
  • flexible_hours

flexible_hours 其实就是根据 schedule_type 算出来的。

这说明 Odoo 一开始就在区分两种完全不同的制度:

Fully Fixed

系统明确知道:

  • 哪天上班
  • 什么时候开始
  • 什么时候结束
  • 午休怎么切分

Flexible

系统更关心的是:

  • 一周该工作多少小时
  • 一天上限如何理解
  • 时间分布可以更弹性

这不是“界面多一个开关”,而是 工时制度本身不同


为什么双周制不是“多建两张周表”这么简单

two_weeks_calendar 打开后,源码会把出勤拆成:

  • attendance_ids_1st_week
  • attendance_ids_2nd_week

并且 _check_attendance_ids() 会分别校验第一周和第二周有没有重叠。

同时,_get_hours_per_week()_get_days_per_week() 也会把两周总量再除以 2,算出平均每周值。

这意味着 Odoo 对双周制的理解不是:

把奇数周、偶数周随便堆在一起。

而是:

先承认这是一个两周循环,再把统计口径重新折算回“平均每周”。

这对 HR 很重要,因为请假、工时比例、FTE 这些口径,最终还是经常要落回“每周多少小时”。


hours_per_dayhours_per_week 为什么不是手工文本说明

源码里这两个字段都是计算字段。

  • hours_per_week 来自出勤区间累加
  • hours_per_day 来自“每周工时 / 每周工作日数”

并且双周制时会自动做平均。

这说明 Odoo 很在意一件事:

工时口径必须能被系统可靠计算,而不是靠 HR 自己脑补。

因为后面很多地方都要用它:

  • 请假天数和小时换算
  • Part-time 比例
  • 工资期间统计
  • 弹性工时校正

FTE 比例为什么长在 calendar 上

源码里还有:

  • full_time_required_hours
  • work_time_rate
  • is_fulltime

work_time_rate 的计算很直接:

  • 当前 calendar 的 hours_per_week
  • 除以公司的 full_time_required_hours

所以 Odoo 的全职/兼职判断,本质上不是“岗位名字里写着兼职”,而是:

这套工作制度相对于公司标准全职工时,占了多少比例。

这比手工标签靠谱得多。

比如:

  • 公司标准 40 小时
  • 某员工日历 30 小时
  • 那就是 75% work time rate

为什么公共休假会从公司日历复制下来

_compute_global_leave_ids() 会把公司默认日历上的 global_leave_ids 复制到具体日历。

这背后有个很典型的 HR 思路:

  • 公司层面的法定节假日、集体停工
  • 应该成为各个工作日历的默认基线
  • 但具体日历仍保留单独调整空间

也就是说,Odoo 不是把“公共休假”当成纯公司公告,而是当成会进入工时计算的正式输入。


弹性工时为什么最容易被误解

很多人以为 flexible = 系统不知道什么时候上班,所以就没法算工时。

源码不是这么做的。

resource.resource 里:

  • 没有日历时,资源甚至会被视作 fully flexible
  • _get_flexible_resource_valid_work_intervals() 会按天和按周计算可用工时
  • _format_leave() 还会在请假时把 hours_per_dayhours_per_week 扣减掉
  • _get_flexible_resource_work_hours() 会在“天上限 + 周上限”里计算真正可计入的工时

这说明弹性工时不是“不算”。

而是:

系统不强依赖固定上下班时刻,但仍然要守住每天/每周的制度边界。

这很适合现实里的顾问、销售、创意岗位、远程团队。


为什么弹性工时下,请假和异常日判断也不同

_get_unusual_days() 在 flexible 和 fixed calendar 上走的是不同逻辑。

  • 固定工时:看当天有没有 work intervals
  • 弹性工时:更看 leave intervals 是否占用了这一天

这个差异很值得注意,因为它说明 Odoo 并没有拿固定排班那套硬套到弹性员工身上。

如果硬套,会出现很多荒唐结果:

  • 明明制度允许弹性,却总被系统判成排班外
  • 请假天数和工作时长经常不对

源码在这里明显做了制度差异化处理。


这套设计的现实价值

1. 同一家公司能表达不同工时制度

固定班、双周制、弹性工时,不必混成一个模板。

2. 请假、出勤和 FTE 的口径更一致

因为它们都回到 calendar 的小时/天/周计算。

3. 兼职与全职不再只靠人工标签判断

而是基于制度工时比例自动算出来。


实施时最容易踩的坑

坑一:把弹性工时当成“没有规则”

实际上只是规则从具体时刻,转成了日/周工时边界。

坑二:双周制只在界面上拆两周,不管统计口径

这样后面天数、工时、FTE 全会偏。

坑三:只配考勤时间,不校验 hours_per_dayhours_per_week

很多“请假换算不对”的根源,其实在 calendar。


一句话记忆

Odoo 的工作日历不是上下班模板,而是 HR 制度引擎:双周制、弹性工时、公共休假、小时/天换算和 FTE 比例都从这里往外长。

DISCUSSION

评论区

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