From 1b8b35e216e62491a04df92fe85ce253f2dbd87e Mon Sep 17 00:00:00 2001 From: dasz Date: Wed, 29 Mar 2023 08:25:16 +0000 Subject: [PATCH] [FIX] hr_attendance: account for lunch break in overtime calculation Remove lunch hours from work intervals so that overtime is not calculated when it's not due tests adapted to take into account lunch period task 3217799 closes odoo/odoo#120455 X-original-commit: 97c052432a25a1461de23fd8ab0c73ffe9b084ff Related: odoo/enterprise#40677 Signed-off-by: Kevin Baptiste --- addons/hr_attendance/models/hr_attendance.py | 8 ++++- .../tests/test_hr_attendance_overtime.py | 36 ++++++++++++++----- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/addons/hr_attendance/models/hr_attendance.py b/addons/hr_attendance/models/hr_attendance.py index 18cc8bf7ae4..5cd254095e1 100644 --- a/addons/hr_attendance/models/hr_attendance.py +++ b/addons/hr_attendance/models/hr_attendance.py @@ -251,7 +251,13 @@ class HrAttendance(models.Model): pre_work_time += (min(planned_start_dt, local_check_out) - local_check_in).total_seconds() / 3600.0 # Interval inside the working hours -> Considered as working time if local_check_in <= planned_end_dt and local_check_out >= planned_start_dt: - work_duration += (min(planned_end_dt, local_check_out) - max(planned_start_dt, local_check_in)).total_seconds() / 3600.0 + start_dt = max(planned_start_dt, local_check_in) + stop_dt = min(planned_end_dt, local_check_out) + work_duration += (stop_dt - start_dt).total_seconds() / 3600.0 + # remove lunch time from work duration + lunch_intervals = calendar._attendance_intervals_batch(start_dt, stop_dt, emp.resource_id, lunch=True) + work_duration -= sum((i[1] - i[0]).total_seconds() / 3600.0 for i in lunch_intervals[emp.resource_id.id]) + # There is an overtime at the end of the day if local_check_out > planned_end_dt: post_work_time += (local_check_out - max(planned_end_dt, local_check_in)).total_seconds() / 3600.0 diff --git a/addons/hr_attendance/tests/test_hr_attendance_overtime.py b/addons/hr_attendance/tests/test_hr_attendance_overtime.py index 4bc7029473b..955aefabe57 100644 --- a/addons/hr_attendance/tests/test_hr_attendance_overtime.py +++ b/addons/hr_attendance/tests/test_hr_attendance_overtime.py @@ -28,6 +28,7 @@ class TestHrAttendanceOvertime(TransactionCase): cls.other_employee = cls.env['hr.employee'].create({ 'name': 'Yolanda', 'company_id': cls.company.id, + 'tz': 'UTC', }) cls.jpn_employee = cls.env['hr.employee'].create({ 'name': 'Sacha', @@ -119,7 +120,7 @@ class TestHrAttendanceOvertime(TransactionCase): self.env['hr.attendance'].create({ 'employee_id': self.employee.id, 'check_in': datetime(2021, 1, 4, 7, 0), - 'check_out': datetime(2021, 1, 4, 16, 0) + 'check_out': datetime(2021, 1, 4, 17, 0) }) self.assertEqual(self.employee.total_overtime, 12) @@ -127,33 +128,34 @@ class TestHrAttendanceOvertime(TransactionCase): self.assertEqual(self.employee.total_overtime, 1) def test_overtime_change_employee(self): - attendance = self.env['hr.attendance'].create({ + Attendance = self.env['hr.attendance'] + attendance = Attendance.create({ 'employee_id': self.employee.id, 'check_in': datetime(2021, 1, 4, 7, 0), 'check_out': datetime(2021, 1, 4, 18, 0) }) - self.assertEqual(self.employee.total_overtime, 3) + self.assertEqual(self.employee.total_overtime, 2) self.assertEqual(self.other_employee.total_overtime, 0) attendance.employee_id = self.other_employee.id - self.assertEqual(self.other_employee.total_overtime, 3) + self.assertEqual(self.other_employee.total_overtime, 2) self.assertEqual(self.employee.total_overtime, 0) def test_overtime_far_timezones(self): + # Since dates have to be stored in utc these are the tokyo timezone times for 7-12 / 13-18 (UTC+9) self.env['hr.attendance'].create({ 'employee_id': self.jpn_employee.id, - # Since dates have to be stored in utc these are the tokyo timezone times for 7-18 (UTC+9) 'check_in': datetime(2021, 1, 3, 22, 0), 'check_out': datetime(2021, 1, 4, 9, 0), }) + # Same but for alaskan times (UTC-10) self.env['hr.attendance'].create({ 'employee_id': self.honolulu_employee.id, - # Same but for alaskan times (UTC-10) 'check_in': datetime(2021, 1, 4, 17, 0), 'check_out': datetime(2021, 1, 5, 4, 0), }) - self.assertEqual(self.jpn_employee.total_overtime, 3) - self.assertEqual(self.honolulu_employee.total_overtime, 3) + self.assertEqual(self.jpn_employee.total_overtime, 2) + self.assertEqual(self.honolulu_employee.total_overtime, 2) def test_overtime_unclosed(self): attendance = self.env['hr.attendance'].create({ @@ -264,3 +266,21 @@ class TestHrAttendanceOvertime(TransactionCase): overtime = self.env['hr.attendance.overtime'].search([('employee_id', '=', self.employee.id)]) self.assertFalse(overtime, 'Overtime entry should be unlinked since both overtime cancel each other.') + + def test_overtime_lunch(self): + attendance = self.env['hr.attendance'].create({ + 'employee_id': self.employee.id, + 'check_in': datetime(2021, 1, 4, 8, 0), + 'check_out': datetime(2021, 1, 4, 17, 0), + }) + self.assertEqual(self.employee.total_overtime, 0, 'There should be no overtime since the employee worked through the lunch period.') + + # check that no overtime is created when employee starts and finishes 1 hour earlier but works through lunch period + attendance.check_in = datetime(2021, 1, 4, 7, 0) + attendance.check_out = datetime(2021, 1, 4, 16, 0) + self.assertEqual(self.employee.total_overtime, 0, 'There should be no overtime since the employee worked through the lunch period.') + + # same but for 1 hour later + attendance.check_in = datetime(2021, 1, 4, 9, 0) + attendance.check_out = datetime(2021, 1, 4, 18, 0) + self.assertEqual(self.employee.total_overtime, 0, 'There should be no overtime since the employee worked through the lunch period.')