Time off allocations and accruals

An allocation (hr.leave.allocation) grants an employee an amount of time off of a type that requires it (requires_allocation, see Time off types). The amount is either fixed, or earned over time by an accrual plan (hr.leave.accrual.plan) and its levels (hr.leave.accrual.level). The time off requests of the employee are deducted from the allocations to compute the balance, which is checked when requesting time off.

Allocations are also created by hr_holidays itself: from the accrual plan of a working schedule, and from the time rules crediting overtime as compensatory time off.

Zobacz także

Time off for the time off types and requests.

Informacja

As in Time off, the examples run in odoo-bin shell with the demo data, as the administrator: env = env(user=env.ref('base.user_admin')).

Allocations

An allocation belongs to an employee (employee_id) and a type (work_entry_type_id), and is valid from date_from to date_to (both date; no date_to means no end). Time off is only deducted from an allocation for its part within the validity of the allocation.

number_of_days

The amount granted, always in days, also for types allocated in hours. number_of_hours is computed from it with the hours per day of the employee’s schedule at date_from (24 for a fully flexible schedule) and can be written, which sets number_of_days (for an accrual allocation, the schedule at its next accrual is used). type_request_unit is the unit the allocation is displayed and accrued in: the added_value_type of the accrual plan, else the type’s unit_of_measure (_get_request_unit()).

state

confirm (To Approve, the only state allowed at creation), validate1 (Second Approval), validate (Approved) or refuse. Only approved allocations count.

accrual_plan_id

Set for accrual allocations, see Accrual plans; the plan’s type must be the allocation’s type.

The approval works as for time off requests, with the type’s allocation_validation_type: types without validation are approved on creation, the others with action_approve() (to validate, or to validate1 for the first approval of a type validated twice) and action_refuse(). Only time off administrators approve their own allocations. Employees can request allocations of the types with employee_requests.

An approved allocation cannot be reduced below the time already taken from it; only allocations to approve or refused, without time off taken, can be deleted. A negative allocation is only allowed for types allowing a negative balance, within their max_allowed_negative.

Balances

_get_consumed_leaves() is the core of every balance. For a set of employees and types, it deducts the requests to approve and approved (confirm, validate1, validate) from the approved allocations, request by request in chronological order, and allocation by allocation in this order:

  1. the allocations with an end date, the one ending first first;

  2. the accrual allocations without end date;

  3. the other allocations without end date.

A request is only deducted from the allocations valid on its dates; the part of a request that no allocation covers is an excess. The result distinguishes:

  • the approved amounts: leaves_taken and remaining_leaves, counting approved requests only;

  • the virtual amounts: virtual_leaves_taken and virtual_remaining_leaves, also counting the requests to approve. The virtual remaining amount is what can still be requested.

Amounts are in the unit_of_measure of the type: keys are prefixed with days_ or hours_ (e.g. days_virtual_remaining_leaves), and the other unit is also given as <unit>_taken and <unit>_remaining. Types without allocation use the key False instead of an allocation.

On top of it:

get_allocation_data()

The balance per employee and type on a date, summed over the allocations valid on that date: {employee: [(type name, data, requires_allocation, type id), ...]}. It feeds the time off dashboard (through get_allocation_data_request) and the checks of the requests.

The computed fields max_leaves, leaves_taken and virtual_remaining_leaves

On hr.leave.allocation (per allocation, today), on hr.work.entry.type (for the employee of the context) and on hr.leave (max_leaves, virtual_remaining_leaves, for the request’s employee and type). They are for display: code should call the methods above.

Creating or changing a request of a type requiring an allocation checks that the virtual balance on its first day covers it (_check_validity()), down to -max_allowed_negative when the type allows a negative balance.

Example

Marc Demo has an allocation of 3 days valid in 2026 and an open-ended one of 10 days, an approved request of 4 days and a request of half a day to approve:

from datetime import date

env = env(user=env.ref('base.user_admin'))
demo = env.ref('base.user_demo')
marc = demo.employee_id
pto = env['hr.work.entry.type'].create({
    'name': 'Paid Time Off',
    'code': 'DOCPTO',
    'count_as': 'absence',
    'requires_allocation': True,
    'request_unit': 'half_day',
    'unit_of_measure': 'day',
    'leave_validation_type': 'manager',
    'allocation_validation_type': 'hr',
})
yearly, open_ended = env['hr.leave.allocation'].create([{
    'employee_id': marc.id,
    'work_entry_type_id': pto.id,
    'number_of_days': 3,
    'date_from': date(2026, 1, 1),
    'date_to': date(2026, 12, 31),
}, {
    'employee_id': marc.id,
    'work_entry_type_id': pto.id,
    'number_of_days': 10,
    'date_from': date(2026, 1, 1),
}])
yearly.state, yearly.name  # ('confirm', 'Paid Time Off (3.0 day(s))')
(yearly | open_ended).action_approve()
yearly.state, yearly.approver_id.name  # ('validate', 'Mitchell Admin')

