人力资源

Odoo 合同为什么不是“选个模板生成 PDF”就结束:看懂 Contract Template、work_entry_source 与日历继承链

很多团队把合同理解成一张 PDF,觉得签完、生成完就结束了。可在 Odoo 里,合同文档模板、员工版本时间线、work entry source 和 resource calendar 其实是四层不同对象。把它们混在一起,最容易出现“合同写了兼职,但工时还是全职”的错觉。

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

先说结论

在 Odoo 里,“合同”至少有四层语义:

  • 合同文档模板:负责生成或承载合同文本
  • 员工版本 / 合同时间线:回答某一天到底适用哪段雇佣事实
  • work entry source:回答工时应从哪种制度生成
  • resource calendar:回答理论排班长什么样

所以很多实施现场最常见的误会其实是:

把“文档生成成功”误以为“工时制度已经切换成功”。

这两件事在 Odoo 里根本不是一层。


第一层:Contract Template 解决的是“合同长什么样”,不是“工资怎么算”

Odoo 文档侧的合同模板,本质上是为了:

  • 复用合同条款
  • 带出候选人 / 员工基本信息
  • 标准化 offer / contract document 的生成
  • 让 HR 不必每次手写一份全新的文本

它回答的问题是:

  • 这份合同该显示哪些条款
  • 试用期、岗位、公司主体怎么呈现
  • 文书怎么生成得一致

不直接回答这些问题:

  • 下周开始员工按哪套班表生成 work entry
  • 这个人是标准班、弹性班还是 fully flexible
  • 薪资期里哪几天算 Attendance、哪几天算 Out of Contract

也就是说,模板更像“法律 / 行政表达层”,不是 payroll engine 本身。


第二层:真正进入时间语义的是员工版本,而不是 PDF

现有 HR 时间线设计里,hr.employee 负责“这个人是谁”,hr.version 负责“这个人在某段时间处于什么状态”。

这层时间语义才会决定:

  • 合同开始和结束边界
  • 某个日期生效的岗位 / 部门 / 审批人
  • 对应的工时日历和排班制度
  • 后续 work entry 该落在哪段区间

所以即使 HR 已经把合同文档发出并签好了,只要对应的版本边界、合同起止或版本生效日没有正确落在系统里,工时世界仍然可能保持旧状态。

这也是为什么很多人会说:

  • 合同上写了 20 小时兼职
  • 结果 payroll 还是跑出 40 小时标准班

往往不是模板错,而是时间线没切过去


第三层:work_entry_source 决定“工时从哪里来”

现有人力资源文章里已经提到,hr.version 这层会影响 work entry 的默认生成逻辑,其中一个关键心智就是:

  • 工时不是从“合同 PDF”直接抄出来
  • 工时也不是看 HR 口头说明
  • 而是看系统当前定义的 work entry source 和对应版本语义

当默认来源是 calendar 时,系统心里真正问的是:

  • 这名员工在这段期间理论上应该按什么排班上班
  • 哪些时段属于工作、午休、请假或合同外
  • 对应该生成哪类 work entry

这解释了一个很现实的问题:

为什么“合同变了”不一定立刻“work entry 跟着变”

因为两者中间还隔着:

  • 版本时间线是否切换
  • resource_calendar_id 是否更新
  • 新版本是否已进入生效区间
  • 当前工资期是否需要重建 work entries

所以合同文书是入口,但不是终点。


第四层:resource calendar 才是理论排班的母体

从考勤和请假相关源码能看出,大量 HR 计算最终都会落到 resource_calendar_id

  • 请假天数怎么算
  • Mandatory Day 要不要挡住申请
  • 当前是否在工作时间内
  • 工作区间和午休区间如何切
  • work entry 的理论工作时段怎么生成

hr_attendance 相关逻辑里,员工排班还会按版本期间去聚合不同日历,最后把:

  • 工作区间
  • 午休区间
  • 日历上的 leave 区间
  • fully flexible 区间

分别组织出来。

这说明 resource_calendar_id 不是“一个显示用字段”,而是工时制度的骨架。


为什么我把它叫“日历继承链”

因为业务方常以为只要改一个地方,所有地方就该自动一致。

实际更接近一条继承 / 投影链:

  1. 合同或版本设定表达雇佣事实
  2. 版本上的 calendar 决定某段时间采用哪套工作制度
  3. work entry source 决定生成逻辑从哪类制度取数
  4. 具体 work entries 才是工资期里可消费的标准化结果

所以“继承”不是说所有字段机械复制,而是说:

上层制度变化,只有在正确时间区间和正确生成入口里,才会投影成下层结果。

如果你跳过中间层,只盯最终结果,就会觉得系统“没同步”。


最容易踩的 5 个坑

1)把合同模板当成制度总开关

模板只保证文书一致,不保证工时制度已经切换。

2)改了员工当前日历,却忘了版本边界

如果系统按版本时间线取日历,直接改当前员工字段很可能污染历史或根本不落到正确期间。

3)未来生效合同录进去了,就以为本月工时会自动改写

如果未来版本还没到生效时间,当前工资期照旧按当前版本生成。

4)月中换班制后不重建 work entry

旧结果仍然带着旧区间语义,手补几条最容易越修越乱。

5)把“兼职 / 全职”理解成文字标签,而不是排班制度

对 payroll 来说,真正有用的不是合同里写了什么形容词,而是:

  • 该员工在对应区间适用哪套日历
  • 由此生成了哪些标准 work entries

这对实施最重要的提醒是什么

如果你们经常要处理:

  • 转正
  • 续签
  • 兼职转全职
  • 全职转弹性工时
  • 月中调整周工时

那内部流程一定要把这四件事拆开确认:

  1. 合同文本是否生成正确
  2. 合同 / 版本起止是否正确
  3. 对应日历是否落在正确版本上
  4. 是否需要重建工资期 work entries

只确认第 1 步,后面三步不做,几乎一定会留下 payroll 雷。


我会怎么跟 HR 解释

如果 HR 问:

“我明明已经生成新合同了,为什么系统工时还是旧的?”

我会说:

因为 Odoo 不会把合同 PDF 当成发薪依据。发薪前真正生效的是版本时间线、work entry source 和资源日历。合同文书只是把制度写出来,不是把工时算出来。


一句话记忆

Odoo 的合同模板负责“写出来”,员工版本和日历负责“何时生效”,work entry 才负责“最后怎么算”。别把三层东西误认为同一个开关。

DISCUSSION

评论区

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