其他深度

Odoo 课程为什么不只是“学员进来点几节课”:邀请链接、过期清理与下一课推荐引擎讲透

很多人研究 Odoo eLearning 时只盯课程和课件,但源码里真正决定学员体验的,是 slide.channel.partner 这张课程成员关系表。它把邀请链接、成员状态、完成度、下一课计算、完课邮件和过期邀请清理收成了一条持续运营链。

其他
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

Odoo eLearning 的难点,不在于“课件能不能上传”,而在于一个人和一门课之间的关系,如何持续被运营

/home/ubuntu/odoo-temp/addons/website_slides/models/slide_channel_partner.py 来看,slide.channel.partner 不只是成员中间表,而是一张完整的学习执行记录。它同时负责:

  • 邀请链接生成
  • 成员状态流转
  • 完成度与已完成课件统计
  • 下一课自动推荐
  • 完课奖励与邮件发送
  • 长期未响应邀请的自动清理

所以它真正解决的问题不是“这个人有没有加入课程”,而是:

课程邀请发出去之后,系统怎样一路把人从 invited 推到 joined、ongoing、completed,并在过程中不断告诉他下一步该做什么。

第一层:为什么课程成员关系要单独建表

很多学习系统会把“课程有哪些学员”做成很薄的一张关联表。 但 Odoo 没这么做。

slide.channel.partner 直接挂了:

  • member_status
  • completion
  • completed_slides_count
  • next_slide_id
  • invitation_link
  • last_invitation_date
  • 课程可见性、报名方式、负责人等 related 信息

这说明官方判断得很明确:

  • 课程是课程
  • 学员是学员
  • 但真正决定运营动作的,是“这个学员此刻以什么状态处在这门课里”

也就是说,Odoo 把课程成员关系当成了一个会变化、会触发动作、会产出下一步建议的业务对象。

第二层:邀请链接为什么不是普通 URL

_compute_invitation_link() 会把课程 URL、partner_id 和一段 hash 拼成邀请地址。 而这个 hash 由 _get_invitation_hash() 生成,本质上是基于 partner 与 channel 的 HMAC。

这背后的设计很值得学。

因为课程邀请并不是随便发一个公开链接就完事了。很多时候你需要的是:

  • 让某个人收到一条可直接加入的课程链接
  • 这个链接能表达“是针对谁、针对哪门课”
  • 同时不能轻易被别人随手伪造

Odoo 在这里没有走重型 token 池,而是做了一个轻量但很实用的绑定方案:

  • 链接里有具体对象
  • hash 负责校验
  • 链接既可点击直达,又保留一定身份约束

这很适合客户学院、内部培训和私有课程发放场景。

第三层:为什么成员状态不是 enrolled / not enrolled 两档

member_status 有四种:

  • invited
  • joined
  • ongoing
  • completed

这比很多系统只做“已报名 / 未报名”细得多。

原因很简单:学习运营不是一次性动作,而是一条持续链路。

invited

说明人还在被激活阶段,最重要的是打开课程。

joined

说明已经进入课程,但还没真正开始推进。

ongoing

说明已经在消化内容,需要的是进度感和继续学习的牵引。

completed

说明流程进入收尾,可触发完课通知、奖励、复训等动作。

所以这四个状态不是“字段很多”,而是在表达不同运营阶段需要不同动作。

第四层:下一课为什么要由系统算出来

_compute_next_slide_id() 的逻辑非常典型:

  • 只看已发布、active、且不是分类节点的内容
  • 排除已经在 slide.slide.partner 中标记为 completed=True 的课件
  • 按 sequence 顺序找到第一条未完成内容
  • 把它作为 next_slide_id

这说明 Odoo 不想让学员每次都重新思考“我下一步学什么”。

这件事看起来像体验优化,实际上很关键。

因为学习系统里最常见的流失点之一就是:

  • 用户知道自己报了课
  • 也知道课程还没学完
  • 但不知道下一步该点哪里