approved = env['hr.leave'].create({
    'employee_id': marc.id,
    'work_entry_type_id': pto.id,
    'request_date_from': date(2026, 12, 1),
    'request_date_to': date(2026, 12, 4),
})
pending = env['hr.leave'].with_user(demo).create({
    'employee_id': marc.id,
    'work_entry_type_id': pto.id,
    'request_date_from': date(2026, 12, 14),
    'request_date_to': date(2026, 12, 14),
    'request_date_to_period': 'am',
})
(approved.state, approved.number_of_days), (pending.state, pending.number_of_days)
# (('validate', 4.0), ('confirm', 0.5))

consumed, to_recheck = marc._get_consumed_leaves(pto)
dict(consumed[marc][pto][yearly])  # a defaultdict
# {'days_max_leaves': 3.0, 'days_accrual_bonus': 0, 'days_leaves_taken': 3.0,
#  'days_virtual_leaves_taken': 3.0, 'days_remaining_leaves': 0.0,
#  'days_virtual_remaining_leaves': 0.0, 'hours_taken': 24.0, 'hours_remaining': 0.0}
dict(consumed[marc][pto][open_ended])
# {'days_max_leaves': 10.0, 'days_accrual_bonus': 0, 'days_leaves_taken': 1.0,
#  'days_virtual_leaves_taken': 1.5, 'days_remaining_leaves': 9.0,
#  'days_virtual_remaining_leaves': 8.5, 'hours_taken': 8.0, 'hours_remaining': 68.0}

name, data, requires_allocation, type_id = pto.get_allocation_data(marc)[marc][0]
{key: data[key] for key in ('max_leaves', 'leaves_taken', 'remaining_leaves',
                            'virtual_leaves_taken', 'virtual_remaining_leaves',
                            'leaves_requested')}
# {'max_leaves': 13.0, 'leaves_taken': 4.0, 'remaining_leaves': 9.0,
#  'virtual_leaves_taken': 4.5, 'virtual_remaining_leaves': 8.5, 'leaves_requested': 0.5}

env['hr.leave'].create({
    'employee_id': marc.id,
    'work_entry_type_id': pto.id,
    'request_date_from': date(2026, 12, 15),
    'request_date_to': date(2026, 12, 31),
})
# ValidationError: Marc Demo does not have a valid allocation for the leave type
# Paid Time Off to cover that request.

Accrual plans

An accrual plan (hr.leave.accrual.plan) earns time for its type (work_entry_type_id) over time. Its levels (level_ids, Milestones), ordered by start, each define a rate:

start_count, start_type, milestone_date

When the level starts, after the start of the allocation (milestone_date = 'creation': immediately). The first level may start later than the allocation.

added_value, added_value_type

The amount earned per period, in days or hours. The first level’s unit is the unit of the plan (added_value_type of the plan) and of its allocations (type_request_unit).

frequency

hourly (per worked hour, see below), daily, weekly (week_day), bimonthly (first_day, second_day), monthly (first_day), biyearly (first_month, first_month_day, second_month, second_month_day) or yearly (yearly_month, yearly_day): the accrual dates, computed by _get_next_date() and _get_previous_date().

cap_accrued_time, maximum_leave

A cap on the balance (earned minus taken): accrual stops while the balance is at the cap.

cap_accrued_time_yearly, maximum_leave_yearly

A cap on the amount earned per year (between two carry-over dates).

action_with_unused_accruals, carryover_options, max_carriedover_duration

What happens to the balance on the carry-over date: lost (reset), or carried over, without limit or up to max_carriedover_duration.

accrual_validity, accrual_validity_count, accrual_validity_type

The time carried over expires after this period.

On the plan:

accrued_gain_time

end (default): the time of a period is earned at its end (on the next accrual date); start: at its start.

transition_mode

immediately: a level starts on its start date; end_of_accrual: on its first accrual date from its start date, so that a period is never split between two levels (_get_lvls_boundaries()).

is_based_on_worked_time

Prorate the earned time on the worked time of the period: absences whose type is not elligible_for_accrual_rate reduce it (_get_accrual_plan_level_work_entry_prorata()). With hourly levels, the amount is earned per hour of the period (worked hours with this option, all planned hours without).

can_be_carryover, carryover_date, carryover_day, carryover_month

Whether unused time is carried over at all (otherwise it is lost on the carry-over date), and when: the start of the year, the anniversary of the allocation, or a custom day.

The first period of an allocation starting between two accrual dates is prorated on its number of days, unless the plan is based on worked time.

Running the accruals

An accrual allocation stores its progress: number_of_days (earned so far), lastcall and nextcall (the last and next accrual events: accrual dates, level transitions, carry-over dates, expirations), last_accrual, yearly_accrued_days, previous_carryover_number_of_days and carried_over_days_expiration_date.

The daily cron (Accrual Time Off: Updates the number of time off) runs model._get_to_update_accrual_allocations()._update_accrual(): _get_to_update_accrual_allocations() selects the approved accrual allocations valid today whose nextcall is due, and _update_accrual() processes every event up to today and writes the result. A new allocation starting in the past catches up at its first run.

