Employees

An employee (hr.employee) is a person working for a company. A person working for two companies has two employees. The model is the hub of the HR applications: time off, attendances, payroll, appraisals, expenses, … all point to it.

An employee is made of several records, each one with its own role:

  • the employee itself, holding the data that does not change over time;

  • its versions (hr.version), holding the data that changes over time, including the contract (see Versions and contracts, to read first);

  • a resource (resource.resource), through resource.mixin, which gives the employee a working schedule and a timezone for the time computations;

  • a work contact (res.partner), used for the messaging and as the partner of the bank accounts;

  • optionally a user (res.users).

Resource, user and work contact

Resource (resource_id, required)

Created with the employee (_prepare_resource_values()). It carries name, user_id and active: these employee fields are related to the resource. resource.employee_id is the reverse link (a one2many holding at most one employee).

The calendar and the timezone of an employee are on its versions. The resource’s calendar_id and tz mirror the current version: they are computed from employee_id.current_version_id, so they follow any change of the current version or of its calendar and timezone. Writing the resource’s calendar or timezone writes the current version.

User (user_id)

A user has at most one employee per company (unique(user_id, company_id)). On the user side, user.employee_id is the employee in the current company (env.company), user.employee_ids the employees in the allowed companies (env.companies) and user.all_employee_ids the employees in all companies.

Linking a user (_sync_user()) sets the user’s partner as work contact and copies the user’s image (if the employee has none) and timezone. Afterwards:

  • writing name, email (copied to work_email), image_1920, tz or the weekly work locations on the user writes them on its employees of the current company (_get_employee_fields_to_sync());

  • writing tz on the employee writes it on the user when both have the same company.

A user can be created with an employee (create_employee=True in the values of res.users.create) or bound to an existing one (create_employee_id); action_create_employee() creates the employee of an existing user. _get_or_create_light_user() returns a user for an employee, creating an internal user with reduced rights if needed.

Many employee fields are exposed on res.users (preferences form) with related_employee_field(): a user reads their own employee in sudo, and writes it in sudo for the fields declared with user_writeable=True.

Work contact (work_contact_id)

Created if missing when the employee is created. work_email and work_phone are computed from it, and writing them on the employee writes the partner. The private address is not a partner: it is stored on the version (private_street, private_city, …). partner.employee_ids lists the employees of a partner; such partners cannot be deleted.

Example

user = env['res.users'].create({
    'name': 'John Smith',
    'login': 'john.smith@example.com',
    'email': 'john.smith@example.com',
    'tz': 'Europe/Brussels',
})
employee = env['hr.employee'].create({'name': 'John Smith', 'user_id': user.id})
employee.work_contact_id == user.partner_id, employee.work_email, employee.tz
# (True, 'john.smith@example.com', 'Europe/Brussels')
employee.resource_id.user_id == user, user.employee_id == employee  # (True, True)

user.write({'email': 'john@example.com'})
employee.work_email  # 'john@example.com'
employee.tz = 'Europe/Paris'
user.tz, employee.resource_id.tz  # ('Europe/Paris', 'Europe/Paris')

other = env['hr.employee'].create({'name': 'Ann Lee', 'work_email': 'ann@example.com'})
other.work_contact_id.name, other.work_contact_id.email  # ('Ann Lee', 'ann@example.com')

Public employees and access rights

Only HR officers (hr.group_hr_user) have access to hr.employee and hr.version. Yet every internal user needs to see the name, job, department, manager or work phone of their colleagues (e.g. in an avatar card, the organization chart, a many2one to an employee). This is what hr.employee.public is for: a read-only SQL view (_auto = False) readable by every internal user, exposing the non-sensitive data. Its ids are the employee ids, and its columns come from the employee joined with its current version (init()). employee_id links a public employee to its employee.

To keep business code simple, hr.employee redirects the operations of users without access to the public model: search, fetch, get_views, get_formview_id, get_record_default_action, … (see _search(), search_fetch() and fetch()). Such a user searching hr.employee gets an hr.employee recordset whose public values are filled from hr.employee.public, including the public fields inherited from the version (job_title, department_id, …); reading a field that is not on the public model raises an AccessError.

Example

As a user who is not an HR officer:

from odoo.exceptions import AccessError

demo = env.ref('base.user_demo')
employees = env['hr.employee'].with_user(demo).search([], limit=2)
employees._name, employees.mapped('name'), employees[0].work_email
# ('hr.employee', ['Abigail Peterson', 'Anita Oliver'], 'abigail.peterson39@example.com')
try:
    employees[0].private_email
except AccessError:
    pass  # not a public field

public = env['hr.employee.public'].with_user(demo).browse(employees.ids)
public.mapped('job_title'), public.mapped('department_id.name')
# (['Consultant', 'Experienced Developer'],
#  ['Professional Services', 'Research & Development'])

Truco

Code running for regular users (portlets, avatar cards, controllers, …) should read hr.employee.public explicitly rather than relying on the redirection, or use sudo() when reading private data is legitimate.

Importante

Rule for adding a field to hr.employee (or hr.version ): a field that is not on hr.employee.public must have groups="hr.group_hr_user" (or a stricter group). Otherwise, the prefetching of fields loads it for users without access to employees, and their reads fail. This is stated in the class docstring of HrEmployee.

To make a field public, declare it on hr.employee.public with the same name:

  • a stored field becomes a column of the view automatically, read from the current version if it is a stored hr.version field, from the employee otherwise (_get_fields());

  • otherwise, use related='employee_id.<field>' (computed in sudo) or a compute calling _compute_from_employee().

class HrEmployee(models.Model):
    _inherit = 'hr.employee'

    expense_manager_id = fields.Many2one('res.users', string='Expense Approver')


class HrEmployeePublic(models.Model):
    _inherit = 'hr.employee.public'

    expense_manager_id = fields.Many2one('res.users', readonly=True)  # column of the view

