INTRODUCTION

Your Team Is Tracking Leave in a Spreadsheet. That Spreadsheet Is Lying to You.

We audit dozens of Odoo implementations every year. The pattern is always the same: Accounting runs on Odoo, Sales runs on Odoo, Inventory runs on Odoo — but leave management still lives in a shared Google Sheet that someone created during onboarding week. HR manually counts remaining days. Managers approve time off via email. Nobody trusts the balances because three people edited the same cell last month.

Odoo 19's Time Off module solves this completely, but only if you configure it correctly. The default installation gives you a single "Legal Leaves" type and a manual allocation workflow — barely better than the spreadsheet. The real power is in the leave type hierarchy, accrual plans, multi-level approval chains, public holiday calendars, and payroll integration that most teams never set up because the documentation assumes you already know what you want.

This guide walks you through the complete configuration — from defining leave types that match your country's labor code, to building accrual plans that auto-allocate days monthly, to wiring approval workflows that route through department heads and HR. Every step is production-tested across companies with 50 to 2,000 employees.

01

How to Configure Leave Types in Odoo 19 for Accurate Absence Tracking

Leave types are the foundation of the entire Time Off module. Every request, allocation, report, and payroll deduction traces back to a leave type. Getting these wrong means every downstream process is wrong too.

Navigate to the Configuration

Go to Time Off → Configuration → Leave Types. Odoo 19 ships with a few defaults, but you should replace them with types that match your actual policies. Here is a typical structure for a mid-size company:

Leave TypeRequires AllocationApprovalAccrualPayroll Code
Annual LeaveYesManager + HRMonthly accrualANNUAL
Sick LeaveYes (capped)Manager onlyYearly resetSICK
Unpaid LeaveNo allocationManager + HRNoneUNPAID
Work From HomeNo allocationManager onlyNone
CompensatoryYes (manual)HR onlyNoneCOMP
Parental LeaveYes (manual)HR onlyNonePARENTAL

Key Leave Type Settings

Each leave type has configuration options that dramatically change behavior. Here are the ones that matter:

XML — Leave Type Data Record
<record id="leave_type_annual" model="hr.leave.type">
  <field name="name">Annual Leave</field>
  <field name="requires_allocation">yes</field>
  <field name="employee_requests">yes</field>
  <field name="leave_validation_type">both</field>
  <field name="allocation_validation_type">hr</field>
  <field name="color">4</field>
  <field name="request_unit">half_day</field>
  <field name="company_id" ref="base.main_company"/>
</record>

<record id="leave_type_sick" model="hr.leave.type">
  <field name="name">Sick Leave</field>
  <field name="requires_allocation">yes</field>
  <field name="employee_requests">no</field>
  <field name="leave_validation_type">manager</field>
  <field name="support_document">True</field>
  <field name="color">2</field>
  <field name="request_unit">day</field>
</record>

<record id="leave_type_unpaid" model="hr.leave.type">
  <field name="name">Unpaid Leave</field>
  <field name="requires_allocation">no</field>
  <field name="leave_validation_type">both</field>
  <field name="unpaid">True</field>
  <field name="color">1</field>
</record>
  • requires_allocation — set to yes for any leave type with a balance (annual, sick). Set to no for unlimited types (unpaid, WFH).
  • leave_validation_type — controls the approval chain: no_validation (auto-approved), manager (single approval), both (manager then HR), or hr (HR only).
  • request_unit — whether employees can request in day, half_day, or hour increments. Half-day is the most common for annual leave.
  • support_document — when enabled, employees can attach a doctor's certificate or supporting file to the leave request. Essential for sick leave compliance.
  • unpaid — flags the leave type so payroll knows to deduct salary for these days. If you miss this flag, unpaid leave employees get paid anyway.
Color-Code Your Leave Types

