企业协同

Odoo 企业版 VoIP 为什么会把未接来电变成待办:call queue、活动创建与回拨入口讲透

Odoo 企业版 VoIP 不是只记录一通电话的开始和结束。源码把 call activity 当作“下一步动作”的协同载体:电话类型不存在时先补齐,缺手机号时拒绝入队,未接来电与今日电话活动再通过队列和 bus 事件连接到前端回拨入口。

企业 协同办公
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 4 阅读

先说结论

Odoo 企业版 VoIP 的重点,不是“电话记录有没有存下来”,而是:

一通电话结束之后,系统能不能把它转成一个明确、可继续处理的协作动作。

所以源码里你会看到两条并行对象:

  • voip.call:描述电话事件本身;
  • mail.activity:描述这通电话之后,谁还需要继续做什么。

这就是为什么在实际使用里,VoIP 看起来不像单纯通话工具,而像“电话 + 待办 + 回拨入口”的组合。


这篇主要看哪里

核心源码在:

  • enterprise/voip/models/voip_call.py
  • enterprise/voip/models/voip_queue_mixin.py
  • enterprise/voip/models/mail_activity.py

重点方法:

  • voip.call.start_call()
  • voip.call.end_call()
  • voip.call.miss_call()
  • voip.queue.mixin.create_call_activity()
  • voip.queue.mixin.delete_call_activity()
  • mail.activity.get_today_call_activities()

第一层:voip.call 负责记录电话状态机,但它不负责“后续动作”

voip.call 这几个方法非常直接:

  • start_call():写入 start_date,状态变 ongoing
  • end_call():写入 end_date,状态变 terminated,必要时记录 activity_name
  • miss_call():状态变 missed
  • reject_call() / abort_call():处理其他结束语义。

它的职责很清楚:

  • 描述电话何时开始、结束;
  • 这通电话结果如何;
  • 通过 Store 把状态推给前端。

但它并不直接回答另一个更重要的问题:

  • 这通电话结束后,接下来谁去跟进?

这个问题交给 call activity 机制。


第二层:create_call_activity() 不是装饰功能,而是把电话接入待办系统

voip_queue_mixin.create_call_activity() 的设计非常有代表性。

它做的事情有三步:

1. 先保证系统里存在“phonecall”类型的 activity

如果 xmlid 不存在,就尝试找同类;还没有,就自动创建一个 mail.activity.type

这说明 Odoo 假定:

VoIP 跟进不是私有模型行为,而应该复用全局活动体系。

2. 再为当前记录批量创建 mail.activity

活动字段里会带上:

  • activity_type_id
  • date_deadline
  • res_id
  • res_model_id
  • user_id

于是“去回拨这个客户”就从一个模糊想法,变成了标准化待办。

3. 如果记录没有手机号,直接报错拒绝入队

这个细节非常关键。

源码不会因为你点了“加入拨号队列”就无脑建活动,而是检查 activity.phone。如果缺手机号,直接抛 UserError

这背后的产品逻辑很合理:

  • 没号就没法回拨;
  • 这种 activity 建出来只会变成假待办;
  • 不如在入队时就阻止。

第三层:为什么说它是 call queue,而不是普通活动列表

因为 VoIP 不是只想让你“看见一个待办”,而是想让这个待办能回到电话流程里。

mail_activity.get_today_call_activities() 与相关格式化逻辑,会把今天的电话活动聚出来,再喂给前端。

这意味着这些活动不是后台静态记录,而是:

  • 能作为拨号入口;
  • 能按今日范围聚合;
  • 能和 VoIP 前端直接联动。

所以 call activity 在 Odoo 里并不是“顺手创建一条备注”,而是电话工作台的任务队列


第四层:删除 call activity 为什么要发 bus 事件

delete_call_activity() 不只是 unlink()

它会先找到:

  • 当前用户;
  • 当前模型;
  • 电话类 activity;
  • 截止日期不晚于今天;
  • 且有 phone 的活动。

然后在删除前,对每条活动执行:

  • self.env.user._bus_send("delete_call_activity", {"id": activity.id})

这说明一件事:

call queue 的删除不是纯数据库操作,而是带前端同步语义。

也就是说,队列里的待办被移除时,前端拨号工作台也应该立即知道,不然列表会脏。

这和普通 mail.activity 的后台管理视角完全不同。


第五层:未接来电为什么适合落到 activity,而不是只留一条 call 记录

因为 voip.call 只能告诉你“刚才发生了什么”,却不能驱动“现在该做什么”。

未接来电最典型的后续动作是:

  • 尽快回拨;
  • 指定某个负责人跟进;
  • 在今天的工作列表里显式可见。

这恰好是 mail.activity 最擅长表达的东西。

所以从协同设计上看:

  • voip.call.miss_call() 保留事实;
  • create_call_activity() 生成下一步动作;
  • get_today_call_activities() 把动作带回前端队列。

这三者合起来,才构成“未接来电处理”。


第六层:这套设计解决了什么现实问题

1. 电话不再是瞬时事件

只记 call log,团队容易漏跟进。

2. 跟进动作可以落入统一协作体系

因为它最终变成 mail.activity,而不是 VoIP 私有待办。

3. 前端拨号台和后台活动表可以共用同一份事实

不是两套平行系统。

4. 数据质量问题在入口就被拦住

没电话号码就不允许加入 call queue,避免垃圾任务。


实战里最容易踩的坑

坑 1:只改 voip.call 状态,不生成 activity

这样你有通话记录,但没有可执行的回拨队列。

坑 2:把 call activity 当普通备注

它实际上是前端拨号工作台的一部分,删除、完成、今天范围筛选都可能影响 UI。

坑 3:忽略手机号校验

如果你手工补 activity,却不保证 phone 可用,前端回拨体验很容易炸。


调试顺序建议

遇到“未接来电为什么没出现在回拨列表”时,建议按这个顺序查:

  1. voip.call 是否已进入 missed
  2. 是否有调用 create_call_activity()
  3. 相关记录是否真的有 phone;
  4. mail.activity 是否为 phonecall 类别;
  5. get_today_call_activities() 的时间与用户范围是否命中;
  6. 前端是否收到 bus 更新。

结论

Odoo 企业版 VoIP 的真正价值,不在于它把电话打进系统,而在于它把电话后的动作也纳入系统:

  • voip.call 记录事实;
  • mail.activity 记录下一步;
  • call queue 把这些待办组织成可回拨工作台;
  • bus 事件保证前后端列表同步。

一句话总结:

Odoo 把未接来电做成待办,不是附加功能,而是把“电话”真正接进协同流程的关键一步。

DISCUSSION

评论区

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