diff --git a/addons/hr_holidays/models/hr_leave_type.py b/addons/hr_holidays/models/hr_leave_type.py index 47e1fd16654..e508922d678 100644 --- a/addons/hr_holidays/models/hr_leave_type.py +++ b/addons/hr_holidays/models/hr_leave_type.py @@ -229,7 +229,7 @@ class HolidaysType(models.Model): ('holiday_status_id', 'in', self.ids) ]) - allocations = self.env['hr.leave.allocation'].search([ + allocations = self.env['hr.leave.allocation'].with_context(active_test=False).search([ ('employee_id', 'in', employee_ids), ('state', 'in', ['validate']), ('holiday_status_id', 'in', self.ids), @@ -328,6 +328,8 @@ class HolidaysType(models.Model): leave_unit = 'hours' if holiday_status_id.requires_allocation != 'no': for available_allocation in sorted_available_allocations: + if available_allocation.date_to and available_allocation.date_to < leave.date_from.date(): + continue virtual_remaining_leaves = (available_allocation.number_of_days if leave_unit == 'days' else available_allocation.number_of_hours_display) - allocations_days_consumed[employee_id][holiday_status_id][available_allocation]['virtual_leaves_taken'] max_leaves = min(virtual_remaining_leaves, leave_duration) days_consumed[available_allocation]['virtual_leaves_taken'] += max_leaves @@ -337,7 +339,6 @@ class HolidaysType(models.Model): if leave_duration > 0: # There are not enough allocation for the number of leaves days_consumed[False]['virtual_remaining_leaves'] -= leave_duration - return allocations_days_consumed else: days_consumed[False]['virtual_leaves_taken'] += leave_duration if leave.state == 'validate': @@ -353,7 +354,10 @@ class HolidaysType(models.Model): fields.datetime.combine(date, time.max) + timedelta(days=5*365), self.env['hr.leave'])]) search_date = date - allocations_of_that_type = intervals._items[0][2].filtered(lambda a: a.date_to and a.state == 'validate' and a.date_to >= date) + closest_allocations = self.env['hr.leave.allocation'] + for interval in intervals._items: + closest_allocations |= interval[2] + allocations_of_that_type = closest_allocations.filtered(lambda a: a.date_to and a.state == 'validate' and a.date_to >= date) allocations_sorted = sorted(allocations_of_that_type, key=lambda a: a.date_to) allocation_closest = allocations_sorted[0] if allocations_sorted else False allocations_days_consumed[employee_id][holiday_status_id][False]['closest_allocation_to_expire'] = allocation_closest @@ -361,6 +365,8 @@ class HolidaysType(models.Model): if future_allocation_interval[0].date() > search_date: continue for allocation in future_allocation_interval[2]: + if not allocation.active: + continue days_consumed = allocations_days_consumed[employee_id][holiday_status_id][allocation] if future_allocation_interval[1] != fields.datetime.combine(date, time.max) + timedelta(days=5*365): # Compute the remaining number of days/hours in the allocation only if it has an end date @@ -404,6 +410,9 @@ class HolidaysType(models.Model): } for employee_id in employee_ids } + if not date: + date = fields.Date.to_date(self.env.context.get('default_date_from')) or fields.Date.context_today(self) + allocations_days_consumed = self._get_employees_days_per_allocation(employee_ids, date) leave_keys = ['max_leaves', 'leaves_taken', 'remaining_leaves', 'virtual_remaining_leaves', 'virtual_leaves_taken'] @@ -412,6 +421,8 @@ class HolidaysType(models.Model): for holiday_status_id in allocations_days_consumed[employee_id]: for allocation in allocations_days_consumed[employee_id][holiday_status_id]: if allocation: + if allocation.date_to and (allocation.date_to < date or allocation.date_from > date): + continue for leave_key in leave_keys: result[employee_id][holiday_status_id if isinstance(holiday_status_id, int) else holiday_status_id.id][leave_key] += allocations_days_consumed[employee_id][holiday_status_id][allocation][leave_key] else: @@ -419,6 +430,7 @@ class HolidaysType(models.Model): for leave_key in leave_keys: if allocations_days_consumed[employee_id][holiday_status_id][False].get(leave_key): result[employee_id][holiday_status_id if isinstance(holiday_status_id, int) else holiday_status_id.id][leave_key] = allocations_days_consumed[employee_id][holiday_status_id][False][leave_key] + return result @api.model diff --git a/addons/hr_holidays/tests/test_leave_requests.py b/addons/hr_holidays/tests/test_leave_requests.py index e07e363344d..6355621f95f 100644 --- a/addons/hr_holidays/tests/test_leave_requests.py +++ b/addons/hr_holidays/tests/test_leave_requests.py @@ -62,6 +62,20 @@ class TestLeaveRequests(TestHrHolidaysCommon): cls.set_employee_create_date(cls.employee_emp_id, '2010-02-03 00:00:00') cls.set_employee_create_date(cls.employee_hruser_id, '2010-02-03 00:00:00') + def _check_holidays_count(self, holidays_count_result, ml, lt, rl, vrl, vlt, closest_allocation): + self.assertEqual( + holidays_count_result, + { + 'closest_allocation_to_expire': closest_allocation, + 'max_leaves': ml, + 'leaves_taken': lt, + 'remaining_leaves': rl, + 'virtual_remaining_leaves': vrl, + 'virtual_leaves_taken': vlt, + } + ) + + @classmethod def set_employee_create_date(cls, _id, newdate): """ This method is a hack in order to be able to define/redefine the create_date @@ -799,16 +813,9 @@ class TestLeaveRequests(TestHrHolidaysCommon): 'number_of_days': 3, }) - self.assertEqual( + self._check_holidays_count( self.holidays_type_2.get_employees_days([self.employee_emp_id])[self.employee_emp_id][self.holidays_type_2.id], - { - 'closest_allocation_to_expire': allocation, - 'max_leaves': 5, - 'leaves_taken': 0, - 'remaining_leaves': 5, - 'virtual_remaining_leaves': 2, - 'virtual_leaves_taken': 3, - } + ml=5, lt=0, rl=5, vrl=2, vlt=3, closest_allocation=allocation, ) self.holidays_type_2.requires_allocation = 'no' @@ -823,32 +830,115 @@ class TestLeaveRequests(TestHrHolidaysCommon): # The 5 allocation days are not consumed anymore # virtual_remaining_leaves reflect the total number of leave days taken - self.assertEqual( + self._check_holidays_count( self.holidays_type_2.get_employees_days([self.employee_emp_id])[self.employee_emp_id][self.holidays_type_2.id], - { - 'closest_allocation_to_expire': allocation, - 'max_leaves': 5, - 'leaves_taken': 0, - 'remaining_leaves': 5, - 'virtual_remaining_leaves': 5, - 'virtual_leaves_taken': 6, - } + ml=5, lt=0, rl=5, vrl=5, vlt=6, closest_allocation=allocation, ) leave1.with_user(self.user_hrmanager_id).action_approve() leave2.with_user(self.user_hrmanager_id).action_approve() # leaves_taken and virtual_leaves_taken reflect the total number of leave days taken - self.assertEqual( + self._check_holidays_count( self.holidays_type_2.get_employees_days([self.employee_emp_id])[self.employee_emp_id][self.holidays_type_2.id], - { - 'closest_allocation_to_expire': allocation, - 'max_leaves': 5, - 'leaves_taken': 6, - 'remaining_leaves': 5, - 'virtual_remaining_leaves': 5, - 'virtual_leaves_taken': 6, - } + ml=5, lt=6, rl=5, vrl=5, vlt=6, closest_allocation=allocation, + ) + + def test_archived_allocation(self): + with freeze_time('2022-09-15'): + allocation_2021 = self.env['hr.leave.allocation'].create({ + 'name': 'Annual Time Off 2021', + 'employee_id': self.employee_emp_id, + 'holiday_status_id': self.holidays_type_2.id, + 'number_of_days': 10, + 'state': 'confirm', + 'date_from': '2021-06-01', + 'date_to': '2021-12-31', + }) + allocation_2022 = self.env['hr.leave.allocation'].create({ + 'name': 'Annual Time Off 2022', + 'employee_id': self.employee_emp_id, + 'holiday_status_id': self.holidays_type_2.id, + 'number_of_days': 20, + 'state': 'confirm', + 'date_from': '2022-01-01', + 'date_to': '2022-12-31', + }) + allocation_2021.action_validate() + allocation_2022.action_validate() + + # Leave taken in 2021 + leave_2021 = self.env['hr.leave'].with_user(self.user_employee_id).create({ + 'name': 'Holiday Request', + 'holiday_type': 'employee', + 'employee_id': self.employee_emp.id, + 'holiday_status_id': self.holidays_type_2.id, + 'date_from': datetime(2021, 8, 9, 0, 0, 0), + 'date_to': datetime(2021, 8, 13, 23, 59, 59), + }) + leave_2021.with_user(self.user_hrmanager_id).action_approve() + + # The holidays count only takes into account the valid allocations at that date + self._check_holidays_count( + self.holidays_type_2.get_employees_days([self.employee_emp_id], date=date(2021, 12, 1))[self.employee_emp_id][self.holidays_type_2.id], + ml=10, lt=5, rl=5, vrl=5, vlt=5, closest_allocation=allocation_2021, + ) + + # Virtual remaining leave is equal to 1 because there is only one day remaining in the allocation based on its validity + self._check_holidays_count( + self.holidays_type_2.get_employees_days([self.employee_emp_id], date=date(2021, 12, 31))[self.employee_emp_id][self.holidays_type_2.id], + ml=10, lt=5, rl=5, vrl=1, vlt=5, closest_allocation=allocation_2021, + ) + + leave_2022 = self.env['hr.leave'].with_user(self.user_employee_id).create({ + 'name': 'Holiday Request', + 'holiday_type': 'employee', + 'employee_id': self.employee_emp.id, + 'holiday_status_id': self.holidays_type_2.id, + 'date_from': datetime(2022, 8, 9, 0, 0, 0), + 'date_to': datetime(2022, 8, 13, 23, 59, 59), + }) + leave_2022.with_user(self.user_hrmanager_id).action_approve() + + # The holidays count in 2022 is not affected by the first leave taken in 2021 + self._check_holidays_count( + self.holidays_type_2.get_employees_days([self.employee_emp_id])[self.employee_emp_id][self.holidays_type_2.id], + ml=20, lt=4, rl=16, vrl=16, vlt=4, closest_allocation=allocation_2022, + ) + + # The holidays count in 2021 is not affected by the leave taken in 2022 + self._check_holidays_count( + self.holidays_type_2.get_employees_days([self.employee_emp_id], date=date(2021, 12, 1))[self.employee_emp_id][self.holidays_type_2.id], + ml=10, lt=5, rl=5, vrl=5, vlt=5, closest_allocation=allocation_2021, + ) + + allocation_2021.active = False + + # If the allocation is archived, the leaves taken are still counted on this allocation + # but the max leaves and remaining leaves are not counted anymore + self._check_holidays_count( + self.holidays_type_2.get_employees_days([self.employee_emp_id], date=date(2021, 12, 1))[self.employee_emp_id][self.holidays_type_2.id], + ml=0, lt=5, rl=0, vrl=0, vlt=5, closest_allocation=allocation_2021, + ) + + # The holidays count in 2022 is not affected by the archived allocation in 2021 + self._check_holidays_count( + self.holidays_type_2.get_employees_days([self.employee_emp_id])[self.employee_emp_id][self.holidays_type_2.id], + ml=20, lt=4, rl=16, vrl=16, vlt=4, closest_allocation=allocation_2022, + ) + + allocation_2021.active = True + + # The holidays count in 2021 is back to what it was when the allocation was active + self._check_holidays_count( + self.holidays_type_2.get_employees_days([self.employee_emp_id], date=date(2021, 12, 1))[self.employee_emp_id][self.holidays_type_2.id], + ml=10, lt=5, rl=5, vrl=5, vlt=5, closest_allocation=allocation_2021, + ) + + # The holidays count in 2022 is still not affected by the allocation in 2021 + self._check_holidays_count( + self.holidays_type_2.get_employees_days([self.employee_emp_id])[self.employee_emp_id][self.holidays_type_2.id], + ml=20, lt=4, rl=16, vrl=16, vlt=4, closest_allocation=allocation_2022, ) def test_cancel_leave(self):