Skip to content

Commit e83f58a

Browse files
[FIX] hr_attendance: auto-checkout on different date
Problem: the auto-checkout feature was basing the computation on the fact that the unclosed attendance was starting today. However, it might not always be the case, for example if the server is shutdown for more than 24 hours after checking in. Steps to reproduce: - Activate the auto-checkout feature - Create an open-ended attendance for two days ago - Run the cron - Result: the attendance is not closed. This commit solves the issue by taking into account the days delta between today and the check-in date. task-5082359 closes odoo#231367 X-original-commit: d5be57c Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
1 parent a7c338d commit e83f58a

File tree

2 files changed

+60
-16
lines changed

2 files changed

+60
-16
lines changed

addons/hr_attendance/models/hr_attendance.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ def action_refuse_overtime(self):
602602
def _cron_auto_check_out(self):
603603
def check_in_tz(attendance):
604604
"""Returns check-in time in calendar's timezone."""
605-
return attendance.check_in.astimezone(pytz.timezone(attendance.employee_id.resource_calendar_id.tz or 'UTC'))
605+
return attendance.check_in.astimezone(pytz.timezone(attendance.employee_id._get_tz()))
606606

607607
to_verify = self.env['hr.attendance'].search(
608608
[('check_out', '=', False),
@@ -613,9 +613,10 @@ def check_in_tz(attendance):
613613
if not to_verify:
614614
return
615615

616+
to_verify_min_date = min(to_verify.mapped('check_in')).replace(hour=0, minute=0, second=0)
616617
previous_attendances = self.env['hr.attendance'].search([
617618
('employee_id', 'in', to_verify.mapped('employee_id').ids),
618-
('check_in', '>', (fields.Datetime.now() - relativedelta(days=1)).replace(hour=0, minute=0, second=0)),
619+
('check_in', '>', to_verify_min_date),
619620
('check_out', '!=', False)
620621
])
621622

@@ -629,20 +630,32 @@ def check_in_tz(attendance):
629630
max_tol = company.auto_check_out_tolerance
630631
to_verify_company = to_verify.filtered(lambda a: a.employee_id.company_id.id == company.id)
631632

632-
# Attendances where Last open attendance time + previously worked time on that day + tolerance greater than the attendances hours (including lunch) in his calendar
633-
to_check_out = to_verify_company.filtered(lambda a: (fields.Datetime.now() - a.check_in).seconds / 3600 + mapped_previous_duration[a.employee_id][check_in_tz(a).date()] - max_tol >
634-
(sum(a.employee_id.resource_calendar_id.attendance_ids.filtered(lambda att: att.dayofweek == str(check_in_tz(a).weekday()) and (not att.two_weeks_calendar or att.week_type == str(att.get_week_type(check_in_tz(a).date())))).mapped(lambda at: at.hour_to - at.hour_from))))
635-
body = _('This attendance was automatically checked out because the employee exceeded the allowed time for their scheduled work hours.')
636-
637-
for att in to_check_out:
638-
expected_worked_hours = sum(att.employee_id.resource_calendar_id.attendance_ids.filtered(lambda a: a.dayofweek == str(check_in_tz(att).weekday()) and (not a.two_weeks_calendar or a.week_type == str(a.get_week_type(check_in_tz(att).date())))).mapped("duration_hours"))
639-
att.check_out = fields.Datetime.now()
640-
excess_hours = att.worked_hours - (expected_worked_hours + max_tol - mapped_previous_duration[att.employee_id][check_in_tz(att).date()])
641-
att.write({
642-
"check_out": max(att.check_out - relativedelta(hours=excess_hours), att.check_in + relativedelta(seconds=1)),
643-
"out_mode": "auto_check_out"
644-
})
645-
att.message_post(body=body)
633+
for att in to_verify_company:
634+
635+
employee_timezone = pytz.timezone(att.employee_id._get_tz())
636+
check_in_datetime = check_in_tz(att)
637+
now_datetime = fields.Datetime.now().astimezone(employee_timezone)
638+
current_attendance_duration = (now_datetime - check_in_datetime).total_seconds() / 3600
639+
previous_attendances_duration = mapped_previous_duration[att.employee_id][check_in_datetime.date()]
640+
641+
expected_worked_hours = sum(
642+
att.employee_id.resource_calendar_id.attendance_ids.filtered(
643+
lambda a: a.dayofweek == str(check_in_datetime.weekday())
644+
and (not a.two_weeks_calendar or a.week_type == str(a.get_week_type(check_in_datetime.date())))
645+
).mapped("duration_hours")
646+
)
647+
648+
# Attendances where Last open attendance time + previously worked time on that day + tolerance greater than the attendances hours (including lunch) in his calendar
649+
if (current_attendance_duration + previous_attendances_duration - max_tol) > expected_worked_hours:
650+
att.check_out = att.check_in.replace(hour=23, minute=59, second=59)
651+
excess_hours = att.worked_hours - (expected_worked_hours + max_tol - previous_attendances_duration)
652+
att.write({
653+
"check_out": max(att.check_out - relativedelta(hours=excess_hours), att.check_in + relativedelta(seconds=1)),
654+
"out_mode": "auto_check_out"
655+
})
656+
att.message_post(
657+
body=_('This attendance was automatically checked out because the employee exceeded the allowed time for their scheduled work hours.')
658+
)
646659

647660
def _cron_absence_detection(self):
648661
"""

addons/hr_attendance/tests/test_hr_attendance_overtime.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,37 @@ def test_auto_check_out(self):
542542
# Employee with flexible working schedule should not be checked out
543543
self.assertEqual(attendance_flexible_pending.check_out, False)
544544

545+
@freeze_time("2024-02-1 23:00:00")
546+
def test_auto_check_out_more_one_day_delta(self):
547+
""" Test that the checkout is correct if the delta between the check in and now is > 24 hours"""
548+
self.company.write({
549+
'auto_check_out': True,
550+
'auto_check_out_tolerance': 1
551+
})
552+
553+
attendance_utc_pending = self.env['hr.attendance'].create({
554+
'employee_id': self.employee.id,
555+
'check_in': datetime(2024, 1, 30, 8, 0)
556+
})
557+
558+
self.assertEqual(attendance_utc_pending.check_out, False)
559+
self.env['hr.attendance']._cron_auto_check_out()
560+
self.assertEqual(attendance_utc_pending.check_out, datetime(2024, 1, 30, 18, 0))
561+
562+
@freeze_time("2024-02-05 23:00:00")
563+
def test_auto_checkout_past_day(self):
564+
self.company.write({
565+
'auto_check_out': True,
566+
'auto_check_out_tolerance': 1,
567+
})
568+
attendance_utc_pending_7th_day = self.env['hr.attendance'].create({
569+
'employee_id': self.employee.id,
570+
'check_in': datetime(2024, 2, 1, 14, 0),
571+
})
572+
self.assertEqual(attendance_utc_pending_7th_day.check_out, False)
573+
self.env['hr.attendance']._cron_auto_check_out()
574+
self.assertEqual(attendance_utc_pending_7th_day.check_out, datetime(2024, 2, 1, 23, 0))
575+
545576
@freeze_time("2024-02-2 20:00:00")
546577
def test_auto_check_out_calendar_tz(self):
547578
"""Check expected working hours and previously worked hours are from the correct day when

0 commit comments

Comments
 (0)