人力资源

Odoo 里“培训也算上班、病假不一定全扣累计”到底怎么实现:Worked Time、Accrual Rate 与按工时累计的边界

很多 HR 政策都会写:某些假别不影响年假累计,另一些会影响。Odoo 不是靠备注实现,而是把 time off type 区分成 Worked Time 与 Absence,再叠加 eligible for accrual rate、is_based_on_worked_time 和 worked_hours 频率来计算。

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

先说结论

Odoo 的 Time Off 里,有一个非常容易被低估的问题:

哪些“没在上班”的时间,仍然应该被当成会继续累计年假的合格时间?

很多企业制度其实都在处理这件事,只是平时不会这样说。

例如:

  • 培训算不算持续服务时间?
  • 带薪病假会不会影响年假累计?
  • 无薪假是不是应该中断累计?
  • 按工时累计时,哪些时间应该算进 worked hours?

Odoo 的源码没有把这些问题藏在备注里,而是拆成了几层模型:

  • time_type = other | leave
  • elligible_for_accrual_rate
  • is_based_on_worked_time
  • frequency = worked_hours

这说明系统真正管理的是:

请假类型如何影响“累计资格”和“累计基数”。


Worked TimeAbsence 不是界面文案,而是两种完全不同的会计语义

hr.leave.type 里,time_type 只有两个值:

  • other:Worked Time
  • leave:Absence

这个命名很妙,因为它不是问“员工在不在岗”,而是在问:

这段时间在累计制度里,应该更像工作时间,还是更像缺勤时间?

例如培训、陪审、某些组织安排的外部活动,业务上虽然人不在工位,但可能仍然算持续服务。

所以 Odoo 没有粗暴地把所有 Time Off 都当缺勤。

有些 Time Off 在累计语义上更接近 Worked Time。


elligible_for_accrual_rate 为什么是核心字段

这个字段名字虽然写得有点别扭,但语义很清楚:

这种 time off type,会不会在累计计算里被当成“可计入累计比例”的时间?

源码里还有一个很重要的约束:

  • 如果 time_type = other,那它必须elligible_for_accrual_rate = True

也就是说,官方认为“Worked Time 类型的 time off”天然应该进入累计资格。

这很合理。

如果一个假别本来就是“工作时间性质”,那它不该再被拿来削减 accrual rate。

反过来,leave 类型就可以按政策决定:

  • 某些 absence 仍然算入累计资格
  • 某些 absence 不算

于是制度差异就被显式建模了。


is_based_on_worked_time 问的不是“有没有上班”,而是“累计基数按什么取”

hr.leave.accrual.plan 里的 is_based_on_worked_time 很容易被误解成“按考勤模块开没开”。

其实它在问:

这套累计计划,是按完整累计周期给额度,还是按实际合格工时比例给额度?

源码 help 甚至直接提醒:

  • 如果按 worked time,通常会排除某些不应进入累计基数的 time off
  • accrued_gain_time = start 时,is_based_on_worked_time 会被强制关掉

这背后逻辑很强:

如果额度是在周期开始就先给你,系统就不可能同时说“我要根据这个周期里后来发生的实际工时再决定给多少”。

所以:

  • 周期开始先给额度
  • 按已发生工时比例累计

这两个语义在源码里是互斥的。


worked_hours 频率为什么不是普通 hourly 的别名

hr_holidays_attendancehr.leave.accrual.level.frequency 增加了一个新值:

  • worked_hours

它不是普通 hourly 的换皮。

源码还专门限制:

  • 如果 accrued_gain_time = start,就不能使用 worked_hours

这说明 worked_hours 的语义是:

按已经发生且被认可的工时,持续累积。

而不是“系统时钟每小时给一点额度”。

这对按小时工、兼职、排班浮动岗位尤其重要。

你可以把它理解成:

  • hourly:按自然时间刻度运行
  • worked_hours:按合格工作投入运行

两者看起来都和小时有关,但业务含义完全不同。


Odoo 是怎么扣除“不应计入累计”的时间的

hr.leave.allocation 的逻辑可以看出,系统在计算 worked-time-based accrual 时,会分别统计:

  • 应计入累计比例的 time off
  • 不应计入累计比例的 time off
  • 实际 worked hours

然后再把这些量折进 accrual amount。

这就是为什么某些企业会出现这种业务现象:

  • 培训一天,不影响年假累计
  • 带薪陪产假,可能部分保留累计资格
  • 无薪长假,则明显拉低累计结果

源码不是靠 HR 手工判断,而是靠 leave type 的时间语义来区分。


为什么“带薪”不一定等于“继续累计”

这是很多业务方的直觉误区。

他们会说:

  • 这类假是 paid
  • 那当然应该继续累计年假

但源码里的判断维度并不是“付不付薪”本身,而是:

  • time_type
  • elligible_for_accrual_rate
  • accrual plan 是否 is_based_on_worked_time

也就是说,发薪语义和累计语义不是同一层规则

某种假可以是 paid,但在 accrual policy 上仍可能被部分排除; 某种组织性活动虽然不是常规出勤,却可能作为 Worked Time 保留累计资格。

这是建模上非常成熟的一点:

工资政策、出勤政策、年假累计政策被拆开处理,而不是绑死。


这对实施最重要的启发是什么

1)不要只问“这是什么假”

更该问的是:

  • 它在累计上属于 Worked Time 还是 Absence?
  • 它是否 eligible for accrual rate?

2)不要把“按工时累计”理解成接 Attendance 秒数

源码关注的是“合格工时”,不是裸打卡秒数。

3)不要用 paid/unpaid 代替全部政策判断

paid/unpaid 很重要,但不足以表达 accrual eligibility。

4)制度不清时,先画资格矩阵

我通常会先让 HR 列一个表:

  • 假别名称
  • 是否视为 Worked Time
  • 是否影响 accrual rate
  • 是否参与 worked-hours-based accumulation

这个表一出来,Odoo 配置思路就会清晰很多。


我会怎么总结给业务方听

如果业务方问:“为什么有些假不影响年假累计,有些却会影响?”

我会说:

因为 Odoo 不是按‘有没有请假’来算累计,而是按‘这段时间在制度上算不算合格服务时间’来算。

这才是源码真正表达的业务语言。


一句话记忆

Odoo 通过 Worked Time / Absence、accrual eligibility 和 worked-hours-based accrual,把“哪些非出勤时间仍算合格服务时间”这件事显式建模了。

DISCUSSION

评论区

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