先说结论
在 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 不是“一个显示用字段”,而是工时制度的骨架。
为什么我把它叫“日历继承链”
因为业务方常以为只要改一个地方,所有地方就该自动一致。
实际更接近一条继承 / 投影链:
- 合同或版本设定表达雇佣事实
- 版本上的 calendar 决定某段时间采用哪套工作制度
- work entry source 决定生成逻辑从哪类制度取数
- 具体 work entries 才是工资期里可消费的标准化结果
所以“继承”不是说所有字段机械复制,而是说:
上层制度变化,只有在正确时间区间和正确生成入口里,才会投影成下层结果。
如果你跳过中间层,只盯最终结果,就会觉得系统“没同步”。
最容易踩的 5 个坑
1)把合同模板当成制度总开关
模板只保证文书一致,不保证工时制度已经切换。
2)改了员工当前日历,却忘了版本边界
如果系统按版本时间线取日历,直接改当前员工字段很可能污染历史或根本不落到正确期间。
3)未来生效合同录进去了,就以为本月工时会自动改写
如果未来版本还没到生效时间,当前工资期照旧按当前版本生成。
4)月中换班制后不重建 work entry
旧结果仍然带着旧区间语义,手补几条最容易越修越乱。
5)把“兼职 / 全职”理解成文字标签,而不是排班制度
对 payroll 来说,真正有用的不是合同里写了什么形容词,而是:
- 该员工在对应区间适用哪套日历
- 由此生成了哪些标准 work entries
这对实施最重要的提醒是什么
如果你们经常要处理:
- 转正
- 续签
- 兼职转全职
- 全职转弹性工时
- 月中调整周工时
那内部流程一定要把这四件事拆开确认:
- 合同文本是否生成正确
- 合同 / 版本起止是否正确
- 对应日历是否落在正确版本上
- 是否需要重建工资期 work entries
只确认第 1 步,后面三步不做,几乎一定会留下 payroll 雷。
我会怎么跟 HR 解释
如果 HR 问:
“我明明已经生成新合同了,为什么系统工时还是旧的?”
我会说:
因为 Odoo 不会把合同 PDF 当成发薪依据。发薪前真正生效的是版本时间线、work entry source 和资源日历。合同文书只是把制度写出来,不是把工时算出来。
一句话记忆
Odoo 的合同模板负责“写出来”,员工版本和日历负责“何时生效”,work entry 才负责“最后怎么算”。别把三层东西误认为同一个开关。
DISCUSSION
评论区