summaryrefslogtreecommitdiff
path: root/addons/hr_holidays/tests
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/hr_holidays/tests
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/hr_holidays/tests')
-rw-r--r--addons/hr_holidays/tests/__init__.py13
-rw-r--r--addons/hr_holidays/tests/common.py56
-rw-r--r--addons/hr_holidays/tests/test_access_rights.py801
-rw-r--r--addons/hr_holidays/tests/test_accrual_allocations.py251
-rw-r--r--addons/hr_holidays/tests/test_allocation_access_rights.py243
-rw-r--r--addons/hr_holidays/tests/test_automatic_leave_dates.py293
-rw-r--r--addons/hr_holidays/tests/test_change_department.py67
-rw-r--r--addons/hr_holidays/tests/test_company_leave.py323
-rw-r--r--addons/hr_holidays/tests/test_holidays_flow.py288
-rw-r--r--addons/hr_holidays/tests/test_hr_leave_type.py43
-rw-r--r--addons/hr_holidays/tests/test_leave_requests.py440
-rw-r--r--addons/hr_holidays/tests/test_out_of_office.py100
12 files changed, 2918 insertions, 0 deletions
diff --git a/addons/hr_holidays/tests/__init__.py b/addons/hr_holidays/tests/__init__.py
new file mode 100644
index 00000000..1fb97c40
--- /dev/null
+++ b/addons/hr_holidays/tests/__init__.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import test_access_rights
+from . import test_automatic_leave_dates
+from . import test_allocation_access_rights
+from . import test_holidays_flow
+from . import test_hr_leave_type
+from . import test_accrual_allocations
+from . import test_change_department
+from . import test_leave_requests
+from . import test_out_of_office
+from . import test_company_leave
diff --git a/addons/hr_holidays/tests/common.py b/addons/hr_holidays/tests/common.py
new file mode 100644
index 00000000..83e0df4d
--- /dev/null
+++ b/addons/hr_holidays/tests/common.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.addons.mail.tests.common import mail_new_test_user
+from odoo.tests import common
+
+
+class TestHrHolidaysCommon(common.TransactionCase):
+
+ def setUp(self):
+ super(TestHrHolidaysCommon, self).setUp()
+ self.env.user.tz = 'Europe/Brussels'
+
+ # Test users to use through the various tests
+ self.user_hruser = mail_new_test_user(self.env, login='armande', groups='base.group_user,hr_holidays.group_hr_holidays_user')
+ self.user_hruser_id = self.user_hruser.id
+
+ self.user_hrmanager = mail_new_test_user(self.env, login='bastien', groups='base.group_user,hr_holidays.group_hr_holidays_manager')
+ self.user_hrmanager_id = self.user_hrmanager.id
+
+ self.user_employee = mail_new_test_user(self.env, login='david', groups='base.group_user')
+ self.user_employee_id = self.user_employee.id
+
+ # Hr Data
+ Department = self.env['hr.department'].with_context(tracking_disable=True)
+
+ self.hr_dept = Department.create({
+ 'name': 'Human Resources',
+ })
+ self.rd_dept = Department.create({
+ 'name': 'Research and devlopment',
+ })
+
+ self.employee_emp = self.env['hr.employee'].create({
+ 'name': 'David Employee',
+ 'user_id': self.user_employee_id,
+ 'department_id': self.rd_dept.id,
+ })
+ self.employee_emp_id = self.employee_emp.id
+
+ self.employee_hruser = self.env['hr.employee'].create({
+ 'name': 'Armande HrUser',
+ 'user_id': self.user_hruser_id,
+ 'department_id': self.rd_dept.id,
+ })
+ self.employee_hruser_id = self.employee_hruser.id
+
+ self.employee_hrmanager = self.env['hr.employee'].create({
+ 'name': 'Bastien HrManager',
+ 'user_id': self.user_hrmanager_id,
+ 'department_id': self.hr_dept.id,
+ 'parent_id': self.employee_hruser_id,
+ })
+ self.employee_hrmanager_id = self.employee_hrmanager.id
+
+ self.rd_dept.write({'manager_id': self.employee_hruser_id})
diff --git a/addons/hr_holidays/tests/test_access_rights.py b/addons/hr_holidays/tests/test_access_rights.py
new file mode 100644
index 00000000..45aafba8
--- /dev/null
+++ b/addons/hr_holidays/tests/test_access_rights.py
@@ -0,0 +1,801 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
+from odoo import tests
+from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
+from odoo.exceptions import AccessError, UserError, ValidationError
+from odoo.tools import mute_logger
+
+
+@tests.tagged('access_rights', 'post_install', '-at_install')
+class TestHrHolidaysAccessRightsCommon(TestHrHolidaysCommon):
+ def setUp(self):
+ super(TestHrHolidaysAccessRightsCommon, self).setUp()
+ self.leave_type = self.env['hr.leave.type'].create({
+ 'name': 'Unlimited',
+ 'leave_validation_type': 'hr',
+ 'allocation_type': 'no',
+ 'validity_start': False,
+ })
+ self.rd_dept.manager_id = False
+ self.hr_dept.manager_id = False
+ self.employee_emp.parent_id = False
+ self.employee_leave = self.env['hr.leave'].with_user(self.user_employee_id).create({
+ 'name': 'Test',
+ 'holiday_status_id': self.leave_type.id,
+ 'department_id': self.employee_emp.department_id.id,
+ 'employee_id': self.employee_emp.id,
+ 'date_from': datetime.now(),
+ 'date_to': datetime.now() + relativedelta(days=1),
+ 'number_of_days': 1,
+ })
+
+ self.lt_no_validation = self.env['hr.leave.type'].create({
+ 'name': 'Validation = no_validation',
+ 'leave_validation_type': 'hr',
+ 'allocation_type': 'no',
+ 'validity_start': False,
+ })
+
+ self.lt_validation_hr = self.env['hr.leave.type'].create({
+ 'name': 'Validation = HR',
+ 'leave_validation_type': 'hr',
+ 'allocation_type': 'no',
+ 'validity_start': False,
+ })
+
+ self.lt_validation_manager = self.env['hr.leave.type'].create({
+ 'name': 'Validation = manager',
+ 'leave_validation_type': 'hr',
+ 'allocation_type': 'no',
+ 'validity_start': False,
+ })
+
+ self.lt_validation_both = self.env['hr.leave.type'].create({
+ 'name': 'Validation = both',
+ 'leave_validation_type': 'hr',
+ 'allocation_type': 'no',
+ 'validity_start': False,
+ })
+
+ self.draft_status = [
+ self.lt_validation_hr,
+ self.lt_validation_manager,
+ self.lt_validation_both
+ ]
+
+ self.confirm_status = [
+ self.lt_no_validation,
+ self.lt_validation_hr,
+ self.lt_validation_manager,
+ self.lt_validation_both
+ ]
+
+ def request_leave(self, user_id, date_from, number_of_days, values=None):
+ values = dict(values or {}, **{
+ 'date_from': date_from,
+ 'date_to': date_from + relativedelta(days=number_of_days),
+ 'number_of_days': number_of_days,
+ })
+ return self.env['hr.leave'].with_user(user_id).create(values)
+
+
+@tests.tagged('access_rights', 'access_rights_states')
+class TestAcessRightsStates(TestHrHolidaysAccessRightsCommon):
+ # ******************************************************
+ # Action draft
+ # ******************************************************
+
+ def test_draft_status(self):
+ """
+ We should only be able to draft a leave that is
+ in confirm or refuse state
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Ranoi',
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.action_draft()
+
+ values = {
+ 'name': 'Ranoi',
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=20 + i), 1, values)
+ # the state has to be set to draft in a write because it is initialized to confirm if it has validation
+ leave.write({'state': 'draft'})
+ with self.assertRaises(UserError):
+ leave.action_draft()
+
+ def test_base_user_draft_his_leave(self):
+ """
+ Should be able to draft his own leave
+ whatever the holiday_status_id
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.with_user(self.user_employee.id).action_draft()
+
+ def test_base_user_draft_other_employee_leave(self):
+ """
+ Should not be able to draft the leave of someone else
+ whatever the holiday_status_id
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ with self.assertRaises(UserError):
+ leave.with_user(self.user_employee.id).action_draft()
+
+ def test_base_user_draft_other_employee_leave_and_is_leave_manager_id(self):
+ """
+ Should not be able to draft the leave of someone else
+ even when being the leave manager id for this person
+ whatever the holiday_status_id
+ """
+ self.employee_hruser.write({'leave_manager_id': self.user_employee.id})
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ with self.assertRaises(UserError):
+ leave.with_user(self.user_employee.id).action_draft()
+
+ def test_base_user_draft_self_and_is_leave_manager_id(self):
+ """
+ Should be able to draft his own leave
+ even when being leave manager id
+ whatever the holiday_status_id
+ """
+ self.employee_emp.write({'leave_manager_id': self.user_employee.id})
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.with_user(self.user_employee.id).action_draft()
+
+ def test_base_user_draft_refused_leave(self):
+ """
+ Should not be able to draft a refused leave
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.action_refuse()
+ with self.assertRaises(UserError):
+ leave.with_user(self.user_employee.id).action_draft()
+
+ def test_base_user_draft_current_leave(self):
+ """
+ Should not be able to draft a passed leave
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=-20 + i), 1, values)
+ with self.assertRaises(UserError):
+ leave.with_user(self.user_employee.id).action_draft()
+
+ def test_holiday_user_draft_his_leave(self):
+ """
+ Should be able to draft his own leave
+ whatever the holiday_status_id
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.with_user(self.user_hruser.id).action_draft()
+
+ def test_holiday_user_draft_other_employee_leave(self):
+ """
+ Should not be able to draft other employee leave
+ whatever the holiday_status_id
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ with self.assertRaises(UserError):
+ leave.with_user(self.user_hruser.id).action_draft()
+
+ def test_holiday_user_draft_other_employee_leave_and_is_leave_manager_id(self):
+ """
+ Should not be able to draft other employee leave
+ even if he is the leave manager id
+ whatever the holiday_status_id
+ """
+ self.employee_emp.write({'leave_manager_id': self.user_hruser.id})
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ with self.assertRaises(UserError):
+ leave.with_user(self.user_hruser.id).action_draft()
+
+ def test_holiday_user_draft_self_and_is_manager_id(self):
+ """
+ Should be able to draft his own leave
+ even if he is leave manager id
+ whatever the holiday_status_id
+ """
+ self.employee_hruser.write({'leave_manager_id': self.user_hruser.id})
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.with_user(self.user_hruser.id).action_draft()
+
+ def test_holiday_user_draft_refused_leave(self):
+ """
+ Should not be able to draft a refused leave
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.action_refuse()
+ with self.assertRaises(UserError):
+ leave.with_user(self.user_hruser.id).action_draft()
+
+ def test_holiday_user_draft_current_leave(self):
+ """
+ Should not be able to draft a passed leave
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=-20 + i), 1, values)
+ with self.assertRaises(UserError):
+ leave.with_user(self.user_hruser.id).action_draft()
+
+ def test_holiday_manager_draft_his_leave(self):
+ """
+ The holiday manager should be able to do everything
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hrmanager.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.with_user(self.user_hrmanager.id).action_draft()
+
+ def test_holiday_manager_draft_other_employee_leave(self):
+ """
+ The holiday manager should be able to do everything
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.with_user(self.user_hrmanager.id).action_draft()
+
+ def test_holiday_manager_draft_other_employee_leave_and_is_leave_manager_id(self):
+ """
+ The holiday manager should be able to do everything
+ """
+ self.employee_hruser.write({'leave_manager_id': self.user_hrmanager.id})
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.with_user(self.user_hrmanager.id).action_draft()
+
+ def test_holiday_manager_draft_self_and_is_manager_id(self):
+ """
+ The holiday manager should be able to do everything
+ """
+ self.employee_hrmanager.write({'leave_manager_id': self.user_hrmanager.id})
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hrmanager.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.with_user(self.user_hrmanager.id).action_draft()
+
+ def test_holiday_manager_draft_refused_leave(self):
+ """
+ The holiday manager should be able to do everything
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=5 + i), 1, values)
+ leave.action_refuse()
+ leave.with_user(self.user_hrmanager.id).action_draft()
+
+ def test_holiday_manager_draft_current_leave(self):
+ """
+ The holiday manager should be able to do everything
+ """
+ for i, status in enumerate(self.draft_status):
+ values = {
+ 'name': 'Random Leave',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': status.id,
+ }
+ leave = self.request_leave(1, datetime.today() + relativedelta(days=-20 + i), 1, values)
+ leave.with_user(self.user_hrmanager.id).action_draft()
+
+@tests.tagged('access_rights', 'access_rights_create')
+class TestAccessRightsCreate(TestHrHolidaysAccessRightsCommon):
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_base_user_create_self(self):
+ """ A simple user can create a leave for himself """
+ values = {
+ 'name': 'Hol10',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.leave_type.id,
+ }
+ self.request_leave(self.user_employee_id, datetime.today() + relativedelta(days=5), 1, values)
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_base_user_create_other(self):
+ """ A simple user cannot create a leave for someone else """
+ values = {
+ 'name': 'Hol10',
+ 'employee_id': self.employee_hruser_id,
+ 'holiday_status_id': self.leave_type.id,
+ }
+ with self.assertRaises(AccessError):
+ self.request_leave(self.user_employee_id, datetime.today() + relativedelta(days=5), 1, values)
+
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_base_user_create_batch(self):
+ """ A simple user cannot create a leave in bacth mode (by company, by department, by tag)"""
+ values = {
+ 'name': 'Hol10',
+ 'holiday_status_id': self.leave_type.id,
+ 'holiday_type': 'company',
+ 'mode_company_id': 1,
+ }
+ with self.assertRaises(AccessError):
+ self.request_leave(self.user_employee_id, datetime.today() + relativedelta(days=5), 1, values)
+
+ # hr_holidays.group_hr_holidays_user
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_holidays_user_create_self(self):
+ """ A holidays user can create a leave for himself """
+ values = {
+ 'name': 'Hol10',
+ 'employee_id': self.employee_hruser_id,
+ 'holiday_status_id': self.leave_type.id,
+ }
+ self.request_leave(self.user_hruser_id, datetime.today() + relativedelta(days=5), 1, values)
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_holidays_user_create_other(self):
+ """ A holidays user can create a leave for someone else """
+ values = {
+ 'name': 'Hol10',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.leave_type.id,
+ }
+ self.request_leave(self.user_hruser_id, datetime.today() + relativedelta(days=5), 1, values)
+
+ # hr_holidays.group_hr_holidays_manager
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_holidays_manager_create_self(self):
+ """ A holidays manager can create a leave for himself """
+ values = {
+ 'name': 'Hol10',
+ 'employee_id': self.employee_hrmanager_id,
+ 'holiday_status_id': self.leave_type.id,
+ }
+ self.request_leave(self.user_hrmanager_id, datetime.today() + relativedelta(days=5), 1, values)
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_holidays_manager_create_other(self):
+ """ A holidays manager can create a leave for someone else """
+ values = {
+ 'name': 'Hol10',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.leave_type.id,
+ }
+ self.request_leave(self.user_hrmanager_id, datetime.today() + relativedelta(days=5), 1, values)
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_holidays_manager_create_batch(self):
+ """ A holidays manager can create a leave in bacth mode (by company, by department, by tag)"""
+ values = {
+ 'name': 'Hol10',
+ 'holiday_status_id': self.leave_type.id,
+ 'holiday_type': 'company',
+ 'mode_company_id': 1,
+ }
+ self.request_leave(self.user_hrmanager_id, datetime.today() + relativedelta(days=5), 1, values)
+
+
+@tests.tagged('access_rights', 'access_rights_read')
+class TestAccessRightsRead(TestHrHolidaysAccessRightsCommon):
+ # base.group_user
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_read_by_user_other(self):
+ """ Users should not be able to read other people requests """
+ other_leave = self.env['hr.leave'].with_user(self.user_hruser).create({
+ 'name': 'Test',
+ 'holiday_status_id': self.leave_type.id,
+ 'department_id': self.employee_hruser.department_id.id,
+ 'employee_id': self.employee_hruser.id,
+ 'date_from': datetime.now(),
+ 'date_to': datetime.now() + relativedelta(days=1),
+ 'number_of_days': 1,
+ })
+ with self.assertRaises(AccessError), self.cr.savepoint():
+ res = other_leave.with_user(self.user_employee_id).read(['number_of_days', 'state', 'name'])
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_read_by_user_other_browse(self):
+ """ Users should not be able to browse other people requests """
+ other_leave = self.env['hr.leave'].with_user(self.user_hruser).create({
+ 'name': 'Test',
+ 'holiday_status_id': self.leave_type.id,
+ 'department_id': self.employee_hruser.department_id.id,
+ 'employee_id': self.employee_hruser.id,
+ 'date_from': datetime.now(),
+ 'date_to': datetime.now() + relativedelta(days=1),
+ 'number_of_days': 1,
+ })
+ with self.assertRaises(AccessError), self.cr.savepoint():
+ other_leave.invalidate_cache(['name'])
+ name = other_leave.with_user(self.user_employee_id).name
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_read_by_user_own(self):
+ """ Users should be able to read name field of own requests """
+ res = self.employee_leave.read(['name', 'number_of_days', 'state'])
+ self.assertEqual(res[0]['name'], 'Test')
+
+
+@tests.tagged('access_rights', 'access_rights_write')
+class TestAccessRightsWrite(TestHrHolidaysAccessRightsCommon):
+ # base.group_user
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_update_by_user(self):
+ """ User may update its leave """
+ self.employee_leave.with_user(self.user_employee_id).write({'name': 'Crocodile Dundee is my man'})
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_update_by_user_other(self):
+ """ User cannot update other people leaves """
+ other_leave = self.env['hr.leave'].with_user(self.user_hruser).create({
+ 'name': 'Test',
+ 'holiday_status_id': self.leave_type.id,
+ 'department_id': self.employee_hruser.department_id.id,
+ 'employee_id': self.employee_hruser.id,
+ 'date_from': datetime.now(),
+ 'date_to': datetime.now() + relativedelta(days=1),
+ 'number_of_days': 1,
+ })
+ with self.assertRaises(AccessError):
+ other_leave.with_user(self.user_employee_id).write({'name': 'Crocodile Dundee is my man'})
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_creation_for_other_user(self):
+ """ Employee cannot creates a leave request for another employee """
+ HolidaysEmployeeGroup = self.env['hr.leave'].with_user(self.user_employee_id)
+ with self.assertRaises(AccessError):
+ HolidaysEmployeeGroup.create({
+ 'name': 'Hol10',
+ 'employee_id': self.employee_hruser_id,
+ 'holiday_status_id': self.leave_type.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': datetime.today(),
+ 'number_of_days': 1,
+ })
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_messaging_by_user(self):
+ """ User may communicate on its own leaves, even if validated """
+ self.employee_leave.with_user(self.user_employee_id).message_post(
+ body='I haz messaging',
+ subtype_xmlid='mail.mt_comment',
+ message_type='comment'
+ )
+
+ self.employee_leave.with_user(self.user_hrmanager_id).action_approve()
+
+ self.employee_leave.with_user(self.user_employee_id).message_post(
+ body='I still haz messaging',
+ subtype_xmlid='mail.mt_comment',
+ message_type='comment'
+ )
+
+ # ----------------------------------------
+ # Validation: one validation, HR
+ # ----------------------------------------
+
+ # base.group_user
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_hr_to_validate_by_user(self):
+ """ User may not validate any leaves in HR mode """
+ with self.assertRaises(UserError):
+ self.employee_leave.with_user(self.user_employee_id).action_approve()
+
+ with self.assertRaises(UserError):
+ self.employee_leave.with_user(self.user_employee_id).write({'state': 'validate'})
+
+ # hr_holidays.group_hr_holidays_user
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_hr_to_validate_by_holiday_user(self):
+ """ Manager can validate leaves in HR mode """
+ self.assertEqual(self.employee_leave.state, 'confirm')
+ self.employee_leave.with_user(self.user_hrmanager_id).action_approve()
+ self.assertEqual(self.employee_leave.state, 'validate')
+
+ # hr_holidays.group_hr_holidays_manager
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_hr_to_validate_by_manager(self):
+ """ Manager validate its own leaves """
+ manager_leave = self.env['hr.leave'].with_user(self.user_hrmanager_id).create({
+ 'name': 'Hol manager',
+ 'holiday_status_id': self.leave_type.id,
+ 'employee_id': self.employee_hrmanager_id,
+ 'date_from': (datetime.today() + relativedelta(days=15)),
+ 'date_to': (datetime.today() + relativedelta(days=16)),
+ 'number_of_days': 1,
+ })
+ self.assertEqual(manager_leave.state, 'confirm')
+ manager_leave.action_approve()
+ self.assertEqual(manager_leave.state, 'validate')
+
+ # ----------------------------------------
+ # Validation: one validation, manager
+ # ----------------------------------------
+
+ # base.group_user
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_manager_to_validate_by_user(self):
+ """ A simple user can validate in manager mode if he is leave_manager_id """
+ self.leave_type.write({'leave_validation_type': 'manager'})
+ values = {
+ 'name': 'Hol HrUser',
+ 'employee_id': self.employee_hruser_id,
+ 'holiday_status_id': self.leave_type.id,
+ 'state': 'confirm',
+ }
+ hr_leave = self.request_leave(self.user_hruser_id, datetime.now() + relativedelta(days=2), 1, values)
+ with self.assertRaises(AccessError):
+ hr_leave.with_user(self.user_employee_id).action_approve()
+ self.employee_hruser.write({'leave_manager_id': self.user_employee_id})
+ hr_leave.with_user(self.user_employee_id).action_approve()
+
+ # hr_holidays.group_hr_holidays_user
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_manager_to_validate_by_holiday_user(self):
+ """ A holiday user can validate in manager mode """
+ self.leave_type.write({'leave_validation_type': 'manager'})
+ values = {
+ 'name': 'Hol HrUser',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.leave_type.id,
+ 'state': 'confirm',
+ }
+ hr_leave = self.request_leave(self.user_hruser_id, datetime.now() + relativedelta(days=2), 1, values)
+ hr_leave.with_user(self.user_hruser_id).action_approve()
+
+ # ----------------------------------------
+ # Validation: double
+ # ----------------------------------------
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_double_validate(self):
+ self.leave_type.write({'leave_validation_type': 'both'})
+ values = {
+ 'name': 'double HrManager',
+ 'employee_id': self.employee_hrmanager_id,
+ 'holiday_status_id': self.leave_type.id,
+ 'state': 'confirm',
+ }
+ self.employee_hrmanager.leave_manager_id = self.env['res.users'].browse(1)
+ hr_leave = self.request_leave(self.user_hruser_id, datetime.now() + relativedelta(days=6), 1, values)
+
+ with self.assertRaises(AccessError):
+ hr_leave.with_user(self.user_employee_id).action_approve()
+
+ self.employee_hrmanager.leave_manager_id = self.user_hruser
+ hr_leave.with_user(self.user_hruser_id).action_approve()
+
+ with self.assertRaises(AccessError):
+ hr_leave.with_user(self.user_employee_id).action_validate()
+ hr_leave.with_user(self.user_hruser_id).action_validate()
+
+ # hr_holidays.group_hr_holidays_manager
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_double_validate_holiday_manager(self):
+ self.leave_type.write({'leave_validation_type': 'both'})
+ values = {
+ 'name': 'double HrManager',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.leave_type.id,
+ 'state': 'confirm',
+ }
+ hr_leave = self.request_leave(self.user_hrmanager_id, datetime.now() + relativedelta(days=4), 1, values).with_user(self.user_hrmanager_id)
+ hr_leave.action_approve()
+ hr_leave.action_validate()
+
+ # ----------------------------------------
+ # State = Refuse
+ # ----------------------------------------
+
+ # base.group_user
+
+ # hr_holidays.group_hr_holidays_user
+
+ # TODO Can refuse
+
+ # hr_holidays.group_hr_holidays_manager
+
+ # TODO Can refuse
+
+ # ----------------------------------------
+ # State = Cancel
+ # ----------------------------------------
+
+ # base.group_user
+
+ # TODO Can Cancel if start_date in the future
+
+ # hr_holidays.group_hr_holidays_user
+
+ # TODO Can Cancel if not in validate
+
+ # hr_holidays.group_hr_holidays_manager
+
+ # TODO Can always cancel with great powers comes great responbilities
+
+
+class TestMultiCompany(TestHrHolidaysCommon):
+
+ def setUp(self):
+ super(TestMultiCompany, self).setUp()
+ self.new_company = self.env['res.company'].create({
+ 'name': 'Crocodile Dundee Company',
+ })
+ self.leave_type = self.env['hr.leave.type'].create({
+ 'name': 'Unlimited - Company New',
+ 'company_id': self.new_company.id,
+ 'leave_validation_type': 'hr',
+ 'allocation_type': 'no',
+ })
+ self.rd_dept.manager_id = False
+ self.hr_dept.manager_id = False
+
+ self.employee_leave = self.env['hr.leave'].create({
+ 'name': 'Test',
+ 'holiday_status_id': self.leave_type.id,
+ 'department_id': self.employee_emp.department_id.id,
+ 'employee_id': self.employee_emp.id,
+ 'date_from': datetime.now(),
+ 'date_to': datetime.now() + relativedelta(days=1),
+ 'number_of_days': 1,
+ })
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_access_other_company_user(self):
+ employee_leave = self.employee_leave.with_user(self.user_employee)
+ employee_leave.invalidate_cache(['name'])
+ with self.assertRaises(AccessError):
+ employee_leave.name
+
+ with self.assertRaises(AccessError):
+ employee_leave.action_approve()
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_access_other_company_officer(self):
+ employee_leave_hruser = self.employee_leave.with_user(self.user_hruser)
+ employee_leave_hruser.invalidate_cache(['name'])
+ with self.assertRaises(AccessError):
+ employee_leave_hruser.name
+
+ with self.assertRaises(AccessError):
+ employee_leave_hruser.action_approve()
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_access_other_company_manager(self):
+ employee_leave_hrmanager = self.employee_leave.with_user(self.user_hrmanager)
+ employee_leave_hrmanager.invalidate_cache(['name'])
+ with self.assertRaises(AccessError):
+ employee_leave_hrmanager.name
+
+ with self.assertRaises(AccessError):
+ employee_leave_hrmanager.action_approve()
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_access_no_company_user(self):
+ self.leave_type.write({'company_id': False})
+ employee_leave = self.employee_leave.with_user(self.user_employee)
+
+ employee_leave.name
+ with self.assertRaises(UserError):
+ employee_leave.action_approve()
+ self.assertEqual(employee_leave.state, 'confirm')
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_access_no_company_officer(self):
+ self.leave_type.write({'company_id': False})
+ employee_leave_hruser = self.employee_leave.with_user(self.user_hruser)
+
+ employee_leave_hruser.name
+ employee_leave_hruser.action_approve()
+ self.assertEqual(employee_leave_hruser.state, 'validate')
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_access_no_company_manager(self):
+ self.leave_type.write({'company_id': False})
+ employee_leave_hrmanager = self.employee_leave.with_user(self.user_hrmanager)
+
+ employee_leave_hrmanager.name
+ employee_leave_hrmanager.action_approve()
+ self.assertEqual(employee_leave_hrmanager.state, 'validate')
diff --git a/addons/hr_holidays/tests/test_accrual_allocations.py b/addons/hr_holidays/tests/test_accrual_allocations.py
new file mode 100644
index 00000000..b46da059
--- /dev/null
+++ b/addons/hr_holidays/tests/test_accrual_allocations.py
@@ -0,0 +1,251 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from datetime import datetime, time
+from dateutil.relativedelta import relativedelta
+
+from odoo import fields
+
+from odoo.tools import mute_logger
+
+from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
+
+
+class TestAccrualAllocations(TestHrHolidaysCommon):
+ def setUp(self):
+ super(TestAccrualAllocations, self).setUp()
+
+ # Make sure we have the rights to create, validate and delete the leaves, leave types and allocations
+ LeaveType = self.env['hr.leave.type'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True)
+
+ self.accrual_type = LeaveType.create({
+ 'name': 'accrual',
+ 'allocation_type': 'fixed',
+ 'validity_start': False,
+ })
+
+ self.unpaid_type = LeaveType.create({
+ 'name': 'unpaid',
+ 'allocation_type': 'no',
+ 'unpaid': True,
+ 'validity_start': False,
+ })
+
+ self.set_employee_create_date(self.employee_emp_id, '2010-02-03 00:00:00')
+ self.set_employee_create_date(self.employee_hruser_id, '2010-02-03 00:00:00')
+
+ def set_employee_create_date(self, id, newdate):
+ """ This method is a hack in order to be able to define/redefine the create_date
+ of the employees.
+ This is done in SQL because ORM does not allow to write onto the create_date field.
+ """
+ self.env.cr.execute("""
+ UPDATE
+ hr_employee
+ SET create_date = '%s'
+ WHERE id = %s
+ """ % (newdate, id))
+
+ def test_accrual_base_no_leaves(self):
+ """ Test if we can allocate some leaves accrually to an employee """
+ alloc = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
+ 'name': 'Accrual allocation for employee',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.accrual_type.id,
+ 'allocation_type': 'accrual',
+ 'number_of_days': 0,
+ 'number_per_interval': 1,
+ 'interval_number': 1,
+ 'unit_per_interval': 'days',
+ 'interval_unit': 'weeks',
+ })
+ alloc.action_approve()
+ alloc._update_accrual()
+
+ self.assertEqual(alloc.number_of_days, 1, 'Employee should have been allocated one leave day')
+
+ def test_accrual_base_leaves(self):
+ """ Test if the accrual allocation take the unpaid leaves into account when allocating leaves """
+ alloc = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
+ 'name': 'Accrual allocation for employee with leaves',
+ 'employee_id': self.employee_hruser_id,
+ 'holiday_status_id': self.accrual_type.id,
+ 'allocation_type': 'accrual',
+ 'number_of_days': 0,
+ 'number_per_interval': 1,
+ 'interval_number': 1,
+ 'unit_per_interval': 'days',
+ 'interval_unit': 'weeks',
+ })
+
+ alloc.action_approve()
+
+ employee = self.env['hr.employee'].browse(self.employee_hruser_id)
+ # Getting the previous work date
+ df = employee.resource_calendar_id.plan_days(-2, fields.Datetime.now()).date()
+
+ leave = self.env['hr.leave'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
+ 'name': 'Leave for hruser',
+ 'employee_id': self.employee_hruser_id,
+ 'holiday_status_id': self.unpaid_type.id,
+ 'date_from': datetime.combine(df, time(0, 0, 0)),
+ 'date_to': datetime.combine(df + relativedelta(days=1), time(0, 0, 0)),
+ 'number_of_days': 1,
+ })
+
+ leave.action_approve()
+
+ alloc._update_accrual()
+
+ self.assertEqual(alloc.number_of_days, .8, 'As employee took some unpaid leaves last week, he should be allocated only .8 days')
+
+ def test_accrual_many(self):
+ """
+ Test different configuration of accrual allocations
+ """
+ Allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True)
+
+ alloc_0 = Allocation.create({
+ 'name': '1 day per 2 weeks',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.accrual_type.id,
+ 'allocation_type': 'accrual',
+ 'number_of_days': 0,
+ 'number_per_interval': 1,
+ 'interval_number': 2,
+ 'unit_per_interval': 'days',
+ 'interval_unit': 'weeks',
+ })
+
+ alloc_1 = Allocation.create({
+ 'name': '4 hours per week',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.accrual_type.id,
+ 'allocation_type': 'accrual',
+ 'number_of_days': 0,
+ 'number_per_interval': 4,
+ 'interval_number': 1,
+ 'unit_per_interval': 'hours',
+ 'interval_unit': 'weeks',
+ })
+
+ alloc_2 = Allocation.create({
+ 'name': '2 day per 1 month',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.accrual_type.id,
+ 'allocation_type': 'accrual',
+ 'number_of_days': 0,
+ 'number_per_interval': 2,
+ 'interval_number': 1,
+ 'unit_per_interval': 'days',
+ 'interval_unit': 'months',
+ })
+
+ alloc_3 = Allocation.create({
+ 'name': '20 days per year',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.accrual_type.id,
+ 'allocation_type': 'accrual',
+ 'number_of_days': 0,
+ 'number_per_interval': 20,
+ 'interval_number': 1,
+ 'unit_per_interval': 'days',
+ 'interval_unit': 'years',
+ })
+
+ (alloc_0 | alloc_1 | alloc_2 | alloc_3).action_approve()
+
+ Allocation._update_accrual()
+
+ self.assertEqual(alloc_0.number_of_days, 1)
+ self.assertEqual(alloc_1.number_of_days, .5)
+ self.assertEqual(alloc_2.number_of_days, 2)
+ self.assertEqual(alloc_3.number_of_days, 20)
+
+ def test_accrual_new_employee(self):
+ """
+ Test if accrual allocation takes into account the creation date
+ of an employee
+ """
+ Allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True)
+
+ self.set_employee_create_date(self.employee_emp_id, fields.Datetime.to_string(fields.Datetime.now()))
+
+ alloc_0 = Allocation.create({
+ 'name': 'one shot one kill',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.accrual_type.id,
+ 'allocation_type': 'accrual',
+ 'number_of_days': 0,
+ 'number_per_interval': 1,
+ 'interval_number': 1,
+ 'unit_per_interval': 'days',
+ 'interval_unit': 'weeks',
+ })
+
+ alloc_0.action_approve()
+
+ Allocation._update_accrual()
+
+ self.assertEqual(alloc_0.number_of_days, 0, 'Employee is new he should not get any accrual leaves')
+
+ def test_accrual_multi(self):
+ """ Test if the cron does not allocate leaves every time it's called but only when necessary """
+ alloc = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
+ 'name': '2 days per week',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.accrual_type.id,
+ 'allocation_type': 'accrual',
+ 'number_of_days': 0,
+ 'number_per_interval': 1,
+ 'interval_number': 2,
+ 'unit_per_interval': 'days',
+ 'interval_unit': 'weeks',
+ })
+ alloc.action_approve()
+ alloc._update_accrual()
+ alloc._update_accrual()
+
+ self.assertEqual(alloc.number_of_days, 1, 'Cron only allocates 1 days every two weeks')
+
+ def test_accrual_validation(self):
+ """
+ Test if cron does not allocate past it's validity date
+ """
+ Allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True)
+
+ alloc_0 = Allocation.create({
+ 'name': '20 days per year',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.accrual_type.id,
+ 'number_of_days': 0,
+ 'number_per_interval': 20,
+ 'interval_number': 1,
+ 'unit_per_interval': 'days',
+ 'interval_unit': 'years',
+ 'date_to': fields.Datetime.from_string('2015-02-03 00:00:00'),
+ })
+
+ alloc_0.action_approve()
+
+ Allocation._update_accrual()
+
+ self.assertEqual(alloc_0.number_of_days, 0, 'Cron validity passed, should not allocate any leave')
+
+ def test_accrual_balance_limit(self):
+ """ Test if accrual allocation does not allocate more than the balance limit"""
+ allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
+ 'name': 'accrual 5 max',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.accrual_type.id,
+ 'allocation_type': 'accrual',
+ 'accrual_limit': 5,
+ 'number_of_days': 0,
+ 'number_per_interval': 6,
+ 'interval_number': 1,
+ 'unit_per_interval': 'days',
+ 'interval_unit': 'weeks',
+ })
+ allocation.action_approve()
+ allocation._update_accrual()
+
+ self.assertEqual(allocation.number_of_days, 5, 'Should have allocated only 5 days as balance limit is 5')
diff --git a/addons/hr_holidays/tests/test_allocation_access_rights.py b/addons/hr_holidays/tests/test_allocation_access_rights.py
new file mode 100644
index 00000000..2d4c973e
--- /dev/null
+++ b/addons/hr_holidays/tests/test_allocation_access_rights.py
@@ -0,0 +1,243 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import tests
+from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
+from odoo.exceptions import AccessError, UserError
+
+
+@tests.tagged('access_rights', 'post_install', '-at_install')
+class TestAllocationRights(TestHrHolidaysCommon):
+
+ def setUp(self):
+ super().setUp()
+ self.rd_dept.manager_id = False
+ self.hr_dept.manager_id = False
+ self.employee_emp.parent_id = False
+ self.employee_emp.leave_manager_id = False
+
+ self.lt_validation_hr = self.env['hr.leave.type'].create({
+ 'name': 'Validation = HR',
+ 'allocation_validation_type': 'hr',
+ 'allocation_type': 'fixed_allocation',
+ 'validity_start': False,
+ })
+
+ self.lt_validation_manager = self.env['hr.leave.type'].create({
+ 'name': 'Validation = manager',
+ 'allocation_validation_type': 'manager',
+ 'allocation_type': 'fixed_allocation',
+ 'validity_start': False,
+ })
+
+ self.lt_validation_both = self.env['hr.leave.type'].create({
+ 'name': 'Validation = both',
+ 'allocation_validation_type': 'both',
+ 'allocation_type': 'fixed_allocation',
+ 'validity_start': False,
+ })
+
+ def request_allocation(self, user, values={}):
+ values = dict(values, **{
+ 'name': 'Allocation',
+ 'number_of_days': 1,
+ })
+ return self.env['hr.leave.allocation'].with_user(user).create(values)
+
+
+class TestAccessRightsSimpleUser(TestAllocationRights):
+
+ def test_simple_user_request_allocation(self):
+ """ A simple user can request an allocation but not approve it """
+ values = {
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.lt_validation_hr.id,
+ }
+ allocation = self.request_allocation(self.user_employee.id, values)
+ with self.assertRaises(UserError):
+ allocation.action_approve()
+
+ def test_simple_user_request_fixed_allocation(self):
+ """ A simple user cannot request an allocation if set by HR """
+ self.lt_validation_hr.allocation_type = 'fixed'
+ values = {
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.lt_validation_hr.id,
+ }
+ with self.assertRaises(AccessError):
+ self.request_allocation(self.user_employee.id, values)
+
+ def test_simple_user_reset_to_draft(self):
+ """ A simple user can reset to draft only his own allocation """
+ values = {
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.lt_validation_hr.id,
+ }
+ allocation = self.request_allocation(self.user_employee.id, values)
+ self.assertEqual(allocation.state, 'confirm')
+ allocation.action_draft()
+ self.assertEqual(allocation.state, 'draft', "It should be reset to draft state")
+
+
+class TestAccessRightsEmployeeManager(TestAllocationRights):
+
+ def setUp(self):
+ super().setUp()
+ self.managed_employee = self.env['hr.employee'].create({
+ 'name': 'Jolly Jumper',
+ 'leave_manager_id': self.user_employee.id,
+ })
+
+ def test_manager_request_allocation_other(self):
+ """ A manager cannot request and approve an allocation for employees he doesn't manage """
+ values = {
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': self.lt_validation_manager.id,
+ }
+ with self.assertRaises(AccessError):
+ self.request_allocation(self.user_employee.id, values) # user is not the employee's manager
+
+ def test_manager_approve_request_allocation(self):
+ """ A manager can request and approve an allocation for managed employees """
+ values = {
+ 'employee_id': self.managed_employee.id,
+ 'holiday_status_id': self.lt_validation_manager.id,
+ }
+ allocation = self.request_allocation(self.user_employee.id, values)
+ allocation.action_approve()
+ self.assertEqual(allocation.state, 'validate', "The allocation should be validated")
+
+ def test_manager_refuse_request_allocation(self):
+ """ A manager can request and refuse an allocation for managed employees """
+ values = {
+ 'employee_id': self.managed_employee.id,
+ 'holiday_status_id': self.lt_validation_manager.id,
+ }
+ allocation = self.request_allocation(self.user_employee.id, values)
+ allocation.action_refuse()
+ self.assertEqual(allocation.state, 'refuse', "The allocation should be validated")
+
+ def test_manager_batch_allocation(self):
+ """ A manager cannot create batch allocation """
+ values = {
+ 'holiday_status_id': self.lt_validation_manager.id,
+ 'holiday_type': 'company',
+ 'mode_company_id': self.user_employee.company_id.id,
+ }
+ with self.assertRaises(AccessError):
+ self.request_allocation(self.user_employee.id, values)
+
+ def test_manager_approve_own(self):
+ """ A manager cannot approve his own allocation """
+ values = {
+ 'employee_id': self.user_employee.employee_id.id,
+ 'holiday_status_id': self.lt_validation_manager.id,
+ }
+ allocation = self.request_allocation(self.user_employee.id, values)
+ with self.assertRaises(UserError):
+ allocation.action_approve()
+
+ def test_manager_only_first_approval(self):
+ """ A manager can only do the first approval """
+ values = {
+ 'employee_id': self.managed_employee.id,
+ 'holiday_status_id': self.lt_validation_both.id,
+ }
+ allocation = self.request_allocation(self.user_employee.id, values)
+ allocation.action_approve()
+ with self.assertRaises(UserError):
+ allocation.action_validate()
+
+
+class TestAccessRightsHolidayUser(TestAllocationRights):
+
+ def test_holiday_user_request_allocation(self):
+ """ A holiday user can request and approve an allocation for any employee """
+ values = {
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.lt_validation_hr.id,
+ }
+ allocation = self.request_allocation(self.user_hruser.id, values)
+ allocation.action_approve()
+ self.assertEqual(allocation.state, 'validate', "It should have been validated")
+
+ def test_holiday_user_request_fixed_allocation(self):
+ """ A holiday user can request and approve an allocation if set by HR """
+ self.lt_validation_hr.allocation_type = 'fixed'
+ values = {
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.lt_validation_hr.id,
+ }
+ allocation = self.request_allocation(self.user_hruser.id, values)
+ allocation.action_approve()
+ self.assertEqual(allocation.state, 'validate', "It should have been validated")
+
+ def test_holiday_user_both_second_approval(self):
+ """ A holiday user can only do the second approval when double validation """
+ values = {
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.lt_validation_both.id,
+ }
+ allocation = self.request_allocation(self.user_hruser.id, values)
+ with self.assertRaises(UserError):
+ allocation.action_approve()
+ allocation.sudo().action_approve() # First approval by someone else
+ allocation.action_validate()
+ self.assertEqual(allocation.state, 'validate', "It should have been validated")
+
+ def test_holiday_user_batch_allocation(self):
+ """ A holiday user cannot create a batch allocation """
+ values = {
+ 'holiday_status_id': self.lt_validation_hr.id,
+ 'holiday_type': 'company',
+ 'mode_company_id': self.user_employee.company_id.id,
+ }
+ with self.assertRaises(AccessError):
+ self.request_allocation(self.user_hruser.id, values)
+
+ def test_holiday_user_cannot_approve_own(self):
+ """ A holiday user cannot approve his own allocation """
+ values = {
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': self.lt_validation_hr.id,
+ }
+ allocation = self.request_allocation(self.user_hruser.id, values)
+ with self.assertRaises(UserError):
+ allocation.action_approve()
+
+
+class TestAccessRightsHolidayManager(TestAllocationRights):
+
+ def test_holiday_manager_can_approve_own(self):
+ """ A holiday manager can approve his own allocation """
+ values = {
+ 'employee_id': self.employee_hrmanager.id,
+ 'holiday_status_id': self.lt_validation_hr.id,
+ }
+ allocation = self.request_allocation(self.user_hrmanager.id, values)
+ allocation.action_approve()
+ self.assertEqual(allocation.state, 'validate', "It should have been validated")
+
+ def test_holiday_manager_both_validation(self):
+ """ A holiday manager can perform both validation """
+ values = {
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.lt_validation_both.id,
+ }
+ allocation = self.request_allocation(self.user_hrmanager.id, values)
+ allocation.action_approve()
+ self.assertEqual(allocation.state, 'validate1', "It should have been validated one time")
+ allocation.action_validate()
+ self.assertEqual(allocation.state, 'validate', "It should have been completely validated")
+
+ def test_holiday_manager_refuse_validated(self):
+ """ A holiday manager can refuse a validated allocation """
+ values = {
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.lt_validation_hr.id,
+ }
+ allocation = self.request_allocation(self.user_hrmanager.id, values)
+ allocation.action_approve()
+ self.assertEqual(allocation.state, 'validate', "It should have been validated")
+ allocation.action_refuse()
+ self.assertEqual(allocation.state, 'refuse', "It should have been refused")
diff --git a/addons/hr_holidays/tests/test_automatic_leave_dates.py b/addons/hr_holidays/tests/test_automatic_leave_dates.py
new file mode 100644
index 00000000..d3774e09
--- /dev/null
+++ b/addons/hr_holidays/tests/test_automatic_leave_dates.py
@@ -0,0 +1,293 @@
+# -*- coding: utf-8 -*-
+from datetime import date, datetime
+
+from odoo.tests.common import Form
+
+from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
+from odoo.exceptions import ValidationError
+
+
+class TestAutomaticLeaveDates(TestHrHolidaysCommon):
+ def setUp(self):
+ super(TestAutomaticLeaveDates, self).setUp()
+
+ self.leave_type = self.env['hr.leave.type'].create({
+ 'name': 'Automatic Test',
+ 'time_type': 'leave',
+ 'allocation_type': 'no',
+ 'validity_start': False,
+ })
+
+ def test_no_attendances(self):
+ calendar = self.env['resource.calendar'].create({
+ 'name': 'No Attendances',
+ 'attendance_ids': [(5, 0, 0)],
+ })
+ employee = self.employee_emp
+ employee.resource_calendar_id = calendar
+
+ with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form:
+ leave_form.holiday_status_id = self.leave_type
+ leave_form.request_date_from = date(2019, 9, 2)
+ leave_form.request_date_to = date(2019, 9, 2)
+ leave_form.request_unit_half = True
+ leave_form.request_date_from_period = 'am'
+
+ self.assertEqual(leave_form.number_of_days_display, 0)
+ self.assertEqual(leave_form.number_of_hours_text, '0 Hours')
+
+ def test_single_attendance_on_morning_and_afternoon(self):
+ calendar = self.env['resource.calendar'].create({
+ 'name': 'simple morning + afternoon',
+ 'attendance_ids': [(5, 0, 0),
+ (0, 0, {
+ 'name': 'monday morning',
+ 'hour_from': 8,
+ 'hour_to': 12,
+ 'day_period': 'morning',
+ 'dayofweek': '0',
+ }),
+ (0, 0, {
+ 'name': 'monday afternoon',
+ 'hour_from': 13,
+ 'hour_to': 17,
+ 'day_period': 'afternoon',
+ 'dayofweek': '0',
+ })]
+ })
+
+ employee = self.employee_emp
+ employee.resource_calendar_id = calendar
+
+ with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form:
+ leave_form.holiday_status_id = self.leave_type
+ leave_form.request_date_from = date(2019, 9, 2)
+ leave_form.request_date_to = date(2019, 9, 2)
+ leave_form.request_unit_half = True
+ leave_form.request_date_from_period = 'am'
+
+ self.assertEqual(leave_form.number_of_days_display, .5)
+ self.assertEqual(leave_form.number_of_hours_text, '4 Hours')
+
+ leave_form.request_date_from_period = 'pm'
+
+ self.assertEqual(leave_form.number_of_days_display, .5)
+ self.assertEqual(leave_form.number_of_hours_text, '4 Hours')
+
+ def test_multiple_attendance_on_morning(self):
+ calendar = self.env['resource.calendar'].create({
+ 'name': 'multi morning',
+ 'attendance_ids': [(5, 0, 0),
+ (0, 0, {
+ 'name': 'monday morning 1',
+ 'hour_from': 8,
+ 'hour_to': 10,
+ 'day_period': 'morning',
+ 'dayofweek': '0',
+ }),
+ (0, 0, {
+ 'name': 'monday morning 2',
+ 'hour_from': 10.25,
+ 'hour_to': 12.25,
+ 'day_period': 'morning',
+ 'dayofweek': '0',
+ }),
+ (0, 0, {
+ 'name': 'monday afternoon',
+ 'hour_from': 13,
+ 'hour_to': 17,
+ 'day_period': 'afternoon',
+ 'dayofweek': '0',
+ })]
+ })
+ employee = self.employee_emp
+ employee.resource_calendar_id = calendar
+
+ with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form:
+ leave_form.holiday_status_id = self.leave_type
+ leave_form.request_date_from = date(2019, 9, 2)
+ leave_form.request_date_to = date(2019, 9, 2)
+ leave_form.request_unit_half = True
+ leave_form.request_date_from_period = 'am'
+
+ self.assertEqual(leave_form.number_of_days_display, .5)
+ self.assertEqual(leave_form.number_of_hours_text, '4 Hours')
+
+ leave_form.request_date_from_period = 'pm'
+
+ self.assertEqual(leave_form.number_of_days_display, .5)
+ self.assertEqual(leave_form.number_of_hours_text, '4 Hours')
+
+ def test_attendance_on_morning(self):
+ calendar = self.env['resource.calendar'].create({
+ 'name': 'Morning only',
+ 'attendance_ids': [(5, 0, 0),
+ (0, 0, {
+ 'name': 'Monday All day',
+ 'hour_from': 8,
+ 'hour_to': 16,
+ 'day_period': 'morning',
+ 'dayofweek': '0',
+ })],
+ })
+ employee = self.employee_emp
+ employee.resource_calendar_id = calendar
+ with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form:
+ leave_form.holiday_status_id = self.leave_type
+ leave_form.request_date_from = date(2019, 9, 2)
+ leave_form.request_date_to = date(2019, 9, 2)
+ leave_form.request_unit_half = True
+ # Ask for morning
+ leave_form.request_date_from_period = 'am'
+
+ self.assertEqual(leave_form.number_of_days_display, 0.5)
+ self.assertEqual(leave_form.number_of_hours_text, '8 Hours')
+
+ # Ask for afternoon
+ leave_form.request_date_from_period = 'pm'
+
+ self.assertEqual(leave_form.number_of_days_display, 0.5)
+ self.assertEqual(leave_form.number_of_hours_text, '8 Hours')
+
+ def test_attendance_next_day(self):
+ self.env.user.tz = 'Europe/Brussels'
+ calendar = self.env['resource.calendar'].create({
+ 'name': 'auto next day',
+ 'attendance_ids': [(5, 0, 0),
+ (0, 0, {
+ 'name': 'tuesday morning',
+ 'hour_from': 8,
+ 'hour_to': 12,
+ 'day_period': 'morning',
+ 'dayofweek': '1',
+ })]
+ })
+ employee = self.employee_emp
+ employee.resource_calendar_id = calendar
+
+ with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form:
+ leave_form.holiday_status_id = self.leave_type
+ # does not work on mondays
+ leave_form.request_date_from = date(2019, 9, 2)
+ leave_form.request_date_to = date(2019, 9, 2)
+ leave_form.request_unit_half = True
+ leave_form.request_date_from_period = 'am'
+
+
+ self.assertEqual(leave_form.number_of_days_display, 0)
+ self.assertEqual(leave_form.number_of_hours_text, '0 Hours')
+ self.assertEqual(leave_form.date_from, datetime(2019, 9, 2, 6, 0, 0))
+ self.assertEqual(leave_form.date_to, datetime(2019, 9, 2, 10, 0, 0))
+
+ def test_attendance_previous_day(self):
+ self.env.user.tz = 'Europe/Brussels'
+ calendar = self.env['resource.calendar'].create({
+ 'name': 'auto next day',
+ 'attendance_ids': [(5, 0, 0),
+ (0, 0, {
+ 'name': 'monday morning',
+ 'hour_from': 8,
+ 'hour_to': 12,
+ 'day_period': 'morning',
+ 'dayofweek': '0',
+ })]
+ })
+ employee = self.employee_emp
+ employee.resource_calendar_id = calendar
+
+ with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form:
+ leave_form.holiday_status_id = self.leave_type
+ # does not work on tuesdays
+ leave_form.request_date_from = date(2019, 9, 3)
+ leave_form.request_date_to = date(2019, 9, 3)
+ leave_form.request_unit_half = True
+ leave_form.request_date_from_period = 'am'
+
+
+ self.assertEqual(leave_form.number_of_days_display, 0)
+ self.assertEqual(leave_form.number_of_hours_text, '0 Hours')
+ self.assertEqual(leave_form.date_from, datetime(2019, 9, 3, 6, 0, 0))
+ self.assertEqual(leave_form.date_to, datetime(2019, 9, 3, 10, 0, 0))
+
+ def test_2weeks_calendar(self):
+ self.env.user.tz = 'Europe/Brussels'
+ calendar = self.env['resource.calendar'].create({
+ 'name': 'auto next day',
+ 'two_weeks_calendar': True,
+ 'attendance_ids': [(5, 0, 0),
+ (0, 0, {
+ 'name': 'monday morning odd week',
+ 'hour_from': 8,
+ 'hour_to': 12,
+ 'day_period': 'morning',
+ 'dayofweek': '0',
+ 'week_type': '0',
+ }),
+ (0, 0, {
+ 'name': 'monday morning even week',
+ 'hour_from': 10,
+ 'hour_to': 12,
+ 'day_period': 'morning',
+ 'dayofweek': '0',
+ 'week_type': '1',
+ })]
+ })
+ employee = self.employee_emp
+ employee.resource_calendar_id = calendar
+
+ with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form:
+ leave_form.holiday_status_id = self.leave_type
+ # even week, works 2 hours
+ leave_form.request_date_from = date(2019, 9, 2)
+ leave_form.request_date_to = date(2019, 9, 2)
+ leave_form.request_unit_half = True
+ leave_form.request_date_from_period = 'am'
+
+ self.assertEqual(leave_form.number_of_days_display, 0.5)
+ self.assertEqual(leave_form.number_of_hours_text, '2 Hours')
+ self.assertEqual(leave_form.date_from, datetime(2019, 9, 2, 8, 0, 0))
+ self.assertEqual(leave_form.date_to, datetime(2019, 9, 2, 10, 0, 0))
+
+ with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form:
+ leave_form.holiday_status_id = self.leave_type
+ # odd week, works 4 hours
+ leave_form.request_date_from = date(2019, 9, 9)
+ leave_form.request_date_to = date(2019, 9, 9)
+ leave_form.request_unit_half = True
+ leave_form.request_date_from_period = 'am'
+
+ self.assertEqual(leave_form.number_of_days_display, 0.5)
+ self.assertEqual(leave_form.number_of_hours_text, '4 Hours')
+ self.assertEqual(leave_form.date_from, datetime(2019, 9, 9, 6, 0, 0))
+ self.assertEqual(leave_form.date_to, datetime(2019, 9, 9, 10, 0, 0))
+
+ def test_2weeks_calendar_next_week(self):
+ self.env.user.tz = 'Europe/Brussels'
+ calendar = self.env['resource.calendar'].create({
+ 'name': 'auto next day',
+ 'two_weeks_calendar': True,
+ 'attendance_ids': [(5, 0, 0),
+ (0, 0, {
+ 'name': 'monday morning odd week',
+ 'hour_from': 8,
+ 'hour_to': 12,
+ 'day_period': 'morning',
+ 'dayofweek': '0',
+ 'week_type': '0',
+ })]
+ })
+ employee = self.employee_emp
+ employee.resource_calendar_id = calendar
+
+ with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form:
+ leave_form.holiday_status_id = self.leave_type
+ # even week, does not work
+ leave_form.request_date_from = date(2019, 9, 2)
+ leave_form.request_date_to = date(2019, 9, 2)
+ leave_form.request_unit_half = True
+ leave_form.request_date_from_period = 'am'
+
+ self.assertEqual(leave_form.number_of_days_display, 0)
+ self.assertEqual(leave_form.number_of_hours_text, '0 Hours')
+ self.assertEqual(leave_form.date_from, datetime(2019, 9, 2, 6, 0, 0))
+ self.assertEqual(leave_form.date_to, datetime(2019, 9, 2, 10, 0, 0))
diff --git a/addons/hr_holidays/tests/test_change_department.py b/addons/hr_holidays/tests/test_change_department.py
new file mode 100644
index 00000000..83ebb158
--- /dev/null
+++ b/addons/hr_holidays/tests/test_change_department.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
+from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
+
+
+class TestChangeDepartment(TestHrHolidaysCommon):
+ def test_employee_change_department_request_change_department(self):
+ self.HolidaysEmployeeGroup = self.env['hr.leave'].with_user(self.user_employee_id)
+
+ HolidayStatusManagerGroup = self.env['hr.leave.type'].with_user(self.user_hrmanager_id)
+ self.holidays_status_1 = HolidayStatusManagerGroup.create({
+ 'name': 'NotLimitedHR',
+ 'allocation_type': 'no',
+ 'validity_start': False,
+ })
+
+ def create_holiday(name, start, end):
+ return self.HolidaysEmployeeGroup.create({
+ 'name': name,
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_status_1.id,
+ 'date_from': (datetime.today() + relativedelta(days=start)).strftime('%Y-%m-%d %H:%M'),
+ 'date_to': datetime.today() + relativedelta(days=end),
+ 'number_of_days': end-start,
+ })
+
+ # Non approved leave request change department
+ self.employee_emp.department_id = self.rd_dept
+ hol1_employee_group = create_holiday("hol1", 1, 2)
+ self.employee_emp.department_id = self.hr_dept
+ self.assertEqual(hol1_employee_group.department_id, self.hr_dept, 'hr_holidays: non approved leave request should change department if employee change department')
+
+ # Approved passed leave request change department
+ self.employee_emp.department_id = self.hr_dept
+ hol2_employee_group = create_holiday("hol2", -4, -3)
+ hol2_user_group = hol2_employee_group.with_user(self.user_hruser_id)
+ hol2_user_group.action_approve()
+ self.employee_emp.department_id = self.rd_dept
+ self.assertEqual(hol2_employee_group.department_id, self.hr_dept, 'hr_holidays: approved passed leave request should stay in previous department if employee change department')
+
+ # Approved futur leave request change department
+ self.employee_emp.department_id = self.hr_dept
+ hol22_employee_group = create_holiday("hol22", 3, 4)
+ hol22_user_group = hol22_employee_group.with_user(self.user_hruser_id)
+ hol22_user_group.action_approve()
+ self.employee_emp.department_id = self.rd_dept
+ self.assertEqual(hol22_employee_group.department_id, self.rd_dept, 'hr_holidays: approved futur leave request should change department if employee change department')
+
+ # Refused passed leave request change department
+ self.employee_emp.department_id = self.rd_dept
+ hol3_employee_group = create_holiday("hol3", -6, -5)
+ hol3_user_group = hol3_employee_group.with_user(self.user_hruser_id)
+ hol3_user_group.action_refuse()
+ self.employee_emp.department_id = self.hr_dept # Change department
+ self.assertEqual(hol3_employee_group.department_id, self.rd_dept, 'hr_holidays: refused passed leave request should stay in previous department if employee change department')
+
+ # Refused futur leave request change department
+ self.employee_emp.department_id = self.rd_dept
+ hol32_employee_group = create_holiday("hol32", 5, 6)
+ hol32_user_group = hol32_employee_group.with_user(self.user_hruser_id)
+ hol32_user_group.action_refuse()
+ self.employee_emp.department_id = self.hr_dept # Change department
+ self.assertEqual(hol32_employee_group.department_id, self.hr_dept, 'hr_holidays: refused futur leave request should change department if employee change department')
diff --git a/addons/hr_holidays/tests/test_company_leave.py b/addons/hr_holidays/tests/test_company_leave.py
new file mode 100644
index 00000000..941a5595
--- /dev/null
+++ b/addons/hr_holidays/tests/test_company_leave.py
@@ -0,0 +1,323 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import date, datetime
+
+from odoo.tests import tagged
+from odoo.tests.common import SavepointCase
+
+
+@tagged('company_leave')
+class TestCompanyLeave(SavepointCase):
+ """ Test leaves for a whole company, conflict resolutions """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestCompanyLeave, cls).setUpClass()
+ cls.company = cls.env['res.company'].create({'name': 'A company'})
+
+ cls.bank_holiday = cls.env['hr.leave.type'].create({
+ 'name': 'Bank Holiday',
+ 'responsible_id': cls.env.user.id,
+ 'company_id': cls.company.id,
+ })
+
+ cls.paid_time_off = cls.env['hr.leave.type'].create({
+ 'name': 'Paid Time Off',
+ 'request_unit': 'day',
+ 'leave_validation_type': 'both',
+ 'company_id': cls.company.id,
+ })
+
+ cls.employee = cls.env['hr.employee'].create({
+ 'name': 'My Employee',
+ 'company_id': cls.company.id,
+ })
+
+ def test_leave_whole_company_01(self):
+ # TEST CASE 1: Leaves taken in days. Take a 3 days leave
+ # Add a company leave on the second day.
+ # Check that leave is split into 2.
+
+ leave = self.env['hr.leave'].create({
+ 'name': 'Hol11',
+ 'employee_id': self.employee.id,
+ 'holiday_status_id': self.paid_time_off.id,
+ 'request_date_from': date(2020, 1, 7),
+ 'date_from': date(2020, 1, 7),
+ 'request_date_to': date(2020, 1, 9),
+ 'date_to': date(2020, 1, 9),
+ 'number_of_days': 3,
+ })
+ leave._compute_date_from_to()
+
+ company_leave = self.env['hr.leave'].create({
+ 'name': 'Bank Holiday',
+ 'holiday_type': 'company',
+ 'mode_company_id': self.company.id,
+ 'holiday_status_id': self.bank_holiday.id,
+ 'date_from': date(2020, 1, 8),
+ 'request_date_from': date(2020, 1, 8),
+ 'date_to': date(2020, 1, 8),
+ 'request_date_to': date(2020, 1, 8),
+ 'number_of_days': 1,
+ })
+ company_leave._compute_date_from_to()
+
+ company_leave.action_validate()
+
+ all_leaves = self.env['hr.leave'].search([('employee_id', '=', self.employee.id)], order='id')
+ self.assertEqual(len(all_leaves), 4)
+ # Original Leave
+ self.assertEqual(leave.state, 'refuse')
+ # before leave
+ self.assertEqual(all_leaves[1].date_from, datetime(2020, 1, 7, 7, 0))
+ self.assertEqual(all_leaves[1].date_to, datetime(2020, 1, 7, 16, 0))
+ self.assertEqual(all_leaves[1].number_of_days, 1)
+ self.assertEqual(all_leaves[1].state, 'confirm')
+ # After leave
+ self.assertEqual(all_leaves[2].date_from, datetime(2020, 1, 9, 7, 0))
+ self.assertEqual(all_leaves[2].date_to, datetime(2020, 1, 9, 16, 0))
+ self.assertEqual(all_leaves[2].number_of_days, 1)
+ self.assertEqual(all_leaves[2].state, 'confirm')
+ # Company Leave
+ self.assertEqual(all_leaves[3].date_from, datetime(2020, 1, 8, 7, 0))
+ self.assertEqual(all_leaves[3].date_to, datetime(2020, 1, 8, 16, 0))
+ self.assertEqual(all_leaves[3].number_of_days, 1)
+ self.assertEqual(all_leaves[3].state, 'validate')
+
+
+ def test_leave_whole_company_02(self):
+ # TEST CASE 2: Leaves taken in half-days. Take a 3 days leave
+ # Add a company leave on the second day
+ # Check that leave is split into 2
+ self.paid_time_off.request_unit = 'half_day'
+
+ leave = self.env['hr.leave'].create({
+ 'name': 'Hol11',
+ 'employee_id': self.employee.id,
+ 'holiday_status_id': self.paid_time_off.id,
+ 'request_date_from': date(2020, 1, 7),
+ 'date_from': date(2020, 1, 7),
+ 'request_date_to': date(2020, 1, 9),
+ 'date_to': date(2020, 1, 9),
+ 'number_of_days': 3,
+ })
+ leave._compute_date_from_to()
+
+ company_leave = self.env['hr.leave'].create({
+ 'name': 'Bank Holiday',
+ 'holiday_type': 'company',
+ 'mode_company_id': self.company.id,
+ 'holiday_status_id': self.bank_holiday.id,
+ 'date_from': date(2020, 1, 8),
+ 'request_date_from': date(2020, 1, 8),
+ 'date_to': date(2020, 1, 8),
+ 'request_date_to': date(2020, 1, 8),
+ 'number_of_days': 1,
+ })
+ company_leave._compute_date_from_to()
+
+ company_leave.action_validate()
+
+ all_leaves = self.env['hr.leave'].search([('employee_id', '=', self.employee.id)], order='id')
+ self.assertEqual(len(all_leaves), 4)
+ # Original Leave
+ self.assertEqual(leave.state, 'refuse')
+ # before leave
+ self.assertEqual(all_leaves[1].date_from, datetime(2020, 1, 7, 7, 0))
+ self.assertEqual(all_leaves[1].date_to, datetime(2020, 1, 7, 16, 0))
+ self.assertEqual(all_leaves[1].number_of_days, 1)
+ self.assertEqual(all_leaves[1].state, 'confirm')
+ # After leave
+ self.assertEqual(all_leaves[2].date_from, datetime(2020, 1, 9, 7, 0))
+ self.assertEqual(all_leaves[2].date_to, datetime(2020, 1, 9, 16, 0))
+ self.assertEqual(all_leaves[2].number_of_days, 1)
+ self.assertEqual(all_leaves[2].state, 'confirm')
+ # Company Leave
+ self.assertEqual(all_leaves[3].date_from, datetime(2020, 1, 8, 7, 0))
+ self.assertEqual(all_leaves[3].date_to, datetime(2020, 1, 8, 16, 0))
+ self.assertEqual(all_leaves[3].number_of_days, 1)
+ self.assertEqual(all_leaves[3].state, 'validate')
+
+ def test_leave_whole_company_03(self):
+ # TEST CASE 3: Leaves taken in half-days. Take a 0.5 days leave
+ # Add a company leave on the same day
+ # Check that leave refused
+ self.paid_time_off.request_unit = 'half_day'
+
+ leave = self.env['hr.leave'].create({
+ 'name': 'Hol11',
+ 'employee_id': self.employee.id,
+ 'holiday_status_id': self.paid_time_off.id,
+ 'request_date_from': date(2020, 1, 7),
+ 'request_date_to': date(2020, 1, 7),
+ 'number_of_days': 0.5,
+ 'request_unit_half': True,
+ 'request_date_from_period': 'am',
+
+ })
+ leave._compute_date_from_to()
+
+ company_leave = self.env['hr.leave'].create({
+ 'name': 'Bank Holiday',
+ 'holiday_type': 'company',
+ 'mode_company_id': self.company.id,
+ 'holiday_status_id': self.bank_holiday.id,
+ 'date_from': date(2020, 1, 7),
+ 'request_date_from': date(2020, 1, 7),
+ 'date_to': date(2020, 1, 7),
+ 'request_date_to': date(2020, 1, 7),
+ 'number_of_days': 1,
+ })
+ company_leave._compute_date_from_to()
+
+ company_leave.action_validate()
+
+ all_leaves = self.env['hr.leave'].search([('employee_id', '=', self.employee.id)], order='id')
+ self.assertEqual(len(all_leaves), 2)
+ # Original Leave
+ self.assertEqual(leave.state, 'refuse')
+ # Company Leave
+ self.assertEqual(all_leaves[1].date_from, datetime(2020, 1, 7, 7, 0))
+ self.assertEqual(all_leaves[1].date_to, datetime(2020, 1, 7, 16, 0))
+ self.assertEqual(all_leaves[1].number_of_days, 1)
+ self.assertEqual(all_leaves[1].state, 'validate')
+
+ def test_leave_whole_company_04(self):
+ # TEST CASE 4: Leaves taken in days. Take a 1 days leave
+ # Add a company leave on the same day
+ # Check that leave is refused
+ self.paid_time_off.request_unit = 'day'
+
+ leave = self.env['hr.leave'].create({
+ 'name': 'Hol11',
+ 'employee_id': self.employee.id,
+ 'holiday_status_id': self.paid_time_off.id,
+ 'request_date_from': date(2020, 1, 9),
+ 'request_date_to': date(2020, 1, 9),
+ 'number_of_days': 1,
+
+ })
+ leave._compute_date_from_to()
+
+ company_leave = self.env['hr.leave'].create({
+ 'name': 'Bank Holiday',
+ 'holiday_type': 'company',
+ 'mode_company_id': self.company.id,
+ 'holiday_status_id': self.bank_holiday.id,
+ 'date_from': date(2020, 1, 9),
+ 'request_date_from': date(2020, 1, 9),
+ 'date_to': date(2020, 1, 9),
+ 'request_date_to': date(2020, 1, 9),
+ 'number_of_days': 1,
+ })
+ company_leave._compute_date_from_to()
+
+ company_leave.action_validate()
+
+ all_leaves = self.env['hr.leave'].search([('employee_id', '=', self.employee.id)], order='id')
+ self.assertEqual(len(all_leaves), 2)
+ # Original Leave
+ self.assertEqual(leave.state, 'refuse')
+ # Company Leave
+ self.assertEqual(all_leaves[1].date_from, datetime(2020, 1, 9, 7, 0))
+ self.assertEqual(all_leaves[1].date_to, datetime(2020, 1, 9, 16, 0))
+ self.assertEqual(all_leaves[1].number_of_days, 1)
+ self.assertEqual(all_leaves[1].state, 'validate')
+
+ def test_leave_whole_company_06(self):
+ # Test case 6: Leaves taken in days. But the employee
+ # only works on Monday, Wednesday and Friday
+ # Takes a time off for all the week (3 days), should be split
+
+ self.employee.resource_calendar_id.write({'attendance_ids': [
+ (5, 0, 0),
+ (0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Monday Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
+ (0, 0, {'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
+ (0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'})
+ ]})
+
+ leave = self.env['hr.leave'].create({
+ 'name': 'Hol11',
+ 'employee_id': self.employee.id,
+ 'holiday_status_id': self.paid_time_off.id,
+ 'request_date_from': date(2020, 1, 6),
+ 'request_date_to': date(2020, 1, 10),
+ 'number_of_days': 3,
+ })
+ leave._compute_date_from_to()
+
+ company_leave = self.env['hr.leave'].create({
+ 'name': 'Bank Holiday',
+ 'holiday_type': 'company',
+ 'mode_company_id': self.company.id,
+ 'holiday_status_id': self.bank_holiday.id,
+ 'date_from': date(2020, 1, 10),
+ 'request_date_from': date(2020, 1, 10),
+ 'date_to': date(2020, 1, 10),
+ 'request_date_to': date(2020, 1, 10),
+ 'number_of_days': 1,
+ })
+ company_leave._compute_date_from_to()
+ company_leave.action_validate()
+
+ all_leaves = self.env['hr.leave'].search([('employee_id', '=', self.employee.id)], order='id')
+ self.assertEqual(len(all_leaves), 3)
+ # Original Leave
+ self.assertEqual(leave.state, 'refuse')
+ # before leave
+ self.assertEqual(all_leaves[1].date_from, datetime(2020, 1, 6, 7, 0))
+ self.assertEqual(all_leaves[1].date_to, datetime(2020, 1, 9, 16, 0))
+ self.assertEqual(all_leaves[1].number_of_days, 2)
+ self.assertEqual(all_leaves[1].state, 'confirm')
+ # Company Leave
+ self.assertEqual(all_leaves[2].date_from, datetime(2020, 1, 10, 7, 0))
+ self.assertEqual(all_leaves[2].date_to, datetime(2020, 1, 10, 16, 0))
+ self.assertEqual(all_leaves[2].number_of_days, 1)
+ self.assertEqual(all_leaves[2].state, 'validate')
+
+ def test_leave_whole_company_07(self):
+ # Test Case 7: Try to create a bank holidays for a lot of
+ # employees, and check the performances
+ # 100 employees - 15 already on holidays that day
+
+ employees = self.env['hr.employee'].create([{
+ 'name': 'Employee %s' % i,
+ 'company_id': self.company.id
+ } for i in range(100)])
+
+ leaves = self.env['hr.leave'].create([{
+ 'name': 'Holiday - %s' % employee.name,
+ 'employee_id': employee.id,
+ 'holiday_status_id': self.paid_time_off.id,
+ 'request_date_from': date(2020, 3, 29),
+ 'request_date_to': date(2020, 4, 1),
+ 'number_of_days': 3,
+ } for employee in employees[0:15]])
+ leaves._compute_date_from_to()
+
+ company_leave = self.env['hr.leave'].create({
+ 'name': 'Bank Holiday',
+ 'holiday_type': 'company',
+ 'mode_company_id': self.company.id,
+ 'holiday_status_id': self.bank_holiday.id,
+ 'date_from': date(2020, 4, 1),
+ 'request_date_from': date(2020, 4, 1),
+ 'date_to': date(2020, 4, 1),
+ 'request_date_to': date(2020, 4, 1),
+ 'number_of_days': 1,
+ })
+ company_leave._compute_date_from_to()
+
+ count = 732
+ with self.assertQueryCount(__system__=count, admin=count):
+ # Original query count: 1987
+ # Without tracking/activity context keys: 5154
+ company_leave.action_validate()
+
+ leaves = self.env['hr.leave'].search([('holiday_status_id', '=', self.bank_holiday.id)])
+ self.assertEqual(len(leaves), 102)
diff --git a/addons/hr_holidays/tests/test_holidays_flow.py b/addons/hr_holidays/tests/test_holidays_flow.py
new file mode 100644
index 00000000..b63c4486
--- /dev/null
+++ b/addons/hr_holidays/tests/test_holidays_flow.py
@@ -0,0 +1,288 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import time
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+from psycopg2 import IntegrityError
+
+from odoo import fields
+from odoo.exceptions import AccessError, ValidationError, UserError
+from odoo.tools import mute_logger, test_reports
+
+from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
+
+
+class TestHolidaysFlow(TestHrHolidaysCommon):
+
+ @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
+ def test_00_leave_request_flow_unlimited(self):
+ """ Testing leave request flow: unlimited type of leave request """
+ Requests = self.env['hr.leave']
+ HolidaysStatus = self.env['hr.leave.type']
+
+ # HrManager creates some holiday statuses
+ HolidayStatusManagerGroup = HolidaysStatus.with_user(self.user_hrmanager_id)
+ HolidayStatusManagerGroup.create({
+ 'name': 'WithMeetingType',
+ 'allocation_type': 'no',
+ })
+ self.holidays_status_hr = HolidayStatusManagerGroup.create({
+ 'name': 'NotLimitedHR',
+ 'allocation_type': 'no',
+ 'leave_validation_type': 'hr',
+ 'validity_start': False,
+ })
+ self.holidays_status_manager = HolidayStatusManagerGroup.create({
+ 'name': 'NotLimitedManager',
+ 'allocation_type': 'no',
+ 'leave_validation_type': 'manager',
+ 'validity_start': False,
+ })
+
+ HolidaysEmployeeGroup = Requests.with_user(self.user_employee_id)
+
+ # Employee creates a leave request in a no-limit category hr manager only
+ hol1_employee_group = HolidaysEmployeeGroup.create({
+ 'name': 'Hol11',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_status_hr.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': datetime.today(),
+ 'number_of_days': 1,
+ })
+ hol1_user_group = hol1_employee_group.with_user(self.user_hruser_id)
+ hol1_manager_group = hol1_employee_group.with_user(self.user_hrmanager_id)
+ self.assertEqual(hol1_user_group.state, 'confirm', 'hr_holidays: newly created leave request should be in confirm state')
+
+ # HrUser validates the employee leave request -> should work
+ hol1_user_group.action_approve()
+ self.assertEqual(hol1_manager_group.state, 'validate', 'hr_holidays: validated leave request should be in validate state')
+
+ # Employee creates a leave request in a no-limit category department manager only
+ hol12_employee_group = HolidaysEmployeeGroup.create({
+ 'name': 'Hol12',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_status_manager.id,
+ 'date_from': (datetime.today() + relativedelta(days=12)),
+ 'date_to': (datetime.today() + relativedelta(days=13)),
+ 'number_of_days': 1,
+ })
+ hol12_user_group = hol12_employee_group.with_user(self.user_hruser_id)
+ hol12_manager_group = hol12_employee_group.with_user(self.user_hrmanager_id)
+ self.assertEqual(hol12_user_group.state, 'confirm', 'hr_holidays: newly created leave request should be in confirm state')
+
+ # HrManager validate the employee leave request
+ hol12_manager_group.action_approve()
+ self.assertEqual(hol1_user_group.state, 'validate', 'hr_holidays: validates leave request should be in validate state')
+
+
+ @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
+ def test_01_leave_request_flow_limited(self):
+ """ Testing leave request flow: limited type of leave request """
+ Requests = self.env['hr.leave']
+ Allocations = self.env['hr.leave.allocation']
+ HolidaysStatus = self.env['hr.leave.type']
+
+ holiday_status_paid_time_off = self.env['hr.leave.type'].create({
+ 'name': 'Paid Time Off',
+ 'allocation_type': 'fixed',
+ 'leave_validation_type': 'both',
+ 'validity_start': time.strftime('%Y-%m-01'),
+ 'responsible_id': self.env.ref('base.user_admin').id,
+ })
+
+ self.env['hr.leave.allocation'].create([
+ {
+ 'name': 'Paid Time off for David',
+ 'holiday_status_id': holiday_status_paid_time_off.id,
+ 'number_of_days': 20,
+ 'employee_id': self.employee_emp_id,
+ 'state': 'validate',
+ }, {
+ 'name': 'Paid Time off for David',
+ 'holiday_status_id': holiday_status_paid_time_off.id,
+ 'number_of_days': 20,
+ 'employee_id': self.ref('hr.employee_admin'),
+ 'state': 'validate',
+ }
+ ])
+
+ def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
+ self.assertEqual(holiday_status.max_leaves, ml,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.leaves_taken, lt,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.remaining_leaves, rl,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.virtual_remaining_leaves, vrl,
+ 'hr_holidays: wrong type days computation')
+
+ # HrManager creates some holiday statuses
+ HolidayStatusManagerGroup = HolidaysStatus.with_user(self.user_hrmanager_id)
+ HolidayStatusManagerGroup.create({
+ 'name': 'WithMeetingType',
+ 'allocation_type': 'no',
+ 'validity_start': False,
+ })
+
+ self.holidays_status_limited = HolidayStatusManagerGroup.create({
+ 'name': 'Limited',
+ 'allocation_type': 'fixed',
+ 'allocation_validation_type': 'both',
+ 'leave_validation_type': 'both',
+ 'validity_start': False,
+ })
+ HolidaysEmployeeGroup = Requests.with_user(self.user_employee_id)
+
+ # HrUser allocates some leaves to the employee
+ aloc1_user_group = Allocations.with_user(self.user_hruser_id).create({
+ 'name': 'Days for limited category',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_status_limited.id,
+ 'number_of_days': 2,
+ })
+ # HrUser validates the first step
+ aloc1_user_group.action_approve()
+
+ # HrManager validates the second step
+ aloc1_user_group.with_user(self.user_hrmanager_id).action_validate()
+ # Checks Employee has effectively some days left
+ hol_status_2_employee_group = self.holidays_status_limited.with_user(self.user_employee_id)
+ _check_holidays_status(hol_status_2_employee_group, 2.0, 0.0, 2.0, 2.0)
+
+ # Employee creates a leave request in the limited category, now that he has some days left
+ hol2 = HolidaysEmployeeGroup.create({
+ 'name': 'Hol22',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_status_limited.id,
+ 'date_from': (datetime.today() + relativedelta(days=2)).strftime('%Y-%m-%d %H:%M'),
+ 'date_to': (datetime.today() + relativedelta(days=3)),
+ 'number_of_days': 1,
+ })
+ hol2_user_group = hol2.with_user(self.user_hruser_id)
+ # Check left days: - 1 virtual remaining day
+ hol_status_2_employee_group.invalidate_cache()
+ _check_holidays_status(hol_status_2_employee_group, 2.0, 0.0, 2.0, 1.0)
+
+ # HrManager validates the first step
+ hol2_user_group.with_user(self.user_hrmanager_id).action_approve()
+ self.assertEqual(hol2.state, 'validate1',
+ 'hr_holidays: first validation should lead to validate1 state')
+
+ # HrManager validates the second step
+ hol2_user_group.with_user(self.user_hrmanager_id).action_validate()
+ self.assertEqual(hol2.state, 'validate',
+ 'hr_holidays: second validation should lead to validate state')
+ # Check left days: - 1 day taken
+ _check_holidays_status(hol_status_2_employee_group, 2.0, 1.0, 1.0, 1.0)
+
+ # HrManager finds an error: he refuses the leave request
+ hol2.with_user(self.user_hrmanager_id).action_refuse()
+ self.assertEqual(hol2.state, 'refuse',
+ 'hr_holidays: refuse should lead to refuse state')
+ # Check left days: 2 days left again
+
+ hol_status_2_employee_group.invalidate_cache(['max_leaves'])
+ _check_holidays_status(hol_status_2_employee_group, 2.0, 0.0, 2.0, 2.0)
+
+ self.assertEqual(hol2.state, 'refuse',
+ 'hr_holidays: hr_user should not be able to reset a refused leave request')
+
+ # HrManager resets the request
+ hol2_manager_group = hol2.with_user(self.user_hrmanager_id)
+ hol2_manager_group.action_draft()
+ self.assertEqual(hol2.state, 'draft',
+ 'hr_holidays: resetting should lead to draft state')
+
+ employee_id = self.ref('hr.employee_admin')
+ # cl can be of maximum 20 days for employee_admin
+ hol3_status = holiday_status_paid_time_off.with_context(employee_id=employee_id)
+ # I assign the dates in the holiday request for 1 day
+ hol3 = Requests.create({
+ 'name': 'Sick Time Off',
+ 'holiday_status_id': hol3_status.id,
+ 'date_from': datetime.today().strftime('%Y-%m-10 10:00:00'),
+ 'date_to': datetime.today().strftime('%Y-%m-11 19:00:00'),
+ 'employee_id': employee_id,
+ 'number_of_days': 1,
+ })
+ # I find a small mistake on my leave request to I click on "Refuse" button to correct a mistake.
+ hol3.action_refuse()
+ self.assertEqual(hol3.state, 'refuse', 'hr_holidays: refuse should lead to refuse state')
+ # I again set to draft and then confirm.
+ hol3.action_draft()
+ self.assertEqual(hol3.state, 'draft', 'hr_holidays: resetting should lead to draft state')
+ hol3.action_confirm()
+ self.assertEqual(hol3.state, 'confirm', 'hr_holidays: confirming should lead to confirm state')
+ # I validate the holiday request by clicking on "To Approve" button.
+ hol3.action_approve()
+ hol3.action_validate()
+ self.assertEqual(hol3.state, 'validate', 'hr_holidays: validation should lead to validate state')
+ # Check left days for casual leave: 19 days left
+ _check_holidays_status(hol3_status, 20.0, 1.0, 19.0, 19.0)
+
+ def test_10_leave_summary_reports(self):
+ # Print the HR Holidays(Summary Employee) Report through the wizard
+ ctx = {
+ 'model': 'hr.employee',
+ 'active_ids': [self.ref('hr.employee_admin')]
+ }
+ data_dict = {
+ 'date_from': datetime.today().strftime('%Y-%m-01'),
+ 'emp': [(6, 0, [self.ref('hr.employee_admin')])],
+ 'holiday_type': 'Approved'
+ }
+ self.env.company.external_report_layout_id = self.env.ref('web.external_layout_standard').id
+ test_reports.try_report_action(self.env.cr, self.env.uid, 'action_hr_holidays_summary_employee', wiz_data=data_dict, context=ctx, our_module='hr_holidays')
+
+ def test_sql_constraint_dates(self):
+ # The goal is mainly to verify that a human friendly
+ # error message is triggered if the date_from is after
+ # date_to. Coming from a bug due to the new ORM 13.0
+
+ holiday_status_paid_time_off = self.env['hr.leave.type'].create({
+ 'name': 'Paid Time Off',
+ 'allocation_type': 'fixed',
+ 'leave_validation_type': 'both',
+ 'validity_start': time.strftime('%Y-%m-01'),
+ 'responsible_id': self.env.ref('base.user_admin').id,
+ })
+
+ self.env['hr.leave.allocation'].create({
+ 'name': 'Paid Time off for David',
+ 'holiday_status_id': holiday_status_paid_time_off.id,
+ 'number_of_days': 20,
+ 'employee_id': self.ref('hr.employee_admin'),
+ 'state': 'validate',
+ })
+
+ leave_vals = {
+ 'name': 'Sick Time Off',
+ 'holiday_status_id': holiday_status_paid_time_off.id,
+ 'date_from': datetime.today().strftime('%Y-%m-11 19:00:00'),
+ 'date_to': datetime.today().strftime('%Y-%m-10 10:00:00'),
+ 'employee_id': self.ref('hr.employee_admin'),
+ 'number_of_days': 1,
+ }
+ with mute_logger('odoo.sql_db'):
+ with self.assertRaises(IntegrityError):
+ with self.cr.savepoint():
+ self.env['hr.leave'].create(leave_vals)
+
+ leave_vals = {
+ 'name': 'Sick Time Off',
+ 'holiday_status_id': holiday_status_paid_time_off.id,
+ 'date_from': datetime.today().strftime('%Y-%m-10 10:00:00'),
+ 'date_to': datetime.today().strftime('%Y-%m-11 19:00:00'),
+ 'employee_id': self.ref('hr.employee_admin'),
+ 'number_of_days': 1,
+ }
+ leave = self.env['hr.leave'].create(leave_vals)
+ with mute_logger('odoo.sql_db'):
+ with self.assertRaises(IntegrityError): # No ValidationError
+ with self.cr.savepoint():
+ leave.write({
+ 'date_from': datetime.today().strftime('%Y-%m-11 19:00:00'),
+ 'date_to': datetime.today().strftime('%Y-%m-10 10:00:00'),
+ })
diff --git a/addons/hr_holidays/tests/test_hr_leave_type.py b/addons/hr_holidays/tests/test_hr_leave_type.py
new file mode 100644
index 00000000..852603a2
--- /dev/null
+++ b/addons/hr_holidays/tests/test_hr_leave_type.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
+from odoo.exceptions import AccessError
+
+from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
+
+
+class TestHrLeaveType(TestHrHolidaysCommon):
+
+ def test_time_type(self):
+ leave_type = self.env['hr.leave.type'].create({
+ 'name': 'Paid Time Off',
+ 'time_type': 'leave',
+ 'allocation_type': 'no',
+ 'validity_start': False,
+ })
+
+ leave_1 = self.env['hr.leave'].create({
+ 'name': 'Doctor Appointment',
+ 'employee_id': self.employee_hruser_id,
+ 'holiday_status_id': leave_type.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': datetime.today(),
+ 'number_of_days': 1,
+ })
+ leave_1.action_approve()
+
+ self.assertEqual(
+ self.env['resource.calendar.leaves'].search([('holiday_id', '=', leave_1.id)]).time_type,
+ 'leave'
+ )
+
+ def test_type_creation_right(self):
+ # HrUser creates some holiday statuses -> crash because only HrManagers should do this
+ with self.assertRaises(AccessError):
+ self.env['hr.leave.type'].with_user(self.user_hruser_id).create({
+ 'name': 'UserCheats',
+ 'allocation_type': 'no',
+ })
diff --git a/addons/hr_holidays/tests/test_leave_requests.py b/addons/hr_holidays/tests/test_leave_requests.py
new file mode 100644
index 00000000..c0c4f3ea
--- /dev/null
+++ b/addons/hr_holidays/tests/test_leave_requests.py
@@ -0,0 +1,440 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import datetime, date
+from dateutil.relativedelta import relativedelta
+from pytz import timezone, UTC
+
+from odoo import fields
+from odoo.exceptions import ValidationError
+from odoo.tools import mute_logger
+from odoo.tests.common import Form
+from odoo.tests import tagged
+
+from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
+
+@tagged('leave_requests')
+class TestLeaveRequests(TestHrHolidaysCommon):
+
+ def _check_holidays_status(self, holiday_status, ml, lt, rl, vrl):
+ self.assertEqual(holiday_status.max_leaves, ml,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.leaves_taken, lt,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.remaining_leaves, rl,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.virtual_remaining_leaves, vrl,
+ 'hr_holidays: wrong type days computation')
+
+ def setUp(self):
+ super(TestLeaveRequests, self).setUp()
+
+ # Make sure we have the rights to create, validate and delete the leaves, leave types and allocations
+ LeaveType = self.env['hr.leave.type'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True)
+
+ self.holidays_type_1 = LeaveType.create({
+ 'name': 'NotLimitedHR',
+ 'allocation_type': 'no',
+ 'leave_validation_type': 'hr',
+ 'validity_start': False,
+ })
+ self.holidays_type_2 = LeaveType.create({
+ 'name': 'Limited',
+ 'allocation_type': 'fixed_allocation',
+ 'leave_validation_type': 'hr',
+ 'validity_start': False,
+ })
+ self.holidays_type_3 = LeaveType.create({
+ 'name': 'TimeNotLimited',
+ 'allocation_type': 'no',
+ 'leave_validation_type': 'manager',
+ 'validity_start': fields.Datetime.from_string('2017-01-01 00:00:00'),
+ 'validity_stop': fields.Datetime.from_string('2017-06-01 00:00:00'),
+ })
+
+ self.set_employee_create_date(self.employee_emp_id, '2010-02-03 00:00:00')
+ self.set_employee_create_date(self.employee_hruser_id, '2010-02-03 00:00:00')
+
+ def set_employee_create_date(self, id, newdate):
+ """ This method is a hack in order to be able to define/redefine the create_date
+ of the employees.
+ This is done in SQL because ORM does not allow to write onto the create_date field.
+ """
+ self.env.cr.execute("""
+ UPDATE
+ hr_employee
+ SET create_date = '%s'
+ WHERE id = %s
+ """ % (newdate, id))
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_overlapping_requests(self):
+ """ Employee cannot create a new leave request at the same time, avoid interlapping """
+ self.env['hr.leave'].with_user(self.user_employee_id).create({
+ 'name': 'Hol11',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_1.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': datetime.today(),
+ 'number_of_days': 1,
+ })
+
+ with self.assertRaises(ValidationError):
+ self.env['hr.leave'].with_user(self.user_employee_id).create({
+ 'name': 'Hol21',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_1.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': datetime.today(),
+ 'number_of_days': 1,
+ })
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_limited_type_no_days(self):
+ """ Employee creates a leave request in a limited category but has not enough days left """
+
+ with self.assertRaises(ValidationError):
+ self.env['hr.leave'].with_user(self.user_employee_id).create({
+ 'name': 'Hol22',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_2.id,
+ 'date_from': (datetime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M'),
+ 'date_to': (datetime.today() + relativedelta(days=2)),
+ 'number_of_days': 1,
+ })
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_limited_type_days_left(self):
+ """ Employee creates a leave request in a limited category and has enough days left """
+ aloc1_user_group = self.env['hr.leave.allocation'].with_user(self.user_hruser_id).create({
+ 'name': 'Days for limited category',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_2.id,
+ 'number_of_days': 2,
+ })
+ aloc1_user_group.action_approve()
+
+ holiday_status = self.holidays_type_2.with_user(self.user_employee_id)
+ self._check_holidays_status(holiday_status, 2.0, 0.0, 2.0, 2.0)
+
+ hol = self.env['hr.leave'].with_user(self.user_employee_id).create({
+ 'name': 'Hol11',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_2.id,
+ 'date_from': (datetime.today() - relativedelta(days=2)),
+ 'date_to': datetime.today(),
+ 'number_of_days': 2,
+ })
+
+ holiday_status.invalidate_cache()
+ self._check_holidays_status(holiday_status, 2.0, 0.0, 2.0, 0.0)
+
+ hol.with_user(self.user_hrmanager_id).action_approve()
+
+ holiday_status.invalidate_cache(['max_leaves'])
+ self._check_holidays_status(holiday_status, 2.0, 2.0, 0.0, 0.0)
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_accrual_validity_time_valid(self):
+ """ Employee ask leave during a valid validity time """
+ self.env['hr.leave'].with_user(self.user_employee_id).create({
+ 'name': 'Valid time period',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_3.id,
+ 'date_from': fields.Datetime.from_string('2017-03-03 06:00:00'),
+ 'date_to': fields.Datetime.from_string('2017-03-11 19:00:00'),
+ 'number_of_days': 1,
+ })
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_accrual_validity_time_not_valid(self):
+ """ Employee ask leav during a not valid validity time """
+ with self.assertRaises(ValidationError):
+ self.env['hr.leave'].with_user(self.user_employee_id).create({
+ 'name': 'Sick Time Off',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_3.id,
+ 'date_from': fields.Datetime.from_string('2017-07-03 06:00:00'),
+ 'date_to': fields.Datetime.from_string('2017-07-11 19:00:00'),
+ 'number_of_days': 1,
+ })
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_department_leave(self):
+ """ Create a department leave """
+ self.employee_hrmanager.write({'department_id': self.hr_dept.id})
+ self.assertFalse(self.env['hr.leave'].search([('employee_id', 'in', self.hr_dept.member_ids.ids)]))
+ leave_form = Form(self.env['hr.leave'].with_user(self.user_hrmanager), view='hr_holidays.hr_leave_view_form_manager')
+ leave_form.holiday_type = 'department'
+ leave_form.department_id = self.hr_dept
+ leave_form.holiday_status_id = self.holidays_type_1
+ leave_form.request_date_from = date(2019, 5, 6)
+ leave_form.request_date_to = date(2019, 5, 6)
+ leave = leave_form.save()
+ leave.action_approve()
+ member_ids = self.hr_dept.member_ids.ids
+ self.assertEqual(self.env['hr.leave'].search_count([('employee_id', 'in', member_ids)]), len(member_ids), "Leave should be created for members of department")
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_allocation_request(self):
+ """ Create an allocation request """
+ # employee should be set to current user
+ allocation_form = Form(self.env['hr.leave.allocation'].with_user(self.user_employee))
+ allocation_form.name = 'New Allocation Request'
+ allocation_form.holiday_status_id = self.holidays_type_2
+ allocation = allocation_form.save()
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_employee_is_absent(self):
+ """ Only the concerned employee should be considered absent """
+ self.env['hr.leave'].with_user(self.user_employee_id).create({
+ 'name': 'Hol11',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_1.id,
+ 'date_from': (fields.Datetime.now() - relativedelta(days=1)),
+ 'date_to': fields.Datetime.now() + relativedelta(days=1),
+ 'number_of_days': 2,
+ })
+ (self.employee_emp | self.employee_hrmanager).mapped('is_absent') # compute in batch
+ self.assertTrue(self.employee_emp.is_absent, "He should be considered absent")
+ self.assertFalse(self.employee_hrmanager.is_absent, "He should not be considered absent")
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_timezone_employee_leave_request(self):
+ """ Create a leave request for an employee in another timezone """
+ self.employee_emp.tz = 'NZ' # GMT+12
+ leave = self.env['hr.leave'].new({
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.holidays_type_1.id,
+ 'request_unit_hours': True,
+ 'request_date_from': date(2019, 5, 6),
+ 'request_date_to': date(2019, 5, 6),
+ 'request_hour_from': '8', # 8:00 AM in the employee's timezone
+ 'request_hour_to': '17', # 5:00 PM in the employee's timezone
+ })
+ self.assertEqual(leave.date_from, datetime(2019, 5, 5, 20, 0, 0), "It should have been localized before saving in UTC")
+ self.assertEqual(leave.date_to, datetime(2019, 5, 6, 5, 0, 0), "It should have been localized before saving in UTC")
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_timezone_company_leave_request(self):
+ """ Create a leave request for a company in another timezone """
+ company = self.env['res.company'].create({'name': "Hergé"})
+ company.resource_calendar_id.tz = 'NZ' # GMT+12
+ leave = self.env['hr.leave'].new({
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.holidays_type_1.id,
+ 'request_unit_hours': True,
+ 'holiday_type': 'company',
+ 'mode_company_id': company.id,
+ 'request_date_from': date(2019, 5, 6),
+ 'request_date_to': date(2019, 5, 6),
+ 'request_hour_from': '8', # 8:00 AM in the company's timezone
+ 'request_hour_to': '17', # 5:00 PM in the company's timezone
+ })
+ self.assertEqual(leave.date_from, datetime(2019, 5, 5, 20, 0, 0), "It should have been localized before saving in UTC")
+ self.assertEqual(leave.date_to, datetime(2019, 5, 6, 5, 0, 0), "It should have been localized before saving in UTC")
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_timezone_company_validated(self):
+ """ Create a leave request for a company in another timezone and validate it """
+ self.env.user.tz = 'NZ' # GMT+12
+ company = self.env['res.company'].create({'name': "Hergé"})
+ employee = self.env['hr.employee'].create({'name': "Remi", 'company_id': company.id})
+ leave_form = Form(self.env['hr.leave'], view='hr_holidays.hr_leave_view_form_manager')
+ leave_form.holiday_type = 'company'
+ leave_form.mode_company_id = company
+ leave_form.holiday_status_id = self.holidays_type_1
+ leave_form.request_date_from = date(2019, 5, 6)
+ leave_form.request_date_to = date(2019, 5, 6)
+ leave = leave_form.save()
+ leave.state = 'confirm'
+ leave.action_validate()
+ employee_leave = self.env['hr.leave'].search([('employee_id', '=', employee.id)])
+ self.assertEqual(
+ employee_leave.request_date_from, date(2019, 5, 6),
+ "Timezone should be kept between company and employee leave"
+ )
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_timezone_department_leave_request(self):
+ """ Create a leave request for a department in another timezone """
+ company = self.env['res.company'].create({'name': "Hergé"})
+ company.resource_calendar_id.tz = 'NZ' # GMT+12
+ department = self.env['hr.department'].create({'name': "Museum", 'company_id': company.id})
+ leave = self.env['hr.leave'].new({
+ 'employee_id': self.employee_emp.id,
+ 'holiday_status_id': self.holidays_type_1.id,
+ 'request_unit_hours': True,
+ 'holiday_type': 'department',
+ 'department_id': department.id,
+ 'request_date_from': date(2019, 5, 6),
+ 'request_date_to': date(2019, 5, 6),
+ 'request_hour_from': '8', # 8:00 AM in the department's timezone
+ 'request_hour_to': '17', # 5:00 PM in the department's timezone
+ })
+ self.assertEqual(leave.date_from, datetime(2019, 5, 5, 20, 0, 0), "It should have been localized before saving in UTC")
+ self.assertEqual(leave.date_to, datetime(2019, 5, 6, 5, 0, 0), "It should have been localized before saving in UTC")
+
+ def test_number_of_hours_display(self):
+ # Test that the field number_of_hours_dispay doesn't change
+ # after time off validation, as it takes the attendances
+ # minus the resource leaves to compute that field.
+ calendar = self.env['resource.calendar'].create({
+ 'name': 'Monday Morning Else Full Time 38h/week',
+ 'hours_per_day': 7.6,
+ 'attendance_ids': [
+ (0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8.5, 'hour_to': 12.5, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8.5, 'hour_to': 12.5, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17.5, 'day_period': 'afternoon'}),
+ (0, 0, {'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8.5, 'hour_to': 12.5, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17.5, 'day_period': 'afternoon'}),
+ (0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8.5, 'hour_to': 12.5, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17.5, 'day_period': 'afternoon'}),
+ (0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8.5, 'hour_to': 12.5, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17.5, 'day_period': 'afternoon'})
+ ],
+ })
+ employee = self.employee_emp
+ employee.resource_calendar_id = calendar
+ self.env.user.company_id.resource_calendar_id = calendar
+ leave_type = self.env['hr.leave.type'].create({
+ 'name': 'Paid Time Off',
+ 'request_unit': 'hour',
+ 'leave_validation_type': 'both',
+ })
+ allocation = self.env['hr.leave.allocation'].create({
+ 'name': '20 days allocation',
+ 'holiday_status_id': leave_type.id,
+ 'number_of_days': 20,
+ 'employee_id': employee.id,
+ })
+ allocation.action_approve()
+
+ leave1 = self.env['hr.leave'].create({
+ 'name': 'Holiday 1 week',
+ 'employee_id': employee.id,
+ 'holiday_status_id': leave_type.id,
+ 'date_from': fields.Datetime.from_string('2019-12-23 06:00:00'),
+ 'date_to': fields.Datetime.from_string('2019-12-27 20:00:00'),
+ 'number_of_days': 5,
+ })
+
+ self.assertEqual(leave1.number_of_hours_display, 38)
+ leave1.action_approve()
+ self.assertEqual(leave1.number_of_hours_display, 38)
+ leave1.action_validate()
+ self.assertEqual(leave1.number_of_hours_display, 38)
+
+ leave2 = self.env['hr.leave'].create({
+ 'name': 'Holiday 1 Day',
+ 'employee_id': employee.id,
+ 'holiday_status_id': leave_type.id,
+ 'date_from': fields.Datetime.from_string('2019-12-30 06:00:00'),
+ 'date_to': fields.Datetime.from_string('2019-12-30 13:00:00'),
+ 'number_of_days': 1,
+ })
+
+ self.assertEqual(leave2.number_of_hours_display, 4)
+ leave2.action_approve()
+ self.assertEqual(leave2.number_of_hours_display, 4)
+ leave2.action_validate()
+ self.assertEqual(leave2.number_of_hours_display, 4)
+
+ def test_number_of_hours_display_global_leave(self):
+ # Check that the field number_of_hours_display
+ # takes the global leaves into account, even
+ # after validation
+ calendar = self.env['resource.calendar'].create({
+ 'name': 'Classic 40h/week',
+ 'hours_per_day': 8.0,
+ 'attendance_ids': [
+ (0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Monday Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
+ (0, 0, {'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
+ (0, 0, {'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
+ (0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
+ (0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
+ (0, 0, {'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'})
+ ],
+ 'global_leave_ids': [(0, 0, {
+ 'name': 'Christmas Leave',
+ 'date_from': fields.Datetime.from_string('2019-12-25 00:00:00'),
+ 'date_to': fields.Datetime.from_string('2019-12-26 23:59:59'),
+ 'resource_id': False,
+ 'time_type': 'leave',
+ })]
+ })
+ employee = self.employee_emp
+ employee.resource_calendar_id = calendar
+ self.env.user.company_id.resource_calendar_id = calendar
+ leave_type = self.env['hr.leave.type'].create({
+ 'name': 'Sick',
+ 'request_unit': 'hour',
+ 'leave_validation_type': 'both',
+ 'allocation_type': 'no',
+ })
+ leave1 = self.env['hr.leave'].create({
+ 'name': 'Sick 1 week during christmas snif',
+ 'employee_id': employee.id,
+ 'holiday_status_id': leave_type.id,
+ 'date_from': fields.Datetime.from_string('2019-12-23 06:00:00'),
+ 'date_to': fields.Datetime.from_string('2019-12-27 20:00:00'),
+ 'number_of_days': 5,
+ })
+ self.assertEqual(leave1.number_of_hours_display, 24)
+ leave1.action_approve()
+ self.assertEqual(leave1.number_of_hours_display, 24)
+ leave1.action_validate()
+ self.assertEqual(leave1.number_of_hours_display, 24)
+
+ def _test_leave_with_tz(self, tz, local_date_from, local_date_to, number_of_days):
+ self.user_employee.tz = tz
+ tz = timezone(tz)
+
+ # Mimic what is done by the calendar widget when clicking on a day. It
+ # will take the local datetime from 7:00 to 19:00 and then convert it
+ # to UTC before sending it. Values here are for PST (UTC -8) and
+ # represent a leave on 2019/1/1 from 7:00 to 19:00 local time.
+ values = {
+ 'date_from': tz.localize(local_date_from).astimezone(UTC).replace(tzinfo=None),
+ 'date_to': tz.localize(local_date_to).astimezone(UTC).replace(tzinfo=None), # note that this can be the next day in UTC
+ }
+ values.update(self.env['hr.leave'].with_user(self.user_employee_id)._default_get_request_parameters(values))
+
+ # Dates should be local to the user
+ self.assertEqual(values['request_date_from'], local_date_from.date())
+ self.assertEqual(values['request_date_to'], local_date_to.date())
+
+ values.update({
+ 'name': 'Test',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_1.id,
+ })
+ leave = self.env['hr.leave'].with_user(self.user_employee_id).new(values)
+ self.assertEqual(leave.number_of_days, number_of_days)
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_defaults_with_timezones(self):
+ """ Make sure that leaves start with correct defaults for non-UTC timezones """
+ timezones_to_test = ('UTC', 'Pacific/Midway', 'US/Pacific', 'Asia/Taipei', 'Pacific/Kiritimati') # UTC, UTC -11, UTC -8, UTC +8, UTC +14
+
+ # January 2020
+ # Su Mo Tu We Th Fr Sa
+ # 1 2 3 4
+ # 5 6 7 8 9 10 11
+ # 12 13 14 15 16 17 18
+ # 19 20 21 22 23 24 25
+ # 26 27 28 29 30 31
+ local_date_from = datetime(2019, 1, 1, 7, 0, 0)
+ local_date_to = datetime(2019, 1, 1, 19, 0, 0)
+ for tz in timezones_to_test:
+ self._test_leave_with_tz(tz, local_date_from, local_date_to, 1)
+
+ # We, Th, Fr, Mo, Tu, We => 6 days
+ local_date_from = datetime(2019, 1, 1, 7, 0, 0)
+ local_date_to = datetime(2019, 1, 8, 19, 0, 0)
+ for tz in timezones_to_test:
+ self._test_leave_with_tz(tz, local_date_from, local_date_to, 6)
diff --git a/addons/hr_holidays/tests/test_out_of_office.py b/addons/hr_holidays/tests/test_out_of_office.py
new file mode 100644
index 00000000..ad316c7a
--- /dev/null
+++ b/addons/hr_holidays/tests/test_out_of_office.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
+from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
+from odoo.tests.common import tagged, users, warmup
+from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon
+
+
+@tagged('out_of_office')
+class TestOutOfOffice(TestHrHolidaysCommon):
+
+ def setUp(self):
+ super().setUp()
+ self.leave_type = self.env['hr.leave.type'].create({
+ 'name': 'Legal Leaves',
+ 'time_type': 'leave',
+ 'allocation_type': 'no',
+ 'validity_start': False,
+ })
+
+ def test_leave_ooo(self):
+ self.assertNotEqual(self.employee_hruser.user_id.im_status, 'leave_offline', 'user should not be on leave')
+ self.assertNotEqual(self.employee_hruser.user_id.partner_id.im_status, 'leave_offline', 'user should not be on leave')
+ leave_date_end = (datetime.today() + relativedelta(days=3))
+ leave = self.env['hr.leave'].create({
+ 'name': 'Christmas',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': self.leave_type.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': leave_date_end,
+ 'number_of_days': 4,
+ })
+ leave.action_approve()
+ self.assertEqual(self.employee_hruser.user_id.im_status, 'leave_offline', 'user should be out (leave_offline)')
+ self.assertEqual(self.employee_hruser.user_id.partner_id.im_status, 'leave_offline', 'user should be out (leave_offline)')
+
+ partner = self.employee_hruser.user_id.partner_id
+ partner2 = self.user_employee.partner_id
+
+ channel = self.env['mail.channel'].with_user(self.user_employee).with_context({
+ 'mail_create_nolog': True,
+ 'mail_create_nosubscribe': True,
+ 'mail_channel_noautofollow': True,
+ }).create({
+ 'channel_partner_ids': [(4, partner.id), (4, partner2.id)],
+ 'public': 'private',
+ 'channel_type': 'chat',
+ 'email_send': False,
+ 'name': 'test'
+ })
+ channel_info = channel.channel_info()[0]
+ self.assertFalse(channel_info['members'][0]['out_of_office_date_end'], "current user should not be out of office")
+ self.assertEqual(channel_info['members'][1]['out_of_office_date_end'], leave_date_end, "correspondent should be out of office")
+
+
+@tagged('out_of_office')
+class TestOutOfOfficePerformance(TestHrHolidaysCommon, TransactionCaseWithUserDemo):
+
+ def setUp(self):
+ super(TestOutOfOfficePerformance, self).setUp()
+ self.leave_type = self.env['hr.leave.type'].create({
+ 'name': 'Legal Leaves',
+ 'time_type': 'leave',
+ 'allocation_type': 'no',
+ 'validity_start': False,
+ })
+ self.leave_date_end = (datetime.today() + relativedelta(days=3))
+ self.leave = self.env['hr.leave'].create({
+ 'name': 'Christmas',
+ 'employee_id': self.employee_hruser_id,
+ 'holiday_status_id': self.leave_type.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': (datetime.today() + relativedelta(days=3)),
+ 'number_of_days': 4,
+ })
+
+ self.hr_user = self.employee_hruser.user_id
+ self.hr_partner = self.employee_hruser.user_id.partner_id
+ self.employer_partner = self.user_employee.partner_id
+
+ @users('__system__', 'demo')
+ @warmup
+ def test_leave_im_status_performance_partner_offline(self):
+ with self.assertQueryCount(__system__=2, demo=2):
+ self.assertEqual(self.employer_partner.im_status, 'offline')
+
+ @users('__system__', 'demo')
+ @warmup
+ def test_leave_im_status_performance_user_leave_offline(self):
+ with self.assertQueryCount(__system__=2, demo=2):
+ self.assertEqual(self.hr_user.im_status, 'leave_offline')
+
+ @users('__system__', 'demo')
+ @warmup
+ def test_leave_im_status_performance_partner_leave_offline(self):
+ with self.assertQueryCount(__system__=2, demo=2):
+ self.assertEqual(self.hr_partner.im_status, 'leave_offline')