企业 年假累计

Odoo 企业版请假累计为什么不是“按月加几天”:carryover cap、等级切换与分段累计讲透

结合 hr_holidays 累计源码与企业版人事场景,讲清 accrual level 的 carryover、上限、transition mode 与分段累计为什么会让余额看起来不像“连续直线”。

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

现有站内已经有几篇请假累计文章,主要讲总体概念、历史不回写和通用 transition。这一篇要刻意避开“大而全复述”,专门讲更细的那层:为什么 carryover 上限、等级切换日期与分段累计会让企业版年假余额在某些日子突然“不连续”。核心源码在 addons/hr_holidays/models/hr_leave_accrual_plan_level.pyaddons/hr_holidays/models/hr_leave_allocation.py,而企业版 HR 场景通常正是这些复杂规则的高频使用者。

一、累计计划不是一条公式,而是一组 level 的时间切片

hr.leave.accrual.plan.level 明确告诉你:累计规则是分 level 运行的。每个 level 都可以有自己的频率、起算偏移、carryover 策略、有效期与上限。_get_level_transition_date() 则负责计算从一个 level 进入下一个 level 的切换点。

所以一名员工从入职第 1 年到第 3 年,未必是在同一条累计公式上滑动,而更可能是在多段规则之间跳转。这也是为什么“改了计划参数,为什么余额没整体重算”会成为常见问题——因为系统是在时间线上逐段处理,不是每次都把历史推翻重来。

二、carryover cap 的本质是“结转政策”,不是余额上限

很多人会把 cap_accrued_timecarryover_options 混在一起。其实它们约束的是两件不同的事:

  • cap_accrued_time / maximum_leave:控制某 level 下累计余额本身最多长到多少;
  • carryover_optionsaction_with_unused_accrualspostpone_max_days:控制到 carryover 日期时,未使用天数如何处理,是清零、无限结转还是限定结转。

这两个机制叠加后,余额曲线就不再是“每月加 X 天”的朴素想象。员工可能在年中因为 cap 停止增长,到结转日又因为 limited carryover 只留下部分余额,随后再按新 level 重新累计。

三、transition mode 决定切换当天算旧规则还是新规则

hr_leave_allocation.py 里对 transition_mode 的处理特别关键。源码中多处判断 accrual_plan_id.transition_mode == 'immediately',这意味着系统需要决定:当员工跨入下一 level 时,累计是立刻按新 level 生效,还是等当前周期走完再切。

这会直接影响两个结果:

  1. 切换当天新增的额度属于旧 level 还是新 level;
  2. carryover 结算时,哪些天数应该受旧 carryover 政策约束,哪些不该再被旧规则吞掉。

也正因为如此,源码专门处理“level transition 发生时,carryover period end 需要被截断”的情况,避免把新等级下刚累出来的额度误算进旧规则结转范围。

四、真正难的是“分段累计”而不是“累计本身”

_process_accrual_plans() 是整条主链路最值得看的地方。它不是简单 balance += x,而是不断围绕几个关键日期切段:

  • 当前 period 的正常结束日;
  • carryover date;
  • level transition date;
  • 失效期截止日;
  • 已执行 nextcall / last_executed_carryover_date。

一旦某个关键日期落在本次处理窗口里,系统就要把原本一整段累计拆成前后两段,分别套不同规则。标题里的“level splitting”本质上说的就是这件事:企业年假并不是一次算完,而是被这些业务节点不断切开。

五、几个最容易误解的点

1. carryover 不是只在年初发生

_get_carryover_date() 支持 year_start、allocation 以及指定月/日。企业里常见的周年制年假,carryover 完全可能跟员工入职日绑定。

2. 改 plan 不会自动重写过去

这不是 bug,而是时间线设计。系统默认尊重已发生历史,否则你一改规则,所有老员工余额都会重算,审计直接失真。

3. 上限不是“多出来直接消失”那么简单

在不同 level、不同 transition mode 下,达到 cap、执行 carryover、进入新 level 的先后顺序会改变最终余额表现。

六、实战建议

  • 设计累计计划前,先画出员工 24 个月时间线,把 carryover 日、level 切换日、失效期全部标出来;
  • 别只测“正常累计”,一定要测切换日前后、结转日前后;
  • 如果要给业务解释余额变化,重点解释“规则切段”而不是“系统突然改数”。

七、结论

Odoo 的请假累计之所以经常看起来“不直观”,不是因为算法乱,而是因为它认真处理了企业现实:工龄分级、结转失效、上限封顶与周期切换。理解这套机制的关键,不是背字段名,而是把每次余额变化都放回它所属的时间段里去看。

主要源码锚点:

  • addons/hr_holidays/models/hr_leave_accrual_plan_level.py
  • addons/hr_holidays/models/hr_leave_allocation.py

DISCUSSION

评论区

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