diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index ecffa3e9cc5..1c56af6cc52 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -330,17 +330,21 @@ class HolidaysAllocation(models.Model): current_level = allocation._get_current_accrual_plan_level_id(first_day_this_year)[0] if not current_level: continue + lastcall = current_level._get_previous_date(first_day_this_year) nextcall = current_level._get_next_date(first_day_this_year) if current_level.action_with_unused_accruals == 'lost': + if lastcall == first_day_this_year: + lastcall = current_level._get_previous_date(first_day_this_year - relativedelta(days=1)) + nextcall = first_day_this_year # Allocations are lost but number_of_days should not be lower than leaves_taken - allocation.write({'number_of_days': allocation.leaves_taken, 'lastcall': first_day_this_year, 'nextcall': nextcall}) + allocation.write({'number_of_days': allocation.leaves_taken, 'lastcall': lastcall, 'nextcall': nextcall}) elif current_level.action_with_unused_accruals == 'postponed' and current_level.postpone_max_days: # Make sure the period was ran until the last day of last year if allocation.nextcall: allocation.nextcall = last_day_last_year allocation._process_accrual_plans(last_day_last_year, True) number_of_days = min(allocation.number_of_days - allocation.leaves_taken, current_level.postpone_max_days) + allocation.leaves_taken - allocation.write({'number_of_days': number_of_days, 'lastcall': first_day_this_year, 'nextcall': nextcall}) + allocation.write({'number_of_days': number_of_days, 'lastcall': lastcall, 'nextcall': nextcall}) def _get_current_accrual_plan_level_id(self, date, level_ids=False): """ @@ -444,33 +448,24 @@ class HolidaysAllocation(models.Model): current_level_last_date = allocation.date_from + get_timedelta(next_level.start_count, next_level.start_type) if allocation.nextcall != current_level_last_date: nextcall = min(nextcall, current_level_last_date) - + # We have to check for end of year actions if it is within our period + # since we can create retroactive allocations. + if allocation.lastcall.year < allocation.nextcall.year and\ + current_level.action_with_unused_accruals == 'postponed' and\ + current_level.postpone_max_days > 0: + # Compute number of days kept + allocation_days = allocation.number_of_days - allocation.leaves_taken + allowed_to_keep = max(0, current_level.postpone_max_days - allocation_days) + number_of_days = min(allocation_days, current_level.postpone_max_days) + allocation.number_of_days = number_of_days + allocation.leaves_taken + total_gained_days = sum(days_added_per_level.values()) + days_added_per_level.clear() + days_added_per_level[current_level] = min(total_gained_days, allowed_to_keep) gained_days = allocation._process_accrual_plan_level( current_level, period_start, allocation.lastcall, period_end, allocation.nextcall) days_added_per_level[current_level] += gained_days if current_level.maximum_leave > 0 and sum(days_added_per_level.values()) > current_level.maximum_leave: days_added_per_level[current_level] -= sum(days_added_per_level.values()) - current_level.maximum_leave - # We have to check for end of year actions if it is within our period - # since we can create retroactive allocations. - if allocation.lastcall.year < allocation.nextcall.year and\ - (current_level.action_with_unused_accruals == 'lost' or\ - current_level.postpone_max_days > 0): - after_period_gains = allocation._process_accrual_plan_level( - current_level, period_start, allocation.nextcall + relativedelta(day=1, month=1), - period_end, allocation.nextcall) - if current_level.action_with_unused_accruals == 'postponed': - # Compute number of days kept - allocation_days = allocation.number_of_days - allocation.leaves_taken - allowed_to_keep = max(0, current_level.postpone_max_days - allocation_days) - number_of_days = min(allocation_days, current_level.postpone_max_days) - allocation.number_of_days = number_of_days + allocation.leaves_taken - total_gained_days = sum(days_added_per_level.values()) - days_added_per_level.clear() - days_added_per_level[current_level] = min(total_gained_days - after_period_gains, allowed_to_keep) - else: - allocation.number_of_days = allocation.leaves_taken - days_added_per_level.clear() - days_added_per_level[current_level] += after_period_gains allocation.lastcall = allocation.nextcall allocation.nextcall = nextcall diff --git a/addons/hr_holidays/tests/test_accrual_allocations.py b/addons/hr_holidays/tests/test_accrual_allocations.py index ac1539b3493..f32aa237b4c 100644 --- a/addons/hr_holidays/tests/test_accrual_allocations.py +++ b/addons/hr_holidays/tests/test_accrual_allocations.py @@ -534,7 +534,7 @@ class TestAccrualAllocations(TestHrHolidaysCommon): accrual_cron.lastcall = datetime.date(2021, 9, 1) with freeze_time('2022-01-01'): allocation._update_accrual() - self.assertEqual(allocation.number_of_days, 0, 'There number of days should be reset') + self.assertEqual(allocation.number_of_days, 1, 'The number of days should be reset') def test_unused_accrual_postponed(self): # 1 accrual with 2 levels and level transition after @@ -733,6 +733,36 @@ class TestAccrualAllocations(TestHrHolidaysCommon): allocation.action_validate() with freeze_time('2022-4-4'): allocation._update_accrual() + self.assertEqual(allocation.number_of_days, 4, "Invalid number of days") + + def test_accrual_lost_first_january(self): + accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({ + 'name': 'Accrual Plan For Test', + 'level_ids': [ + (0, 0, { + 'start_count': 0, + 'start_type': 'day', + 'added_value': 3, + 'added_value_type': 'days', + 'frequency': 'yearly', + 'maximum_leave': 12, + 'action_with_unused_accruals': 'lost', + }) + ], + }) + allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({ + 'name': 'Accrual Allocation - Test', + 'accrual_plan_id': accrual_plan.id, + 'employee_id': self.employee_emp.id, + 'holiday_status_id': self.leave_type.id, + 'number_of_days': 0, + 'allocation_type': 'accrual', + 'date_from': datetime.date(2019, 1, 1), + }) + allocation.action_confirm() + allocation.action_validate() + with freeze_time('2022-4-1'): + allocation._update_accrual() self.assertEqual(allocation.number_of_days, 3, "Invalid number of days") def test_accrual_maximum_leaves(self):