Data that only managers should see on a public profile is declared by overriding _get_manager_only_fields(): such fields are filled by _compute_manager_only_fields() for the user’s own employee and the employees below it (is_manager), and are False otherwise.

Models with a many2many field to hr.employee should inherit hr.mixin (see The hr.mixin mixin), so that users without access to employees can still write that field.

The access rights defined by hr are summarized in Access rights.

Hierarchy

parent_id is the manager, child_ids the direct (active) subordinates, and subordinate_ids all the direct and indirect subordinates (_get_subordinates(), safe against cycles). coach_id defaults to the manager. Setting a new manager on a department (hr.department.manager_id) makes it the manager of the department members who were managed by the previous one.

Archiving an employee empties the many2one fields pointing to it on other employees: parent_id and coach_id by default, extended by overriding _get_employee_m2o_to_empty_on_archived_employees() (fields to hr.employee) or _get_user_m2o_to_empty_on_archived_employees() (fields to res.users, e.g. the time off approver in hr_holidays).

boss = env['hr.employee'].create({'name': 'Boss'})
sub = env['hr.employee'].create({'name': 'Sub', 'parent_id': boss.id})
env['hr.employee'].create({'name': 'Sub of sub', 'parent_id': sub.id})
sub.coach_id == boss, boss.child_ids.mapped('name'), boss.subordinate_ids.mapped('name')
# (True, ['Sub'], ['Sub of sub', 'Sub'])

boss.action_archive()
sub.parent_id, sub.coach_id, boss.resource_id.active  # (hr.employee(), hr.employee(), False)

Working time and calendars

hr.employee inherits the time computations of resource.mixin (see The resource mixin), but its working schedule and timezone come from its versions, so they change over time. hr adapts the resource methods in two ways:

  • the calendar of an employee at a date is the one of the version in contract on that date;

  • for an employee who has ever had a contract, the periods out of contract have no calendar, hence no work time.

Employees who never had a contract (and non-employee resources) keep the plain resource behavior: their current calendar over the whole period.

The methods below take date objects or timezone-aware datetimes (see Conventions); they work in batch unless stated otherwise.

_get_calendars()

{employee_id: resource.calendar}. With a date, the calendar of the version in contract on that date; employees out of contract keep the current calendar. Without date, the current calendar. _get_hours_per_week_batch() and _get_hours_per_day_batch() follow the same rule.

_get_calendar_periods()

{employee record: [(date_from, date_to, resource.calendar), ...]}: the calendars in contract within a period, see Working with versions.

_get_expected_attendances() (single employee)

The work intervals of each version in contract during the period (calendar and timezone of each version), leaves counted as absence deducted.

_get_calendar_attendances() (single employee)

{'days': float, 'hours': float}: the work time of each version in contract during the period, summed.

_get_unusual_days() (single employee)

{'YYYY-MM-DD': bool}: the days to grey out in calendar widgets. Takes '%Y-%m-%d %H:%M:%S' strings. Days between contract periods are unusual.

_get_employee_unavailable_intervals()

The unavailable intervals of each employee, version by version, for Gantt views.

_get_working_periods_by_field()

The contract periods of the employees, keyed by a many2one of the employee (resource_id, user_id, …), for the Gantt views of other models.

_get_tz() / _get_resources_per_tz()

The timezone of the version in effect on a date (the current one by default), and the {ZoneInfo: resource.resource} grouping expected by the resource.calendar methods.

On the resource side, hr overrides _get_calendars_validity_within_period() (one entry per calendar of the versions in contract, valid only while in contract) and _get_calendar_data_at(). Hence resource.resource._get_valid_work_intervals (see Resources with several calendars) is the method to use to get the work intervals of employees whose calendar may change.

Advertencia

Not every method splits the period per version:

  • the resource.mixin methods use one calendar per employee for the whole period: _get_work_days_data_batch uses the one of _get_calendars() at the start of the period (the current calendar if the employee is out of contract on that date), _list_work_time_per_day and _get_leave_days_data_batch use the current calendar (resource_calendar_id);

  • when no version is in contract during the period, _get_expected_attendances and _get_calendar_attendances use the current calendar for the whole period, and _get_unusual_days returns every day as not unusual.

Example

An employee working full time, then three days a week from July, with a first contract ending on December 31 and a second one starting on February 1:

from datetime import date, datetime
from zoneinfo import ZoneInfo

full_time = env.ref('resource.resource_calendar_std')
part_time = full_time.copy({'name': 'Monday to Wednesday'})
part_time.attendance_ids.filtered(lambda a: a.dayofweek in ('3', '4')).unlink()

employee = env['hr.employee'].create({
    'name': 'Jane Doe',
    'date_version': date(2026, 1, 1),
    'contract_date_start': date(2026, 1, 1),
    'contract_date_end': date(2026, 12, 31),
    'resource_calendar_id': full_time.id,
    'tz': 'Europe/Brussels',
})
employee.create_version({
    'date_version': date(2026, 7, 1),
    'resource_calendar_id': part_time.id,
})
employee.create_contract(date(2027, 2, 1))

employee._get_calendars(date(2026, 3, 1))[employee.id].name  # '40 hours/week'
employee._get_hours_per_week_batch(date(2026, 8, 1))  # {employee.id: 24.0}
[(start, stop, calendar.name) for start, stop, calendar
 in employee._get_calendar_periods(date(2026, 6, 1), date(2027, 3, 31))[employee]]
# [(date(2026, 6, 1), date(2026, 6, 30), '40 hours/week'),
#  (date(2026, 7, 1), date(2026, 12, 31), 'Monday to Wednesday'),
#  (date(2027, 2, 1), date(2027, 3, 31), 'Monday to Wednesday')]

