diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/hr_holidays/tests/test_leave_requests.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/hr_holidays/tests/test_leave_requests.py')
| -rw-r--r-- | addons/hr_holidays/tests/test_leave_requests.py | 440 |
1 files changed, 440 insertions, 0 deletions
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) |