_process_accrual_plans() does the computation without writing: it starts from the stored values (or the precomputed_allocations given), processes the events up to a date and returns {allocation: allocation_data}. The Accrual inconsistent helper methods (see the class docstring of HrLeaveAllocation) only read and update allocation_data, so that the same code serves:

  • the cron, which writes the result;

  • the future balance: the balance on a future date includes what the accrual will have earned by then (accrual_bonus, _get_additionnal_future_leaves_on()), and future requests are checked against it (to_recheck_leaves, exceeding_duration in the second result of _get_consumed_leaves());

  • the allocation form, which simulates the plan from date_from until today.

A second daily cron (_cancel_invalid_leaves()) cancels the requests of the next 31 days that the accruals will not cover.

Example

A plan earning 1.5 days per month at the end of the month, carrying over at most 5 days at the start of the year, and 2 days per month after two years:

from odoo.fields import Command

plan = env['hr.leave.accrual.plan'].create({
    'name': 'Monthly 1.5 days',
    'work_entry_type_id': pto.id,
    'accrued_gain_time': 'end',
    'can_be_carryover': True,
    'carryover_date': 'year_start',
    'level_ids': [Command.create({
        'added_value': 1.5,
        'added_value_type': 'day',
        'frequency': 'monthly',
        'first_day': '1',
        'action_with_unused_accruals': 'all',
        'carryover_options': 'limited',
        'max_carriedover_duration': 5,
    }), Command.create({
        'milestone_date': 'after',
        'start_count': 2,
        'start_type': 'year',
        'added_value': 2,
        'added_value_type': 'day',
        'frequency': 'monthly',
        'first_day': '1',
        'action_with_unused_accruals': 'all',
    })],
})
employee = env['hr.employee'].search([('name', '=', 'Ronnie Hart')])
allocation = env['hr.leave.allocation'].create({
    'employee_id': employee.id,
    'work_entry_type_id': pto.id,
    'accrual_plan_id': plan.id,
    'date_from': date(2026, 1, 1),
    'number_of_days': 0,
})
allocation.action_approve()
allocation.number_of_days, allocation.nextcall  # (0.0, False): not run yet

allocation._update_accrual()  # what the cron does (today is 2026-10-23)
allocation.number_of_days, allocation.lastcall, allocation.nextcall
# (13.5, datetime.date(2026, 10, 1), datetime.date(2026, 11, 1)): 9 months
allocation._get_lvls_boundaries()
# [datetime.date(2026, 1, 1), datetime.date(2028, 1, 1)]

allocation._process_accrual_plans(date(2027, 1, 1))[allocation]
# {'last_accrual': datetime.date(2027, 1, 1), 'lastcall': datetime.date(2027, 1, 1),
#  'nextcall': datetime.date(2027, 2, 1), 'allocated_duration': 6.5,
#  'yearly_accrued_duration': 0, 'carried_over_days_expiration_date': False,
#  'previous_carryover_allocated_duration': 5, 'leaves_taken': 0}
allocation.number_of_days  # 13.5: nothing written

data = pto.get_allocation_data(employee, date(2026, 12, 31))[employee][0][1]
data['max_leaves'], data['accrual_bonus']  # (16.5, 3.0)

On January 1, 2027, the 16.5 days earned in 2026 are capped to 5 by the carry-over, then December’s 1.5 days are earned (at the end of the period): 6.5 days.

Allocations from working schedules

A working schedule can have an accrual plan (resource.calendar.leave_accrual_plan_id). When an employee’s version uses a schedule with a work time rate above 100% and such a plan, the version gets an accrual allocation of the plan over its period (hr.version.allocation_from_working_schedule_id), recreated when the version’s schedule changes (_action_trigger_accrual_plan_working_schedule()). It compensates schedules longer than the legal working time with time off.

Allocations from time rules

hr_holidays adds two fields to the time rules (hr.time.rule, see Time rules): allocation_type_id (Allocate to, a type requiring an allocation) and leave_compensation_rate (Allocate %). The excess hours of such a rule, times the rate, divided by the employee’s hours per day, are credited to the employee’s approved allocation without end date of that type, created and approved if needed (_apply_allocation_credits()); the missing hours of an undertime rule are deducted from it, unless the allocation cannot cover them all (_filter_deficit_exceeding_allocation()).

Each credit is logged in a hr.time.rule.allocation.log record (the source record res_model and res_id, the allocation and the days). When the source is evaluated again, changed, refused or deleted, its logged credits are reversed first (_reverse_allocation_credits()); a reversal that would bring the allocation below what was already taken raises a ValidationError.

The sources are the time off requests (validated working time requests exceeding the schedule, e.g. a long business trip) and, with hr_holidays_attendance, the attendances: see Overtime and time off: hr_holidays_attendance.

Example

A business trip of 12 hours on a day of 8 planned hours, with a rule crediting the excess as compensatory days:

trip, compensatory = env['hr.work.entry.type'].create([{
    'name': 'Business Trip',
    'code': 'DOCTRIP',
    'count_as': 'working_time',
    'requires_allocation': False,
    'request_unit': 'hour',
    'leave_validation_type': 'no_validation',
}, {
    'name': 'Compensatory Days',
    'code': 'DOCCOMP',
    'count_as': 'absence',
    'requires_allocation': True,
    'request_unit': 'half_day',
    'unit_of_measure': 'day',
}])
env['hr.time.rule'].create({
    'name': 'Trip overtime to compensatory days',
    'company_id': employee.company_id.id,
    'threshold_operator': 'exceed',
    'working_hours_mode': 'schedule_day',
    'condition_work_entry_type_ids': [Command.set(trip.ids)],
    'leave_compensation_rate': 1.0,
    'allocation_type_id': compensatory.id,
})
leave = env['hr.leave'].create({
    'employee_id': employee.id,
    'work_entry_type_id': trip.id,
    'request_date_from': date(2026, 12, 2),
    'request_date_to': date(2026, 12, 2),
    'request_hour_from': 7.0,
    'request_hour_to': 19.0,
})
leave.state, leave.number_of_hours  # ('validate', 12.0)
credit = env['hr.leave.allocation'].search([
    ('employee_id', '=', employee.id), ('work_entry_type_id', '=', compensatory.id),
])
credit.state, credit.number_of_days, credit.date_to  # ('validate', 0.5, False)
env['hr.time.rule.allocation.log'].search([]).read(['res_model', 'res_id', 'days'])
# [{'id': ..., 'res_model': 'hr.leave', 'res_id': leave.id, 'days': 0.5}]

leave.action_refuse()
credit.number_of_days  # 0.0: the credit is reversed

Pitfalls

  • number_of_days is in days even for types allocated in hours; the hours are converted with the employee’s hours per day, so an allocation in hours keeps its hours when the schedule changes only because hr.version rewrites number_of_days.

  • The computed balance fields (max_leaves, virtual_remaining_leaves, …) are computed for today, or for the employee of the context on types: use get_allocation_data() with a date in code.

  • Approving an accrual allocation does not run its plan: number_of_days stays at its initial value until the cron or a call to _update_accrual().

  • A request is deducted from the allocations valid on its dates only: an allocation starting after the request, or ending before it, does not cover it, and an allocation ending during a request only covers its first part.

  • The accrual helpers flagged Accrual inconsistent must not read the allocation fields nor write: override them consistently with allocation_data.

  • Time rule credits go to the allocation without end date of the type: a fixed allocation of the same type without end date receives them too.

Extending

Accrual frequencies

_get_hourly_frequencies() (frequencies accruing per hour), with _get_next_date() and _get_previous_date() for new frequencies; hr_holidays_attendance adds a frequency on attendances this way.

Accrual amounts

_get_accrual_plan_level_work_entry_prorata() and _get_period_added_duration().

Balances

_set_leaves_from_consumption_data() to reuse an already computed _get_consumed_leaves().

Time rule credits

_get_output_leave_merge_key() (when consecutive outputs make one request) and the source hooks of Time rule sources.

Reference

Original definition from hr_holidays

model odoo.addons.hr_holidays.models.hr_leave_allocation.HrLeaveAllocation[źródło]
_name = hr.leave.allocation

Allocation of time off: credits an employee with days or hours of a time type.

A regular allocation grants number_of_days (number_of_hours for types allocated in hours); an accrual allocation (accrual_plan_id set) earns it over time, see _update_accrual().

Some accrual methods are tagged Accrual inconsistent: they must not read the allocation fields loaded by _get_accrual_allocation_data() nor write any field, but read and update their allocation_data dict instead, so that the accrual can be simulated (e.g. a future balance) without writing anything. Methods tagged Possibly accrual inconsistent take allocation_data={} and read the fields when it is empty.

employee_id: int
Required

Name

Employee

Comodel

hr.employee

work_entry_type_id: int
Required

Name

Time Type

Comodel

hr.work.entry.type

state: str
Name

Status

Readonly

this field is not supposed to/cannot be set manually

Selection

confirm: To Approve refuse: Refused validate1: Second Approval validate: Approved

Default

confirm

The status is «To Approve», when an allocation request is created. The status is «Refused», when an allocation request is refused by manager. The status is «Approved», when an allocation request is approved by manager.

date_from: datetime.date
Required

Name

Start Date

date_to: datetime.date
Name

End Date

number_of_days: float
Name

Number of Days

Default

1

Duration in days. Reference field to use when necessary.

number_of_hours: float
Name

Duration (hours)

For an Accrual Allocation, this field contains the theorical amount of time given to the employee, due to a previous start date, on the first run of the plan. This can be manually edited.

type_request_unit: str
Name

Type Request Unit

Readonly

this field is not supposed to/cannot be set manually

Store

this field is there only for technical reasons

Selection

hour: Hours half_day: Half-Day day: Day

accrual_plan_id: int
Name

Accrual Plan

Comodel

hr.leave.accrual.plan

max_leaves: float
Name

Max Leaves

Readonly

this field is not supposed to/cannot be set manually

Store

this field is there only for technical reasons

leaves_taken: float
Name