# the week from Monday June 29 to Sunday July 5 spans both versions
brussels = ZoneInfo('Europe/Brussels')
start = datetime(2026, 6, 29, tzinfo=brussels)
end = datetime(2026, 7, 5, 23, 59, tzinfo=brussels)
employee._get_calendar_attendances(start, end)  # {'days': 3.0, 'hours': 24.0}
[(a.strftime('%m-%d %H:%M'), b.strftime('%H:%M'))
 for a, b, _attendance in employee._get_expected_attendances(start, end)]
# [('06-29 08:00', '16:00'), ('06-30 08:00', '16:00'), ('07-01 08:00', '16:00')]
work_intervals, _calendar_intervals = employee.resource_id._get_valid_work_intervals(
    start, end)
len(work_intervals[employee.resource_id.id])  # 3, the same intervals
employee._get_work_days_data_batch(start, end)  # {employee.id: {'days': 5.0, 'hours': 40.0}}
# (resource.mixin: the full-time calendar of June 29 for the whole week)

employee._get_unusual_days('2026-12-30 00:00:00', '2027-01-04 00:00:00')
# {'2026-12-30': False, '2026-12-31': True, '2027-01-01': True, '2027-01-02': True,
#  '2027-01-03': True, '2027-01-04': True}
# (December 31 is a Thursday, January is out of contract)
Presence

hr_presence_state (present, absent, out_of_working_hour) and hr_icon_display are computed by _compute_presence_state() and _compute_presence_icon(). hr only implements the presence based on the user’s login (company setting); hr_attendance and hr_holidays extend them.

Lifecycle

Creation

create() creates, for each employee, the resource, the first version (values of version fields go to it, see How an employee delegates to its versions), the work contact if missing and an avatar if there is no image. Employees are created per company, so that each version gets the company of its employee.

Changes over time

New versions and contracts: see Working with versions.

Archiving

active is related to the resource. action_archive() also empties the references of other employees (see Hierarchy).

Departure

The end of a collaboration is an hr.employee.departure record (Departures and departure reasons), created by the End of collaboration wizard (action_new_departure(), which requires the employee to be in contract today) or in code. It goes through two steps:

  1. create() links the departure to the versions of the contract in effect on the departure date (departure_id) and ends that contract on the departure date (contract_date_end). The previous end date is kept in last_contract_date_end.

  2. action_register() applies it: the versions starting after the departure date are deleted, the employee is archived, and so is its user if all the user’s active employees are leaving; apply_date is set. The departures whose date is reached are applied by the daily cron HR: Apply employee departures (_cron_apply_departure()).

action_cancel_departure() unarchives the employee, restores the contract end date and deletes the departure; deleted versions are not restored.

Example

from datetime import date

leaver = env['hr.employee'].create({
    'name': 'Leaver',
    'date_version': date(2026, 1, 1),
    'contract_date_start': date(2026, 1, 1),
})
leaver.create_version({'date_version': date(2026, 11, 1), 'wage': 2000})
departure = env['hr.employee.departure'].create({
    'employee_id': leaver.id,
    'departure_reason_id': env.ref('hr.departure_resigned').id,
    'dismissal_date': date(2026, 9, 30),
})
departure.departure_date  # date(2026, 9, 30): no notice period in hr
[(v.date_version, v.contract_date_end) for v in leaver.version_ids]
# [(date(2026, 1, 1), date(2026, 9, 30)), (date(2026, 11, 1), date(2026, 9, 30))]

departure.action_register()
leaver.active, leaver.with_context(active_test=False).version_ids.mapped('date_version')
# (False, [date(2026, 1, 1)])

leaver.action_cancel_departure()
leaver.active, leaver.version_ids.mapped('contract_date_end')  # (True, [False])

Localizations hook into the departure with _compute_departure_date() (notice period) and _get_departures_to_archive().

Multi-company

  • company_id is required on the employee; the company of its versions follows it. Record rules restrict employees to company_id in company_ids + [False] and versions to company_id in company_ids. hr.employee.public has no record rule in hr.

  • One employee per user and company; user.employee_id depends on env.company.

  • Moving an employee to another company is discouraged (the form shows a warning): create a new employee in the other company.

  • The default salary structure type of a version is the one of its company’s country (or one without country).

Extension points

Besides the fields (see Adding a field and Public employees and access rights), the methods commonly overridden are:

Pitfalls

  • An employee field without groups must exist on hr.employee.public.

  • The resource’s calendar and timezone are those of the current version only; use the employee methods above for other dates.

  • _get_version_periods/_get_calendar_periods are keyed by employee record, most other methods by employee id.

  • user.employee_id is the employee of the current company; use employee_ids or all_employee_ids across companies.

Reference

Original definition from hr

model odoo.addons.hr.models.hr_employee.HrEmployee[fuente]
_name = hr.employee

NB: Any field only available on the model hr.employee (i.e. not on the hr.employee.public model) should have groups="hr.group_hr_user" on its definition to avoid being prefetched when the user hasn’t access to the hr.employee model. Indeed, the prefetch loads the data for all the fields that are available according to the group defined on them.

name: str
Name

None

version_id: int
Required

Name

Employee Record

Readonly

this field is not supposed to/cannot be set manually

Store

this field is there only for technical reasons

Comodel

hr.version

current_version_id: int
Name

Current Employee Record

Readonly

this field is not supposed to/cannot be set manually

Comodel

hr.version

version_ids: Sequence[odoo.orm.commands.Command]
Required

Name

Employee Records

Comodel

hr.version

user_id: int
Name

None

Comodel

:ref:` <model->`

company_id: int
Required

Name

Company

Comodel

res.company

work_contact_id: int
Name

Work Contact

Comodel

res.partner

work_email: str
Name

Work Email

resource_calendar_id: int
Name

None

Comodel

:ref:` <model->`

tz: str
Name

None

contract_date_start: datetime.date
Name

None

contract_date_end: datetime.date
Name

None

first_contract_date: datetime.date
Name

