1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/hr_holidays/tests/test_accrual_allocations.py
2024-12-10 09:04:09 +07:00

2317 lines
106 KiB
Python

import datetime
from freezegun import freeze_time
from datetime import date
from dateutil.relativedelta import relativedelta
from odoo import Command
from odoo.exceptions import UserError
from odoo.tests import tagged, Form
from odoo.exceptions import ValidationError
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': 'Paid Time Off',
'time_type': 'leave',
'requires_allocation': 'yes',
'allocation_validation_type': 'officer',
})
cls.leave_type_hour = cls.env['hr.leave.type'].create({
'name': 'Paid Time Off',
'time_type': 'leave',
'requires_allocation': 'yes',
'allocation_validation_type': 'officer',
'request_unit': 'hour',
})
def setAllocationCreateDate(self, allocation_id, date):
""" This method is a hack in order to be able to define/redefine the create_date
of the allocations.
This is done in SQL because ORM does not allow to write onto the create_date field.
"""
self.env.cr.execute("""
UPDATE
hr_leave_allocation
SET create_date = '%s'
WHERE id = %s
""" % (date, allocation_id))
def test_consistency_between_cap_accrued_time_and_maximum_leave(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': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'day',
'frequency': 'hourly',
'cap_accrued_time': True,
'maximum_leave': 10000
})],
})
level = accrual_plan.level_ids
level.maximum_leave = 10
self.assertEqual(accrual_plan.level_ids.maximum_leave, 10)
with self.assertRaises(UserError):
level.maximum_leave = 0
level.cap_accrued_time = False
self.assertEqual(accrual_plan.level_ids.maximum_leave, 0)
def test_accrual_unlink(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
with self.assertRaises(ValidationError):
accrual_plan.unlink()
allocation.unlink()
accrual_plan.unlink()
def test_frequency_hourly_calendar(self):
with freeze_time("2017-12-5"):
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': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'day',
'frequency': 'hourly',
'cap_accrued_time': True,
'maximum_leave': 10000
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
tomorrow = datetime.date.today() + relativedelta(days=2)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
with freeze_time(tomorrow):
allocation._update_accrual()
nextcall = datetime.date.today() + relativedelta(days=1)
self.assertEqual(allocation.number_of_days, 8, 'There should be 8 day allocated.')
self.assertEqual(allocation.nextcall, nextcall, 'The next call date of the cron should be in 2 days.')
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 8, 'There should be only 8 day allocated.')
def test_frequency_hourly_worked_hours(self):
with freeze_time("2017-12-5"):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'is_based_on_worked_time': True,
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'day',
'frequency': 'hourly',
'cap_accrued_time': True,
'maximum_leave': 10000
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
tomorrow = datetime.date.today() + relativedelta(days=2)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
holiday_type = self.env['hr.leave.type'].create({
'name': 'Paid Time Off',
'requires_allocation': 'no',
'responsible_ids': [(4, self.user_hrmanager_id)],
'time_type': 'leave',
'request_unit': 'half_day',
})
leave = self.env['hr.leave'].create({
'name': 'leave',
'employee_id': self.employee_emp.id,
'holiday_status_id': holiday_type.id,
'request_date_from': '2017-12-06 08:00:00',
'request_date_to': '2017-12-06 17:00:00',
'request_unit_half': True,
'request_date_from_period': 'am',
})
leave.action_validate()
with freeze_time(tomorrow):
allocation._update_accrual()
nextcall = datetime.date.today() + relativedelta(days=1)
self.assertEqual(allocation.number_of_days, 4, 'There should be 4 day allocated.')
self.assertEqual(allocation.nextcall, nextcall, 'The next call date of the cron should be in 2 days.')
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 4, 'There should be only 4 day allocated.')
def test_frequency_daily(self):
with freeze_time("2017-12-5"):
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': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'day',
'frequency': 'daily',
'cap_accrued_time': True,
'maximum_leave': 10000
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
tomorrow = datetime.date.today() + relativedelta(days=2)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
with freeze_time(tomorrow):
allocation._update_accrual()
nextcall = datetime.date.today() + relativedelta(days=1)
self.assertEqual(allocation.number_of_days, 1, 'There should be 1 day allocated.')
self.assertEqual(allocation.nextcall, nextcall, 'The next call date of the cron should be in 2 days.')
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, 'There should be only 1 day allocated.')
def test_frequency_weekly(self):
with freeze_time("2017-12-5"):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'weekly',
'cap_accrued_time': True,
'maximum_leave': 10000
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2021-09-03',
})
with freeze_time(datetime.date.today() + relativedelta(days=2)):
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
nextWeek = allocation.date_from + relativedelta(days=1, weekday=0)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
with freeze_time(nextWeek):
allocation._update_accrual()
nextWeek = datetime.date.today() + relativedelta(days=1, weekday=0)
# Prorated
self.assertAlmostEqual(allocation.number_of_days, 0.2857, 4, 'There should be 0.2857 day allocated.')
self.assertEqual(allocation.nextcall, nextWeek, 'The next call date of the cron should be in 2 weeks')
with freeze_time(nextWeek):
allocation._update_accrual()
nextWeek = datetime.date.today() + relativedelta(days=1, weekday=0)
self.assertAlmostEqual(allocation.number_of_days, 1.2857, 4, 'There should be 1.2857 day allocated.')
self.assertEqual(allocation.nextcall, nextWeek, 'The next call date of the cron should be in 2 weeks')
def test_frequency_bimonthly(self):
with freeze_time('2021-09-01'):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'bimonthly',
'first_day': 1,
'second_day': 15,
'cap_accrued_time': True,
'maximum_leave': 10000,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2021-09-03',
})
self.setAllocationCreateDate(allocation.id, '2021-09-01 00:00:00')
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
next_date = datetime.date(2021, 9, 15)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
with freeze_time(next_date):
next_date = datetime.date(2021, 10, 1)
allocation._update_accrual()
# Prorated
self.assertAlmostEqual(allocation.number_of_days, 0.7857, 4, 'There should be 0.7857 day allocated.')
self.assertEqual(allocation.nextcall, next_date, 'The next call date of the cron should be October 1st')
with freeze_time(next_date):
allocation._update_accrual()
# Not Prorated
self.assertAlmostEqual(allocation.number_of_days, 1.7857, 4, 'There should be 1.7857 day allocated.')
def test_frequency_monthly(self):
with freeze_time('2021-09-01'):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'monthly',
'cap_accrued_time': True,
'maximum_leave': 10000
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2021-08-31',
})
self.setAllocationCreateDate(allocation.id, '2021-09-01 00:00:00')
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
next_date = datetime.date(2021, 10, 1)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
with freeze_time(next_date):
next_date = datetime.date(2021, 11, 1)
allocation._update_accrual()
# Prorata = 1 since a whole month passed
self.assertEqual(allocation.number_of_days, 1, 'There should be 1 day allocated.')
self.assertEqual(allocation.nextcall, next_date, 'The next call date of the cron should be November 1st')
def test_frequency_biyearly(self):
with freeze_time('2021-09-01'):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'biyearly',
'cap_accrued_time': True,
'maximum_leave': 10000,
})],
})
# this sets up an accrual on the 1st of January and the 1st of July
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
self.setAllocationCreateDate(allocation.id, '2021-09-01 00:00:00')
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
next_date = datetime.date(2022, 1, 1)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
with freeze_time(next_date):
next_date = datetime.date(2022, 7, 1)
allocation._update_accrual()
# Prorated
self.assertAlmostEqual(allocation.number_of_days, 0.6576, 4, 'There should be 0.6576 day allocated.')
self.assertEqual(allocation.nextcall, next_date, 'The next call date of the cron should be July 1st')
with freeze_time(next_date):
allocation._update_accrual()
# Not Prorated
self.assertAlmostEqual(allocation.number_of_days, 1.6576, 4, 'There should be 1.6576 day allocated.')
def test_frequency_yearly(self):
with freeze_time('2021-09-01'):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'yearly',
'cap_accrued_time': True,
'maximum_leave': 10000,
})],
})
# this sets up an accrual on the 1st of January
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
self.setAllocationCreateDate(allocation.id, '2021-09-01 00:00:00')
allocation.action_validate()
self.assertFalse(allocation.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet.')
allocation._update_accrual()
next_date = datetime.date(2022, 1, 1)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
with freeze_time(next_date):
next_date = datetime.date(2023, 1, 1)
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 0.3315, 4, 'There should be 0.3315 day allocated.')
self.assertEqual(allocation.nextcall, next_date, 'The next call date of the cron should be January 1st 2023')
with freeze_time(next_date):
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 1.3315, 4, 'There should be 1.3315 day allocated.')
def test_check_gain(self):
# 2 accruals, one based on worked time, one not
# check gain
with freeze_time('2021-08-30'):
attendances = []
for index in range(5):
attendances.append((0, 0, {
'name': '%s_%d' % ('40 Hours', index),
'hour_from': 8,
'hour_to': 12,
'dayofweek': str(index),
'day_period': 'morning'
}))
attendances.append((0, 0, {
'name': '%s_%d' % ('40 Hours', index),
'hour_from': 12,
'hour_to': 13,
'dayofweek': str(index),
'day_period': 'lunch'
}))
attendances.append((0, 0, {
'name': '%s_%d' % ('40 Hours', index),
'hour_from': 13,
'hour_to': 17,
'dayofweek': str(index),
'day_period': 'afternoon'
}))
calendar_emp = self.env['resource.calendar'].create({
'name': '40 Hours',
'tz': self.employee_emp.tz,
'attendance_ids': attendances,
})
self.employee_emp.resource_calendar_id = calendar_emp.id
accrual_plan_not_based_on_worked_time = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 5,
'frequency': 'weekly',
'cap_accrued_time': True,
'maximum_leave': 10000,
})],
})
accrual_plan_based_on_worked_time = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'is_based_on_worked_time': True,
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 5,
'frequency': 'weekly',
'cap_accrued_time': True,
'maximum_leave': 10000,
})],
})
allocation_not_worked_time = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan_not_based_on_worked_time.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
'state': 'confirm',
})
allocation_worked_time = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan_based_on_worked_time.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 0,
'allocation_type': 'accrual',
'state': 'confirm',
})
(allocation_not_worked_time | allocation_worked_time).action_validate()
self.setAllocationCreateDate(allocation_not_worked_time.id, '2021-08-01 00:00:00')
self.setAllocationCreateDate(allocation_worked_time.id, '2021-08-01 00:00:00')
holiday_type = self.env['hr.leave.type'].create({
'name': 'Paid Time Off',
'requires_allocation': 'no',
'responsible_ids': [Command.link(self.user_hrmanager_id)],
'time_type': 'leave',
})
leave = self.env['hr.leave'].create({
'name': 'leave',
'employee_id': self.employee_emp.id,
'holiday_status_id': holiday_type.id,
'request_date_from': '2021-09-02',
'request_date_to': '2021-09-02',
})
leave.action_validate()
self.assertFalse(allocation_not_worked_time.nextcall, 'There should be no nextcall set on the allocation.')
self.assertFalse(allocation_worked_time.nextcall, 'There should be no nextcall set on the allocation.')
self.assertEqual(allocation_not_worked_time.number_of_days, 0, 'There should be no days allocated yet.')
self.assertEqual(allocation_worked_time.number_of_days, 0, 'There should be no days allocated yet.')
next_date = datetime.date(2021, 9, 6)
with freeze_time(next_date):
# next_date = datetime.date(2021, 9, 13)
self.env['hr.leave.allocation']._update_accrual()
# Prorated
self.assertAlmostEqual(allocation_not_worked_time.number_of_days, 4.2857, 4, 'There should be 4.2857 days allocated.')
# 3.75 -> starts 1 day after allocation date -> 31/08-3/09 => 4 days - 1 days time off => (3 / 4) * 5 days
# ^ result without prorata
# Prorated
self.assertAlmostEqual(allocation_worked_time.number_of_days, 3, 4, 'There should be 3 days allocated.')
self.assertEqual(allocation_not_worked_time.nextcall, datetime.date(2021, 9, 13), 'The next call date of the cron should be the September 13th')
self.assertEqual(allocation_worked_time.nextcall, datetime.date(2021, 9, 13), 'The next call date of the cron should be the September 13th')
with freeze_time(next_date + relativedelta(days=7)):
next_date = datetime.date(2021, 9, 20)
self.env['hr.leave.allocation']._update_accrual()
self.assertAlmostEqual(allocation_not_worked_time.number_of_days, 9.2857, 4, 'There should be 9.2857 days allocated.')
self.assertEqual(allocation_not_worked_time.nextcall, next_date, 'The next call date of the cron should be September 20th')
self.assertAlmostEqual(allocation_worked_time.number_of_days, 8, 4, 'There should be 8 days allocated.')
self.assertEqual(allocation_worked_time.nextcall, next_date, 'The next call date of the cron should be September 20th')
def test_check_max_value(self):
with freeze_time("2017-12-5"):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'daily',
'cap_accrued_time': True,
'maximum_leave': 1,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
allocation.action_validate()
allocation._update_accrual()
tomorrow = datetime.date.today() + relativedelta(days=2)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
with freeze_time(tomorrow):
allocation._update_accrual()
nextcall = datetime.date.today() + relativedelta(days=1)
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, 'There should be only 1 day allocated.')
with freeze_time(nextcall):
allocation._update_accrual()
nextcall = datetime.date.today() + relativedelta(days=1)
# The maximum value is 1 so this shouldn't change anything
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, 'There should be only 1 day allocated.')
def test_check_max_value_hours(self):
with freeze_time("2017-12-5"):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'hour',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'daily',
'cap_accrued_time': True,
'maximum_leave': 4,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
allocation.action_validate()
allocation._update_accrual()
tomorrow = datetime.date.today() + relativedelta(days=2)
self.assertEqual(allocation.number_of_days, 0, 'There should be no days allocated yet. The accrual starts tomorrow.')
with freeze_time(tomorrow):
allocation._update_accrual()
nextcall = datetime.date.today() + relativedelta(days=10)
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 0.125, 'There should be only 0.125 days allocated.')
with freeze_time(nextcall):
allocation._update_accrual()
nextcall = datetime.date.today() + relativedelta(days=1)
# The maximum value is 1 so this shouldn't change anything
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 0.5, 'There should be only 0.5 days allocated.')
def test_accrual_hours_with_max_carryover(self):
with freeze_time("2024-10-10"):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual plan - hours and max postpone',
'level_ids': [(0, 0, {
'added_value_type': 'hour',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'monthly',
'action_with_unused_accruals': 'maximum',
'postpone_max_days': 4, # confusing name but is in hours when added_value_type == 'hour'
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'date_from': datetime.date(2025, 1, 1),
'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',
})
allocation.action_validate()
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 0)
hours_per_day = self.employee_emp.resource_calendar_id.hours_per_day
allocation_data = self.leave_type.get_allocation_data(self.employee_emp, "2025-12-02")[self.employee_emp][0][1]
self.assertAlmostEqual(allocation_data["remaining_leaves"], 11 / hours_per_day, 1, '11 hours accrued.')
allocation_data = self.leave_type.get_allocation_data(self.employee_emp, "2026-02-02")[self.employee_emp][0][1]
self.assertAlmostEqual(allocation_data["remaining_leaves"], 5 / hours_per_day, 1, '5 hours accrued.')
def test_accrual_transition_immediately(self):
with freeze_time("2017-12-5"):
# 1 accrual with 2 levels and level transition immediately
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'transition_mode': 'immediately',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'weekly',
'cap_accrued_time': True,
'maximum_leave': 1,
}), (0, 0, {
'start_count': 10,
'start_type': 'day',
'added_value': 1,
'frequency': 'weekly',
'cap_accrued_time': True,
'maximum_leave': 1,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
allocation.action_validate()
next_date = datetime.date.today() + relativedelta(days=11)
second_level = self.env['hr.leave.accrual.level'].search([('accrual_plan_id', '=', accrual_plan.id), ('start_count', '=', 10)])
self.assertEqual(allocation._get_current_accrual_plan_level_id(next_date)[0], second_level, 'The second level should be selected')
def test_accrual_transition_after_period(self):
with freeze_time("2017-12-5"):
# 1 accrual with 2 levels and level transition after
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'transition_mode': 'end_of_accrual',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'weekly',
'cap_accrued_time': True,
'maximum_leave': 1,
}), (0, 0, {
'start_count': 10,
'start_type': 'day',
'added_value': 1,
'frequency': 'weekly',
'cap_accrued_time': True,
'maximum_leave': 1,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
allocation.action_validate()
next_date = datetime.date.today() + relativedelta(days=11)
second_level = self.env['hr.leave.accrual.level'].search([('accrual_plan_id', '=', accrual_plan.id), ('start_count', '=', 10)])
self.assertEqual(allocation._get_current_accrual_plan_level_id(next_date)[0], second_level, 'The second level should be selected')
def test_unused_accrual_lost(self):
# 1 accrual with 2 levels and level transition immediately
with freeze_time('2021-12-15'):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'daily',
'cap_accrued_time': True,
'maximum_leave': 1,
'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 for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 10,
'allocation_type': 'accrual',
})
allocation.action_validate()
# Reset the cron's lastcall
accrual_cron = self.env['ir.cron'].sudo().env.ref('hr_holidays.hr_leave_allocation_cron_accrual')
accrual_cron.lastcall = datetime.date(2021, 12, 15)
with freeze_time('2022-01-01'):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 0, 'The number of days should be reset')
def test_unused_accrual_postponed(self):
# 1 accrual with 2 levels and level transition after
# This also tests retroactivity
with freeze_time('2021-12-15'):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'daily',
'cap_accrued_time': True,
'maximum_leave': 25,
'action_with_unused_accruals': 'all',
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 10,
'allocation_type': 'accrual',
})
allocation.action_validate()
# Reset the cron's lastcall
accrual_cron = self.env['ir.cron'].sudo().env.ref('hr_holidays.hr_leave_allocation_cron_accrual')
accrual_cron.lastcall = datetime.date(2021, 12, 15)
with freeze_time('2022-01-01'):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 25, 'The maximum number of days should be reached and kept.')
def test_unused_accrual_postponed_2(self):
with freeze_time('2021-01-01'):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 2,
'frequency': 'yearly',
'cap_accrued_time': True,
'maximum_leave': 100,
'action_with_unused_accruals': 'maximum',
'postpone_max_days': 10,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
allocation.action_validate()
# Reset the cron's lastcall
accrual_cron = self.env['ir.cron'].sudo().env.ref('hr_holidays.hr_leave_allocation_cron_accrual')
accrual_cron.lastcall = datetime.date(2021, 1, 1)
with freeze_time('2023-01-26'):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 4, 'The maximum number of days should be reached and kept.')
def test_unused_accrual_postponed_limit(self):
# 1 accrual with 2 levels and level transition after
# This also tests retroactivity
with freeze_time('2021-12-15'):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'accrued_gain_time': 'start',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'daily',
'cap_accrued_time': True,
'maximum_leave': 25,
'action_with_unused_accruals': 'maximum',
'postpone_max_days': 15,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 10,
'allocation_type': 'accrual',
})
allocation.action_validate()
# Reset the cron's lastcall
accrual_cron = self.env['ir.cron'].sudo().env.ref('hr_holidays.hr_leave_allocation_cron_accrual')
accrual_cron.lastcall = datetime.date(2021, 12, 15)
with freeze_time('2022-01-01'):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 16, 'The maximum number of days should be reached and kept.')
def test_unused_accrual_postponed_limit_2(self):
with freeze_time('2021-01-01'):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 15,
'frequency': 'yearly',
'cap_accrued_time': True,
'maximum_leave': 100,
'action_with_unused_accruals': 'maximum',
'postpone_max_days': 7,
})],
})
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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',
})
allocation.action_validate()
# Reset the cron's lastcall
accrual_cron = self.env['ir.cron'].sudo().env.ref('hr_holidays.hr_leave_allocation_cron_accrual')
accrual_cron.lastcall = datetime.date(2021, 1, 1)
with freeze_time('2023-01-26'):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 7, 'The maximum number of days should be reached and kept.')
def test_accrual_skipped_period(self):
# Test that when an allocation is made in the past and the second level is technically reached
# that the first level is not skipped completely.
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 15,
'frequency': 'biyearly',
'cap_accrued_time': True,
'maximum_leave': 100,
'action_with_unused_accruals': 'all',
}), (0, 0, {
'start_count': 4,
'start_type': 'month',
'added_value': 10,
'frequency': 'biyearly',
'cap_accrued_time': True,
'maximum_leave': 500,
'action_with_unused_accruals': 'all',
})],
})
with freeze_time('2020-8-16'):
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(2020, 8, 16),
})
allocation.action_validate()
with freeze_time('2022-1-10'):
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 30.82, 2, "Invalid number of days")
def test_three_levels_accrual(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 2,
'start_type': 'month',
'added_value': 3,
'frequency': 'monthly',
'cap_accrued_time': True,
'maximum_leave': 3,
'action_with_unused_accruals': 'all',
'first_day': 31,
}), (0, 0, {
'start_count': 3,
'start_type': 'month',
'added_value': 6,
'frequency': 'monthly',
'cap_accrued_time': True,
'maximum_leave': 6,
'action_with_unused_accruals': 'all',
'first_day': 31,
}), (0, 0, {
'start_count': 4,
'start_type': 'month',
'added_value': 1,
'frequency': 'monthly',
'cap_accrued_time': True,
'maximum_leave': 100,
'action_with_unused_accruals': 'all',
'first_day': 31,
})],
})
with freeze_time('2022-1-31'):
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(2022, 1, 31),
})
allocation.action_validate()
with freeze_time('2022-7-20'):
allocation._update_accrual()
# The first level gives 3 days
# The second level could give 6 days but since the first level was already giving
# 3 days, the second level gives 3 days to reach the second level's limit.
# The third level gives 1 day since it only counts for one iteration.
self.assertAlmostEqual(allocation.number_of_days, 7, 2)
def test_accrual_lost_previous_days(self):
# Test that when an allocation with two levels is made and that the first level has it's action
# with unused accruals set as lost that the days are effectively lost
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [
(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'monthly',
'cap_accrued_time': True,
'maximum_leave': 12,
'action_with_unused_accruals': 'lost',
}),
(0, 0, {
'start_count': 1,
'start_type': 'year',
'added_value': 1,
'frequency': 'monthly',
'cap_accrued_time': True,
'maximum_leave': 12,
'action_with_unused_accruals': 'lost',
}),
],
})
with freeze_time('2021-1-1'):
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(2021, 1, 1),
})
allocation.action_validate()
with freeze_time('2022-4-4'):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 3, "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',
'accrued_gain_time': 'start',
'level_ids': [
(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 3,
'frequency': 'yearly',
'cap_accrued_time': True,
'maximum_leave': 12,
'action_with_unused_accruals': 'lost',
})
],
})
with freeze_time('2019-1-1'):
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_validate()
with freeze_time('2022-4-1'):
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 3, 2, "Invalid number of days")
def test_accrual_maximum_leaves(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'daily',
'cap_accrued_time': True,
'maximum_leave': 5,
})],
})
with freeze_time("2021-9-3"):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2021-09-03',
})
with freeze_time("2021-10-3"):
allocation.action_validate()
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 5, "Should accrue maximum 5 days")
def test_accrual_maximum_leaves_no_limit(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'daily',
'cap_accrued_time': False,
})],
})
with freeze_time("2021-9-3"):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2021-09-03',
})
with freeze_time("2021-10-3"):
allocation.action_validate()
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 29, "No limits for accrued days")
def test_accrual_leaves_taken_maximum(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'weekly',
'week_day': 'mon',
'cap_accrued_time': True,
'maximum_leave': 5,
})],
})
with freeze_time("2022-1-1"):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2022-01-01',
})
allocation.action_validate()
with freeze_time("2022-3-2"):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 5, "Maximum of 5 days accrued")
leave = self.env['hr.leave'].create({
'name': 'leave',
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'request_date_from': '2022-03-07',
'request_date_to': '2022-03-11',
})
leave.action_validate()
with freeze_time("2022-6-1"):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 10, "Should accrue 5 additional days")
def test_accrual_leaves_taken_maximum_hours(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'level_ids': [(0, 0, {
'added_value_type': 'hour',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'weekly',
'week_day': 'mon',
'cap_accrued_time': True,
'maximum_leave': 10,
})],
})
with freeze_time(datetime.date(2022, 1, 1)):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type_hour.id,
'number_of_days': 0,
'allocation_type': 'accrual',
'date_from': '2022-01-01',
})
allocation.action_validate()
with freeze_time(datetime.date(2022, 4, 1)):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 10 / self.hours_per_day, "Maximum of 10 hours accrued")
leave = self.env['hr.leave'].create({
'name': 'leave',
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type_hour.id,
'request_date_from': '2022-03-07',
'request_date_to': '2022-03-07',
})
leave.action_validate()
with freeze_time(datetime.date(2022, 6, 1)):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 18 / self.hours_per_day, "Should accrue 8 additional hours")
def test_accrual_period_start(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'accrued_gain_time': 'end',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'weekly',
'week_day': 'mon',
'cap_accrued_time': True,
'maximum_leave': 5,
})],
})
with freeze_time("2023-4-24"):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2023-04-24',
})
allocation.action_validate()
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 0, "Should accrue 0 days, because the period is not done yet.")
accrual_plan.accrued_gain_time = 'start'
with freeze_time("2023-4-24"):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2023-04-24',
})
allocation.action_validate()
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, "Should accrue 1 day, at the start of the period.")
def test_accrual_period_start_multiple_runs(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'accrued_gain_time': 'start',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1.5,
'frequency': 'monthly',
'first_day': 13,
'cap_accrued_time': True,
'maximum_leave': 15,
})],
})
with freeze_time("2023-4-13"):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2023-04-13',
})
allocation.action_validate()
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 1.5, 2)
with freeze_time("2023-9-13"):
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 9, 2)
def test_accrual_period_start_level_transfer(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'accrued_gain_time': 'start',
'level_ids': [
(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'weekly',
'week_day': 'wed',
'cap_accrued_time': True,
'maximum_leave': 10,
}),
(0, 0, {
'start_count': 3,
'start_type': 'month',
'added_value': 2,
'frequency': 'weekly',
'week_day': 'wed',
'cap_accrued_time': True,
'maximum_leave': 5,
})
],
})
with freeze_time("2023-4-26"):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2023-04-26',
})
allocation.action_validate()
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, "Should accrue 1 day, at the start of the period.")
with freeze_time("2023-7-5"):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 10, "Should accrue 10 days, days received, but not over limit.")
# first wednesday at the second level
with freeze_time("2023-8-02"):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 5, "Should accrue 5 days, after level transfer 10 are cut to 5")
def test_accrual_carryover_at_allocation(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'accrued_gain_time': 'start',
'carryover_date': 'allocation',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'monthly',
'first_day': 27,
'cap_accrued_time': False,
'action_with_unused_accruals': 'lost',
})],
})
with freeze_time("2023-4-26"):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2023-04-26',
})
allocation.action_validate()
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 0.03, 2, "Should accrue 0.03 days, accrued_gain_time == start.")
with freeze_time("2023-4-27"):
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 1.03, 2, "Should accrue 1 day, days are added on 27th.")
with freeze_time("2023-12-27"):
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 9.03, 2, "Should accrue 9 day, after 8 months.")
with freeze_time("2024-04-26"):
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 0.03, 2, "Allocations not lost on 1st of January, but on allocation date.")
with freeze_time("2024-04-27"):
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 1.03, 2, "Allocations lost, then 1 accrued.")
def test_accrual_carryover_at_other(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'accrued_gain_time': 'start',
'carryover_date': 'other',
'carryover_day': 20,
'carryover_month': 'apr',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 10,
'frequency': 'monthly',
'first_day': 11,
'cap_accrued_time': False,
'action_with_unused_accruals': 'maximum',
'postpone_max_days': 69,
})],
})
with freeze_time("2023-04-20"):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'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': '2023-04-20',
})
allocation.action_validate()
with freeze_time("2024-04-20"):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 76, "Carryover at other date, level's maximum leave is 69 + Partial for 4/20 to 5/11 (Accrued gain time is 'start').")
def test_accrual_carrover_other_period_end_multi_level(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
'accrued_gain_time': 'end',
'carryover_date': 'other',
'carryover_day': 5,
'carryover_month': 'jun',
'level_ids': [
(0, 0, {
'added_value_type': 'day',
'start_count': 5,
'start_type': 'day',
'added_value': 1,
'frequency': 'monthly',
'first_day': 9,
'cap_accrued_time': True,
'maximum_leave': 15,
'action_with_unused_accruals': 'maximum',
'postpone_max_days': 13,
}),
(0, 0, {
'start_count': 9,
'start_type': 'month',
'added_value': 2,
'frequency': 'biyearly',
'first_month_day': 17,
'first_month': 'feb',
'second_month_day': 29,
'second_month': 'oct',
'cap_accrued_time': True,
'maximum_leave': 10,
'action_with_unused_accruals': 'maximum',
'postpone_max_days': 20,
}),
(0, 0, {
'start_count': 17,
'start_type': 'month',
'added_value': 12,
'frequency': 'yearly',
'yearly_month': 'jul',
'yearly_day': 15,
'cap_accrued_time': True,
'maximum_leave': 21,
'action_with_unused_accruals': 'lost',
}),
],
})
with freeze_time("2023-04-04"):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 9,
'allocation_type': 'accrual',
'date_from': '2023-04-4',
})
allocation.action_validate()
with freeze_time("2026-08-01"):
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 1.315, 3)
def test_accrual_creation_on_anterior_date(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Weekly accrual',
'carryover_date': 'allocation',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'weekly',
'cap_accrued_time': False,
'action_with_unused_accruals': 'lost',
})],
})
with freeze_time('2023-09-01'):
accrual_allocation = self.env['hr.leave.allocation'].new({
'name': 'Employee allocation',
'holiday_status_id': self.leave_type.id,
'date_from': '2023-01-01',
'employee_id': self.employee_emp.id,
'allocation_type': 'accrual',
'accrual_plan_id': accrual_plan.id,
})
# As the duration is set to a onchange, we need to force that onchange to run
accrual_allocation._onchange_date_from()
accrual_allocation.action_validate()
# The amount of days should be computed as if it was accrued since
# the start date of the allocation.
self.assertAlmostEqual(accrual_allocation.number_of_days, 34.0, places=0)
self.assertFalse(accrual_allocation.lastcall == accrual_allocation.date_from)
accrual_allocation._update_accrual()
# The amount being already computed, the amount should stay the same after the cron
# running on the same day.
self.assertAlmostEqual(accrual_allocation.number_of_days, 34.0, places=0)
def test_future_accural_time(self):
leave_type = self.env['hr.leave.type'].create({
'name': 'Test Leave Type',
'time_type': 'leave',
'requires_allocation': 'yes',
'allocation_validation_type': 'no',
'request_unit': 'hour',
})
with freeze_time("2023-12-31"):
accrual_plan = self.env['hr.leave.accrual.plan'].create({
'name': 'Accrual Plan For Test',
'is_based_on_worked_time': False,
'accrued_gain_time': 'end',
'carryover_date': 'year_start',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'hour',
'frequency': 'monthly',
'cap_accrued_time': True,
'maximum_leave': 100,
})],
})
allocation = self.env['hr.leave.allocation'].create({
'name': 'Accrual allocation for employee',
'accrual_plan_id': accrual_plan.id,
'employee_id': self.employee_emp.id,
'holiday_status_id': leave_type.id,
'number_of_days': 0.125,
'allocation_type': 'accrual',
'holiday_type': 'employee',
})
allocation.action_validate()
allocation_data = leave_type.get_allocation_data(self.employee_emp, datetime.date(2024, 2, 1))
self.assertEqual(allocation_data[self.employee_emp][0][1]['virtual_remaining_leaves'], 2)
def test_added_type_during_onchange(self):
"""
The purpose is to test whether the value of the `added_value_type`
field is correctly propagated from the first level to the second
during creation on the dialog form view.
"""
accrual_plan = self.env['hr.leave.accrual.plan'].create({
'name': 'Accrual Plan For Test',
'is_based_on_worked_time': False,
'accrued_gain_time': 'end',
'carryover_date': 'year_start',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 4,
'added_value_type': 'hour',
'frequency': 'monthly',
'cap_accrued_time': True,
'maximum_leave': 100,
})],
})
# Simulate the onchange of the dialog form view
# Trigger the `_compute_added_value_type` method (with virtual records)
res = self.env['hr.leave.accrual.level'].onchange({'accrual_plan_id': {'id': accrual_plan.id}}, [], {'added_value_type': {}})
self.assertEqual(res['value']['added_value_type'], accrual_plan.level_ids[0].added_value_type)
def test_accrual_immediate_cron_run(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Weekly accrual',
'carryover_date': 'allocation',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'daily',
'cap_accrued_time': False,
'action_with_unused_accruals': 'lost',
})],
})
with freeze_time('2023-09-01'):
accrual_allocation = self.env['hr.leave.allocation'].new({
'name': 'Employee allocation',
'holiday_status_id': self.leave_type.id,
'date_from': '2023-08-01',
'employee_id': self.employee_emp.id,
'allocation_type': 'accrual',
'accrual_plan_id': accrual_plan.id,
})
# As the duration is set to a onchange, we need to force that onchange to run
accrual_allocation._onchange_date_from()
accrual_allocation.action_validate()
# The amount of days should be computed as if it was accrued since
# the start date of the allocation.
self.assertEqual(accrual_allocation.number_of_days, 31.0, "The allocation should have given 31 days")
accrual_allocation._update_accrual()
self.assertEqual(accrual_allocation.number_of_days, 31.0,
"the amount shouldn't have changed after running the cron")
def test_accrual_creation_for_history(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Monthly accrual',
'carryover_date': 'year_start',
'accrued_gain_time': 'end',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'monthly',
'first_day_display': 'last',
'cap_accrued_time': False,
'action_with_unused_accruals': 'lost',
})],
})
with freeze_time('2024-03-02'):
accrual_allocation = self.env['hr.leave.allocation'].new({
'name': 'History allocation',
'holiday_status_id': self.leave_type.id,
'date_from': '2024-03-01',
'employee_id': self.employee_emp.id,
'allocation_type': 'accrual',
'accrual_plan_id': accrual_plan.id,
})
# As the duration is set to an onchange, we need to force that onchange to run
accrual_allocation._onchange_date_from()
self.assertAlmostEqual(accrual_allocation.number_of_days, 0, places=0)
# Yearly Report lost
accrual_allocation.write({'date_from': '2022-01-01'})
accrual_allocation._onchange_date_from()
self.assertAlmostEqual(accrual_allocation.number_of_days, 2, places=0)
# Update date_to
accrual_allocation.write({'date_to': '2022-12-31'})
accrual_allocation._onchange_date_from()
self.assertAlmostEqual(accrual_allocation.number_of_days, 12, places=0)
def test_accrual_with_report_creation_for_history(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Monthly accrual',
'carryover_date': 'year_start',
'accrued_gain_time': 'end',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'monthly',
'first_day_display': 'last',
'cap_accrued_time': False,
'action_with_unused_accruals': 'maximum',
'postpone_max_days': 5
})],
})
with freeze_time('2024-03-02'):
accrual_allocation = self.env['hr.leave.allocation'].new({
'name': 'History allocation',
'holiday_status_id': self.leave_type.id,
'date_from': '2024-03-01',
'employee_id': self.employee_emp.id,
'allocation_type': 'accrual',
'accrual_plan_id': accrual_plan.id,
})
# As the duration is set to an onchange, we need to force that onchange to run
accrual_allocation._onchange_date_from()
self.assertAlmostEqual(accrual_allocation.number_of_days, 0, places=0)
# Yearly Report capped to 5 after 2022 and after 2023
accrual_allocation.write({'date_from': '2022-01-01'})
accrual_allocation._onchange_date_from()
self.assertAlmostEqual(accrual_allocation.number_of_days, 7, places=0)
# Update date_to
accrual_allocation.write({'date_to': '2022-12-31'})
accrual_allocation._onchange_date_from()
self.assertAlmostEqual(accrual_allocation.number_of_days, 12, places=0)
def test_accrual_period_start_past_start_date(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Monthly accrual',
'carryover_date': 'year_start',
'accrued_gain_time': 'start',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'monthly',
'first_day_display': '1',
'cap_accrued_time': False,
})],
})
with freeze_time('2024-03-01'):
with Form(self.env['hr.leave.allocation']) as f:
f.allocation_type = "accrual"
f.accrual_plan_id = accrual_plan
f.date_from = '2024-01-01'
f.employee_ids.add(self.employee_emp)
f.holiday_status_id = self.leave_type
f.private_name = "Employee Allocation"
accrual_allocation = f.record
accrual_allocation.action_validate()
self.assertAlmostEqual(accrual_allocation.number_of_days, 3.0, places=0)
with freeze_time('2024-04-01'):
accrual_allocation._update_accrual()
self.assertAlmostEqual(accrual_allocation.number_of_days, 4.0, places=0)
def test_cancel_invalid_leaves_with_regular_and_accrual_allocations(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Monthly accrual',
'carryover_date': 'year_start',
'accrued_gain_time': 'start',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'monthly',
'first_day_display': '1',
'cap_accrued_time': False,
})],
})
allocations = self.env['hr.leave.allocation'].create([
{
'name': 'Regular allocation',
'allocation_type': 'regular',
'date_from': '2024-05-01',
'holiday_status_id': self.leave_type.id,
'employee_id': self.employee_emp.id,
'number_of_days': 2,
},
{
'name': 'Accrual allocation',
'allocation_type': 'accrual',
'date_from': '2024-05-01',
'holiday_status_id': self.leave_type.id,
'employee_id': self.employee_emp.id,
'accrual_plan_id': accrual_plan.id,
'number_of_days': 3,
}
])
allocations.action_validate()
leave = self.env['hr.leave'].create({
'name': 'Leave',
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'request_date_from': '2024-05-13',
'request_date_to': '2024-05-17',
})
leave.action_validate()
with freeze_time('2024-05-06'):
self.env['hr.leave']._cancel_invalid_leaves()
self.assertEqual(leave.active, True, "Leave must not be canceled")
def test_accrual_leaves_cancel_cron(self):
leave_type_no_negative = self.env['hr.leave.type'].create({
'name': 'Test Accrual - No negative',
'time_type': 'leave',
'requires_allocation': 'yes',
'allocation_validation_type': 'no',
'leave_validation_type': 'no_validation',
'allows_negative': False,
})
leave_type_negative = self.env['hr.leave.type'].create({
'name': 'Test Accrual - Negative',
'time_type': 'leave',
'requires_allocation': 'yes',
'allocation_validation_type': 'no',
'leave_validation_type': 'no_validation',
'allows_negative': True,
'max_allowed_negative': 1,
})
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Monthly accrual',
'carryover_date': 'year_start',
'accrued_gain_time': 'end',
'level_ids': [(0, 0, {
'added_value_type': 'day',
'start_count': 0,
'start_type': 'day',
'added_value': 1,
'frequency': 'monthly',
'first_day_display': 'last',
'cap_accrued_time': False,
'action_with_unused_accruals': 'maximum',
'postpone_max_days': 5
})],
})
with freeze_time("2024-01-01"):
self.env['hr.leave.allocation'].create([{
'employee_id': self.employee_emp.id,
'holiday_status_id': leave_type_no_negative.id,
'allocation_type': 'accrual',
'accrual_plan_id': accrual_plan.id,
'number_of_days': 1,
}, {
'employee_id': self.employee_emp.id,
'holiday_status_id': leave_type_negative.id,
'allocation_type': 'accrual',
'accrual_plan_id': accrual_plan.id,
'number_of_days': 1,
}])
excess_leave = self.env['hr.leave'].create([{
'employee_id': self.employee_emp.id,
'holiday_status_id': leave_type_no_negative.id,
'request_date_from': '2024-01-05',
'request_date_to': '2024-01-05',
}])
allowed_negative_leave = self.env['hr.leave'].create([{
'employee_id': self.employee_emp.id,
'holiday_status_id': leave_type_negative.id,
'request_date_from': '2024-01-12',
'request_date_to': '2024-01-12',
}])
# As accrual allocation don't take into account future leaves,
# it should be possible to take both leaves.
self.env['hr.leave'].create([{
'employee_id': self.employee_emp.id,
'holiday_status_id': leave_type_no_negative.id,
'request_date_from': '2024-01-04',
'request_date_to': '2024-01-04',
}, {
'employee_id': self.employee_emp.id,
'holiday_status_id': leave_type_negative.id,
'request_date_from': '2024-01-11',
'request_date_to': '2024-01-11',
}])
self.env.flush_all()
self.env['hr.leave']._cancel_invalid_leaves()
# Since both leave are outside an allocation validity,
# they are detected as discrepancies. However, the
# leave that is not exceeding the negative amount should be kept
# as it is valid according to the configuration.
self.assertFalse(excess_leave.active)
self.assertTrue(allowed_negative_leave.active)
self.env['hr.leave'].create([{
'employee_id': self.employee_emp.id,
'holiday_status_id': leave_type_negative.id,
'request_date_from': '2024-01-10',
'request_date_to': '2024-01-10',
}])
self.env['hr.leave']._cancel_invalid_leaves()
# The last added leave creates a discrepancy that exceeds the
# maximum amount allowed in negative.
self.assertFalse(allowed_negative_leave.active)
def test_check_lastcall_change_regular_to_accrual(self):
with freeze_time("2017-12-5"):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Accrual Plan For Test',
})
allocation = self.env['hr.leave.allocation'].with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 10,
'allocation_type': 'regular',
})
allocation.action_validate()
self.assertEqual(allocation.lastcall, False)
allocation.action_refuse()
allocation.write({
'allocation_type': 'accrual',
'accrual_plan_id': accrual_plan.id,
})
self.assertEqual(allocation.lastcall, datetime.date(2017, 12, 5))
def test_accrual_allocation_data_persists(self):
leave_type = self.env['hr.leave.type'].create({
'name': 'Test Leave Type',
'time_type': 'leave',
'requires_allocation': 'yes',
'allocation_validation_type': 'no',
})
accrual_plan = self.env['hr.leave.accrual.plan'].create({
'name': 'Accrual Plan For Test',
'accrued_gain_time': 'start',
'carryover_date': 'year_start',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'day',
'frequency': 'daily',
'cap_accrued_time': True,
'maximum_leave': 10
})],
})
def get_remaining_leaves(*args):
return leave_type.get_allocation_data(self.employee_emp, datetime.date(*args))[self.employee_emp][0][1][
'remaining_leaves']
with freeze_time("2024-03-01"):
# Simulate creating an allocation from frontend interface
with Form(self.env['hr.leave.allocation']) as f:
f.allocation_type = "accrual"
f.accrual_plan_id = accrual_plan
f.employee_ids.add(self.employee_emp)
f.holiday_status_id = leave_type
f.date_from = '2024-02-01'
f.private_name = "Accrual allocation for employee"
allocation = f.record
allocation.action_validate()
first_result = get_remaining_leaves(2024, 2, 21)
self.assertEqual(get_remaining_leaves(2024, 2, 21), first_result, "Function return result should persist")
def test_future_accural_time_with_leaves_taken_in_the_past(self):
leave_type = self.env['hr.leave.type'].create({
'name': 'Test Leave Type',
'time_type': 'leave',
'requires_allocation': 'yes',
'allocation_validation_type': 'no',
})
accrual_plan = self.env['hr.leave.accrual.plan'].create({
'name': 'Accrual Plan For Test',
'accrued_gain_time': 'start',
'carryover_date': 'year_start',
'level_ids': [(0, 0, {
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'added_value_type': 'day',
'frequency': 'daily',
'cap_accrued_time': True,
'maximum_leave': 10
})],
})
def get_remaining_leaves(*args):
return leave_type.get_allocation_data(self.employee_emp, datetime.date(*args))[self.employee_emp][0][1][
'remaining_leaves']
with freeze_time("2024-03-01"):
# Simulate creating an allocation from frontend interface
with Form(self.env['hr.leave.allocation']) as f:
f.allocation_type = "accrual"
f.accrual_plan_id = accrual_plan
f.employee_ids.add(self.employee_emp)
f.holiday_status_id = leave_type
f.date_from = '2024-02-01'
f.private_name = "Accrual allocation for employee"
allocation = f.record
allocation.action_validate()
self.assertEqual(get_remaining_leaves(2024, 3, 1), 10, "The cap is reached, no more leaves should be accrued")
leave = self.env['hr.leave'].create({
'name': 'leave',
'employee_id': self.employee_emp.id,
'holiday_status_id': leave_type.id,
'request_date_from': '2024-02-26',
'request_date_to': '2024-03-01',
})
leave.action_validate()
self.assertEqual(get_remaining_leaves(2024, 3, 1), 5, "5 day should be deduced from the allocation")
self.assertEqual(get_remaining_leaves(2024, 3, 3), 7, "2 days should be added to the accrual allocation")
self.assertEqual(get_remaining_leaves(2024, 3, 10), 10, "Accrual allocation should be capped at 10")
leave = self.env['hr.leave'].create({
'name': 'leave',
'employee_id': self.employee_emp.id,
'holiday_status_id': leave_type.id,
'request_date_from': '2024-03-04',
'request_date_to': '2024-03-08',
})
leave.action_validate()
self.assertEqual(get_remaining_leaves(2024, 3, 4), 3, "5 days should be deduced from the allocation and a new day should be accrued")
self.assertEqual(get_remaining_leaves(2024, 3, 11), 10, "Accrual allocation should be capped at 10")
@freeze_time('2024-01-01')
def test_validate_leaves_with_more_days_than_allocation(self):
allocation = self.env['hr.leave.allocation'].with_context(tracking_disable=True).create({
'name': 'Accrual allocation for employee',
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 1,
'allocation_type': 'regular',
})
allocation.action_validate()
with self.assertRaises(ValidationError):
self.env['hr.leave'].create([{
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'request_date_from': '2024-01-09',
'request_date_to': '2024-01-12',
}])
leave = self.env['hr.leave'].create([{
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'request_date_from': '2024-01-09 08:00:00',
'request_date_to': '2024-01-09 17:00:00',
}])
leave.action_validate()
leave.action_refuse()
leave.action_draft()
leave.write({
'request_date_from': '2024-01-09',
'request_date_to': '2024-01-12',
})
with self.assertRaises(ValidationError):
leave.action_confirm()
def test_compute_allocation_days_after_adding_employee(self):
"""
Test the addition of the employee after the date when creating an allocation
will the number_of_days be computed or not. Also that the number_of_days
gets recomputed when changing the employee
"""
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': 'Monthly accrual',
'is_based_on_worked_time': True,
'transition_mode': 'immediately',
'carryover_date': 'year_start',
'accrued_gain_time': 'end',
'level_ids':
[(0, 0, {
'added_value_type': 'day',
'start_count': 1,
'start_type': 'day',
'added_value': 1,
'frequency': 'daily',
'first_day_display': '1',
'cap_accrued_time': False,
'action_with_unused_accruals': 'all',
}),
],
})
with freeze_time('2024-08-19'):
attendances = []
for index in range(3):
attendances.extend([
(0, 0, {
'name': '%s_%d' % ('20 Hours', index),
'hour_from': 8,
'hour_to': 10,
'dayofweek': str(index),
'day_period': 'morning'
}),
(0, 0, {
'name': '%s_%d' % ('20 Hours', index),
'hour_from': 10,
'hour_to': 11,
'dayofweek': str(index),
'day_period': 'lunch'
}),
(0, 0, {
'name': '%s_%d' % ('20 Hours', index),
'hour_from': 11,
'hour_to': 13,
'dayofweek': str(index),
'day_period': 'afternoon'
})
])
calendar_emp = self.env['resource.calendar'].create({
'name': '20 Hours',
'tz': self.employee_hrmanager.tz,
'attendance_ids': attendances,
})
self.employee_hrmanager.resource_calendar_id = calendar_emp.id
with Form(self.env['hr.leave.allocation']) as f:
f.allocation_type = "accrual"
f.accrual_plan_id = accrual_plan
f.date_from = '2024-08-07'
f.holiday_status_id = self.leave_type
f.employee_ids.add(self.employee_emp)
f.private_name = "Employee Allocation"
accrual_allocation = f.record
allocation_days = accrual_allocation.number_of_days
self.assertEqual(accrual_allocation.number_of_days, 7.0)
with Form(accrual_allocation) as accForm:
accForm.employee_ids.remove(self.employee_emp.id)
accForm.employee_ids.add(self.employee_hrmanager)
updated_allocation = accForm.record
self.assertNotEqual(updated_allocation.number_of_days, allocation_days)
self.assertEqual(updated_allocation.number_of_days, 3.0)
def test_start_accrual_gain_time_immediately(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': '1.25 days each 1st of the month',
'transition_mode': 'immediately',
'carryover_date': 'year_start',
'accrued_gain_time': 'start',
'level_ids':
[(0, 0, {
'start_type': 'day',
'start_count': 0,
'added_value_type': 'day',
'added_value': 1.25,
'frequency': 'monthly',
'cap_accrued_time': False,
'action_with_unused_accruals': 'all',
}),
],
})
with freeze_time('2024-09-02'):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation',
'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',
})
allocation.action_validate()
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 1.21, 2, 'Days for the current month should be granted immediately')
leave = self.env['hr.leave'].create({
'employee_id': self.employee_emp.id,
'holiday_status_id': self.leave_type.id,
'request_date_from': '2024-09-13 08:00:00',
'request_date_to': '2024-09-13 17:00:00',
})
leave.action_validate()
remaining_leaves = self.leave_type.get_allocation_data(self.employee_emp, date(2024, 9, 14))[self.employee_emp][0][1]['remaining_leaves']
self.assertAlmostEqual(remaining_leaves, 0.21, 2, 'Leave should be deducted from accrued days')
with freeze_time("2024-10-01"):
allocation._update_accrual()
self.assertAlmostEqual(allocation.number_of_days, 2.46, 2, 'Days for the upcoming month should be granted on the 1st')
def test_cache_invalidation_with_future_leaves(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': '1 days every last day of the month',
'transition_mode': 'immediately',
'carryover_date': 'year_start',
'accrued_gain_time': 'end',
'level_ids':
[(0, 0, {
'start_type': 'day',
'start_count': 0,
'added_value_type': 'day',
'added_value': 1,
'frequency': 'monthly',
'cap_accrued_time': False,
'action_with_unused_accruals': 'all',
'first_day': 31,
})
],
})
with freeze_time('2024-06-30'):
allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
'name': 'Accrual allocation',
'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',
})
allocation.action_validate()
allocation._update_accrual()
leave = self.env['hr.leave'].create({
'employee_id': self.employee_emp_id,
'holiday_status_id': self.leave_type.id,
'request_date_from': '2024-09-02',
'request_date_to': '2024-09-03',
})
leave.action_validate()
with freeze_time('2024-07-31'):
allocation._update_accrual()
self.assertEqual(allocation.number_of_days, 1, 'Days should be allocated even when leave is taken in the future')
def test_accrual_days_left_under_carryover_maximum(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': '21 days per year, 28 days cap, 7 carryover max',
'transition_mode': 'immediately',
'carryover_date': 'year_start',
'accrued_gain_time': 'start',
'level_ids':
[(0, 0, {
"accrued_gain_time": "start",
"action_with_unused_accruals": "maximum",
"added_value": 21,
"cap_accrued_time": True,
"first_day": 1,
"first_month": "jan",
"first_month_day": 1,
"frequency": "yearly",
"maximum_leave": 28,
"postpone_max_days": 7,
"start_count": 0,
"start_type": "day",
"yearly_day": 1,
"yearly_month": "jan"
})
],
})
with freeze_time('2024-11-25'):
with Form(self.env['hr.leave.allocation']) as f:
f.allocation_type = "accrual"
f.accrual_plan_id = accrual_plan
f.date_from = '2024-01-01'
f.employee_ids.add(self.employee_emp)
f.holiday_status_id = self.leave_type
f.private_name = "Employee Allocation"
allocation = f.record
allocation.action_validate()
# take 15 days, left with 6 days on the alloc
leave = self.env['hr.leave'].create({
'employee_id': self.employee_emp_id,
'holiday_status_id': self.leave_type.id,
'request_date_from': '2024-10-07',
'request_date_to': '2024-10-25',
})
leave.action_validate()
data = self.leave_type.get_allocation_data(self.employee_emp, date(2025, 1, 15))
remaining_future = data[self.employee_emp][0][1]["remaining_leaves"]
self.assertEqual(remaining_future, 27)
def test_accrual_unused_accrual_reset_to_lost(self):
accrual_plan = self.env['hr.leave.accrual.plan'].with_context(tracking_disable=True).create({
'name': '21 days per year, 28 days cap, 7 carryover max',
'transition_mode': 'immediately',
'carryover_date': 'year_start',
'accrued_gain_time': 'start',
})
plan = self.env["hr.leave.accrual.level"].with_context(tracking_disable=True).create({
"accrual_plan_id" : accrual_plan.id,
})
with Form(plan) as f:
f.added_value = 21
f.frequency = 'yearly'
f.yearly_day_display = "1"
f.cap_accrued_time = True
f.maximum_leave = 28
f.start_count = 0
# Set a maximum carry-over
f.action_with_unused_accruals = 'maximum'
f.postpone_max_days = 7
# Set it back to 'lost'
f.action_with_unused_accruals = 'lost'
with freeze_time('2024-11-25'):
with Form(self.env['hr.leave.allocation']) as f:
f.allocation_type = "accrual"
f.accrual_plan_id = accrual_plan
f.date_from = '2024-01-01'
f.employee_ids.add(self.employee_emp)
f.holiday_status_id = self.leave_type
f.private_name = "Employee Allocation"
allocation = f.record
allocation.action_validate()
# take 15 days, left with 6 days on the alloc
leave = self.env['hr.leave'].create({
'employee_id': self.employee_emp_id,
'holiday_status_id': self.leave_type.id,
'request_date_from': '2024-10-07',
'request_date_to': '2024-10-25',
})
leave.action_validate()
data = self.leave_type.get_allocation_data(self.employee_emp, date(2025, 1, 15))
remaining_future = data[self.employee_emp][0][1]["remaining_leaves"]
self.assertEqual(remaining_future, 21)