Odoo 19 uses the color field to differentiate leave types on the calendar view. Assign distinct colors (0–11) to each type. When a manager looks at the team calendar, they should instantly distinguish annual leave (green) from sick leave (red) from WFH (blue) without reading labels. This small detail dramatically improves adoption.

02

Setting Up Leave Allocation Rules in Odoo 19 for Automatic Balance Management

Allocations define how many days of each leave type an employee gets. Without allocations, employees with allocation-required leave types cannot submit requests. Odoo 19 offers three allocation strategies:

StrategyHow It WorksBest For
ManualHR creates allocation records one by oneCompensatory days, special grants
Bulk by DepartmentHR allocates N days to all employees in a department or tag at onceFixed annual entitlements
Accrual PlanDays accumulate automatically based on a scheduleAnnual leave that accrues monthly

Creating a Bulk Allocation

For companies that grant all annual leave on January 1st, bulk allocation is the fastest approach. Navigate to Time Off → Managers → Allocations → New:

Python — Bulk Allocation via Server Action
# Server action: Allocate annual leave to all active employees
# Triggered by a scheduled action on January 1st each year

employees = env['hr.employee'].search([
    ('active', '=', True),
    ('company_id', '=', env.company.id),
])

leave_type = env.ref('hr_holidays.leave_type_annual')

for employee in employees:
    # Check seniority for tiered allocation
    years = (fields.Date.today() - employee.first_contract_date).days / 365.25
    if years <= 2:
        days = 20
    elif years <= 5:
        days = 23
    elif years <= 10:
        days = 25
    else:
        days = 28

    env['hr.leave.allocation'].create({{
        'name': f'Annual Leave {{fields.Date.today().year}} - {{employee.name}}',
        'employee_id': employee.id,
        'holiday_status_id': leave_type.id,
        'number_of_days': days,
        'date_from': fields.Date.today().replace(month=1, day=1),
        'date_to': fields.Date.today().replace(month=12, day=31),
        'state': 'validate',
    }})

env.cr.commit()

This script implements seniority-tiered allocations — employees with more tenure get more days. The date_from and date_to fields scope the allocation to the calendar year so balances reset automatically. Setting state to validate skips the approval step since this is an administrative bulk operation.

Carryover Warning

If your policy allows unused days to carry over, do not set date_to on the allocation. Instead, create a new allocation each year for the new entitlement and let the old one remain open. If you set date_to to December 31st and an employee has 5 unused days, those days vanish at midnight. For carryover with a cap (e.g., max 5 days), use an accrual plan with the carryover setting.

03

Configuring Accrual Plans in Odoo 19 for Automatic Monthly Leave Accumulation

Accrual plans are the most powerful allocation mechanism in Odoo 19. Instead of granting 24 days upfront, you grant 2 days per month — which means an employee who joins in July gets 12 days for the year, not 24. This is how most labor codes actually work, and it prevents the "new hire takes 3 weeks off in February" problem.

Navigate to Accrual Plan Configuration

Go to Time Off → Configuration → Accrual Plans and create a new plan:

XML — Accrual Plan Data Record
<record id="accrual_plan_annual" model="hr.leave.accrual.plan">
  <field name="name">Standard Annual Leave Accrual</field>
  <field name="transition_mode">immediately</field>
  <field name="carryover_date">year_start</field>
  <field name="carryover_day">1</field>
  <field name="carryover_month">jan</field>
</record>

<!-- Level 1: First 2 years of service -->
<record id="accrual_level_junior"
        model="hr.leave.accrual.level">
  <field name="accrual_plan_id"
         ref="accrual_plan_annual"/>
  <field name="start_count">0</field>
  <field name="start_type">day</field>
  <field name="added_value">1.67</field>
  <field name="added_value_type">day</field>
  <field name="frequency">monthly</field>
  <field name="first_day">1</field>
  <field name="cap_accrued_time">True</field>
  <field name="maximum_leave">20</field>
</record>