First Contract Date

Readonly

this field is not supposed to/cannot be set manually

The date of the first contract of the employee in the company.

parent_id: int
Name

Manager

Comodel

hr.employee

coach_id: int
Name

Coach

Comodel

hr.employee

Select the «Employee» who is the coach of this employee. The «Coach» has no specific rights or responsibilities by default.

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

Tags

Comodel

hr.employee.category

hr_presence_state: str
Name

Hr Presence State

Readonly

this field is not supposed to/cannot be set manually

Store

this field is there only for technical reasons

Selection

present: Present absent: Absent out_of_working_hour: Off-Hours

Default

out_of_working_hour

create(vals_list)[fuente]

Create employees, each with its resource, first version and work contact.

Values of inherited hr.version fields (date_version, contract_date_start, wage, job_id, resource_calendar_id, …) go to the first version, created through _inherits; creation is done per company so that the version gets the right company. The values of a contract_template_id are applied to the first version (explicit values have priority). If user_id is given, the user’s partner, image and timezone are used (_sync_user()). A work contact is created for employees without one, an avatar is generated if there is no image, department channels are subscribed and an onboarding note is logged.

Example:

env['hr.employee'].create({
    'name': 'John Doe',
    'job_id': job.id,
    'date_version': date(2026, 1, 1),
    'contract_date_start': date(2026, 1, 1),
    'wage': 3000,
})
write(vals)[fuente]

Write on employees, sending inherited fields to their version_id in a single write.

Values of inherited hr.version fields are written on version_id, i.e. the current version, or the version given by the version_id context key (see _compute_version_id()). To change data from a given date on, create a version instead (create_version()). Writing on versions also sets last_modified_date/uid and logs an «As of <date>» tracking message.

Side effects: user_id syncs work contact/image/timezone (_sync_user()); tz is propagated to the linked user (same company); changing work_contact_id moves the bank accounts to the new partner (and untrusts them); resource_calendar_id updates the resource’s calendar when the written version is the current one; current_version_id always copies the new version’s calendar onto the resource; department_id/user_id subscribe the user to the department’s auto-subscription channels.

action_archive()[fuente]

Archive the employees and clear the references other employees have to them.

The fields listed by _get_employee_m2o_to_empty_on_archived_employees() and _get_user_m2o_to_empty_on_archived_employees() are emptied on the employees pointing to the archived ones (or their users). Archiving an employee also archives its resource (active is related).

_compute_version_id()[fuente]

Compute version_id, the version that the inherited (_inherits) fields read and write.

It is current_version_id, unless the context key version_id holds the id of a version of the employee: the employee then reads and writes that version (this is how the form view displays and edits a past or future version):

employee.with_context(version_id=old_version.id).wage
_compute_current_version_id()[fuente]

Compute the stored current_version_id.

The current version is the active version with the latest date_version on or before today (user’s timezone); if all versions are in the future, the first one. Contract dates are not considered: the current version may be out of contract. Since «today» changes, the value is refreshed daily by _cron_update_current_version_id().

_cron_update_current_version_id()[fuente]

Recompute current_version_id of all active employees (daily cron).

Needed because the current version depends on today’s date: a future version only becomes current when this cron runs on or after its date_version. The calendar and timezone of the employee’s resource_id follow the new current version (computed fields).

_get_version(date=None)[fuente]

Return the version in effect on date.

This is the active version with the latest date_version on or before date. If date is before all versions, the first version is returned. Archived versions are only used when the employee has no active version. Contract dates are ignored: the result may be out of contract (check version._is_in_contract(date) if needed). Single employee only.

Parámetros

date (datetime.date or None) – the reference date, today (user’s timezone) by default

Devuelve

a single version, or an empty recordset if the employee has none

Tipo del valor devuelto

hr.version

create_version(values)[fuente]

Create a new version of the employee, effective from values['date_version'].

The new version is a copy of the version in effect on that date (see _get_version()) on which values are written. If values does not specify them, the contract dates are those of the contract running on that date ((False, False) if none). If a contract_date_end different from the running contract’s end is given (with the same start), the end date is propagated to all versions of that contract. A contract_template_id in values is applied on the new version. Single employee only.

Pitfall: if a version already exists with exactly this date_version, it is returned unchanged (values are not written).

Parámetros

values (dict) – hr.version values; date_version (date, datetime or string) is mandatory

Devuelve

the new (or already existing) version

Tipo del valor devuelto

hr.version

Muestra
  • ValueError – if date_version is missing

  • AccessError – if the user cannot write on the employee and the copied version

create_contract(date)[fuente]

Start a new contract on date and return the version carrying it.

The contract starts on date and ends the day before the next future contract, or is open-ended (contract_date_end = False) if there is none. If a version already exists on date, its contract dates are overwritten; otherwise a new version is created with create_version() (copying the version in effect on date). The caller must make sure the employee is not already in contract on date (see check_no_existing_contract()). Single employee only.

Parámetros

date (datetime.date or str) – contract start date

Tipo del valor devuelto

hr.version

check_no_existing_contract(date)[fuente]

Raise if the employee is already in contract on date.

Call it before create_contract(). Single employee only.

Parámetros

date (datetime.date or str) – the date to check

Muestra

ValidationError – if a contract (of any active version) covers date

_is_in_contract(date)[fuente]

Return whether a contract of the employee covers date.

Based on the contract ranges (_get_contract_dates()), not on a single version: unlike hr.version._is_in_contract(), it answers True whatever the version in effect on date. Single employee only.

Parámetros

date (datetime.date) – the date to check

Tipo del valor devuelto

bool

_get_contracts(date_start=None, date_end=None, use_latest_version=True, domain=None)[fuente]

Return one representative version per contract overlapping the period, per employee.

