From 1e27f665d4e94b40d5b024cf874289c85fa605b2 Mon Sep 17 00:00:00 2001 From: Dossogne Bertrand Date: Fri, 3 Nov 2023 16:12:10 +0100 Subject: [PATCH] [FIX] hr_holidays: fix nextcall on create To reproduce the bug: - create an accrual allocation with the seniority plan - set the start date way in the past like in 2000 - validate the allocation - go on the dashboard, the accrued amount should be broken when setting the balance on a future date - additional days accrued if the cron is run Fix: nextcall being unset upon creation by default, the allocation would consider the start of the second level as the nextcall if the allocation is created in the past. This commits sets nextcall to a correct value if the lastcall is set in the past upon creation. closes odoo/odoo#141006 Signed-off-by: Bertrand Dossogne (bedo) --- .../hr_holidays/models/hr_leave_allocation.py | 27 +++++--- addons/hr_holidays/tests/__init__.py | 1 + .../hr_holidays/tests/test_past_accruals.py | 67 +++++++++++++++++++ 3 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 addons/hr_holidays/tests/test_past_accruals.py diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index fafc55a3aab..75807316027 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -609,17 +609,26 @@ class HolidaysAllocation(models.Model): def _add_lastcalls(self): for allocation in self: - if allocation.allocation_type != 'accrual' or allocation.lastcall: + if allocation.allocation_type != 'accrual': continue today = fields.Date.today() - current_level = allocation._get_current_accrual_plan_level_id(today)[0] - if not current_level: - allocation.lastcall = today - continue - allocation.lastcall = max( - current_level._get_previous_date(today), - allocation.date_from + get_timedelta(current_level.start_count, current_level.start_type) - ) + (current_level, current_level_idx) = allocation._get_current_accrual_plan_level_id(today) + if not allocation.lastcall: + if not current_level: + allocation.lastcall = today + continue + allocation.lastcall = max( + current_level._get_previous_date(today), + allocation.date_from + get_timedelta(current_level.start_count, current_level.start_type) + ) + if not allocation.nextcall and allocation.lastcall < today: + accrual_plan = allocation.accrual_plan_id + next_level = False + allocation.nextcall = current_level._get_next_date(allocation.lastcall) + if current_level_idx < (len(accrual_plan.level_ids) - 1) and accrual_plan.transition_mode == 'immediately': + next_level = allocation.accrual_plan_id.level_ids[current_level_idx + 1] + next_level_start = allocation.date_from + get_timedelta(next_level.start_count, next_level.start_type) + allocation.nextcall = min(allocation.nextcall, next_level_start) def add_follower(self, employee_id): employee = self.env['hr.employee'].browse(employee_id) diff --git a/addons/hr_holidays/tests/__init__.py b/addons/hr_holidays/tests/__init__.py index 575737bd337..8b1189393be 100644 --- a/addons/hr_holidays/tests/__init__.py +++ b/addons/hr_holidays/tests/__init__.py @@ -19,3 +19,4 @@ from . import test_global_leaves from . import test_uninstall from . import test_holidays_calendar from . import test_negative +from . import test_past_accruals diff --git a/addons/hr_holidays/tests/test_past_accruals.py b/addons/hr_holidays/tests/test_past_accruals.py new file mode 100644 index 00000000000..419a309f03f --- /dev/null +++ b/addons/hr_holidays/tests/test_past_accruals.py @@ -0,0 +1,67 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from datetime import date +from freezegun import freeze_time + +from odoo.tests import tagged + +from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon + + +@tagged('post_install', '-at_install', 'accruals') +class TestAccrualAllocations(TestHrHolidaysCommon): + @classmethod + def setUpClass(cls): + super(TestAccrualAllocations, cls).setUpClass() + cls.leave_type = cls.env['hr.leave.type'].create({ + 'name': 'Accrual Time Off', + 'time_type': 'leave', + 'requires_allocation': 'yes', + 'allocation_validation_type': 'no', + }) + cls.accrual_plan = cls.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({ + 'name': 'Test Seniority Plan', + 'level_ids': [ + (0, 0, { + 'start_count': 1, + 'start_type': 'day', + 'added_value': 1, + 'added_value_type': 'day', + 'frequency': 'yearly', + 'cap_accrued_time': True, + 'maximum_leave': 10000, + }), + (0, 0, { + 'start_count': 4, + 'start_type': 'year', + 'added_value': 1, + 'added_value_type': 'day', + 'frequency': 'yearly', + 'cap_accrued_time': True, + 'maximum_leave': 10000, + }), + (0, 0, { + 'start_count': 8, + 'start_type': 'year', + 'added_value': 1, + 'added_value_type': 'day', + 'frequency': 'yearly', + 'cap_accrued_time': True, + 'maximum_leave': 10000, + }), + ] + }) + + def _test_past_accrual(self): + with freeze_time("2023-12-01"): + allocation = self.env['hr.leave.allocation'].create({ + 'employee_id': self.employee_emp_id, + 'allocation_type': 'accrual', + 'accrual_plan_id': self.accrual_plan.id, + 'holiday_status_id': self.leave_type.id, + 'date_from': date(2000, 1, 1), + 'number_of_days': 0, + }) + + allocation._process_accrual_plans() + + self.assertEqual(allocation.number_of_days, 0)