协同办公

Odoo Calendar 为什么改一条循环会议会连带未来实例:recurrence 拆分与 attendee 同步讲透

Odoo 里修改一条循环会议时,系统并不是简单“批量改掉后面所有记录”。源码把 recurrence 拆分、基准事件重建、未来实例重算和参会人 attendee 命令分开处理,因此你看到的“只改当前及未来”其实是一套状态拆分逻辑。

协同办公
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

Odoo 处理循环会议时,核心思想不是“已有 N 条会议记录,改一下字段然后批量写回去”,而是:

把 recurrence 当成一套生成规则,把当前事件当成分割点,把 future-only 修改当成“从这里开始生成一条新规则”。

这就是为什么你在界面里看到“只修改当前及未来事件”,最终效果常常像:

  • 旧系列保留到当前节点之前;
  • 当前节点以后挂到一条新的 recurrence;
  • 未来实例按新规则重新生成;
  • 参会人不是直接改 partner_ids 完事,而是转成 calendar.attendee 的增删命令。

如果把它误解成“多条普通事件的批量更新”,很多现场现象都解释不通。


这篇主要看哪里

核心源码在:

  • addons/calendar/models/calendar_event.py
  • addons/calendar/models/calendar_recurrence.py

重点方法:

  • calendar.event._attendees_values()
  • calendar.event._apply_recurrence_values()
  • calendar.event._get_recurrence_params()
  • calendar.recurrence._split_from()
  • calendar.recurrence._apply_recurrence()

这些方法组合起来,定义了“会议规则如何拆”“未来实例如何重建”“参会人如何同步”。


第一层:参会人不是 partner_ids 的镜像字段,而是独立 attendee 记录

很多开发者第一次看 Calendar 会误以为:

  • 改了 partner_ids,参会人就只是 Many2many 跟着变一下。

_attendees_values() 说明事情没这么简单。

它做的是把 partner 变更命令翻译成 attendee 命令:

  • 删除 partner → 查对应 calendar.attendee 再删;
  • 替换整组 partner → 算出 removed 和 added;
  • 新增 partner → 为每个新增人生成新的 attendee 创建命令。

也就是说:

Calendar 真正承载 RSVP、出席状态、邀请反馈的,是 attendee,而不是 partner_ids 本身。

所以“参会人列表”只是表层;底下真正有状态的是 attendee 记录。


第二层:future-only 修改的本质,是从当前节点切一条新 recurrence

_apply_recurrence_values() 的注释已经把核心意图写得很清楚:

  • 如果事件还没有 recurrence,就新建一条;
  • 如果已经有 recurrence 且只改未来,就调用 recurrence_id._split_from(event, values)
  • 然后统一 _apply_recurrence(),把缺失事件生成出来。

这段逻辑的关键是:

它不是原地改整条旧规则

而是把“当前事件”视为分割点。

这意味着:

  • 历史实例不需要跟着新规则重写;
  • 当前及未来可以切到一套新 recurrence;
  • 旧 recurrence 仍然能保存此前的链路。

这正是业务上最合理的做法。

比如:

  • 每周一例会原本在 10:00;
  • 从下周开始改到 15:00;
  • 过去开过的会议,显然不该被强行改成 15:00。

Odoo 就是按这个思路落地的。


第三层:为什么系统需要 _get_recurrence_params()

_get_recurrence_params() 看起来像个小工具,但它其实决定了 recurrence 的“锚点语义”。

它会从当前事件日期里推导:

  • 星期几;
  • 第几个星期几;
  • 当月第几天。

这意味着 recurrence 不是只保留一个 RRULE 字符串就完事,而是要把“当前事件在时间轴上的位置”转换成结构化参数。

这在两类场景里尤其重要:

场景 1:按周重复

例如每周二。

场景 2:按月重复

例如“每月第三个周四”或“每月 18 号”。

如果没有这层参数抽取,你对基准事件做 future split 时,就无法稳定地从当前节点继续生成后续实例。


第四层:为什么你会觉得“只改一条,系统却动了很多条”

因为 Calendar 看的不是“单条 event 记录”,而是:

  • 当前 event;
  • 它所属的 recurrence;
  • 以当前节点为边界的 future segment;
  • 由 recurrence 重新推导出来的所有 future events。

所以只要你改的是影响规则的字段,比如:

  • 重复频率;
  • 间隔;
  • 星期几;
  • 时间段;
  • 终止条件;

系统就不是在改单条记录,而是在改一段生成逻辑。

这就是“界面操作像改单条,结果却影响未来一串实例”的本质原因。


第五层:attendee 同步和 recurrence 拆分为什么要一起理解

很多问题都出在这里。

如果你只理解 recurrence,不理解 attendee,就会以为“未来实例重建后,参会人自然都对了”。

但真实情况是:

  • recurrence 决定会生成哪些 event;
  • attendee 命令决定每个 event 上有哪些参与者以及他们的状态承载体。

所以当你对 partner 做 setlinkunlink 时,系统不是偷懒直接覆写 partner_ids,而是先算出 attendee 的增删。

这让 Odoo 能保留正确的邀请语义:

  • 谁被加进会议;
  • 谁被移出;
  • 哪些 attendee 应该删掉;
  • 新 attendee 应该重新创建并走自己的 RSVP 生命周期。

第六层:这套设计解决了什么业务问题

它解决的不是“代码优雅”这么简单,而是现实里的协同办公约束:

1. 历史会议不应被未来规则污染

已经开完的例会,不该因为你今天改规则而回写成新时间。

2. 未来实例必须从正确边界继续生成

不是从第一条会议重新算,而是从当前拆分点往后算。

3. 参与人状态需要有独立生命周期

受邀、接受、拒绝、待确认,这些不是 partner_ids 能表达的,需要 attendee。

4. 邮件邀请与会议状态要可追踪

如果 attendee 只是列表影子,就很难稳妥支撑 RSVP、提醒和参与状态。


实战里最容易踩的坑

坑 1:直接批量改 recurrence 相关字段

如果你二开时直接对一串 event write(),却没有走相应 recurrence 语义,很容易造成:

  • 历史和未来混在一起;
  • UI 上看着像一组循环会议,底层却已经断链;
  • 未来实例和规则不一致。

坑 2:只改 partner_ids,不理解 attendee 的存在

你会在后续邀请、参会状态、邮件提醒里遇到“为什么跟我想的不一样”。

因为真正的参会状态对象是 calendar.attendee

坑 3:把“当前及未来”理解成 SQL 条件过滤

它不是单纯 start >= 当前日期 的批量更新,而是 recurrence split。

这两者完全不是一个层级。


调试这类问题时该怎么看

排查顺序建议是:

  1. 先看当前 event 有没有 recurrence_id
  2. 看这次修改是否走了 _apply_recurrence_values()
  3. 看是否触发 _split_from() 生成新 recurrence;
  4. 再看未来实例是不是由 _apply_recurrence() 重建;
  5. 最后检查 partner 变更是否正确生成 attendee 命令。

如果你一上来只看最终生成的 event 列表,通常很难倒推出问题发生在哪一步。


结论

Odoo Calendar 的循环会议机制,本质上是:

  • 用 recurrence 保存规则
  • 用 event 表达实例
  • 用 attendee 表达参与人状态
  • 用 split 机制把“当前及未来”修改从历史里切出来。

所以一句话总结:

Odoo 改循环会议,不是在“修改一堆已有记录”,而是在“重新定义从当前节点开始的时间规则,并把参与人状态同步到新实例上”。

DISCUSSION

评论区

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