先说结论
Odoo 的周期任务不是“先复制一长串未来任务”。
从 project.task 的 _inverse_state() 可以直接看出,官方核心动作是:
- 找到某条 recurrence 下最后一个任务
- 当它被关到 closed state 时
- 才调用
project.task.recurrence._create_next_occurrences()生成下一次
也就是说:
项目里的周期任务更像“完成一轮,再续下一轮”,而不是“提前铺满整条时间线”。
为什么官方不喜欢预先复制很多未来任务
预先复制听起来省事,但会带来大量现实问题:
- 未来半年任务全出现在列表里,噪音极大
- 规则一改,历史和未来副本很难一起修正
- 依赖、负责人、截止日很容易一起失真
Odoo 选择在关闭时再生成下一次,本质上是在降低未来任务的“提前承诺量”。
这很符合项目协作现实:
- 本周例行复盘今天先做完
- 下周要不要继续,规则可能会变
- 未来实例不应该比当前实例更重
_inverse_state() 为什么是关键入口
任务状态被改为关闭时,_inverse_state() 会检查:
- 这条任务是不是该 recurrence 的最后一个实例
- 如果是,才继续创建下一次
这里有两个重要含义。
第一,不是随便一条历史实例关闭都会触发续期。
第二,系统明确避免同一 recurrence 同时往前乱长多条后续任务。
这说明 Odoo 把周期任务当成“串行接力”,而不是“无限复制”。
为什么“最后一个实例”这个判断很重要
如果不判断 last task,最容易出现两个坏结果:
- 旧实例补关一次,又多生出一条未来任务
- 并行编辑时,多个实例重复生成后续任务
官方先通过 recurrence 维度确定“谁是当前链尾”,再决定是否生成下一次,就是为了防止重复生长。
这一步虽然不显眼,但它决定了周期任务是否会失控。
周期任务更像业务节奏,而不是排班日历
和 calendar.event 相比,project.task 的周期逻辑更偏业务循环:
- 周报整理
- 月度复盘
- 每周跟进客户
- 每月结算检查
这些工作常常需要“上一轮真的完成”,下一轮才有意义。
所以 project 模块的 recurrence 设计天然更适合:
- 任务驱动
- 结果驱动
- 完成后再延续
而不是纯时间到点就平铺一堆实例。
周期任务最容易踩的误区
误区一:把它当日历 recurrence 理解
项目任务不是会议;很多任务下一次何时出现,应该依赖本次是否完成。
误区二:手工复制很多未来任务,以为更稳
短期看有安全感,长期看会制造大量脏任务和假负载。
误区三:不区分“关闭任务”和“只是改阶段”
真正触发下一次生成的是进入 closed states,而不是任意拖进某一列。
排错顺序
如果用户说“为什么没生成下一次”或“为什么多生成了”,建议查:
- 这条任务是否真的挂了
recurrence_id - 当前状态是否属于 closed states
- 它是不是该 recurrence 的最后一个实例
- 是否有自定义流程绕开了正常的状态反写逻辑
- 是否手工复制过历史实例,干扰了 recurrence 链条判断
一句话记忆
Odoo 周期任务不是提前排满未来,而是在你真正完成这一轮之后,谨慎地续出下一轮。
DISCUSSION
评论区