先说结论
很多用户会把请假理解成一句话:
余额够,就能提;余额不够,就不能提。
但 Odoo 的 hr_holidays 源码显示,真正决定一张请假单能不能成立的,往往至少有三道闸:
- 审批闸:这类假走经理批、HR 批,还是双重审批
- 额度闸:是否允许负余额,以及能透支到什么程度
- 重叠闸:同一个人、同一时间段的请假能不能叠加
所以请假不是简单减法,而是一套制度校验。
第一层:为什么审批不是统一一条线
在 hr.leave.type 里,源码定义了 leave_validation_type。
常见值包括:
no_validationmanagerhrboth
这意味着 Odoo 默认就承认:不同假种,审批权不一定在同一层。
比如:
- 有些假可以自动批准
- 有些只需要直属经理
- 有些必须 HR 复核
- 有些要经理先批,再到 HR 二批
这对现实企业非常重要,因为年假、病假、调休、无薪假,本来就不该一把尺子量到底。
双重审批为什么不是“多点一步流程”而已
源码里的 _check_double_validation_rules() 很值得看。
它会检查:
- 当前用户是不是 Time Off Officer / Manager
- 第一批的时候,是不是该员工的 leave manager
- 第二批的时候,有没有更高层的审批权限
也就是说,双重审批不是表面上“点两次 Approve”。
它真正表达的是:
第一层和第二层批准,不只是状态变化,而是不同角色的业务责任。
这和很多项目里的误区正好相反:有些团队以为二次审批只是为了“更严格”,实际上它是在明确责任边界。
第二层:为什么允许负余额后,系统也不会无限放行
很多人看到 allows_negative,会误以为只要打开了,员工就能一直请到负数。
源码不是这样干的。
在 hr.leave.type 里,Odoo 同时还有:
allows_negativemax_allowed_negative
而在 hr.leave._check_validity() 里,它会用 allocation 数据判断:
- 有没有有效 allocation
- 当前
virtual_remaining_leaves是否低于负余额上限
也就是说,允许负余额的真正含义不是“随便透支”,而是:
制度允许在明确阈值内透支。
这很符合 HR 现实:
- 新员工年假先借用几天
- 调休先休后补
- 特殊情况允许小幅透支
但企业通常不希望它失控,所以 Odoo 用 max_allowed_negative 把边界钉死了。
为什么没有 allocation 时,哪怕允许负余额也不一定能过
源码里有一个很容易被忽略但很关键的判断:
如果 max_leaves 根本不存在,系统会报:
- 你没有任何 allocation
- 请先申请 allocation 再提交请假
这说明 Odoo 不是简单看“剩余额度能不能小于 0”,而是先问一个更根本的问题:
这类假有没有被制度性地发给过你。
所以负余额不是“无中生有”,而是“在既有制度类型下的可控透支”。
第三层:为什么重叠请假不会被默默合并
在 _compute_dashboard_warning_message() 里,Odoo 会搜索同员工、同时间段、状态未取消/拒绝的其他请假。
并且只要假种配置了 allow_request_on_top = False,就会把重叠记录列出来。
这很有意思,因为它告诉你:
- Odoo 默认认为请假时间段是一种占用
- 同一时间的占用,通常不应随便叠加
- 但某些特殊假种可以允许叠加
比如现实里可能出现:
- 公司统一停工日
- 特殊福利假与别的制度并存
- 某些技术性类型不应阻塞正常 Time Off
所以 Odoo 没有一刀切,而是把是否允许“盖在上面”交给假种配置。
allow_request_on_top 解决的不是 UI 提示,而是制度例外
很多人会把这个字段理解成“避免前端弹警告”。
其实不是。
它的设计意义更像是:
有些时间占用是互斥的,有些可以共存。
如果一家公司把所有假种都设成不可叠加,结果往往是:
- 公共假期、补偿性休假、特殊技术假全混在一起互相打架
但如果全部都允许叠加,又会让同一时段同时出现多张逻辑上冲突的申请。
所以这不是一个“友好提示开关”,而是制度语义开关。
为什么请假创建后还要再次 _check_validity()
在 create() 里,Odoo 建完 hr.leave 后会立刻调用 _check_validity()。
这说明系统的思路是:
- 先形成记录
- 再用统一逻辑校验它是否真成立
而不是完全依赖表单 onchange。
这对实施很重要,因为你以后无论是:
- 前端提交
- 批量导入
- 自动化脚本
- 自定义 API
最终都得过同一套有效性约束。
也就是说,真正的业务边界在模型层,不在界面层。
为什么已经开始的请假更难改
write() 里还有一层保护:
如果普通用户去改一个已经开始的请假,而且他不是相应经理,就会被拦下。
这很合理,因为假一旦开始,影响往往已经外溢到:
- 排班
- 出勤
- Work Entry
- 审批责任
所以系统不鼓励员工在事后随便回头改历史。
这套设计的现实价值
1. 它把“有没有假”与“谁来批”分开了
余额问题不等于审批问题。
2. 它把“能不能透支”做成制度边界,而不是人为口头规则
允许负余额可以,但上限必须清楚。
3. 它把时间冲突也纳入了请假模型
不是等后面出错再补救,而是在申请阶段就尽量提示和拦截。
最容易犯的误解
最常见的误解是:
“只要给员工 allocation,这个假种就已经配好了。”
其实还不够。
你还得一起看:
leave_validation_typeallows_negativemax_allowed_negativeallow_request_on_top- 是否已有其他重叠请假
这些一起决定了 Time Off 是“能申请”,还是“看起来能填,实际上过不了”。
一句话记忆
Odoo 请假不是只看余额,而是同时过审批、额度和时间冲突三道闸;负余额也不是无限透支,而是受上限约束的制度例外。
DISCUSSION
评论区