<!-- Level 2: After 2 years -->
<record id="accrual_level_senior"
        model="hr.leave.accrual.level">
  <field name="accrual_plan_id"
         ref="accrual_plan_annual"/>
  <field name="start_count">730</field>
  <field name="start_type">day</field>
  <field name="added_value">2.08</field>
  <field name="added_value_type">day</field>
  <field name="frequency">monthly</field>
  <field name="first_day">1</field>
  <field name="cap_accrued_time">True</field>
  <field name="maximum_leave">25</field>
</record>
  • added_value: 1.67 — this equals 20 days per year (1.67 × 12 = 20.04). The slight overshoot is harmless; the maximum_leave cap catches it.
  • transition_mode: immediately — when an employee crosses the 730-day threshold, they immediately start accruing at the senior rate. The alternative, end_of_accrual, waits until the next accrual period.
  • carryover_date: year_start — unused days carry over (or reset) on January 1st. Combined with a carryover cap on the allocation, this enforces "use it or lose it" policies.
  • Multi-level accrual — the plan has two levels: junior employees (0–2 years) accrue 20 days/year; senior employees (2+ years) accrue 25 days/year. Odoo automatically transitions between levels based on the employee's start date.

Linking an Accrual Plan to Allocations

An accrual plan does nothing until it is linked to an allocation. Create an allocation for each employee (or use a scheduled action to bulk-create them) and set the accrual_plan_id field:

Python — Linking Accrual Plan to Employee Allocations
# Scheduled action: Create accrual-based allocations
# for new employees who don't have one yet

employees = env['hr.employee'].search([
    ('active', '=', True),
    ('company_id', '=', env.company.id),
])

plan = env.ref('your_module.accrual_plan_annual')
leave_type = env.ref('hr_holidays.leave_type_annual')

existing = env['hr.leave.allocation'].search([
    ('accrual_plan_id', '=', plan.id),
    ('state', '=', 'validate'),
]).mapped('employee_id')

new_employees = employees - existing

for emp in new_employees:
    env['hr.leave.allocation'].create({{
        'name': f'Annual Accrual - {{emp.name}}',
        'employee_id': emp.id,
        'holiday_status_id': leave_type.id,
        'number_of_days': 0,
        'accrual_plan_id': plan.id,
        'date_from': emp.first_contract_date or fields.Date.today(),
        'state': 'validate',
    }})
Accrual Cron Timing

Odoo 19 runs the accrual cron (hr.leave.allocation.accrual.cron) once per day at midnight UTC. If your company is in UTC+8 and you expect accruals to update on the 1st of each month local time, they will actually update on the 1st at midnight UTC — which is 8 AM local. This is rarely an issue, but it confuses HR teams who check balances at 7 AM on the 1st and see "last month's" numbers. Adjust the cron schedule if needed via Settings → Technical → Automation → Scheduled Actions.

04

Building Multi-Level Leave Approval Workflows in Odoo 19

Approval workflows determine who can approve a leave request and in what order. Odoo 19 provides four validation modes, but the real complexity comes from combining them with employee hierarchy, department structure, and leave type rules.

Validation ModeApproval ChainUse Case
no_validationAuto-approved immediatelyWork From Home, training
managerDirect manager approvesSick leave, short absences
hrHR Officer approvesParental leave, compensatory
bothManager approves first, then HR confirmsAnnual leave, extended absences

Setting Up the Employee Hierarchy

The manager and both validation modes rely on the Leave Manager field on the employee record. If this field is empty, Odoo falls back to the department manager. If both are empty, the request sits in limbo forever — no one sees it in their approval queue.

Python — Audit Script: Find Employees Without Leave Approvers
# Run in shell or as a server action to find orphaned employees
orphaned = env['hr.employee'].search([
    ('active', '=', True),
    ('leave_manager_id', '=', False),
    ('department_id.manager_id', '=', False),
])