Contracts are found with _get_contract_versions(). For each contract, a single version is kept: with use_latest_version, the version in effect on date_end (the last version of the contract if date_end is not given); otherwise the version in effect on date_start (the first version if not given). If no version of the contract has started yet on that date, its first version is used.

Parámetros
  • date_start (datetime.date or None) – start of the period (inclusive), unbounded if None

  • date_end (datetime.date or None) – end of the period (inclusive), unbounded if None

  • use_latest_version (bool) – pick the version in effect at the end (True) or at the start (False) of the period

  • domain (list or Domain or None) – extra hr.version domain

Devuelve

{employee_id: hr.version} (one record per contract), a defaultdict returning an empty recordset for unknown ids

Tipo del valor devuelto

dict[int, hr.version]

_get_contract_versions(date_start=None, date_end=None, domain=None)[fuente]

Return the versions of the contracts overlapping the period, grouped by employee and contract.

A contract is the set of active versions of an employee sharing the same contract_date_start. A contract overlaps the period if contract_date_start <= date_end and its contract_date_end is empty or >= date_start; all its versions are returned, even those whose own date_start/date_end lie outside the period.

Pitfall: on an empty recordset, versions of all employees are returned.

Parámetros
  • date_start (datetime.date or None) – start of the period (inclusive), unbounded if None

  • date_end (datetime.date or None) – end of the period (inclusive), unbounded if None

  • domain (list or Domain or None) – extra hr.version domain

Devuelve

{employee_id: {contract_date_start: hr.version}}, versions ordered by date_version (nested defaultdict)

Tipo del valor devuelto

dict[int, dict[datetime.date, hr.version]]

_get_all_contract_dates()[fuente]

Return the contract periods of the employee, ordered by start date.

One tuple per distinct (contract_date_start, contract_date_end) among the active versions. Single employee only.

Devuelve

[(date_from, date_to), ...] with inclusive dates; date_to is False for an open-ended contract

Tipo del valor devuelto

list[tuple[datetime.date, datetime.date | bool]]

_get_contract_dates(date)[fuente]

Return the period of the contract covering date.

Single employee only.

Parámetros

date (datetime.date) – the date to check

Devuelve

(date_from, date_to) (date_to is False if open-ended), or (False, False) if not in contract on date

Tipo del valor devuelto

tuple

_get_versions_with_contract_overlap_with_period(date_from, date_to)[fuente]

Return the versions in effect, and in contract, at least one day of [date_from, date_to].

Within a contract, a version is effective from its date_version (the first version from the contract start) until the day before the next version of the same contract (the last one until the contract end). Versions of an overlapping contract that were superseded before the period (e.g. replaced on 1 March when asking for April) are excluded. Works in batch.

Parámetros
Devuelve

versions ordered by date_version

Tipo del valor devuelto

hr.version

_get_all_versions_with_contract_overlap_with_period(date_from, date_to)[fuente]

Return the versions of all employees in contract during [date_from, date_to].

Model-level variant of _get_versions_with_contract_overlap_with_period() (searches the employees accessible to the user, active ones only).

Parámetros
Devuelve

versions ordered by date_version

Tipo del valor devuelto

hr.version

_get_first_contract_version_at(date)[fuente]

Return, per employee in contract on date, the version of that contract in effect on date.

Among the versions whose contract covers date: the latest one with date_version <= date, or the first one of the contract if none has started yet. Employees not in contract on date are absent from the result.

Parámetros

date (datetime.date) – the reference date

Devuelve

{employee_id: hr.version} (sudoed versions)

Tipo del valor devuelto

dict[int, hr.version]

_get_version_periods(start, stop, field=None, check_contract=False)[fuente]

Split [start, stop] into the periods covered by each version, per employee.

Each period is the intersection of [start, stop] with the version’s [date_start, date_end] (an open-ended version is clipped at stop). Days not covered by any version (e.g. before the first version) are simply absent. Works in batch.

Example:

employee._get_version_periods(date(2026, 1, 1), date(2026, 12, 31), 'wage')
# {hr.employee(23,): [(date(2026, 1, 1), date(2026, 5, 31), 3000.0),
#                     (date(2026, 6, 1), date(2026, 12, 31), 3500.0)]}
Parámetros
Devuelve

{employee record: [(date_from, date_to, value), ...]} with inclusive dates, ordered by date (defaultdict(list))

Tipo del valor devuelto

dict

Muestra

UserError – if a field does not exist

_get_first_contract_date(date_limit=datetime.date(9999, 12, 31))[fuente]

Return the first contract start date of the last uninterrupted block of versions.

This is the value of the stored first_contract_date field (computed with the default date_limit): the seniority start date, ignoring older contracts separated from the current ones by a gap with working days. See _get_last_consecutive_versions().

Parámetros

date_limit (datetime.date) – only consider versions starting on or before this date

Devuelve

the smallest contract_date_start of the block, or False

Tipo del valor devuelto

datetime.date or bool

_get_last_consecutive_versions(date_limit=datetime.date(9999, 12, 31))[fuente]

Return the last uninterrupted block of versions starting on or before date_limit.

Starting from the latest version, earlier versions are included as long as there are no working hours (according to the earlier version’s calendar) between the end of a version and the start of the next one (see hr.version._has_work_hours_between_versions()). A gap of non-working days only (e.g. a weekend) does not break the block.

Example: with versions 01/01-31/07 (3 versions, consecutive), then a working-days gap, then 01/09-20/11 (2 versions), only the last 2 are returned.

Restricted to HR officers (or superuser). Single employee only.

Parámetros

date_limit (datetime.date) – upper bound on the versions date_start

Devuelve

versions ordered by date_start

Tipo del valor devuelto

hr.version

Muestra

AccessError – if the user is not an HR officer

_get_calendars(date_from=None)[fuente]

Return the working schedule of each employee, on date_from if given.

Without date_from, the calendar of the current version. With date_from, employees in contract on that date get the calendar of the version in effect (_get_first_contract_version_at()); the others keep the current version’s calendar.

