All posts
Integrator11 min read

Odoo time-off and leave management: configuration guide for HR integrators

Odoo's time-off module handles allocation, multi-level approval, carry-overs, and payroll integration out of the box - if you configure it correctly. Here is the complete setup reference from leave types to reporting.

Odoo's time-off module (hr.leave) is deceptively deep. On the surface it is a request-and-approval form. Under the hood it ties together allocation policies, multi-level approval, carry-over rules, public holidays, work schedules, and payroll integration - any of which can silently produce wrong results if misconfigured.

This guide covers everything an integrator needs to set up time-off correctly from scratch, including the non-obvious constraints that support questions never surface until after go-live.


Core data model#

Three models do most of the work:

ModelPurpose
hr.leave.typeLeave type definition: annual leave, sick leave, unpaid, etc.
hr.leave.allocationHow many days an employee is entitled to for a given type
hr.leaveIndividual leave request (an absence)

A leave request (hr.leave) cannot be validated unless the employee has a validated allocation (hr.leave.allocation) with enough remaining days for that leave type - unless the type is configured to allow negative balance or no allocation at all.


Leave type configuration#

Navigate to Time Off → Configuration → Activity Types (or in Odoo 17: Time Off → Configuration → Time Off Types).

The fields that matter most:

Approval#

validation_type controls the approval chain:

ValueBehaviour
no_validationAuto-confirmed on submission
hrRequires one approval (HR manager or direct manager depending on settings)
bothTwo-step: employee's manager approves first, then HR validates
managerDirect manager only
setNo allocation required; unlimited

Two-step approval (both) is correct for annual leave in most companies. Single-step (manager) works for sick leave where HR retroactively records rather than approves.

Allocation mode#

allocation_type determines whether employees need a formal allocation record:

  • Fixed by HR (fixed by hr): HR creates allocations manually. Common for annual leave with specific entitlements.
  • Allocated by employee (no): No allocation required. Suitable for unpaid leave or special leave where you track time off but not a balance.
  • Fixed by HR (accrual): Allocation accrues over time via an accrual plan (see below).

Leave validation from payroll#

If you have Odoo Payroll, set Leave Validation to sync absences as payroll inputs automatically. This generates a hr.payslip.worked_days line when a leave is validated - do not configure this without confirming the payroll structure handles it.


Work schedules (critical dependency)#

Time-off days are calculated against an employee's work schedule. A 5-day leave request on an employee with a 4-day work week (Mon–Thu) results in 4 days deducted, not 5.

Before setting up leave types, confirm every employee has:

  1. A work schedule assigned (resource.calendar) via the employee form → Work Information tab
  2. The correct schedule for their contract period

Common mistakes:

  • Using the default 40-hour schedule for part-time employees, causing over-deduction
  • Not setting a work schedule at all (Odoo falls back to the company default)
  • Changing a schedule mid-year without accounting for leaves already validated under the old schedule

Public holidays#

Public holidays are defined as hr.leave.type records with is_public_holiday = True (in Odoo 16+) or as resource.calendar.leaves with a leave type flag. They are globally time-off days that apply to all employees.

Create them under Time Off → Configuration → Public Holidays. Link each holiday to a country if you have multi-country operations - Odoo applies only country-matching holidays to each employee.

Public holidays do not automatically reduce leave deduction. If an employee takes a week off that spans a public holiday and the public holiday is properly configured in their work schedule, the deduction will be 4 working days, not 5. This is correct behaviour but surprises users who manually count days.


Allocations in depth#

Manual allocation#

HR creates hr.leave.allocation records individually or in bulk (Configuration → Allocation Requests or through the employee form). Each allocation has:

  • Employee: can be an individual or a department
  • Leave type
  • Number of days (or hours, if the leave type uses hours)
  • Validity dates: start and optional end date; expired allocations cannot be used
  • Carry-over: whether unused days roll over to the next allocation period