if orphaned:
    names = ', '.join(orphaned.mapped('name'))
    raise UserError(
        f"These employees have no leave approver: {{names}}. "
        "Set a Leave Manager on the employee form or assign "
        "a manager to their department."
    )

Run this script before going live. We have seen companies go months without realizing that 15% of their workforce had no leave approver configured — those employees submitted requests that nobody ever saw.

Custom Approval Rules Based on Duration

A common policy: leave requests under 3 days need only manager approval; requests of 3 days or more need manager and HR. Odoo 19 doesn't support this natively, but you can implement it with a small module override:

Python — Custom Duration-Based Validation
from odoo import models, api

class HrLeave(models.Model):
    _inherit = 'hr.leave'

    @api.depends('number_of_days', 'holiday_status_id')
    def _compute_validation_type(self):
        """Override validation type based on leave duration.
        Requests >= 3 days require both manager and HR.
        """
        for leave in self:
            base_type = leave.holiday_status_id.leave_validation_type
            if (base_type == 'manager'
                    and leave.number_of_days >= 3):
                leave.validation_type = 'both'
            else:
                leave.validation_type = base_type
Manager Self-Approval Problem

By default, Odoo 19 prevents employees from approving their own leave requests. But what about department managers? They are their own "leave manager." If a department head submits annual leave with manager validation, Odoo lets them approve their own request. To prevent this, either set the department manager's leave_manager_id to their own manager or a senior HR officer, or enforce both validation for all leave types so HR always has the final say.

05

Managing Public Holidays and Company-Wide Closures in Odoo 19

Public holidays prevent the Time Off module from counting those days against an employee's balance. If Christmas is a public holiday, a leave request from December 23rd to 27th should deduct 3 working days, not 5. But this only works if the public holiday calendar is correctly configured and linked.

Configure Public Holidays

Navigate to Time Off → Configuration → Public Holidays. Create entries for each statutory holiday:

XML — Public Holiday Data Records
<record id="ph_2026_new_year"
        model="resource.calendar.leaves">
  <field name="name">New Year's Day</field>
  <field name="date_from">2026-01-01 00:00:00</field>
  <field name="date_to">2026-01-01 23:59:59</field>
  <field name="resource_id" eval="False"/>
  <field name="calendar_id"
         ref="resource.resource_calendar_std"/>
</record>

<record id="ph_2026_christmas"
        model="resource.calendar.leaves">
  <field name="name">Christmas Day</field>
  <field name="date_from">2026-12-25 00:00:00</field>
  <field name="date_to">2026-12-25 23:59:59</field>
  <field name="resource_id" eval="False"/>
  <field name="calendar_id"
         ref="resource.resource_calendar_std"/>
</record>

<!-- Company-wide closure: Dec 25-Jan 1 -->
<record id="ph_2026_winter_closure"
        model="resource.calendar.leaves">
  <field name="name">Winter Closure</field>
  <field name="date_from">2026-12-25 00:00:00</field>
  <field name="date_to">2027-01-01 23:59:59</field>
  <field name="resource_id" eval="False"/>
  <field name="calendar_id"
         ref="resource.resource_calendar_std"/>
</record>
  • resource_id set to False — this is the critical setting. When resource_id is empty, the leave applies to all employees using this work schedule. If you set a resource, it only applies to that specific employee.
  • calendar_id — links the holiday to a specific work schedule. If you have multiple schedules (e.g., full-time 40h vs. part-time 20h), you may need to create public holidays on each calendar — or create them on all calendars via a scheduled action.

Multi-Country Public Holidays

For companies with employees in multiple countries, create one work schedule per country and attach country-specific public holidays to each. An employee in France gets French holidays; an employee in Canada gets Canadian ones. The work schedule is set on the employee's contract under Working Schedule.

Regional vs. National Holidays