Parámetros

date_from (datetime.date or datetime.datetime or None) – reference date (a datetime is truncated to its date)

Devuelve

{employee_id: resource.calendar}

Tipo del valor devuelto

dict[int, resource.calendar]

_get_hours_per_week_batch(date_from=None)[fuente]

Return the hours_per_week of each employee’s calendar, see _get_calendars().

Devuelve

{employee_id: float}

Tipo del valor devuelto

dict[int, float]

_get_hours_per_day_batch(date_from=None)[fuente]

Return the hours_per_day of each employee’s calendar, see _get_calendars().

Devuelve

{employee_id: float}

Tipo del valor devuelto

dict[int, float]

_get_calendar_periods(start, stop, check_contract=True)[fuente]

Return the working schedule periods of the employees within [start, stop].

Shortcut for _get_version_periods(start, stop, 'resource_calendar_id', check_contract) in sudo; note that check_contract defaults to True here, so periods out of contract are excluded.

Parámetros
  • start (datetime.date) – start of the period (inclusive)

  • stop (datetime.date) – end of the period (inclusive)

  • check_contract (bool) – only consider the versions in contract

Devuelve

{employee record: [(date_from, date_to, resource.calendar), ...]}

Tipo del valor devuelto

dict

_get_expected_attendances(date_from, date_to)[fuente]

Return the work intervals the employee is expected to attend between two datetimes.

Computed per version in contract during the period, each with its own calendar and timezone, minus the leaves counted as absence. Without any version in contract, the current calendar is used for the whole period. Single employee only.

Parámetros
Devuelve

intervals of (start, stop, resource.calendar.attendance) with aware datetimes

Tipo del valor devuelto

Intervals

_get_calendar_attendances(date_from, date_to)[fuente]

Return the working time (days and hours) between two datetimes, per the calendars in contract.

Sums resource.calendar.get_work_duration_data over the versions in contract during the period (each with its calendar; the leaves counted as absence of the version’s company or without company are deduced). Without any version in contract, the current calendar is used for the whole period. Single employee only.

Parámetros
Devuelve

{'days': float, 'hours': float}

Tipo del valor devuelto

dict

_get_unusual_days(date_from, date_to=None)[fuente]

Return, for each day of the period, whether it is an «unusual» (non-working) day.

Used by the calendar widgets to grey out days. Each day covered by a version in contract is evaluated with that version’s calendar (resource.calendar._get_unusual_days, including public holidays of the company); days between contract periods are unusual. If no version is in contract during the period, all days are returned as not unusual. Single employee (or empty recordset).

Parámetros
  • date_from (str) – start, formatted '%Y-%m-%d %H:%M:%S'

  • date_to (str) – end, same format; None means date_from (a single day)

Devuelve

{'YYYY-MM-DD': bool}, True for unusual days, both bounds included

Tipo del valor devuelto

dict[str, bool]

_get_employee_unavailable_intervals(start, stop)[fuente]

Return the unavailability intervals of each employee within [start, stop] (Gantt views).

The available time is computed per version in contract (calendar and timezone of each version): work intervals for regular calendars, whole attendance days for duration-based calendars, everything but leaves for flexible calendars. Unavailability is the complement within [start, stop]; an employee without contract in the period is unavailable the whole period.

Parámetros
Devuelve

{employee_id: [{'start': datetime, 'stop': datetime}, ...]} with UTC-aware datetimes

Tipo del valor devuelto

dict[int, list[dict]]

_get_working_periods_by_field(start, stop, field_key)[fuente]

Return the contract periods of the employees overlapping [start, stop], keyed by field_key.

Used by Gantt views of non-employee models (keyed by e.g. resource or user). Each contract overlapping the period gives one entry (not clipped to the period); an employee without any contract gets the whole [start, stop] period.

Parámetros
  • start – start of the period (string or naive UTC datetime)

  • stop – end of the period (string or naive UTC datetime)

  • field_key (str) – many2one field of hr.employee used as key, e.g. 'resource_id' or 'user_id'

Devuelve

{field_key id: [{'start': ..., 'end': ...}, ...]}. For contracts, start is the contract start date as a datetime string (midnight, not timezone-converted) and end is the end of the contract’s last day in the user’s timezone, as a naive UTC datetime string, or False if open-ended. For employees without contract, a single entry whose start/end are the given bounds as datetime objects (not strings).

Tipo del valor devuelto

dict[int, list[dict]]

_adjust_leaves(leave_intervals)[fuente]

Hook to alter the leave intervals subtracted from duration-based work intervals.

Used by _get_employee_unavailable_intervals(); overridden in hr_holidays. Returns leave_intervals unchanged by default.

Parámetros

leave_intervals (Intervals) – leave intervals of one employee

Tipo del valor devuelto

Intervals

_get_tz(date=None)[fuente]

Return the timezone name of the employee, on date if given.

The timezone is stored on the version: without date, the one of version_id (current version) is used, otherwise the one of the version in effect on date. Falls back on the user’s partner timezone, the company timezone, then 'UTC'. Single employee only.

Parámetros

date (datetime.date or datetime.datetime or str or None) – reference date

Tipo del valor devuelto

str

_get_resources_per_tz(date=None)[fuente]

Group the employees” resources by timezone, as expected by resource.calendar batch methods.

Parámetros

date (datetime.date or datetime.datetime or None) – reference date for the timezone (see _get_tz())

Devuelve

{ZoneInfo: resource.resource}

Tipo del valor devuelto

dict

_is_flexible(date=None)[fuente]

Return whether the working schedule in effect on date is flexible.

Parámetros

date (datetime.date or None) – reference date (today by default), see _get_version()

Tipo del valor devuelto

bool

_is_fully_flexible(date=None)[fuente]

Return whether the working schedule in effect on date is fully flexible.

Parámetros