只要这个摩擦存在,完成率就会明显下滑。

所以 next_slide_id 本质上是在把“课程结构”翻译成“学员此刻的下一动作”。

这正是 LMS 该做的事情。

第五层:完成度为什么不是人工写,而是从真实完成记录反推

_recompute_completion() 会按课程和学员去统计:

  • 该学员在该课程里已完成多少条已发布内容
  • 占课程总课件数的比例是多少
  • 然后把 completioncompleted_slides_countmember_status 一并更新

更重要的是,它还有两层业务判断:

  • 之前没完成、现在完成了,要进入 completed 流程
  • 之前完成过、后来因为课程内容变化又没满 100%,也要退回未完成状态

这个设计非常成熟。

因为课程不是永远静止的:

  • 可能会新增一节课
  • 可能会下架一节课
  • 可能某些课件后来才发布

如果完成度只是一次性写死,课程一更新,学员状态就会失真。

Odoo 选择每次从真实完成记录回算,就是为了让“完课”始终保持业务真实性。

第六层:为什么完课后不只是改个状态,还要发邮件和奖励

当学员从未完成转成完成时,系统会:

  • 触发 _post_completion_update_hook()
  • 视课程配置给用户加 karma
  • _send_completed_mail() 发送完课邮件

这说明在 Odoo 眼里,学习完成不是内部统计数字,而是一个需要被明确反馈的业务事件。

这很重要,因为真正能提高课程完成率的,不只是课件质量,还包括:

  • 用户有没有感知到“我完成了”
  • 完成后有没有即时反馈
  • 平台有没有把这件事转成可见奖励

也就是说,Odoo 不是只做内容承载,而是在做持续激励。

第七层:为什么还要定期清理过期邀请

_gc_slide_channel_partner() 是个很容易被忽略的自动清理逻辑。

它会删除满足这些条件的邀请记录:

  • member_status = invited
  • completion = 0
  • 邀请时间缺失或早于 3 个月前

这代表官方默认认为:

长期没响应、也没开始学习的邀请,不应该无限制留在课程成员池里。

这个判断很实用。

因为如果不清理:

  • 课程后台会堆满失效邀请
  • 运营会误以为课程成员很多
  • 实际活跃度会被严重高估

自动清理不是为了“数据库整洁”这么简单,而是为了让课程成员列表继续代表真实的运营状态。

最容易误解的四个点

误区一:成员关系表只是课程和学员的连接

不对。它本质上是学习执行单,承载状态、进度、邀请和下一步动作。

误区二:邀请链接只是邮件方便点击

也不止。它其实是课程准入逻辑的一部分,背后还有 hash 绑定。

误区三:下一课推荐只是 UX 小优化

不是。它直接影响学习摩擦与完成率。

误区四:完课了就永远完课

源码并不这么想。课程内容变化后,完成状态是可以被重新计算的。

实战上怎么把课程运营做得更稳

如果你要把 Odoo eLearning 用在内部培训或客户学院,我建议重点关注:

  1. 先定义课程是公开报名还是邀请制,再决定是否大量使用 invitation link
  2. 不要只看课程总人数,更要看 invited、joined、ongoing、completed 各自比例
  3. 课程内容调整后,记得复核完成率变化,不要假定历史状态永远有效
  4. 把下一课入口放到最显眼位置,它比你想象中更影响完课率
  5. 完课邮件和奖励机制最好一起配置,不要只改状态不反馈

最后总结

Odoo eLearning 最值得学的地方,不是“能不能上传视频或文档”,而是它很清楚学习运营真正的难点不在内容本身,而在于:

  • 邀请之后有没有真正加入
  • 加入之后有没有持续推进
  • 推进过程中下一步够不够明确
  • 完成之后有没有及时闭环

所以 slide.channel.partner 看似只是成员关系,实际上已经是一台小型学习运营引擎。

DISCUSSION

评论区

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