Skip to content

Commit b716506

Browse files
committed
[FIX] resource: fully-flexible attendance hours
This commit fixes the calculation of the duration of an attendance record for an employee on a fully flexible working schedule. Previously, we were using the adjusted start and end times, which were adjusted from their original values to be the outer bounds of the interval made up of the original times, and the UTC-converted times. This resulted in a duration that was too long. Now, we use the original start and end times to calculate the duration of the attendance because this value is timezone-agnostic. closes odoo#216750 X-original-commit: 955fa9b Signed-off-by: Jurgen Gjini (jugj) <[email protected]> Signed-off-by: Harrison Hutton (hahu) <[email protected]>
1 parent 622df6e commit b716506

File tree

3 files changed

+52
-3
lines changed

3 files changed

+52
-3
lines changed

addons/resource/models/resource_calendar.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -436,11 +436,14 @@ def _attendance_intervals_batch(self, start_dt, end_dt, resources=None, domain=N
436436

437437
for resource in resources:
438438
if resource and resource._is_fully_flexible():
439+
# If the resource is fully flexible, return the whole period from start_dt to end_dt with a dummy attendance
440+
hours = (end_dt - start_dt).total_seconds() / 3600
441+
days = hours / 24
439442
dummy_attendance = self.env['resource.calendar.attendance'].new({
440-
'duration_hours': (end - start).total_seconds() / 3600,
441-
'duration_days': (end - start).days + 1,
443+
'duration_hours': hours,
444+
'duration_days': days,
442445
})
443-
result_per_resource_id[resource.id] = Intervals([(start, end, dummy_attendance)], keep_distinct=True)
446+
result_per_resource_id[resource.id] = Intervals([(start_dt, end_dt, dummy_attendance)], keep_distinct=True)
444447
elif resource and resource.calendar_id.flexible_hours:
445448
# For flexible Calendars, we create intervals to fill in the weekly intervals with the average daily hours
446449
# until the full time required hours are met. This gives us the most correct approximation when looking at a daily

addons/resource/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
# -*- coding: utf-8 -*-
33

44
from . import test_utils
5+
from . import test_resource_calendar
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
import pytz
3+
from datetime import datetime
4+
5+
from odoo.tests.common import TransactionCase
6+
7+
8+
class TestResourceCalendar(TransactionCase):
9+
10+
def test_fully_flexible_attendance_interval_duration(self):
11+
"""
12+
Test that the duration of a fully flexible attendance interval is correctly computed.
13+
"""
14+
calendar = self.env['resource.calendar'].create({
15+
'name': 'Standard Calendar',
16+
'two_weeks_calendar': False,
17+
})
18+
resource = self.env['resource.resource'].create({
19+
'name': 'Wade Wilson',
20+
'calendar_id': False, # Fully-flexible because no calendar is set
21+
'tz': 'America/New_York', # -04:00 UTC offset in the summer
22+
})
23+
self.env['resource.calendar.attendance'].create({
24+
'name': 'TEMP',
25+
'calendar_id': calendar.id,
26+
'dayofweek': '2', # Wednesday
27+
'hour_from': 14, # 18:00 UTC
28+
'hour_to': 17, # 21:00 UTC
29+
'date_from': datetime(2025, 6, 4, 0, 0, 0).date(),
30+
})
31+
UTC = pytz.timezone('UTC')
32+
start_dt = datetime(2025, 6, 4, 18, 0, 0).astimezone(UTC)
33+
end_dt = datetime(2025, 6, 4, 21, 0, 0).astimezone(UTC)
34+
result_per_resource_id = calendar._attendance_intervals_batch(
35+
start_dt, end_dt, resource
36+
)
37+
start, end, attendance = result_per_resource_id[resource.id]._items[0]
38+
# For a flexible resource, we expect the output times to match the
39+
# input times exactly, since the resource has no fixed calendar.
40+
# Further, the dummy attendance that is created should have a duration
41+
# equal to the difference between the start and end times.
42+
self.assertEqual(start, start_dt, "Output start time should match the input start time")
43+
self.assertEqual(end, end_dt, "Output end time should match the input end time")
44+
self.assertEqual(attendance.duration_hours, 3.0, "Attendance duration should be 3 hours")
45+
self.assertEqual(attendance.duration_days, 0.125, "Attendance duration should be 0.125 days (3 hours)")

0 commit comments

Comments
 (0)