diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 25ed2b52945..437fbbac2c0 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -6,6 +6,8 @@ import logging import math +from collections import namedtuple + from datetime import datetime, time from pytz import timezone, UTC @@ -18,6 +20,9 @@ from odoo.tools.translate import _ _logger = logging.getLogger(__name__) +# Used to agglomerate the attendances in order to find the hour_from and hour_to +# See _onchange_request_parameters +DummyAttendance = namedtuple('DummyAttendance', 'hour_from, hour_to, dayofweek, day_period') class HolidaysRequest(models.Model): """ Leave Requests Access specifications @@ -276,12 +281,17 @@ class HolidaysRequest(models.Model): return domain = [('calendar_id', '=', self.employee_id.resource_calendar_id.id or self.env.user.company_id.resource_calendar_id.id)] - attendances = self.env['resource.calendar.attendance'].search(domain, order='dayofweek, day_period DESC') + attendances = self.env['resource.calendar.attendance'].read_group(domain, ['ids:array_agg(id)', 'hour_from:min(hour_from)', 'hour_to:max(hour_to)', 'dayofweek', 'day_period'], ['dayofweek', 'day_period'], lazy=False) + + # Must be sorted by dayofweek ASC and day_period DESC + attendances = sorted([DummyAttendance(group['hour_from'], group['hour_to'], group['dayofweek'], group['day_period']) for group in attendances], key=lambda att: (att.dayofweek, att.day_period != 'morning')) + + default_value = DummyAttendance(0, 0, 0, 'morning') # find first attendance coming after first_day - attendance_from = next((att for att in attendances if int(att.dayofweek) >= self.request_date_from.weekday()), attendances[0]) + attendance_from = next((att for att in attendances if int(att.dayofweek) >= self.request_date_from.weekday()), attendances[0] if attendances else default_value) # find last attendance coming before last_day - attendance_to = next((att for att in reversed(attendances) if int(att.dayofweek) <= self.request_date_to.weekday()), attendances[-1]) + attendance_to = next((att for att in reversed(attendances) if int(att.dayofweek) <= self.request_date_to.weekday()), attendances[-1] if attendances else default_value) if self.request_unit_half: if self.request_date_from_period == 'am': diff --git a/addons/hr_holidays/tests/__init__.py b/addons/hr_holidays/tests/__init__.py index ba8bdaabb15..e1d3523517c 100644 --- a/addons/hr_holidays/tests/__init__.py +++ b/addons/hr_holidays/tests/__init__.py @@ -2,6 +2,7 @@ # 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_holidays_flow from . import test_hr_leave_type from . import test_accrual_allocations 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 00000000000..5b39259d053 --- /dev/null +++ b/addons/hr_holidays/tests/test_automatic_leave_dates.py @@ -0,0 +1,212 @@ +# -*- coding: utf-8 -*- +from datetime import date, datetime + +from odoo.tests.common import Form, tagged + +from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase + + +@tagged('prout') +class TestAutomaticLeaveDates(TestHrHolidaysBase): + 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']) as leave_form: + leave_form.employee_id = employee + 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_display, 0) + + 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']) as leave_form: + leave_form.employee_id = employee + 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_display, 4) + + leave_form.request_date_from_period = 'pm' + + self.assertEqual(leave_form.number_of_days_display, .5) + self.assertEqual(leave_form.number_of_hours_display, 4) + + 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']) as leave_form: + leave_form.employee_id = employee + 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_display, 4) + + leave_form.request_date_from_period = 'pm' + + self.assertEqual(leave_form.number_of_days_display, .5) + self.assertEqual(leave_form.number_of_hours_display, 4) + + 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']) as leave_form: + leave_form.employee_id = employee + 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, 1) + self.assertEqual(leave_form.number_of_hours_display, 8) + + # Ask for afternoon + leave_form.request_date_from_period = 'pm' + + self.assertEqual(leave_form.number_of_days_display, 1) + self.assertEqual(leave_form.number_of_hours_display, 8) + + def test_attendance_next_day(self): + 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']) as leave_form: + leave_form.employee_id = employee + 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_display, 0) + 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): + 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']) as leave_form: + leave_form.employee_id = employee + leave_form.holiday_status_id = self.leave_type + 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_display, 0) + 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))