diff options
| -rw-r--r-- | addons/hr_holidays/models/hr_leave.py | 34 | ||||
| -rw-r--r-- | addons/hr_holidays/tests/test_leave_requests.py | 56 |
2 files changed, 74 insertions, 16 deletions
diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index fb97d73eb84..a63fd51641e 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -8,7 +8,7 @@ import math from collections import namedtuple -from datetime import datetime, time +from datetime import datetime, date, timedelta, time from dateutil.rrule import rrule, DAILY from pytz import timezone, UTC @@ -356,6 +356,9 @@ class HolidaysRequest(models.Model): # 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] if attendances else default_value) + compensated_request_date_from = self.request_date_from + compensated_request_date_to = self.request_date_to + if self.request_unit_half: if self.request_date_from_period == 'am': hour_from = float_to_time(attendance_from.hour_from) @@ -369,12 +372,15 @@ class HolidaysRequest(models.Model): elif self.request_unit_custom: hour_from = self.date_from.time() hour_to = self.date_to.time() + compensated_request_date_from = self._adjust_date_based_on_tz(self.request_date_from, hour_from) + compensated_request_date_to = self._adjust_date_based_on_tz(self.request_date_to, hour_to) else: hour_from = float_to_time(attendance_from.hour_from) hour_to = float_to_time(attendance_to.hour_to) - self.date_from = timezone(self.tz).localize(datetime.combine(self.request_date_from, hour_from)).astimezone(UTC).replace(tzinfo=None) - self.date_to = timezone(self.tz).localize(datetime.combine(self.request_date_to, hour_to)).astimezone(UTC).replace(tzinfo=None) + self.date_from = timezone(self.tz).localize(datetime.combine(compensated_request_date_from, hour_from)).astimezone(UTC).replace(tzinfo=None) + self.date_to = timezone(self.tz).localize(datetime.combine(compensated_request_date_to, hour_to)).astimezone(UTC).replace(tzinfo=None) + self._onchange_leave_dates() @api.onchange('request_unit_half') @@ -571,6 +577,28 @@ class HolidaysRequest(models.Model): return {'days': hours / (today_hours or HOURS_PER_DAY), 'hours': hours} + def _adjust_date_based_on_tz(self, leave_date, hour): + """ request_date_{from,to} are local to the user's tz but hour_{from,to} are in UTC. + + In some cases they are combined (assuming they are in the same tz) as a datetime. When + that happens it's possible we need to adjust one of the dates. This function adjust the + date, so that it can be passed to datetime(). + + E.g. a leave in US/Pacific for one day: + - request_date_from: 1st of Jan + - request_date_to: 1st of Jan + - hour_from: 15:00 (7:00 local) + - hour_to: 03:00 (19:00 local) <-- this happens on the 2nd of Jan in UTC + """ + user_tz = timezone(self.env.user.tz if self.env.user.tz else 'UTC') + request_date_to_utc = UTC.localize(datetime.combine(leave_date, hour)).astimezone(user_tz).replace(tzinfo=None) + if request_date_to_utc.date() < leave_date: + return leave_date + timedelta(days=1) + elif request_date_to_utc.date() > leave_date: + return leave_date - timedelta(days=1) + else: + return leave_date + #################################################### # ORM Overrides methods #################################################### diff --git a/addons/hr_holidays/tests/test_leave_requests.py b/addons/hr_holidays/tests/test_leave_requests.py index a0cbcc0c79f..fd9652d6d9a 100644 --- a/addons/hr_holidays/tests/test_leave_requests.py +++ b/addons/hr_holidays/tests/test_leave_requests.py @@ -2,6 +2,7 @@ # 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 @@ -366,23 +367,52 @@ class TestLeaveRequests(TestHrHolidaysCommon): leave1.action_validate() self.assertEqual(leave1.number_of_hours_display, 24) - @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 """ - self.user_employee.tz = 'US/Pacific' + 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. - webclient_values = { - 'date_from': datetime(2019, 1, 1, 15, 0, 0), - 'date_to': datetime(2019, 1, 2, 3, 0), # note that this is the next day in UTC + 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 = self.env['hr.leave'].sudo(self.user_employee_id)._default_get_request_parameters(webclient_values) + 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) + leave._onchange_request_parameters() + self.assertEqual(leave.number_of_days, number_of_days) - # Dates should be local to the user, they do not and cannot be - # converted using the user's timezone. - first_of_january = date(2019, 1, 1) - self.assertEqual(values['request_date_from'], first_of_january) - self.assertEqual(values['request_date_to'], first_of_january) + @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) |