Time off Taken

Readonly

this field is not supposed to/cannot be set manually

Store

this field is there only for technical reasons

virtual_remaining_leaves: float
Name

Available Time Off

Readonly

this field is not supposed to/cannot be set manually

Store

this field is there only for technical reasons

last_accrual: datetime.date
Name

Date of the last accrual allocation

lastcall: datetime.date
Name

Date of the last executed accrual event (accrual, carryover date, expiring carriedover days, …)

nextcall: datetime.date
Name

Date of the closest next accrual event (accrual, carryover date, expiring carriedover days, …)

yearly_accrued_days: float
Name

Number of accrued days since the last carryover

previous_carryover_number_of_days: float
Name

The number of days on previous carryover date

carried_over_days_expiration_date: datetime.date
Name

Carried over days expiration date

create(vals_list)[źródło]

Create allocations in 'confirm' and start their approval.

Types without validation are approved at once.

Wyrzuca

UserError – when another state is given

write(vals)[źródło]

Write on allocations, checking the state transitions.

Wyrzuca

ValidationError – when the change reduces the balance below the time already taken (beyond the negative cap of the type)

action_approve()[źródło]

Approve the allocations: validate them, or give the first approval ('validate1').

Wyrzuca

UserError – when the user can neither validate nor approve an allocation

_action_validate()[źródło]

Set the allocations to 'validate' and record the approvers.

action_refuse()[źródło]

Refuse the allocations.

Wyrzuca

UserError – when an allocation is not 'confirm', 'validate1' or 'validate'

_check_approval_update(state, raise_if_not_possible=True)[źródło]

Check that the current user can move the allocations to state.

Only time off administrators can approve their own allocations (unless the type needs no validation). The superuser can do everything.

Parametry
  • state (str) – target state

  • raise_if_not_possible (bool) – raise instead of returning False

Typ zwracany

bool

Wyrzuca

UserError – when not possible and raise_if_not_possible

_get_next_states_by_state()[źródło]

Return the states the current user can move the allocation to, from each state.

Same rules as for time off; types without validation can always be approved.

Zwraca

{state: set(next_states)}

Typ zwracany

dict

_get_responsible_for_approval()[źródło]

Return the user who must approve the allocation in its current state.

Same rules as _get_responsible_for_approval().

Typ zwracany

res.users

_check_negative_allocation()[źródło]

Allow negative allocations only for types allowing a negative balance, within their cap.

The negative allocations of the employee and type overlapping the validity period may not exceed max_allowed_negative in total.

_set_leaves_from_consumption_data(employee_days_per_allocation)[źródło]

Set max_leaves, leaves_taken and virtual_remaining_leaves.

Lets extensions reuse an already fetched result (e.g. l10n_be_hr_payroll).

Parametry

employee_days_per_allocation (dict) – first element of the result of _get_consumed_leaves()

_get_request_unit()[źródło]

Return the unit the allocation is computed in (type_request_unit).

The added_value_type of the accrual plan, else the unit_of_measure of the type.

Zwraca

'day' or 'hour'

Typ zwracany

str

_get_employee_hours_per_day(allocation_data={})[źródło]

Return the hours per day of the employee, to convert days into hours.

Possibly accrual inconsistent. The schedule is the one at the accrual’s nextcall (accrual allocations) or at date_from; 24 for fully flexible employees.

Parametry

allocation_data (dict) – accrual data, see _process_accrual_plans()

Typ zwracany

float

_convert_duration(duration, src_unit, dst_unit, allocation_data={})[źródło]

Convert a duration between days and hours with the employee’s hours per day.

Possibly accrual inconsistent, see _get_employee_hours_per_day().

Parametry
  • duration (float) – duration to convert

  • src_unit (str) – 'day', 'half_day' or 'hour'

  • dst_unit (str) – 'day', 'half_day' or 'hour'

  • allocation_data (dict) – accrual data, see _process_accrual_plans()

Typ zwracany

float

_convert_to_type_request_unit(duration, duration_unit, allocation_data={})[źródło]

Convert a duration into the allocation’s unit (type_request_unit).

Possibly accrual inconsistent, see _convert_duration().

Parametry
  • duration (float) – duration to convert

  • duration_unit (str) – 'day', 'half_day' or 'hour'

Typ zwracany

float

_convert_from_type_request_unit(duration, duration_unit, allocation_data={})[źródło]

Convert a duration expressed in the allocation’s unit into duration_unit.

Possibly accrual inconsistent, see _convert_duration().

Parametry
  • duration (float) – duration in type_request_unit

  • duration_unit (str) – 'day', 'half_day' or 'hour'

Typ zwracany

float

_get_to_update_accrual_allocations()[źródło]

Return the allocations the accrual cron must update.

Validated accrual allocations valid today, whose nextcall is not set or is due.

Typ zwracany

hr.leave.allocation

_update_accrual(date_to=None)[źródło]

Run the accrual plans of the allocations up to date_to and write the result.

