[FIX] hr_work_entry_contract: Correctly compute work entries for edge timezones

closes odoo/odoo#124222

Related: odoo/upgrade#4699
Related: odoo/enterprise#41253
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Yannick Tivisse
2023-08-17 10:32:54 +02:00
parent 8a83cbeb28
commit b9b165db7d
4 changed files with 33 additions and 4 deletions
@@ -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: ([], []))
@@ -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'
+1 -1
View File
@@ -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
@@ -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):