人力资源

人才库为什么不是“把候选人打个标签”就完事:Odoo Talent Pool 的去重、复制与复用边界

很多团队以为人才库就是在候选人身上挂个 pool 标签。Odoo 源码其实做得更细:有直接入池、复制成 talent、通过邮箱/电话/LinkedIn 间接识别同人,以及专门避免重复加池的搜索逻辑。

Odoo 开发 人力资源
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 38 阅读

先说结论

如果你把 Odoo 的 Talent Pool 理解成“给候选人多打一个标签”,那就低估它了。

hr_recruitment 源码看,人才库至少在做四件事:

  • 把某些候选人变成可以长期复用的 talent 记录
  • pool_applicant_id 把“原申请”和“池中代表记录”串起来
  • 通过邮箱、电话、LinkedIn 识别是不是同一个人
  • 在加池动作里尽量避免重复塞进同一套人才池体系

所以 Talent Pool 不是装饰功能,而是在解决招聘里非常现实的问题:

同一个人会被不同岗位、不同时间、不同招聘者反复接触,系统怎样既保留申请上下文,又不要把人才资产弄碎。


为什么人才库不能只是 many2many tags

如果只是给 hr.applicant 挂一个 pool 标签,会马上遇到几个问题:

1. 原申请和长期人才资产会混在一起

某次 Java 后端岗位的申请,只是一个具体场景;但这个人的能力和潜在可复用价值,可能应该跨岗位、跨时间保留。

2. 同一个人再次投递时,很难判断是不是“已在池中”

如果只是标签化,系统通常只知道“这条申请有没有标签”,不容易知道“另一个申请是不是同一个人”。

3. 不同申请上下文会互相污染

岗位、阶段、面试反馈、拒绝原因,本来属于具体申请,不一定都该沉淀到长期人才资产上。

所以 Odoo 没把 Talent Pool 做成一层轻标签,而是做成了一套“申请记录 + 人才池代表记录 + 同人识别”的结构。


pool_applicant_id 在表达什么

源码里最关键的字段之一,就是 pool_applicant_id

它的含义可以理解成:

  • 这条申请本身是不是池中代表记录
  • 如果不是,它对应的池中代表是谁

而当系统把一个普通申请加入人才池时,向导 talent.pool.add.applicants 有两条分支:

情况一:这个 applicant 本来就已经有 talent_pool_ids

那就直接给它继续追加池子和分类。

情况二:它还不是一个 talent

系统会 copy() 一份新记录:

  • job_id 被清空
  • 写入 talent_pool_ids
  • 合并 categ_ids
  • 新记录把 pool_applicant_id 指向自己
  • 原申请也回指到这个 talent

这非常关键。

它说明 Odoo 的思路不是“把原申请直接改造成人才池记录”,而是:

必要时复制出一个更适合长期复用的 talent 视角。


为什么复制后要把 job_id 清空

这是很多人第一次看源码时最能看出设计味道的细节。

把候选人放入人才池时,新复制的 talent 会把 job_id 设成 False

这几乎是在明说:

人才池不是某个岗位下的附属页签,而是脱离单一岗位上下文的人才资产。

也就是说,原申请可以保留“我当时投的是销售经理”;但池中 talent 代表的是“这个人未来可能还适合别的岗位”。


系统怎么判断“是不是同一个人”

Odoo 在人才池识别上没有只盯一个主键,而是组合使用:

  • email_normalized
  • partner_phone_sanitized
  • linkedin_profile

_compute_is_applicant_in_pool()_compute_talent_pool_count() 里,源码都会去搜:

  • 直接已在池中的申请
  • 与它们在邮箱 / 电话 / LinkedIn 上相同的其他申请

这说明 Odoo 已经接受了招聘里的一个现实:

同一个候选人可能会用不同申请记录出现,但仍然应该被识别成同一个人才资产网络的一部分。

这比只靠姓名靠谱得多,也比只靠邮箱更稳,因为现实里有人会换邮箱、只留电话,或只给 LinkedIn。


为什么“间接在池中”也算在池里

源码明确区分了:

  • direct:自己就有 talent_pool_idspool_applicant_id
  • indirect:虽然自己没直接挂池,但和池中记录共享邮箱、电话或 LinkedIn

这很聪明。

因为招聘现场经常发生这种情况:

  • 候选人去年投过 A 岗并被拉进人才池
  • 今年又投了 B 岗
  • 新申请并没有手工重新挂池

如果系统不能把这两条连起来,人才库就会逐渐碎成很多孤岛。

Odoo 的实现等于在说:

“是否在池中”不只看当前这条记录被没被手工打标,还看它是否能和已知 talent 识别为同一个人。


为什么搜索逻辑甚至专门为去重写了 SQL

_search_is_applicant_in_pool() 里直接写了一段 SQL,用来搜索:

  • 直接已在池中的申请
  • 邮箱命中池中申请的申请
  • 电话命中池中申请的申请
  • LinkedIn 命中池中申请的申请

而注释里也写得很直白:

这是为了在“添加到人才池”的动作里,隐藏重复项

这说明 Odoo 不是把去重留给招聘顾问肉眼判断,而是尽量在搜索域层就减少重复操作。


这套设计最聪明的地方:既保留申请历史,又复用人才资产

如果只保留一个 Applicant,你会失去每次投递的上下文。

如果每次投递都完全独立,又会失去“这是同一个人”的长期积累。

Talent Pool 这套结构恰好在两者之间找了平衡:

  • 原申请继续保留具体岗位、阶段、面试过程
  • 池中 talent 代表长期可复用的人才对象
  • 系统再用邮箱/电话/LinkedIn 把相关申请串起来

这比“标签化”成熟很多。


实施时最容易犯的误解

误解一:人才库就是 candidate tag

这样会低估去重和复用的复杂度。

误解二:进池以后还应保留原岗位 job_id

源码相反,复制成 talent 时会清掉 job_id,因为人才池要脱离单岗位语境。

误解三:只靠邮箱就够了

Odoo 明确同时用了标准化邮箱、清洗后的电话和 LinkedIn,说明单一标识在招聘场景里并不可靠。


一句话记忆

Odoo 的 Talent Pool 不是给申请贴标签,而是把“具体申请”与“长期人才资产”分开,再通过邮箱、电话、LinkedIn 把同一个人重新连起来。

DISCUSSION

评论区

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