Writes number_of_days (the credit of the allocation) and the accrual fields (last_accrual, lastcall, nextcall, yearly_accrued_days, previous_carryover_number_of_days, carried_over_days_expiration_date) computed by _process_accrual_plans(). Called daily by the cron on _get_to_update_accrual_allocations().

Parametry

date_to (datetime.date) – last day to process, today by default

_process_accrual_plans(date_to=None, precomputed_allocations={})[źródło]

Compute the accrual of the allocations up to date_to, without writing.

Runs every accrual event (accrual dates, level transitions, carry-over dates, expiration of the carried over time) from nextcall up to date_to (today by default), from the values stored on the allocation or from precomputed_allocations. Allocations without accrual plan are left out.

Parametry
  • date_to (datetime.date) – last day to process

  • precomputed_allocations (dict) – {allocation: allocation_data} to start from, instead of the stored values

Zwraca

{allocation: allocation_data} where allocation_data holds last_accrual, lastcall, nextcall, carried_over_days_expiration_date (dates), allocated_duration, yearly_accrued_duration, previous_carryover_allocated_duration and leaves_taken (floats in type_request_unit)

Typ zwracany

dict

_get_accrual_allocation_data()[źródło]

Return the accrual data of the allocations, read from their fields.

Zwraca

{allocation: allocation_data}, see _process_accrual_plans(); leaves_taken is None

Typ zwracany

dict

_init_accrual_calls(allocation_data)[źródło]

Initialize last_accrual, lastcall and nextcall for a first accrual run.

Accrual inconsistent. lastcall is the start of the first level; nextcall is the same date when accruing at the start of the period, else the first accrual, carry-over or level transition date.

Parametry

allocation_data (dict) – accrual data, updated in place

_process_accrual_start(allocation_data, current_level, next_accrual)[źródło]

Accrue the period starting on allocation_data['nextcall'], if it is an accrual date.

Accrual inconsistent. For plans accruing at the start of the period (accrued_gain_time = 'start').

Parametry
_process_accrual_end(allocation_data, current_level, current_level_idx, first_level_start_date)[źródło]

Accrue the period ending on allocation_data['nextcall'], if it is an accrual date.

Accrual inconsistent. For plans accruing at the end of the period (accrued_gain_time = 'end').

Parametry
  • allocation_data (dict) – accrual data, see _process_accrual_plans()

  • current_level – hr.leave.accrual.level accruing

  • current_level_idx (int) – index of the level

  • first_level_start_date (datetime.date) – start of the first level

_add_days_to_allocation(allocation_data, current_level, period_start, period_end, start_date, end_date)[źródło]

Add the gain of an accrual period to allocation_data.

Accrual inconsistent. The gain (_get_period_added_duration()) is capped by the yearly cap of the level and by its balance cap (maximum_leave, on the balance allocated_duration - leaves_taken). Updates allocated_duration, yearly_accrued_duration and last_accrual.

Parametry
_get_period_added_duration(allocation_data, level, period_start, start_date, period_end, end_date)[źródło]

Return the duration earned over an accrual period, in type_request_unit.

Accrual inconsistent. added_value times the prorata of _get_accrual_plan_level_work_entry_prorata(); a partial period (e.g. the first one) is prorated on its number of days, unless the plan is based on worked time.

Typ zwracany

float

_get_accrual_plan_level_work_entry_prorata(level, start_period, start_date, end_period, end_date)[źródło]

Return the factor applied to the added_value of the level for a period.

Possibly accrual inconsistent. 1 unless the plan is based on worked time or the frequency is hourly. For hourly frequencies, the number of hours of the period: worked hours plus absences eligible for accrual (plus the other absences when the plan is not based on worked time). Otherwise the ratio of those hours to all the planned hours.

Parametry
Typ zwracany

float

_process_carryover_date(allocation_data, current_level, carryover_date)[źródło]

Apply the carry-over policy of the level on its carry-over date.

Accrual inconsistent. Unused time is lost (action_with_unused_accruals = 'lost'), kept, or kept up to max_carriedover_duration; with accrual_validity, the time carried over expires after the validity period (carried_over_days_expiration_date).

Parametry
_get_next_carryover_date(date_from, date_from_included=True)[źródło]

Return the next carry-over date of the accrual plan from date_from.

Parametry
  • date_from (datetime.date) – reference date

  • date_from_included (bool) – return date_from if it is a carry-over date

Typ zwracany

datetime.date

_has_expiring_days(allocation_data)[źródło]

Tell whether carried over time is still to expire after nextcall.

Accrual inconsistent.

Typ zwracany

bool

_get_days_after_expiration(allocation_data)[źródło]

Return allocated_duration once the carried over time not taken has expired.

Accrual inconsistent.

Parametry

allocation_data (dict) – accrual data, see _process_accrual_plans()

Typ zwracany

float

_get_lvls_boundaries() → list[datetime.date] | None[źródło]

Return the start date of each level of the accrual plan for this allocation.

A level starts start_count start_type after date_from; with the transition mode 'end_of_accrual', the levels after the first one start on their first accrual date on or after that date.

Zwraca

one date per level, in order

Typ zwracany

