先说结论
Odoo 的官网招聘,不是“网站上展示几条职位,再把表单内容塞进 hr.applicant”这么简单。
从 website_hr_recruitment 的 controller 和 model 看,它真正关心的是:
- 岗位如何在官网上被搜索到;
- 访客如何按国家、部门、办公地、合同类型筛选;
- 已关闭岗位怎样阻止继续投递;
- 同一候选人最近有没有重复申请;
- 新申请进入系统时,该落到哪个招聘阶段。
所以这个模块本质上不是“官网页面接 HR 后台”,而是:
把招聘官网、候选人投递体验和后端招聘流程的起点对齐。
一、为什么职位页不是简单列表,而是一套可搜索内容对象
jobs() 入口里最值得注意的是:
- 它不是普通
search(); - 而是走
website._search_with_fuzzy("jobs", ...)。
这说明 Odoo 没把职位页当成静态展示,而是当成网站搜索体系里的一类内容。
同时 hr.job 还继承了:
website.seo.metadatawebsite.published.multi.mixinwebsite.searchable.mixin
这几层叠在一起,意味着官网职位并不是 HR 模块的副产物,而是:
- 可以被搜索;
- 可以有 SEO;
- 可以参与网站发布管理;
- 可以按网站域隔离。
所以如果你做官网招聘,只把它当个表单页,实际上会低估 Odoo 已经做好的内容侧能力。
二、为什么筛选器不是前端拼 URL,而是“互相污染”的结果集计算
jobs() 里有一段比较有意思的逻辑:
- 先取到搜索结果;
- 再计算各筛选条件下的计数;
- 还会在计算某一类筛选时,临时禁用这类筛选本身,去看其他筛选条件下还有多少岗位。
这就是源码里注释说的“contaminating the jobs with the other filters”。
通俗说,Odoo 想解决的是:
- 用户现在按“国家 + 部门”筛完后,页面上“办公地”“合同类型”这些筛选项还能不能显示一个可信数量;
- 而不是把筛选器做成一堆互相打架的死按钮。
这点看似只是列表页体验,实际上很影响投递效率。因为职位筛选一旦做得粗糙,候选人会很快失去耐心。
三、为什么官网会主动提醒“你最近已经投过了”
/website_hr_recruitment/check_recent_application 这条 JSON RPC 很值得讲。
它会根据候选人输入的:
- 姓名
- 邮箱
- 电话
去找同岗位、同网站下的历史申请,并区分:
- 仍在进行中的
ongoing - 近 6 个月内被拒且已关闭的
refused
如果命中,系统会给出提醒,而不是傻傻地再收一份一模一样的申请。
这背后的价值非常现实:
- 候选人少做无效重复劳动;
- HR 少看重复简历;
- 官网投递体验更像“我在和一个有记忆的招聘系统互动”,不是“往黑洞里再扔一次表单”。
四、为什么岗位关闭后,website form 还要再做一次拦截
在 hr.applicant.website_form_input_filter() 里,源码明确检查:
- 如果传入了
job_id,但该岗位active=False,直接报错:The job offer has been closed.
这个设计很重要。
因为官网页面可能存在这些现实情况:
- 候选人开着旧页面很久没刷新;
- 外部分享链接还在流传;
- 搜索引擎缓存了旧职位页;
- HR 在后台刚把职位关闭。
如果没有模型层这一挡,前台很容易继续接到“已经不该收”的简历。
所以 Odoo 不是只在页面上隐藏按钮,而是在真正写入 applicant 前再做一次业务拦截。
五、为什么申请创建时要顺手设置初始阶段
还是这个 website_form_input_filter(),另一个关键动作是:
- 按岗位找第一个未 fold 的招聘阶段;
- 如果该阶段适用于该岗位或是全局阶段,就把它写到
stage_id。
很多人觉得建 applicant 默认进第一阶段不是理所当然吗?
其实不一定。
招聘流程往往有:
- 通用阶段;
- 针对某岗位的专属阶段;
- 已折叠的历史阶段;
- 不同团队自己维护的阶段顺序。
Odoo 在网站入站时就把这件事处理好,目的很明确:
官网投递不是孤零零造一条记录,而是要让它一进入后端就站在正确流程位置上。
六、为什么“已发布职位”不等于普通 active 记录
hr.job 里同时有:
website_publishedis_publishedpublished_datewebsite_url
并且 website_published 的 onchange 会驱动 is_published。
这说明 Odoo 区分了:
- 岗位记录在后端是否存在;
- 岗位是否允许在官网公开显示。
再加上 _search_get_detail() 里会把 website.website_domain() 纳入搜索域,你就会发现:
- 多网站情况下,职位是否该在当前官网出现,也不是随便的。
所以官网招聘不是“HR 有岗位 -> 网站自动展示”,中间还有一层网站发布语义。
七、实施时最容易踩的坑
1. 只管投递表单,不管职位搜索体验
候选人首先看到的是职位列表,不是 applicant 表。
2. 只在前端隐藏关闭岗位,忘了模型层二次拦截
这会导致旧链接照样继续收简历。
3. 不做重复申请提醒
HR 会被重复候选人淹没,候选人也会怀疑系统有没有记住自己。
4. 让官网投递生成的 applicant 落到错误阶段
后端一开始就乱,后续统计和流程都会偏。
最后总结
Odoo 的 website_hr_recruitment 真正难的地方,不是把职位放到官网,而是把:
- 网站搜索和筛选;
- 岗位发布状态;
- 候选人重复识别;
- 关闭岗位拦截;
- applicant 流程初始化;
几件事拼成同一条链路。
所以更准确地说,它是一套:
把“官网获客入口”直接接到“招聘流程起跑线”的职位投递系统。
DISCUSSION
评论区