Many countries have regional holidays (e.g., US state holidays, German Bundesland holidays, Canadian provincial holidays). The cleanest approach is to create work schedule variants per region: "Standard 40h — Ontario" and "Standard 40h — Quebec." This avoids the complexity of employee-level holiday overrides and keeps the calendar clean for managers reviewing team availability.

06

Calendar Integration and Leave Reporting in Odoo 19

The Time Off module shines when connected to the rest of Odoo. Calendar integration gives managers real-time visibility into team availability, and the reporting tools surface trends that HR needs for workforce planning.

Team Calendar View

Managers access the team calendar via Time Off → Managers → Overview. This shows all employees in the manager's department(s) on a Gantt-style timeline. Color-coded by leave type, it immediately answers the question "who's off next week?"

To make this view useful, ensure every leave type has a distinct color and that department hierarchies are correct. A manager only sees employees in departments they manage — if the org chart is wrong, the calendar is incomplete.

Outlook and Google Calendar Sync

Odoo 19 syncs approved leave requests to the employee's linked calendar (Google or Outlook) if the Calendar module is installed. The sync creates all-day events with the leave type as the title. This means colleagues outside Odoo can see when someone is off without logging into Odoo.

Key Reports

Navigate to Time Off → Reporting → Analysis for the pivot and graph views. The most useful analyses:

Python — Custom Leave Report: Department Utilization
# Server action or Jupyter-style analysis
# Shows leave utilization rate per department

from collections import defaultdict

departments = env['hr.department'].search([])
report = []

for dept in departments:
    employees = env['hr.employee'].search([
        ('department_id', '=', dept.id),
        ('active', '=', True),
    ])
    if not employees:
        continue

    total_allocated = 0
    total_taken = 0

    for emp in employees:
        allocations = env['hr.leave.allocation'].search([
            ('employee_id', '=', emp.id),
            ('state', '=', 'validate'),
            ('holiday_status_id.requires_allocation', '=', 'yes'),
        ])
        total_allocated += sum(allocations.mapped('number_of_days'))

        leaves = env['hr.leave'].search([
            ('employee_id', '=', emp.id),
            ('state', '=', 'validate'),
            ('date_from', '>=', '2026-01-01'),
            ('date_to', '<=', '2026-12-31'),
        ])
        total_taken += sum(leaves.mapped('number_of_days'))

    utilization = (total_taken / total_allocated * 100
                   if total_allocated else 0)
    report.append({{
        'department': dept.name,
        'allocated': total_allocated,
        'taken': total_taken,
        'utilization': f'{{utilization:.1f}}%',
    }})

# Sort by lowest utilization — these departments
# may have employees at risk of losing days
report.sort(key=lambda r: r['utilization'])

This report highlights departments where employees are not using their leave — a compliance risk in many jurisdictions and a burnout signal in all of them. Run it quarterly and share results with department heads.

Payroll Integration

When the Payroll module is installed, approved leave requests automatically generate work entries that affect salary calculation:

  • Paid leave (annual, sick) — creates a work entry with type "Leave" that does not reduce salary.
  • Unpaid leave — creates a work entry with type "Unpaid" that reduces salary proportionally. The deduction formula is: monthly_salary / working_days_in_month * unpaid_days.
  • Leave type → work entry type mapping — configured in Time Off → Configuration → Leave Types → Payroll tab. Each leave type maps to a work entry type which maps to a salary rule. If this mapping is missing, the leave won't affect payroll at all.
07

4 Time Off Configuration Mistakes That Create Payroll Errors and Compliance Gaps

1

Missing Work Schedule on Employee Contracts

The Time Off module calculates leave duration based on the employee's working schedule (resource calendar). If the contract has no working schedule, Odoo defaults to the company's standard 40-hour week. A part-time employee on a 3-day week who requests Monday to Friday off gets charged 5 days instead of 3 because the system doesn't know they only work Mon/Wed/Fri.

Our Fix

Create a working schedule for every employment pattern in your organization. Link it to the employee's active contract. Audit with: env['hr.employee'].search([('resource_calendar_id', '=', False)]).

