# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.tests.common import TransactionCase, tagged @tagged('post_install_l10n', 'post_install', '-at_install', 'french_leaves') class TestFrenchLeaves(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() country_fr = cls.env.ref('base.fr') cls.company = cls.env['res.company'].create({ 'name': 'French Company', 'country_id': country_fr.id, }) cls.base_calendar = cls.env['resource.calendar'].create({ 'name': 'default calendar', 'company_id': cls.company.id, }) cls.company.resource_calendar_id = cls.base_calendar cls.employee = cls.env['hr.employee'].with_company(cls.company).create({ 'name': 'Camille', 'gender': 'other', 'birthday': '1973-03-29', 'country_id': country_fr.id, 'company_id': cls.company.id, }) cls.time_off_type = cls.env['hr.leave.type'].create({ 'name': 'Time Off', 'requires_allocation': 'no', 'request_unit': 'half_day', }) cls.company.write({ 'l10n_fr_reference_leave_type': cls.time_off_type.id, }) def _set_employee_calendar(self, calendar): """Set employee resource calendar and update hr.employee.calendar planning record if hr_employee_calendar_planning is installed. The planning module overrides resource_calendar_id on hr.leave via _compute_resource_calendar_id, so we must keep the planning record in sync with the test's intended employee calendar. """ self.employee.resource_calendar_id = calendar if 'hr.employee.calendar' in self.env: planning = self.env['hr.employee.calendar'].search( [('employee_id', '=', self.employee.id)], limit=1) if planning: planning.calendar_id = calendar else: self.env['hr.employee.calendar'].create({ 'employee_id': self.employee.id, 'calendar_id': calendar.id, }) def test_no_differences(self): # Base case that should not have a different behaviour self.company.resource_calendar_id = self.base_calendar self._set_employee_calendar(self.base_calendar) leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-06', 'request_date_to': '2021-09-10', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 5, 'The number of days should be equal to 5.') leave.unlink() def test_end_of_week(self): employee_calendar = self.env['resource.calendar'].create({ 'name': 'Employee Calendar', 'company_id': self.company.id, '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'}), ], }) self.company.resource_calendar_id = self.base_calendar self._set_employee_calendar(employee_calendar) leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-06', #monday 'request_date_to': '2021-09-08', #wednesday 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 5, 'The number of days should be equal to 5.') leave.unlink() def test_start_of_week(self): employee_calendar = self.env['resource.calendar'].create({ 'name': 'Employee Calendar', 'company_id': self.company.id, 'attendance_ids': [ (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'}), ], }) self.company.resource_calendar_id = self.base_calendar self._set_employee_calendar(employee_calendar) leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-08', 'request_date_to': '2021-09-10', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 5, 'The number of days should be equal to 5.') leave.unlink() def test_last_day_half(self): employee_calendar = self.env['resource.calendar'].create({ 'name': 'Employee Calendar', 'company_id': self.company.id, 'attendance_ids': [ (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'}), ], }) self.company.resource_calendar_id = self.base_calendar self._set_employee_calendar(employee_calendar) leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-10', #friday 'request_date_to': '2021-09-10', 'request_unit_half': True, 'request_date_from_period': 'am', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() # Since the employee works on the afternoon, the date_to is not post-poned self.assertEqual(leave.number_of_days, 0.5, 'The number of days should be equal to 0.5.') leave.request_date_from_period = 'pm' # This however should push the date_to self.assertEqual(leave.number_of_days, 2.5, 'The number of days should be equal to 2.5.') leave.unlink() def test_full_time_am_day_half(self): employee_calendar = self.env['resource.calendar'].create({ 'name': 'Employee Calendar', 'company_id': self.company.id, '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'}), ], }) self.company.resource_calendar_id = self.base_calendar self._set_employee_calendar(employee_calendar) leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-10', #friday 'request_date_to': '2021-09-10', 'request_unit_half': True, 'request_date_from_period': 'am', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() # Since the employee works doesnt work the afternoon, the date_to is post-poned self.assertEqual(leave.number_of_days, 1, 'The number of days should be equal to 1.') leave.unlink() def test_am_day_half(self): employee_calendar = self.env['resource.calendar'].create({ 'name': 'Employee Calendar', 'company_id': self.company.id, 'attendance_ids': [ (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'}), ], }) self.company.resource_calendar_id = self.base_calendar self._set_employee_calendar(employee_calendar) leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-24', #friday 'request_date_to': '2021-09-24', 'request_unit_half': True, 'request_date_from_period': 'am', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() # Since the employee works doesnt work the afternoon, the date_to is post-poned self.assertEqual(leave.number_of_days, 3, 'The number of days should be equal to 3.') leave.unlink() def test_calendar_with_holes(self): employee_calendar = self.env['resource.calendar'].create({ 'name': 'Employee Calendar', 'company_id': self.company.id, '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': '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'}), ], }) self.company.resource_calendar_id = self.base_calendar self._set_employee_calendar(employee_calendar) leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-06', 'request_date_to': '2021-09-10', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 5, 'The number of days should be equal to 5.') leave.unlink() def test_calendar_end_week_hole(self): employee_calendar = self.env['resource.calendar'].create({ 'name': 'Employee Calendar', 'company_id': self.company.id, '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': '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'}), ], }) self.company.resource_calendar_id = self.base_calendar self._set_employee_calendar(employee_calendar) leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-06', 'request_date_to': '2021-09-08', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 5, 'The number of days should be equal to 5.') leave.unlink() def test_2_weeks_calendar(self): company_calendar = self.env['resource.calendar'].create({ 'name': 'Company Calendar', 'company_id': self.company.id, 'two_weeks_calendar': True, 'attendance_ids': [ (0, 0, {'week_type': '0', 'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'week_type': '0', 'name': 'Monday Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), (0, 0, {'week_type': '0', 'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'week_type': '0', 'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), (0, 0, {'week_type': '0', 'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'week_type': '0', 'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), (0, 0, {'week_type': '0', 'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'week_type': '0', 'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), (0, 0, {'week_type': '0', 'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'week_type': '0', 'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), (0, 0, {'week_type': '1', 'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'week_type': '1', 'name': 'Monday Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), (0, 0, {'week_type': '1', 'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'week_type': '1', 'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), (0, 0, {'week_type': '1', 'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'week_type': '1', 'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), ], }) employee_calendar = self.env['resource.calendar'].create({ 'name': 'Employee Calendar', 'company_id': self.company.id, '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'}), ], }) self.company.resource_calendar_id = company_calendar self._set_employee_calendar(employee_calendar) # Week type 0 leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-06', 'request_date_to': '2021-09-08', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 5, 'The number of days should be equal to 5.') leave.unlink() # Week type 1 leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-13', 'request_date_to': '2021-09-15', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 3, 'The number of days should be equal to 3.') leave.unlink() # Both ending with week type 1 leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-06', 'request_date_to': '2021-09-15', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 8, 'The number of days should be equal to 3.') leave.unlink() leave = self.env['hr.leave'].create({ 'name': 'Test', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-13', 'request_date_to': '2021-09-22', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 8, 'The number of days should be equal to 3.') leave.unlink() def test_part_time_different_hours(self): # Regression test: when the employee's working hours differ from the # company's on the boundary day, the hourly-ratio computation used to # return a fractional number of days (e.g. 12.86 instead of 13). # French legal rule: paid leaves are counted in company working days # (jours ouvrables) as full days. # Company works 6 days/week (Mon-Sat), 9-12 + 13-17 = 7h/day. company_calendar = self.env['resource.calendar'].create({ 'name': 'Company Calendar 6d/week', 'company_id': self.company.id, 'attendance_ids': [ (0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 9, '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': 9, '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': 9, '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': 9, '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': 9, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), (0, 0, {'name': 'Saturday Morning', 'dayofweek': '5', 'hour_from': 9, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'name': 'Saturday Afternoon', 'dayofweek': '5', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), ], }) # Employee works Mon, Tue, Thu (part-time), 8-12 + 13-16 = 7h/day but # with shifted hours vs the company, so the boundary day is fractional # under the old hourly-ratio computation. employee_calendar = self.env['resource.calendar'].create({ 'name': 'Employee Calendar Part Time', 'company_id': self.company.id, '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': 16, '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': 16, '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': 16, 'day_period': 'afternoon'}), ], }) self.company.resource_calendar_id = company_calendar self._set_employee_calendar(employee_calendar) self.employee.tz = 'Europe/Paris' # Leave from 12/01/2026 (Monday) to 26/01/2026 (Monday) inclusive. # Period: 15 calendar days, 2 Sundays -> 13 company working days. # Dates must be in the past: default_get sets date_to to today if # request_date_to is not in defaults, and the SQL constraint # CHECK(date_from <= date_to) fails if date_from > today during # the intermediate flush in _compute_date_from_to. leave = self.env['hr.leave'].create({ 'name': 'Test part-time bug', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2026-01-12', 'request_date_to': '2026-01-26', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 13.0, 'The number of days should be 13 (jours ouvrables), not 12.86.') leave.unlink() def test_part_time_half_day_different_hours(self): # Regression test for French part-time half-day leave counting. # Employee works: Mon, Tue, Wed(AM only), Thu, Fri(AM only). # Company works 6 days/week (Mon-Sat), 9-17h. # French rule: count company working days (jours ouvrables) from # first day of absence to return day (exclusive). company_calendar = self.env['resource.calendar'].create({ 'name': 'Company Calendar 6d/week', 'company_id': self.company.id, 'attendance_ids': [ (0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 9, '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': 9, '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': 9, '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': 9, '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': 9, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), (0, 0, {'name': 'Saturday Morning', 'dayofweek': '5', 'hour_from': 9, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'name': 'Saturday Afternoon', 'dayofweek': '5', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), ], }) employee_calendar = self.env['resource.calendar'].create({ 'name': 'Employee Calendar Part Time', 'company_id': self.company.id, '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': 16, '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': 16, 'day_period': 'afternoon'}), (0, 0, {'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), (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': 16, 'day_period': 'afternoon'}), (0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), ], }) self.company.resource_calendar_id = company_calendar self._set_employee_calendar(employee_calendar) self.employee.tz = 'Europe/Paris' # Case 1: Wednesday AM half-day. # Employee doesn't work Wed PM -> return = Thu. # Company working days from Wed to Thu (exclusive) = Wed = 1 day. leave = self.env['hr.leave'].create({ 'name': 'Test Wed AM', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-08', 'request_date_to': '2021-09-08', 'request_unit_half': True, 'request_date_from_period': 'am', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 1.0, 'Wed AM half-day should count as 1 jour ouvrable.') leave.unlink() # Case 2: Thursday + Friday full days. # Return = Monday. Company working days: Thu, Fri, Sat = 3 days. leave = self.env['hr.leave'].create({ 'name': 'Test Thu+Fri', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-09', 'request_date_to': '2021-09-10', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 3.0, 'Thu+Fri full days should count as 3 jours ouvrables (Thu, Fri, Sat).') leave.unlink() # Case 3: Friday AM half-day. # Employee doesn't work Fri PM -> return = Monday. # Company working days from Fri to Mon (exclusive) = Fri, Sat = 2 days. leave = self.env['hr.leave'].create({ 'name': 'Test Fri AM', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2021-09-10', 'request_date_to': '2021-09-10', 'request_unit_half': True, 'request_date_from_period': 'am', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 2.0, 'Fri AM half-day should count as 2 jours ouvrables (Fri, Sat).') leave.unlink() def test_public_holiday_exclusion(self): # Regression test: public holidays (global leaves on the company # calendar) must be excluded from the jours ouvrables count. # Company works 6 days/week (Mon-Sat). Public holiday on 14/07/2020. # Leave 13/07/2020 (Mon) -> 19/07/2020 (Sun). # Company working days without holiday: 13, 14, 15, 16, 17, 18 = 6 # With 14/07 being a public holiday: 13, 15, 16, 17, 18 = 5 company_calendar = self.env['resource.calendar'].create({ 'name': 'Company Calendar 6d/week', 'company_id': self.company.id, 'tz': 'Europe/Paris', 'attendance_ids': [ (0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 9, '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': 9, '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': 9, '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': 9, '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': 9, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), (0, 0, {'name': 'Saturday Morning', 'dayofweek': '5', 'hour_from': 9, 'hour_to': 12, 'day_period': 'morning'}), (0, 0, {'name': 'Saturday Afternoon', 'dayofweek': '5', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), ], }) employee_calendar = self.env['resource.calendar'].create({ 'name': 'Employee Calendar Part Time', 'company_id': self.company.id, 'tz': 'Europe/Paris', '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': 16, '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': 16, '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': 16, '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': 16, 'day_period': 'afternoon'}), ], }) self.company.resource_calendar_id = company_calendar self._set_employee_calendar(employee_calendar) self.employee.tz = 'Europe/Paris' # Create a public holiday on 14/07/2020 (Tuesday) on the company calendar self.env['resource.calendar.leaves'].create({ 'name': 'Bastille Day', 'calendar_id': company_calendar.id, 'date_from': '2020-07-14 00:00:00', 'date_to': '2020-07-14 23:59:59', }) # Leave from 13/07/2020 (Monday) to 19/07/2020 (Sunday). # Employee works Mon, Tue, Thu, Fri. Leave covers Mon-Sun. # Return = Monday 20/07. date_to extended to Sunday 19/07 (non-working). # Company working days (Mon-Sat) excluding 14/07 holiday: # 13(Mon), 15(Wed), 16(Thu), 17(Fri), 18(Sat) = 5 days leave = self.env['hr.leave'].create({ 'name': 'Test public holiday', 'holiday_status_id': self.time_off_type.id, 'employee_id': self.employee.id, 'request_date_from': '2020-07-13', 'request_date_to': '2020-07-19', 'employee_company_id': self.company.id, 'resource_calendar_id': self.employee.resource_calendar_id.id, }) leave._compute_date_from_to() self.assertEqual(leave.number_of_days, 5.0, 'The number of days should be 5 (14/07 excluded as public holiday).') leave.unlink()