[FIX] hr{,_contract,_holidays}: default to company calendar
Versions
--------
- 17.0+
Steps
-----
1. Go to Employees app as admin;
2. clear the "Working Hours" field & save;
3. go to Time Off app.
Issue
-----
Odoo Server Error.
Cause
-----
Commit 8f87e102a9 added the
`_get_consumed_leaves` method to `hr.employee`, which calls on
`resource.calendar` methods with `ensure_one()` active. The
`resource_calendar_id` is not a required field for employees, so an
error occurs when these methods are called on an empty record.
Solution
--------
Default to `company_id.resource_calendar_id` where a calendar is
expected.
Also fixes a potential issue in `hr_contract` when getting attendances
between a time interval that includes multiple contracts using different
calendars.
opw-3665412
closes odoo/odoo#149908
Signed-off-by: Bertrand Dossogne (bedo) <bedo@odoo.com>
This commit is contained in:
@@ -517,7 +517,8 @@ class HrEmployeePrivate(models.Model):
|
||||
def _get_calendar_attendances(self, date_from, date_to):
|
||||
self.ensure_one()
|
||||
employee_timezone = timezone(self.tz) if self.tz else None
|
||||
return self.resource_calendar_id\
|
||||
calendar = self.resource_calendar_id or self.company_id.resource_calendar_id
|
||||
return calendar\
|
||||
.with_context(employee_timezone=employee_timezone)\
|
||||
.get_work_duration_data(
|
||||
date_from,
|
||||
|
||||
@@ -145,13 +145,21 @@ class Employee(models.Model):
|
||||
valid_contracts = self.sudo()._get_contracts(date_from, date_to, states=['open', 'close'])
|
||||
if not valid_contracts:
|
||||
return super()._get_calendar_attendances(date_from, date_to)
|
||||
employee_timezone = timezone(self.tz) if self.tz else None
|
||||
return valid_contracts.resource_calendar_id\
|
||||
.with_context(employee_timezone=employee_timezone)\
|
||||
.get_work_duration_data(
|
||||
date_from,
|
||||
date_to,
|
||||
domain=[('company_id', 'in', [False, self.company_id.id])])
|
||||
employee_tz = timezone(self.tz) if self.tz else None
|
||||
duration_data = {'days': 0, 'hours': 0}
|
||||
for contract in valid_contracts:
|
||||
contract_start = datetime.combine(contract.date_start, time.min, employee_tz)
|
||||
contract_end = datetime.combine(contract.date_end or date.max, time.max, employee_tz)
|
||||
calendar = contract.resource_calendar_id or contract.company_id.resource_calendar_id
|
||||
contract_duration_data = calendar\
|
||||
.with_context(employee_timezone=employee_tz)\
|
||||
.get_work_duration_data(
|
||||
max(date_from, contract_start),
|
||||
min(date_to, contract_end),
|
||||
domain=[('company_id', 'in', [False, contract.company_id.id])])
|
||||
duration_data['days'] += contract_duration_data['days']
|
||||
duration_data['hours'] += contract_duration_data['hours']
|
||||
return duration_data
|
||||
|
||||
def write(self, vals):
|
||||
res = super().write(vals)
|
||||
|
||||
@@ -111,3 +111,22 @@ class TestResource(TestContractCommon):
|
||||
work_intervals, _ = self.employee.resource_id._get_valid_work_intervals(start, end)
|
||||
sum_work_intervals = sum_intervals(work_intervals[self.employee.resource_id.id])
|
||||
self.assertEqual(75, sum_work_intervals, "Sum of the work intervals for the employee should be 35h+40h = 75h")
|
||||
|
||||
def test_multi_contract_attendance(self):
|
||||
""" Verify whether retrieving an employee's calendar attendances can
|
||||
handle multiple contracts with different calendars.
|
||||
"""
|
||||
|
||||
date_from = utc.localize(datetime(2021, 10, 1, 0, 0, 0))
|
||||
date_to = utc.localize(datetime(2021, 11, 30, 0, 0, 0))
|
||||
|
||||
attendances = self.employee._get_calendar_attendances(date_from, date_to)
|
||||
self.assertEqual(21 * 7, attendances['hours'],
|
||||
"Attendances should only include running or finished contracts.")
|
||||
|
||||
self.contract_cdd.state = 'close'
|
||||
self.contract_cdi.state = 'open'
|
||||
|
||||
attendances = self.employee._get_calendar_attendances(date_from, date_to)
|
||||
self.assertEqual(21 * 7 + 21 * 8, attendances['hours'],
|
||||
"Attendances should add up multiple contracts with varying work weeks.")
|
||||
|
||||
@@ -462,9 +462,11 @@ class HolidaysType(models.Model):
|
||||
closest_allocation_remaining += allocations_leaves_consumed[employee][leave_type][closest_allocation]['virtual_remaining_leaves']
|
||||
if closest_allocation.date_to:
|
||||
closest_allocation_expire = format_date(self.env, closest_allocation.date_to)
|
||||
calendar = employee.resource_calendar_id\
|
||||
or employee.company_id.resource_calendar_id
|
||||
# closest_allocation_duration corresponds to the time remaining before the allocation expires
|
||||
closest_allocation_duration =\
|
||||
employee.resource_calendar_id._attendance_intervals_batch(
|
||||
calendar._attendance_intervals_batch(
|
||||
datetime.combine(closest_allocation.date_to, time.min).replace(tzinfo=pytz.UTC),
|
||||
datetime.combine(target_date, time.max).replace(tzinfo=pytz.UTC))\
|
||||
if leave_type.request_unit in ['hour']\
|
||||
|
||||
@@ -1082,3 +1082,27 @@ class TestLeaveRequests(TestHrHolidaysCommon):
|
||||
for leave_validation_type in types:
|
||||
with self.assertRaises(RuntimeError), self.env.cr.savepoint():
|
||||
run_validation_flow(leave_validation_type)
|
||||
|
||||
@freeze_time('2024-01-18')
|
||||
def test_undefined_working_hours(self):
|
||||
""" Ensure time-off can also be allocated without ResourceCalendar. """
|
||||
employee = self.employee_emp
|
||||
employee.resource_calendar_id = False
|
||||
self.env['hr.leave.allocation'].create({
|
||||
'name': 'Annual Time Off',
|
||||
'employee_id': employee.id,
|
||||
'holiday_status_id': self.holidays_type_4.id,
|
||||
'number_of_days': 20,
|
||||
'state': 'confirm',
|
||||
'date_from': '2024-01-01',
|
||||
'date_to': '2024-12-31',
|
||||
})
|
||||
self.env['hr.leave'].with_user(self.user_employee_id).create({
|
||||
'name': 'Holiday Request',
|
||||
'employee_id': employee.id,
|
||||
'holiday_status_id': self.holidays_type_4.id,
|
||||
'request_date_from': '2024-01-23',
|
||||
'request_date_to': '2024-01-27',
|
||||
})
|
||||
holiday_status = self.holidays_type_4.with_user(self.user_employee_id)
|
||||
self._check_holidays_status(holiday_status, employee, 20.0, 0.0, 20.0, 16.0)
|
||||
|
||||
Reference in New Issue
Block a user