Bulk allocation#

For annual leave, create allocations in bulk:

  1. Go to Time Off → Managers → Allocation Requests
  2. Click New and set Mode to By department or By employee
  3. Set the number of days and validity dates

This creates one allocation record per employee. Validate them in batch with the Action → Validate menu.

Accrual plans#

Accrual plans (hr.leave.accrual.plan) accumulate allocation days over time. Configure them under Time Off → Configuration → Accrual Plans.

Key fields in an accrual plan:

FieldMeaning
Level added perAccrual frequency: daily, weekly, twice a month, monthly
RateDays earned per accrual period
CapMaximum accrued days (leave balance ceiling)
Carry overAt year-end: all days, up to a maximum, or none
MilestoneTime-in-service threshold to unlock higher accrual rate

A typical setup for 20 days/year with monthly accrual: frequency = monthly, rate = 1.67 days/month (20 / 12), cap = 20, carry-over = max 5.

After creating the accrual plan, assign it when creating an allocation: set Accrual Plan on the allocation record and validate it. Odoo runs a cron job nightly to add accrued days.


Leave request flow#

The employee lifecycle for a leave request:

  1. Draft: Employee submits (or HR submits on behalf)
  2. Confirmed: Awaiting manager approval
  3. Validated (first approval): Manager approved; awaiting HR if two-step
  4. Validated: Fully approved; balance deducted and absence shown on calendar
  5. Refused: Rejected at any step

States map to hr.leave.state field values: draft, confirm, validate1, validate, refuse.

Programmatic approval#

To approve or refuse leaves in code (e.g., an automated action):

python
leave = env['hr.leave'].browse(leave_id)
leave.action_validate()  # approve
leave.action_refuse()    # refuse
leave.action_draft()     # reset to draft

action_validate() requires the calling user to have the correct group (hr.group_hr_manager or group_hr_user depending on validation_type). Use sudo() carefully - it bypasses the approval chain entirely.


Calendar integration#

Validated leaves automatically appear on the employee's calendar and on the team calendar view. In Odoo 16+, they also appear in the Gantt view of Project/Planning if the employee is assigned to tasks during the absence period.

The hr.leave record creates a corresponding resource.calendar.leaves entry for the employee's work schedule, which is what prevents task scheduling during that period.


Reporting#

Key reports under Time Off → Reporting:

  • By Employee: leave days per employee per type, current year
  • By Department: summary table; useful for HR budget planning
  • Allocation Analysis: outstanding allocations, expiry dates, remaining balance

The Analysis view (hr.leave.report) joins leaves and allocations into one flat view, which is useful for exports. Filter by status = Validated to see only confirmed absences.


Payroll integration notes#

If you have Odoo Payroll enabled, validated leaves automatically appear in Worked Days on payslips for leave types with payroll sync enabled. Verify:

  1. The leave type has a Work Entry Type configured (hr.work.entry.type)
  2. The work entry type has the correct Time Off flag
  3. The payroll structure includes a rule that handles that work entry type

Missing the work entry type is the most common payroll integration bug - absences disappear from payslips silently.


Common configuration mistakes#

MistakeSymptomFix
No work schedule on employeeAll leave requests deduct 0 daysAssign schedule in employee Work Information tab
Allocation expired before leave taken"Insufficient allocation" on a valid requestExtend validity end date on the allocation
Wrong validation_typeLeaves auto-approve or need unnecessary stepsMatch validation_type to the company policy
Accrual not runningBalance does not growConfirm the nightly cron (Accrual Plans cron job) is active in Settings → Technical → Automation
Public holiday not in work scheduleHolidays count against leave balanceEnsure public holidays are registered AND linked to the company/country

For HR payroll configuration see the Odoo HR and payroll guide. For multi-company leave management see the multi-company setup guide.

Try ERPeek on your own Odoo module - ask questions, scaffold tests, and explore your codebase in plain language.

Get started free