list[datetime.date] | None

_get_next_lvl_start(level_idx)[źródło]

Return the start date of the level following level_idx, None for the last one.

Typ zwracany

datetime.date or None

_get_current_accrual_plan_levels(target_date) → dict[str, tuple] | None[źródło]

Return the level(s) of the accrual plan in effect on target_date.

On a level transition, both levels are returned. None before the first level starts, after date_to, or without levels.

Parametry

target_date (datetime.date) – reference date

Zwraca

{'current_level': (level, index)}, plus 'previous_level': (level, index) on a transition

Typ zwracany

dict[str, tuple] | None

_get_current_accrual_plan_level_idx(date)[źródło]

Return the level in effect on date; on a transition, the level that starts.

Zwraca

(level, index), (None, None) when no level applies

Typ zwracany

tuple

_get_current_accrual_plan_level_idx_for_accrual(target_date)[źródło]

Return the level in effect and the level accruing on target_date.

They differ on a level transition when the plan accrues at the end of the period (accrued_gain_time = 'end'): the period that ends is accrued with the policy of the previous level.

Zwraca

{'current_level': (level, index), 'accrual_level': (level, index)}, or None when no level applies

Typ zwracany

dict or None

_get_additionnal_future_leaves_on(accrual_date, allocations_data={})[źródło]

Return the time the accrual will add between now and accrual_date.

Possibly accrual inconsistent. 0 for past dates, non accrual or non validated allocations, and allocations expired on accrual_date.

Parametry
  • accrual_date (datetime.date) – future date

  • allocations_data (dict) – {allocation: allocation_data}; when the allocation is in it, the gain is computed from these data instead of the stored values

Zwraca

added duration, in the unit_of_measure of the type

Typ zwracany

float

_get_init_accrual_plan_values()[źródło]

Return the values resetting the accrual of an allocation.

Typ zwracany

dict

_onchange_process_accrual_plans()[źródło]

Simulate in the form the time the accrual plan grants from date_from until today.

Original definition from hr_holidays

model odoo.addons.hr_holidays.models.hr_leave_accrual_plan.HrLeaveAccrualPlan[źródło]
_name = hr.leave.accrual.plan
work_entry_type_id: int
Name

Time Type

Comodel

hr.work.entry.type

Time type to be accrued

level_ids: Sequence[odoo.orm.commands.Command]
Name

Milestones

Comodel

hr.leave.accrual.level

accrued_gain_time: str
Required

Name

Accrued Gain Time

Selection

start: At the start of the accrual period end: At the end of the accrual period

Default

end

transition_mode: str
Required

Name

Transition Mode

Selection

immediately: Immediately end_of_accrual: After this accrual’s period

Default

immediately

is_based_on_worked_time: bool
Name

Is Based On Worked Time

Only excludes requests where the time type is set as unpaid kind of.

can_be_carryover: bool
Name

Can Be Carryover

carryover_date: str
Required

Name

Carry-Over Time

Selection

year_start: At the start of the year allocation: At the allocation date other: Custom date

Default

year_start

added_value_type: str
Name

Added Value Type

Selection

day: Days hour: Hours

Default

day

Original definition from hr_holidays

model odoo.addons.hr_holidays.models.hr_leave_accrual_plan_level.HrLeaveAccrualLevel[źródło]
_name = hr.leave.accrual.level
start_count: int
Name

Start Count

The accrual starts after a defined period from the allocation start date. This field defines the number of days, months or years after which accrual is used.

start_type: str
Required

Name

Start Type

Selection

day: Days month: Months year: Years

Default

day

This field defines the unit of time after which the accrual starts.

added_value: float
Required

Name

Added Value

Default

1

added_value_type: str
Required

Name

Added Value Type

Selection

day: Day(s) hour: Hour(s)

frequency: str
Required

Name

Frequency

Selection

hourly: Hourly daily: Daily weekly: Weekly bimonthly: Twice a month monthly: Monthly biyearly: Twice a year yearly: Yearly

Default

daily

cap_accrued_time: bool
Name

Cap Accrued Time

When the field is checked the balance of an allocation using this accrual plan will never exceed the specified amount.

maximum_leave: float
Name

Maximum Leave

Default

0

Choose a cap for this accrual.

cap_accrued_time_yearly: bool
Name

Cap Accrued Time Yearly

When the field is checked the total amount accrued each year will be capped at the specified amount

maximum_leave_yearly: float
Name

Maximum Leave Yearly

action_with_unused_accruals: str
Required

Name

Action With Unused Accruals

Selection

lost: Lost (Reset) all: Carried over

Default

lost

When the Carry-Over Time is reached, according to Plan’s setting, select what you want to happen with the unused time off: Lost (time will be reset to zero), Carried over (accrued time carried over to the next period.)

carryover_options: str
Required

Name

Carryover Options

Selection

unlimited: Unlimited limited: Up to

Default

unlimited

You can limit the accrued time carried over for the next period.

max_carriedover_duration: int
Name

Max Carriedover Duration

Set a maximum of accruals an allocation keeps at the end of the year.