date (datetime.date or None) – reference date (today by default), see _get_version()

Tipo del valor devuelto

bool

_prepare_resource_values(vals, tz)[fuente]

Return the values of the resource.resource created with the employee.

Extends resource.mixin with user_id and active. Pops name and user_id from vals (they are related to the resource) to avoid a useless write after creation.

Parámetros
  • vals (dict) – employee creation values (modified in place)

  • tz (str) – timezone of the resource

Tipo del valor devuelto

dict

_sync_user(user, employee_has_image=False)[fuente]

Return the employee values to write when linking user to the employee.

The user’s partner becomes the work contact, and the user’s image (unless the employee has one) and timezone (if set) are copied. Used by create(), write(), the user_id onchange and res.users.action_create_employee. Does not write anything itself.

Parámetros
  • user (res.users) – the user to link (may be empty)

  • employee_has_image (bool) – if True, keep the employee’s image

Devuelve

values for hr.employee.write/create (work_contact_id, user_id, optionally image_1920 and tz)

Tipo del valor devuelto

dict

_get_or_create_light_user(invite=False)[fuente]

Return a user for the employee, creating a light (less billable) internal user if needed.

Reuses, in order: the resource’s user, the work contact’s user, or an existing active internal user whose login/email matches the work email. Otherwise creates a user restricted to the light groups, in the employee’s company. Does not link the user: the caller assigns user_id (see action_send_invitation()). Single employee only.

Parámetros

invite (bool) – send the signup/reset password e-mail to the reused user; refuses to reuse a user only matched by e-mail

Tipo del valor devuelto

res.users

Muestra
  • ValidationError – if no e-mail is available to build the login

  • UserError – if the matching user is archived, a portal user, already has an employee in the company, or (with invite) was only matched by e-mail

_get_subordinates(parents=None)[fuente]

Return all direct and indirect subordinates (via child_ids) of the employees in self.

Cycles in the hierarchy are supported (e.g. the CEO manages the CTO who manages the R&D department the CEO belongs to): employees already in parents or self are never returned. Only active subordinates are followed (child_ids domain).

Parámetros

parents (hr.employee or None) – employees already visited, used by the recursion

Tipo del valor devuelto

hr.employee

_get_employee_m2o_to_empty_on_archived_employees()[fuente]

Return the hr.employee many2one fields (to hr.employee) emptied when the target is archived.

Hook for action_archive(), e.g. a manager or coach who is archived is removed from their subordinates.

Tipo del valor devuelto

list[str]

_get_user_m2o_to_empty_on_archived_employees()[fuente]

Return the hr.employee many2one fields (to res.users) emptied when the target’s employee is archived.

Hook for action_archive() (e.g. hr_holidays adds the time off approver leave_manager_id).

Tipo del valor devuelto

list[str]

_compute_presence_state()[fuente]

Compute hr_presence_state (present, absent or out_of_working_hour).

In hr, only the login-based presence control is implemented (company setting hr_presence_control_login): out of contract today gives out_of_working_hour; an online user gives present; an offline user expected to work now (_get_employee_working_now()) gives absent. Without the setting, the state is always out_of_working_hour. Extended by hr_attendance, hr_holidays, …

_compute_presence_icon()[fuente]

Compute hr_icon_display and show_hr_icon_display (presence icon of kanban/avatar cards).

Today’s work location (exceptional location, else the weekday location) wins and gives presence_home/presence_office/presence_other. Custom location types use presence_other. Otherwise the icon is 'presence_' + hr_presence_state, shown only for employees linked to a user. Overridden by e.g. hr_holidays and hr_attendance to add time off / attendance based icons.

_get_employee_working_now()[fuente]

Return the ids of the employees of self expected to work now.

An employee is «working now» when their current working schedule has work intervals within the next hour (in the employee’s timezone). Intervals are computed per calendar, so only company-wide leaves are considered, not the employee’s own time off. Employees without calendar are never returned. Despite @api.model, it works on self.

Tipo del valor devuelto

list[int]

_get_new_hire_field()[fuente]

Return the name of the date(time) field used to compute newly_hired.

An employee is newly hired when this field is less than 90 days old. Override to use another field (default: 'create_date').

Tipo del valor devuelto

str

Return the company country codes for which the legal name is split into first/last name.

Hook for localizations (e.g. l10n_be_hr_payroll); drives split_legal_name.

Tipo del valor devuelto

list[str]

Return the partners related to the employees (work contacts and users” partners).

Hook extended by other modules (e.g. hr_recruitment adds the applicants” partners); used by the «Related Contacts» smart button.

Tipo del valor devuelto

res.partner

Search employees, through hr.employee.public for users without read access.

_search is where access rights are checked, so overriding it makes env['hr.employee'].search(...) return an hr.employee recordset even for users without access to the model: the domain is evaluated on hr.employee.public, whose ids are the employee ids. Conditions on current_version_id (produced by the optimization of inherited fields) are converted to conditions on id.

Muestra

AccessError – if the domain uses fields not available on hr.employee.public

search_fetch(domain, field_names=None, offset=0, limit=None, order=None)[fuente]

Search and fetch, through hr.employee.public for users without read access.

Users without access to hr.employee (e.g. regular internal users) still get an hr.employee recordset: the search runs on hr.employee.public (conditions on version_id/current_version_id are mapped or dropped) and the public values are copied into the cache of the hr.employee records.

Muestra

AccessError – if field_names or domain contain fields not available on hr.employee.public

fetch(field_names=None)[fuente]

Fetch fields, reading them from hr.employee.public for users without read access.

This is what makes employee.name work for a regular internal user: the public values are copied into the hr.employee cache; reading a non-public field raises an AccessError.

Muestra

AccessError – if field_names contains fields not available on hr.employee.public

action_new_departure()[fuente]

Open the «End of collaboration» wizard (hr.employee.departure) for the employee.

