diff --git a/addons/hr_work_entry_contract/models/hr_contract.py b/addons/hr_work_entry_contract/models/hr_contract.py index 80b5f55c193..d2f6cbb1c4d 100644 --- a/addons/hr_work_entry_contract/models/hr_contract.py +++ b/addons/hr_work_entry_contract/models/hr_contract.py @@ -124,7 +124,6 @@ class HrContract(models.Model): def _get_contract_work_entries_values(self, date_start, date_stop): start_dt = pytz.utc.localize(date_start) if not date_start.tzinfo else date_start end_dt = pytz.utc.localize(date_stop) if not date_stop.tzinfo else date_stop - contract_vals = [] bypassing_work_entry_type_codes = self._get_bypassing_work_entry_type_codes() @@ -253,7 +252,18 @@ class HrContract(models.Model): Generate a work_entries list between date_start and date_stop for one contract. :return: list of dictionnary. """ - contract_vals = self._get_contract_work_entries_values(date_start, date_stop) + if isinstance(date_start, datetime): + contract_vals = self._get_contract_work_entries_values(date_start, date_stop) + else: + contract_vals = [] + contracts_by_tz = defaultdict(lambda: self.env['hr.contract']) + for contract in self: + contracts_by_tz[contract.resource_calendar_id.tz] += contract + for contract_tz, contracts in contracts_by_tz.items(): + tz = pytz.timezone(contract_tz) if contract_tz else pytz.utc + contract_vals += contracts._get_contract_work_entries_values( + tz.localize(date_start), + tz.localize(date_stop)) # {contract_id: ([dates_start], [dates_stop])} mapped_contract_dates = defaultdict(lambda: ([], [])) diff --git a/addons/hr_work_entry_holidays/tests/test_leave.py b/addons/hr_work_entry_holidays/tests/test_leave.py index 1b0fb8f2766..f7046b5f001 100644 --- a/addons/hr_work_entry_holidays/tests/test_leave.py +++ b/addons/hr_work_entry_holidays/tests/test_leave.py @@ -114,7 +114,6 @@ class TestWorkEntryLeave(TestWorkEntryHolidaysBase): def test_refuse_approved_leave(self): start = datetime(2019, 10, 10, 6, 0) end = datetime(2019, 10, 10, 18, 0) - # Setup contract generation state contract = self.richard_emp.contract_ids[:1] contract.state = 'open' diff --git a/addons/resource/models/resource_calendar.py b/addons/resource/models/resource_calendar.py index 8e4f1a20e0a..57094969adf 100644 --- a/addons/resource/models/resource_calendar.py +++ b/addons/resource/models/resource_calendar.py @@ -272,7 +272,6 @@ class ResourceCalendar(models.Model): def _attendance_intervals_batch(self, start_dt, end_dt, resources=None, domain=None, tz=None, lunch=False): assert start_dt.tzinfo and end_dt.tzinfo self.ensure_one() - if not resources: resources = self.env['resource.resource'] resources_list = [resources] @@ -340,6 +339,7 @@ class ResourceCalendar(models.Model): else: base_result.append((day_from, day_to, attendance)) + # Copy the result localized once per necessary timezone # Strictly speaking comparing start_dt < time or start_dt.astimezone(tz) < time # should always yield the same result. however while working with dates it is easier diff --git a/addons/test_resource/tests/test_resource.py b/addons/test_resource/tests/test_resource.py index ad33358d5a6..e114ef1b86e 100644 --- a/addons/test_resource/tests/test_resource.py +++ b/addons/test_resource/tests/test_resource.py @@ -532,6 +532,26 @@ class TestCalendar(TestResourceCommon): calendar_dt = self.calendar_john._get_closest_work_time(dt, resource=self.john.resource_id) self.assertEqual(calendar_dt, start, "It should have found the attendance on the 3rd April") + def test_attendance_interval_edge_tz(self): + # When genereting the attendance intervals in an edge timezone, the last interval shouldn't + # be truncated if the timezone is correctly set + self.env.user.tz = "America/Los_Angeles" + self.calendar_jean.tz = "America/Los_Angeles" + attendances = self.calendar_jean._attendance_intervals_batch( + datetime.combine(date(2023, 1, 1), datetime.min.time(), tzinfo=timezone("UTC")), + datetime.combine(date(2023, 1, 31), datetime.max.time(), tzinfo=timezone("UTC"))) + last_attendance = list(attendances[False])[-1] + self.assertEqual(last_attendance[0].replace(tzinfo=None), datetime(2023, 1, 31, 8)) + self.assertEqual(last_attendance[1].replace(tzinfo=None), datetime(2023, 1, 31, 15, 59, 59, 999999)) + + attendances = self.calendar_jean._attendance_intervals_batch( + datetime.combine(date(2023, 1, 1), datetime.min.time(), tzinfo=timezone("America/Los_Angeles")), + datetime.combine(date(2023, 1, 31), datetime.max.time(), tzinfo=timezone("America/Los_Angeles"))) + last_attendance = list(attendances[False])[-1] + self.assertEqual(last_attendance[0].replace(tzinfo=None), datetime(2023, 1, 31, 8)) + self.assertEqual(last_attendance[1].replace(tzinfo=None), datetime(2023, 1, 31, 16)) + + class TestResMixin(TestResourceCommon): def test_adjust_calendar(self):