先说结论
Odoo 里一条记录显示为 overdue、today 还是 planned,并不是“取最新那条活动看看日期”这么简单。
更准确地说:
记录上的活动状态,是
mail.activity.mixin对这条记录所有有效活动做聚合后得到的结果;只要其中有一条逾期,整条记录就会被判成 overdue。
这也是为什么销售、项目、审批、CRM 这些模块里,只要挂了活动,卡片颜色和提示就会跟着变。
如果你排查时只盯着某一条活动,很容易得出错误结论。
一、为什么它不是“单条活动状态”
在 addons/mail/models/mail_activity_mixin.py 里,activity_state 是一个计算字段。
源码逻辑很直接:
- 如果活动集合里出现
overdue,整条记录就是overdue - 否则只要出现
today,整条记录就是today - 再否则只要出现
planned,整条记录就是planned - 一条活动都没有,才是空值
这意味着它不是“下一条活动状态”,而是风险优先的聚合状态。
所以一个常见误区是:
- 用户看到表单里最上面那条活动是明天到期
- 就以为记录应该显示 planned
- 但实际上同一条记录下面可能还挂着一条昨天截止、尚未完成的活动
这时系统显示 overdue 才是对的。
二、为什么同一天里,不同用户看到的状态可能不同
很多团队第一次遇到这个现象时会怀疑缓存或者前端 bug。
其实源码已经写明了:
_search_activity_state() 和相关 SQL 计算会用 mail_activity.user_tz,如果没有,再回退到 utc。
这背后的含义是:
- 活动不是用“服务器今天”粗暴比较
- 而是尽量按负责人自己的时区定义“今天是否已经过去”
因此:
- 上海用户看到还是今天
- 纽约用户那里可能已经是昨天或明天
- 同一批活动在不同人界面上可能会落到不同状态边界
这不是不一致,而是系统在尽量贴近责任人的本地工作日。
三、为什么一条记录会突然从绿色变成红色
最常见的触发点有三个:
1)旧活动没关闭
业务已经推进到下一步,但旧活动没有 done,于是系统仍把它算在聚合里。
2)自动化又补出一条活动
某些模块、自动化规则、活动计划会在写入或完成动作后重新生成活动。用户以为“我明明处理过”,其实新活动已经补回来了。
3)时区边界跨天
昨天晚上的活动,在某个用户时区里已经跨过当天零点,于是状态自然从 today 变成 overdue。
四、为什么搜索 activity_state 不能简单靠 ORM 拼域名
源码里 _search_activity_state() 没有走最朴素的 ORM 关系搜索,而是专门下了 SQL,把状态先映射成整数:
overdue = -1today = 0planned = 1
然后对每条业务记录取最小值。
为什么这么设计?
因为这正好符合“风险优先”原则:
- 只要有逾期,最小值就是 -1
- 没有逾期但有今天到期,就是 0
- 都在未来,才是 1
这也说明一个重要边界:
前台看到的是聚合后的业务信号,不是单条
mail.activity行本身。
如果你只在 mail.activity 上看单条数据,很容易和列表页、看板页的颜色不一致。
五、activity_date_deadline 为什么也容易让人误判
activity_date_deadline 通常会被理解成“下一条活动截止日期”。
从实现看,它取的是活动集合中的首条记录对应的截止日,而不是“最危险那条”或“最晚那条”统一口径。
所以两个字段不要混着理解:
activity_state:偏聚合风险信号activity_date_deadline:偏界面展示和快速入口
如果你把后者当成前者的解释依据,经常会得到“怎么日期是明天但状态是 overdue”的错觉。
六、实战里最容易踩的三个误区
误区 1:只看当前打开的那条活动
实际要看整条记录所有未关闭活动。
误区 2:以服务器时间判断 overdue
实际要看负责人时区、用户上下文和活动持有人。
误区 3:把活动状态当作纯前端颜色
实际上它是很多列表、看板、统计入口共用的后端业务字段。
七、排错顺序建议
遇到“为什么这条记录一直红着不变”时,我建议按这个顺序查:
- 先看该记录是否存在多条未完成活动
- 再看这些活动分别属于谁,负责人时区是什么
- 确认是否有自动化、活动计划或业务动作会重新补活动
- 区分你在看的是
activity_state还是activity_date_deadline - 如果是搜索结果不对,再检查是否命中了
_search_activity_state()的聚合逻辑
这个顺序比一上来怀疑缓存更靠谱。
八、对实施和开发的实际启发
这套设计说明 Odoo 对活动的定位不是“便签”,而是业务推进的风险灯。
所以在做二开时,最好遵守这两个原则:
- 不要随便制造隐形活动,否则看板颜色会失真
- 不要在用户不知情时保留大量过期活动,否则所有记录都会长期泛红
如果团队想让活动真正变成协作工具,而不是噪音来源,关键不是多建几种活动类型,而是把“谁负责、什么时候关、是否自动续下一个动作”设计清楚。
最后一句
很多人把活动状态当成 UI 小细节,但源码给出的答案很清楚:
它本质上是 Odoo 用来表达“这条业务记录当前协作压力有多大”的聚合信号。
一旦从这个角度理解,很多看似古怪的颜色变化、搜索结果和跨时区现象,都会变得顺理成章。
DISCUSSION
评论区