If a departure is already planned (not applied), returns a client action asking to cancel it first.

Muestra

UserError – if the employee is not in contract today, or has already departed

action_cancel_departure()[fuente]

Cancel the employee’s departure: unarchive, restore the contract end date and delete the departure.

The contract end date saved on the departure (last_contract_date_end) is restored on the contract that was ended. Versions deleted when the departure was applied are not restored.

Original definition from hr

model odoo.addons.hr.models.hr_employee_public.HrEmployeePublic[fuente]
_name = hr.employee.public
employee_id: int
Name

Employee

Readonly

this field is not supposed to/cannot be set manually

Comodel

hr.employee

is_manager: bool
Name

Is Manager

Readonly

this field is not supposed to/cannot be set manually

Store

this field is there only for technical reasons

init()[fuente]

(Re)create the SQL view: hr_employee joined with its current hr_version.

hr.employee.public is a read-only view exposing the non-sensitive employee data to all internal users (hr.employee itself is restricted to HR officers). Its ids are the employee ids. Archived employees are included (filtered by the active column).

_get_fields()[fuente]

Yield the SQL column expressions of the hr_employee_public view.

Every stored field declared on this model becomes a column of the view, taken from the current version (alias v) if it is a stored hr.version field, from the employee (alias e) otherwise. To expose a new stored field publicly, declare it on hr.employee.public with the same name; no SQL is needed.

Tipo del valor devuelto

Iterator[SQL]

_compute_from_employee(field_names)[fuente]

Copy the values of field_names from the corresponding hr.employee records (read in sudo).

Helper for computed fields of the public employee that mirror a non-stored employee field.

Parámetros

field_names (str or list[str]) – field name(s), identical on both models

_get_manager_only_fields()[fuente]

Return the fields only visible on public employees managed by the current user.

Hook for modules (e.g. hr_appraisal): such fields must be computed by _compute_manager_only_fields(), which reads them in sudo on the employee when is_manager is set (False otherwise).

Tipo del valor devuelto

list[str]

_compute_manager_only_fields()[fuente]

Compute the _get_manager_only_fields() from the employee, for managers only.

is_manager is set on the current user’s own employee and all employees below it in the parent_id hierarchy.

Additional fields with hr

model odoo.addons.hr.models.resource.ResourceResource[fuente]
employee_id: Sequence[odoo.orm.commands.Command]
Name

Employee

Comodel

hr.employee

_get_calendars_validity_within_period(start, end, default_company=None)[fuente]

Return the calendars of each resource with their validity intervals, following the employees” contracts.

Resources of employees who ever had a contract get one entry per calendar used by their versions in contract, valid only while in contract (_get_contracts_valid_periods()): out of contract, they have no calendar, hence no work time. Other resources keep the resource behavior (own calendar, whole period).

Parámetros
  • start (datetime.datetime) – timezone-aware start

  • end (datetime.datetime) – timezone-aware end

  • default_company (res.company or None) – company whose calendar is used when self is empty

Devuelve

{resource_id: {resource.calendar: Intervals}}

Tipo del valor devuelto

dict

_get_contracts_valid_periods(start, end)[fuente]

Return, per resource and calendar, the intervals within [start, end] covered by a version in contract.

Each version in contract during the period (hr.employee._get_versions_with_contract_overlap_with_period) contributes the interval from its date_start 00:00 to its date_end 23:59:59 (in the employee’s timezone), clipped to [start, end]. Periods out of contract are absent.

Parámetros
Devuelve

{resource_id: {resource.calendar: Intervals}} (nested defaultdict)

Tipo del valor devuelto

dict

_get_resource_without_contract()[fuente]

Return the resources that are not employees, or whose employee never had a contract.

«Ever»: any active version with a contract_date_start, whatever the dates. Such resources keep the plain resource behavior (their own calendar_id over the whole period).

Tipo del valor devuelto

resource.resource

_get_calendar_data_at(date_target, tz=False)[fuente]

Return the calendar, hours per week and hours per day of each resource on a given day.

For employees, the calendar of the version in contract on the day (date_target converted to tz) is used, see hr.employee._get_calendars; other resources use their own calendar.

Parámetros
Devuelve

{resource: {'resource_calendar_id': resource.calendar, 'hours_per_week': float, 'hours_per_day': float}}

Tipo del valor devuelto

dict

Additional fields with hr

model odoo.addons.hr.models.res_users.ResUsers[fuente]
employee_id: int
Name

Company employee

Readonly

this field is not supposed to/cannot be set manually

Store

this field is there only for technical reasons

Comodel

hr.employee

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

Related employee

Comodel

hr.employee

create(vals_list)[fuente]

Create users, optionally creating or binding their employee.

With create_employee=True in the values, an employee is created in the current company (hr.employee._sync_user()); with create_employee_id, that existing employee is linked to the user.

write(vals)[fuente]

Write on users and synchronize their employees in the current company.

Writing employee-related fields (see related_employee_field()) notifies the partners given by _get_personal_info_partner_ids_to_notify(). The fields of _get_employee_fields_to_sync() are then written (in sudo) on the users” employees of the current company.

action_create_employee()[fuente]

Create an employee for the user in the current company and return it.

Tipo del valor devuelto

hr.employee

Muestra

AccessError – if the user is not allowed in the current company

_get_employee_fields_to_sync()[fuente]

Return the res.users fields copied to the related employees when written on the user.

email is copied to work_email; image_1920 only to employees without image. Only employees of the current company are updated.

Tipo del valor devuelto

list[str]

_get_personal_info_partner_ids_to_notify(employee)[fuente]

Return who to notify when a user updates the employee data exposed on their user.

Parámetros

employee (hr.employee) – the employee whose data is modified

Devuelve

(reason_message, partner_ids); by default the employee’s HR responsible, ('', []) if none

Tipo del valor devuelto

tuple[str, list[int]]