accrual_validity: bool
Name

Accrual Validity

_get_hourly_frequencies()[źródło]

Return the frequencies accruing per hour of the period instead of per period.

Typ zwracany

list[str]

_get_next_date(last_call)[źródło]

Return the first accrual date of the level after last_call (excluded).

Parametry

last_call (datetime.date) – reference date

Typ zwracany

datetime.date

_get_previous_date(last_call)[źródło]

Return the last accrual date of the level on or before last_call (included).

For instance, the 1st of the month for a monthly level.

Parametry

last_call (datetime.date) – reference date

Typ zwracany

datetime.date

_get_start_date(date_from)[źródło]

Return date_from plus the start delay of the level (start_count start_type).

Typ zwracany

datetime.date

_get_level_transition_date(allocation_start)[źródło]

Return the date the level starts for an allocation starting on allocation_start.

Typ zwracany

datetime.date

Additional fields with hr_holidays

model odoo.addons.hr_holidays.models.hr_time_rule.HrTimeRule[źródło]
allocation_type_id: int
Name

Allocate to

Comodel

hr.work.entry.type

leave_compensation_rate: float
Name

Allocate %

Default

0.0

Leave allocated or taken at this rate of the excess or deficit. 100% = 1:1.

_apply_leave_output(excess, deficit, active_iv=None)[źródło]

Apply the time rule result on time off sources.

Creates the outputs (_apply_output, defined by hr_work_entry), credits the allocations (_apply_allocation_credits()) and recreates the resource leaves of the sources and outputs.

Parametry
  • excess (dict) – {employee: {source: [_Iv]}}

  • deficit (dict) – {employee: {source: [(start, stop, rule)]}}

  • active_iv (dict) – {employee: {source: Intervals}}

_apply_allocation_credits(excess_alloc, deficit_alloc, *, approve_ctx=None)[źródło]

Credit (or deduct) the excess (or missing) hours of rules to allocations, and log it.

For rules with leave_compensation_rate and allocation_type_id, the hours times the rate, divided by the employee’s hours per day, are added to (or deducted from) the validated allocation without end date of that type, created if needed. Each credit is logged in hr.time.rule.allocation.log so it can be reversed.

Parametry
  • excess_alloc (list) – [employee, rule, hours, source, log_source] rows

  • deficit_alloc (list) – same rows for the deficits

  • approve_ctx (dict) – context to approve the created allocations with

_filter_deficit_exceeding_allocation(deficit)[źródło]

Drop the deficits the allocation of the rule cannot cover.

When the total deduction of an employee and allocation type exceeds the balance, all the deficit intervals of that pair are dropped, so no output is created for them.

Parametry

deficit (dict) – {employee: {source: [(start, stop, rule)]}}

Zwraca

the filtered deficit

Typ zwracany

dict

_reverse_allocation_credits(source_model, source_ids)[źródło]

Reverse the allocation credits logged for the given source records.

Parametry
  • source_model (str) – 'hr.leave' or 'hr.attendance'

  • source_ids – ids of the sources

Wyrzuca

ValidationError – when the reversal would bring a balance below its minimum

_get_output_leave_vals(employee, rule, date_from, date_to, source_leave, accumulated_pp=frozenset({}))[źródło]

Return the creation values of a validated time off output by rule.

The output has the rule’s type, time_rule_id, the given span and is linked to source_leave.

Parametry
  • employee – hr.employee

  • rule – hr.time.rule producing the output

  • date_from (datetime) – start, naive UTC

  • date_to (datetime) – end, naive UTC

  • source_leave – source hr.leave

  • accumulated_pp (frozenset) – premium pay ids

Typ zwracany

dict

_get_output_leave_merge_key(accumulated_pp=frozenset({}))[źródło]

Return the hashable key deciding when consecutive excess slices make one time off.

Override to add discriminators (e.g. the premium pays in Belgium). Default: the rule.

_resolve_output_intervals(intervals)[źródło]

Resolve the output intervals, then merge consecutive slices with the same merge key.

See _get_output_leave_merge_key().

Typ zwracany

list

Additional fields with hr_holidays

model odoo.addons.hr_holidays.models.hr_time_rule.HrTimeRuleSourceMixin[źródło]
_on_sources_collected(sources)[źródło]

Reverse the allocation credits of the sources before they are evaluated again.

Parametry

sources – records of the source model

Original definition from hr_holidays

model odoo.addons.hr_holidays.models.hr_time_rule_allocation_log.HrTimeRuleAllocationLog[źródło]
_name = hr.time.rule.allocation.log
res_model: str
Required

Name

Model

res_id: dict
Required

Name

Record

allocation_id: int
Required

Name

Allocation

Comodel

hr.leave.allocation

days: float
Required

Name

Days

Net days credited to the allocation from this source record.Positive = excess credit added; negative = deficit deduction applied.

Additional fields with hr_holidays

model odoo.addons.hr_holidays.models.resource_calendar.ResourceCalendar[źródło]
leave_accrual_plan_id: int
Name

Accrual Plan

Comodel

hr.leave.accrual.plan