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), throughresource.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 carriesname,user_idandactive: these employee fields are related to the resource.resource.employee_idis 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_idandtzmirror the current version: they are computed fromemployee_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_idis the employee in the current company (env.company),user.employee_idsthe employees in the allowed companies (env.companies) anduser.all_employee_idsthe 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 towork_email),image_1920,tzor the weekly work locations on the user writes them on its employees of the current company (_get_employee_fields_to_sync());writing
tzon the employee writes it on the user when both have the same company.
A user can be created with an employee (
create_employee=Truein the values ofres.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) withrelated_employee_field(): a user reads their own employee in sudo, and writes it in sudo for the fields declared withuser_writeable=True.- Work contact (
work_contact_id) Created if missing when the employee is created.
work_emailandwork_phoneare 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_idslists 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.versionfield, 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 theresource.calendarmethods.
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.mixinmethods use one calendar per employee for the whole period:_get_work_days_data_batchuses 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_dayand_get_leave_days_data_batchuse the current calendar (resource_calendar_id);when no version is in contract during the period,
_get_expected_attendancesand_get_calendar_attendancesuse the current calendar for the whole period, and_get_unusual_daysreturns 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) andhr_icon_displayare computed by_compute_presence_state()and_compute_presence_icon().hronly implements the presence based on the user’s login (company setting);hr_attendanceandhr_holidaysextend 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
activeis 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:
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 inlast_contract_date_end.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_dateis 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_idis required on the employee; the company of its versions follows it. Record rules restrict employees tocompany_id in company_ids + [False]and versions tocompany_id in company_ids.hr.employee.publichas no record rule inhr.One employee per user and company;
user.employee_iddepends onenv.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:
_get_employee_m2o_to_empty_on_archived_employees()and_get_user_m2o_to_empty_on_archived_employees();_get_new_hire_field()(field used for «newly hired»);_get_splitting_legal_name_countries()(countries where the legal name is split into first and last names);_adjust_leaves()(leaves of duration-based calendars, used byhr_holidays);
Pitfalls¶
An employee field without
groupsmust exist onhr.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_periodsare keyed by employee record, most other methods by employee id.user.employee_idis the employee of the current company; useemployee_idsorall_employee_idsacross 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.- 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
- current_version_id: int¶
- Name
Current Employee Record
- Readonly
this field is not supposed to/cannot be set manually
- Comodel
- version_ids: Sequence[odoo.orm.commands.Command]¶
- Required
- Name
Employee Records
- Comodel
- 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.
- coach_id: int¶
- Name
Coach
- Comodel
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_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: Presentabsent: Absentout_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.versionfields (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 acontract_template_idare applied to the first version (explicit values have priority). Ifuser_idis 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_idin a single write.Values of inherited
hr.versionfields are written onversion_id, i.e. the current version, or the version given by theversion_idcontext key (see_compute_version_id()). To change data from a given date on, create a version instead (create_version()). Writing on versions also setslast_modified_date/uidand logs an «As of <date>» tracking message.Side effects:
user_idsyncs work contact/image/timezone (_sync_user());tzis propagated to the linked user (same company); changingwork_contact_idmoves the bank accounts to the new partner (and untrusts them);resource_calendar_idupdates the resource’s calendar when the written version is the current one;current_version_idalways copies the new version’s calendar onto the resource;department_id/user_idsubscribe 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 (activeis 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 keyversion_idholds 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_versionon 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_idof 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’sresource_idfollow 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_versionon or beforedate. Ifdateis 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 (checkversion._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 whichvaluesare written. Ifvaluesdoes not specify them, the contract dates are those of the contract running on that date ((False, False)if none). If acontract_date_enddifferent from the running contract’s end is given (with the same start), the end date is propagated to all versions of that contract. Acontract_template_idinvaluesis applied on the new version. Single employee only.Pitfall: if a version already exists with exactly this
date_version, it is returned unchanged (valuesare not written).- Parámetros
values (dict) –
hr.versionvalues;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_versionis missingAccessError – if the user cannot write on the employee and the copied version
- create_contract(date)[fuente]¶
Start a new contract on
dateand return the version carrying it.The contract starts on
dateand 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 ondate, its contract dates are overwritten; otherwise a new version is created withcreate_version()(copying the version in effect ondate). The caller must make sure the employee is not already in contract ondate(seecheck_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: unlikehr.version._is_in_contract(), it answersTruewhatever the version in effect ondate. Single employee only.- Parámetros
date (datetime.date) – the date to check
- Tipo del valor devuelto
- _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: withuse_latest_version, the version in effect ondate_end(the last version of the contract ifdate_endis not given); otherwise the version in effect ondate_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
Nonedate_end (datetime.date or None) – end of the period (inclusive), unbounded if
Noneuse_latest_version (bool) – pick the version in effect at the end (
True) or at the start (False) of the period
- Devuelve
{employee_id: hr.version}(one record per contract), adefaultdictreturning an empty recordset for unknown ids- Tipo del valor devuelto
- _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 ifcontract_date_start <= date_endand itscontract_date_endis empty or>= date_start; all its versions are returned, even those whose owndate_start/date_endlie 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
Nonedate_end (datetime.date or None) – end of the period (inclusive), unbounded if
None
- Devuelve
{employee_id: {contract_date_start: hr.version}}, versions ordered bydate_version(nesteddefaultdict)- 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_toisFalsefor an open-ended contract- Tipo del valor devuelto
- _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_toisFalseif open-ended), or(False, False)if not in contract ondate- Tipo del valor devuelto
- _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
date_from (datetime.date) – start of the period (inclusive)
date_to (datetime.date) – end of the period (inclusive)
- 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
date_from (datetime.date) – start of the period (inclusive)
date_to (datetime.date) – end of the period (inclusive)
- 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 ondate.Among the versions whose contract covers
date: the latest one withdate_version <= date, or the first one of the contract if none has started yet. Employees not in contract ondateare absent from the result.- Parámetros
date (datetime.date) – the reference date
- Devuelve
{employee_id: hr.version}(sudoed versions)- Tipo del valor devuelto
- _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 atstop). 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
start (datetime.date) – start of the period (inclusive)
stop (datetime.date) – end of the period (inclusive)
field (str or list[str] or None) –
hr.versionfield name to map on each period, or a list of field names (values are then tuples); the version itself if emptycheck_contract (bool) – only consider the versions in contract during the period (
_get_versions_with_contract_overlap_with_period())
- Devuelve
{employee record: [(date_from, date_to, value), ...]}with inclusive dates, ordered by date (defaultdict(list))- Tipo del valor devuelto
- 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_datefield (computed with the defaultdate_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_startof the block, orFalse- Tipo del valor devuelto
- _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_fromif given.Without
date_from, the calendar of the current version. Withdate_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
- _get_hours_per_week_batch(date_from=None)[fuente]¶
Return the
hours_per_weekof each employee’s calendar, see_get_calendars().
- _get_hours_per_day_batch(date_from=None)[fuente]¶
Return the
hours_per_dayof each employee’s calendar, see_get_calendars().
- _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 thatcheck_contractdefaults toTruehere, 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
- _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
date_from (datetime.datetime) – timezone-aware start
date_to (datetime.datetime) – timezone-aware end
- 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_dataover 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
date_from (datetime.datetime) – start (timezone-aware if the employee has a timezone)
date_to (datetime.datetime) – end (same remark)
- Devuelve
{'days': float, 'hours': float}- Tipo del valor devuelto
- _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).
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
start (datetime.datetime) – timezone-aware start
stop (datetime.datetime) – timezone-aware end
- Devuelve
{employee_id: [{'start': datetime, 'stop': datetime}, ...]}with UTC-aware datetimes- Tipo del valor devuelto
- _get_working_periods_by_field(start, stop, field_key)[fuente]¶
Return the contract periods of the employees overlapping
[start, stop], keyed byfield_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.employeeused as key, e.g.'resource_id'or'user_id'
- Devuelve
{field_key id: [{'start': ..., 'end': ...}, ...]}. For contracts,startis the contract start date as a datetime string (midnight, not timezone-converted) andendis the end of the contract’s last day in the user’s timezone, as a naive UTC datetime string, orFalseif open-ended. For employees without contract, a single entry whosestart/endare the given bounds asdatetimeobjects (not strings).- Tipo del valor devuelto
- _adjust_leaves(leave_intervals)[fuente]¶
Hook to alter the leave intervals subtracted from duration-based work intervals.
Used by
_get_employee_unavailable_intervals(); overridden inhr_holidays. Returnsleave_intervalsunchanged 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
dateif given.The timezone is stored on the version: without
date, the one ofversion_id(current version) is used, otherwise the one of the version in effect ondate. 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
- _get_resources_per_tz(date=None)[fuente]¶
Group the employees” resources by timezone, as expected by
resource.calendarbatch 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
- _is_flexible(date=None)[fuente]¶
Return whether the working schedule in effect on
dateis flexible.- Parámetros
date (datetime.date or None) – reference date (today by default), see
_get_version()- Tipo del valor devuelto
- _is_fully_flexible(date=None)[fuente]¶
Return whether the working schedule in effect on
dateis fully flexible.- Parámetros
date (datetime.date or None) – reference date (today by default), see
_get_version()- Tipo del valor devuelto
- _prepare_resource_values(vals, tz)[fuente]¶
Return the values of the
resource.resourcecreated with the employee.Extends
resource.mixinwithuser_idandactive. Popsnameanduser_idfromvals(they are related to the resource) to avoid a useless write after creation.
- _sync_user(user, employee_has_image=False)[fuente]¶
Return the employee values to write when linking
userto 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(), theuser_idonchange andres.users.action_create_employee. Does not write anything itself.
- _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(seeaction_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 inself.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
parentsorselfare never returned. Only active subordinates are followed (child_idsdomain).- 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.employeemany2one fields (tohr.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.
- _get_user_m2o_to_empty_on_archived_employees()[fuente]¶
Return the
hr.employeemany2one fields (tores.users) emptied when the target’s employee is archived.Hook for
action_archive()(e.g.hr_holidaysadds the time off approverleave_manager_id).
- _compute_presence_state()[fuente]¶
Compute
hr_presence_state(present,absentorout_of_working_hour).In
hr, only the login-based presence control is implemented (company settinghr_presence_control_login): out of contract today givesout_of_working_hour; an online user givespresent; an offline user expected to work now (_get_employee_working_now()) givesabsent. Without the setting, the state is alwaysout_of_working_hour. Extended byhr_attendance,hr_holidays, …
- _compute_presence_icon()[fuente]¶
Compute
hr_icon_displayandshow_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 usepresence_other. Otherwise the icon is'presence_' + hr_presence_state, shown only for employees linked to a user. Overridden by e.g.hr_holidaysandhr_attendanceto add time off / attendance based icons.
- _get_employee_working_now()[fuente]¶
Return the ids of the employees of
selfexpected 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 onself.
- _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
- _get_splitting_legal_name_countries()[fuente]¶
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); drivessplit_legal_name.
Return the partners related to the employees (work contacts and users” partners).
Hook extended by other modules (e.g.
hr_recruitmentadds the applicants” partners); used by the «Related Contacts» smart button.- Tipo del valor devuelto
res.partner
- _search(domain, offset=0, limit=None, order=None, *, bypass_access=False, **kwargs)[fuente]¶
Search employees, through
hr.employee.publicfor users without read access._searchis where access rights are checked, so overriding it makesenv['hr.employee'].search(...)return anhr.employeerecordset even for users without access to the model: the domain is evaluated onhr.employee.public, whose ids are the employee ids. Conditions oncurrent_version_id(produced by the optimization of inherited fields) are converted to conditions onid.- 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.publicfor users without read access.Users without access to
hr.employee(e.g. regular internal users) still get anhr.employeerecordset: the search runs onhr.employee.public(conditions onversion_id/current_version_idare mapped or dropped) and the public values are copied into the cache of thehr.employeerecords.- Muestra
AccessError – if
field_namesordomaincontain fields not available onhr.employee.public
- fetch(field_names=None)[fuente]¶
Fetch fields, reading them from
hr.employee.publicfor users without read access.This is what makes
employee.namework for a regular internal user: the public values are copied into thehr.employeecache; reading a non-public field raises anAccessError.- Muestra
AccessError – if
field_namescontains fields not available onhr.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
- 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_employeejoined with its currenthr_version.hr.employee.publicis a read-only view exposing the non-sensitive employee data to all internal users (hr.employeeitself is restricted to HR officers). Its ids are the employee ids. Archived employees are included (filtered by theactivecolumn).
- _get_fields()[fuente]¶
Yield the SQL column expressions of the
hr_employee_publicview.Every stored field declared on this model becomes a column of the view, taken from the current version (alias
v) if it is a storedhr.versionfield, from the employee (aliase) otherwise. To expose a new stored field publicly, declare it onhr.employee.publicwith the same name; no SQL is needed.- Tipo del valor devuelto
Iterator[SQL]
- _compute_from_employee(field_names)[fuente]¶
Copy the values of
field_namesfrom the correspondinghr.employeerecords (read in sudo).Helper for computed fields of the public employee that mirror a non-stored employee field.
- _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 whenis_manageris set (Falseotherwise).
- _compute_manager_only_fields()[fuente]¶
Compute the
_get_manager_only_fields()from the employee, for managers only.is_manageris set on the current user’s own employee and all employees below it in theparent_idhierarchy.
Additional fields with hr¶
- model odoo.addons.hr.models.resource.ResourceResource[fuente]¶
- employee_id: Sequence[odoo.orm.commands.Command]¶
- Name
Employee
- Comodel
- _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 theresourcebehavior (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
selfis empty
- Devuelve
{resource_id: {resource.calendar: Intervals}}- Tipo del valor devuelto
- _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 itsdate_start00:00 to itsdate_end23:59:59 (in the employee’s timezone), clipped to[start, end]. Periods out of contract are absent.- Parámetros
start (datetime.datetime) – timezone-aware start
end (datetime.datetime) – timezone-aware end
- Devuelve
{resource_id: {resource.calendar: Intervals}}(nesteddefaultdict)- Tipo del valor devuelto
- _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 plainresourcebehavior (their owncalendar_idover 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_targetconverted totz) is used, seehr.employee._get_calendars; other resources use their own calendar.- Parámetros
date_target (datetime.datetime) – timezone-aware datetime
tz (zoneinfo.ZoneInfo) – timezone in which
date_target’s day is evaluated
- Devuelve
{resource: {'resource_calendar_id': resource.calendar, 'hours_per_week': float, 'hours_per_day': float}}- Tipo del valor devuelto
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
- employee_ids: Sequence[odoo.orm.commands.Command]¶
- Name
Related employee
- Comodel
- create(vals_list)[fuente]¶
Create users, optionally creating or binding their employee.
With
create_employee=Truein the values, an employee is created in the current company (hr.employee._sync_user()); withcreate_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.usersfields copied to the related employees when written on the user.emailis copied towork_email;image_1920only to employees without image. Only employees of the current company are updated.