2

Public Holidays Not Linked to All Work Schedules

You created Christmas as a public holiday on the "Standard 40h" calendar. But your part-time employees use "Part-Time 20h" — a different calendar. They request December 23–27 and get charged for Christmas Day because their calendar doesn't know it's a holiday. The fix is simple but tedious: public holidays must exist on every active work schedule.

Our Fix

Write a scheduled action that copies public holidays across all active calendars. Run it whenever you add new holidays or new work schedules. This prevents silent drift between calendars.

3

Accrual Plans Without a Maximum Cap

An accrual plan without cap_accrued_time enabled will keep adding days forever. An employee who never takes leave will accumulate 40, 60, 80+ days over time. When they eventually resign, your company owes a massive payout for unused leave. In most jurisdictions, accrued leave is a financial liability that must appear on your balance sheet.

Our Fix

Always set maximum_leave on accrual levels. For annual leave, cap at the annual entitlement (e.g., 25 days). For carryover policies, set a separate carryover maximum. Alert managers when employees exceed 80% of their balance without a planned vacation.

4

Leave Type "Unpaid" Flag Missing from Payroll Mapping

You created an "Unpaid Leave" type and set unpaid=True on the leave type. But in the Payroll module, the work entry type mapped to this leave type doesn't have the corresponding salary rule to deduct pay. Result: the employee is marked as "unpaid leave" in Time Off, but their payslip shows full salary. Nobody catches it until the quarterly audit.

Our Fix

After creating any leave type, immediately verify the full chain: Leave Type → Work Entry Type → Salary Rule. Generate a test payslip for an employee with that leave type and confirm the deduction appears. Document this chain in your implementation notes.

BUSINESS ROI

What Proper Leave Management Saves Your Organization

Time Off management isn't an HR convenience feature — it's a financial control. Misconfigured leave systems create real costs: overpayments, compliance fines, understaffed teams, and burned-out employees who never take vacation.

90%Less HR Admin Time

Accrual plans, auto-approval workflows, and self-service requests eliminate the back-and-forth emails. HR stops being a leave calculator and starts being strategic.

0Payroll Errors from Leave

When leave types map correctly to work entries and salary rules, unpaid days deduct automatically. No manual payroll adjustments, no correction payslips.

24/7Employee Self-Service

Employees check balances, submit requests, and track approvals from the Odoo portal or mobile app. No emails to HR asking "how many days do I have left?"

The hidden ROI is compliance confidence. When leave balances, accruals, and payroll deductions are automated end-to-end, your year-end audit becomes a data export instead of a spreadsheet reconstruction exercise. In regulated industries, this alone justifies the configuration effort.

SEO NOTES

Optimization Metadata

Meta Desc

Configure Odoo 19 Time Off management: leave types, accrual plans, multi-level approval workflows, public holidays, calendar sync, and payroll integration.

H2 Keywords

1. "How to Configure Leave Types in Odoo 19 for Accurate Absence Tracking"
2. "Setting Up Leave Allocation Rules in Odoo 19 for Automatic Balance Management"
3. "Configuring Accrual Plans in Odoo 19 for Automatic Monthly Leave Accumulation"
4. "Building Multi-Level Leave Approval Workflows in Odoo 19"
5. "4 Time Off Configuration Mistakes That Create Payroll Errors and Compliance Gaps"

Stop Tracking Leave in Spreadsheets

Every manual leave calculation is a payroll error waiting to happen. Every email-based approval is a compliance gap waiting to be audited. Every spreadsheet balance is a number that three people will disagree about next month.

If you're running Odoo 19 and still managing time off outside the system, we can help. We configure leave types, accrual plans, approval workflows, and payroll integration tailored to your country's labor code and your company's policies. The setup takes days; the spreadsheet headaches it eliminates last years.

Book a Free HR Module Assessment