先说结论
Odoo 里的 accrual plan 不是“一个公式”,而是一条正在运行的余额时间线。
这意味着当你中途修改:
- milestone
- carryover 规则
- transition mode
- cap
- validity
系统并不会天然把整段过去历史全部重新改写。
从 hr.leave.allocation 的累计逻辑能看得很清楚:它关心的是一组正在推进的状态,例如:
lastcallactual_lastcallnextcalllast_executed_carryover_dateexpiring_carryover_dayscarried_over_days_expiration_date
这代表它更像一个沿时间滚动执行的状态机,而不是一个随时可无损重算的静态公式表。
为什么 HR 会直觉觉得“改计划就该刷新旧余额”
因为从界面视角看,accrual plan 很像一张配置表:
- 每月加多少
- 满几年切哪个 milestone
- 年初怎么 carryover
- 结转后多久失效
所以很容易脑补成:
- 规则变了
- 余额当然应该一起重算
但源码里的真实对象不是“规则表 + 即时总额”这么简单。
它管理的是:
- 这个 allocation 从哪天开始跑
- 跑到哪次
nextcall - 什么时候发生了 carryover
- 有多少 carryover 额度会在未来某天失效
- 当前是按哪个 level 在继续推进
一旦理解成这条“运行中的余额轨迹”,你就会明白为什么系统对历史改写会很保守。
_process_accrual_plans() 的心智:往前推进,不是反复重写过去
_process_accrual_plans() 做的核心事情是:
- 从当前 allocation 的状态出发
- 一步步推进到今天或目标日期
- 在推进过程中处理 level transition、carryover、cap、有效期失效
它不是每次都从 allocation 起点无脑重算到今天,而是大量依赖现有状态字段作为“运行现场”。
这就意味着两件事:
1)历史不是免费的
你不是随便改个配置,系统就自动把每一个过去节点都重新解释。
2)当前余额带着历史痕迹
今天看到的余额,里面可能已经包含:
- 之前某次 carryover 后留下的额度
- 某个旧 milestone 下累计出来的值
- 某次过期日已经扣掉的一批天数
这些都不是单靠“看现在这张 plan”就能瞬间逆推出完整历史的。
源码里甚至直接写明:已运行过的 allocation 不建议靠修改配置补救
hr.leave.allocation 里有一段提示非常直白,大意是:
- 这条 allocation 已经跑过一次
- 后续再改配置,不会影响已经分配给员工的天数
- 如果你要改配置,应该删除并新建 allocation
这句话很重要。
它不是在说系统完全不能处理任何变化,而是在提醒你:
累计分配一旦进入运行态,系统优先保证时间线一致性,而不是替你无成本重写历史。
这跟很多 HR 的直觉正好相反。
为什么 Milestone / Transition 更不适合回头重写
我们前面那篇累计文章已经讲过,milestone 和 transition mode 决定的是:
- 员工何时进入下一个 level
- 到门槛当天立刻切,还是等当前累计周期跑完再切
这本身就是时间条件。
如果你后来把:
start_countstart_typetransition_mode
改掉,那就等于在问系统:
- 过去几个月本来已经按旧 level 跑过的 nextcall,要不要重新定义?
- 当时已经发生过的 carryover,要不要按新 level 重新裁剪?
- 过期额度要不要重新复活?
这些问题每一个都会牵动历史状态,绝不是简单“重算一下 added_value”能解决的。
Carryover 更说明它是状态机,而不是普通公式
看 carryover 相关字段你就知道事情没那么简单:
last_executed_carryover_dateexpiring_carryover_dayscarried_over_days_expiration_date
这代表系统不仅知道“有没有结转”,还记得:
- 上次结转是什么时候执行的
- 结转后有多少额度在等待未来过期
- 那批额度会在哪天失效
如果你此时把 carryover 规则从:
- lost 改成 carried over
- unlimited 改成 limited
- 6 个月有效改成 12 个月有效
系统并不会默认替你把已经发生过的历史结转重新演算一遍。否则它还得同时回答:
- 已经失效的额度要不要恢复
- 已经被员工用掉的额度怎么回填来源
- 历史工资 / 请假记录是否要连带修正
这就是为什么“改规则自动追写历史”看似方便,实则风险巨大。
真正该怎么改,才比较稳
场景一:未来政策变更
最稳的方式通常是:
- 保留旧 allocation 的历史
- 从新日期开始启用新 plan 或新 allocation
这样边界最清楚。
场景二:配置刚上线、还没真正跑几次
如果还处于很早期、影响范围可控,可以考虑重建相关 allocation,再让系统按新规则重新跑。
场景三:已经跑很久、员工还在持续用假
这时更应该谨慎。
因为你改的不是一个静态数字,而是一条已经参与过:
- 请假可用余额
- 结转失效
- 未来累计预测
的运行时间线。
这对实施最大的提醒
很多团队把 accrual plan 当成“可随时回头修正的参数表”。
我更建议把它当成:
- 制度定义 + 运行状态 的组合
一旦进入运行态,你要优先尊重历史边界,而不是期待系统帮你做魔法回写。
所以实施时,最好一开始就确认:
- carryover 日期
- milestone 门槛
- transition mode
- 有效期
- cap
因为这些字段越晚改,历史包袱越重。
我会怎么跟 HR 解释
如果 HR 问:
“我把累计计划改对了,为什么员工旧余额没自动刷新?”
我会说:
因为 Odoo 的累计不是静态公式,而是沿
nextcall、carryover 和过期节点逐步跑出来的余额历史。你改的是未来制度,不一定是过去结果;历史若要重写,往往要靠重建 allocation,而不是指望计划字段自动追写。
一句话记忆
Odoo 的 accrual plan 修改,更像改变未来轨道,而不是重写过去轨迹;milestone、carryover 和 transition 一旦跑进历史,就不能把它们当普通公式字段随手回刷。
DISCUSSION
评论区