# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import date, datetime
from freezegun import freeze_time

from odoo import Command
from odoo.tests import Form, HttpCase, new_test_user
from odoo.tests.common import tagged


@tagged('hr_attendance_overtime')
class TestHrAttendanceOvertime(HttpCase):
    """ Tests for overtime """

    @classmethod
    def setUpClass(cls):
        def set_calendar_and_tz(employee, tz):
            calendar = employee.resource_calendar_id.copy()
            calendar.write({
                'name': f'Default Calendar ({tz})',
                'tz': tz,
            })
            employee.resource_calendar_id = calendar
        super().setUpClass()
        cls.ruleset = cls.env['hr.attendance.overtime.ruleset'].create({
            'name': 'Ruleset schedule quantity',
            'rule_ids': [Command.create({
                    'name': 'Rule schedule quantity',
                    'base_off': 'quantity',
                    'expected_hours_from_contract': True,
                    'quantity_period': 'day',
                })],
        })

        cls.company = cls.env['res.company'].create({
            'name': 'SweatChipChop Inc.',
            'attendance_overtime_validation': 'no_validation',
            # 'overtime_company_threshold': 10,
            # 'overtime_employee_threshold': 10,
        })
        cls.company.resource_calendar_id.tz = 'Europe/Brussels'
        cls.company_1 = cls.env['res.company'].create({
            'name': 'Overtime Inc.',
        })
        cls.company_1.resource_calendar_id.tz = 'Europe/Brussels'
        cls.user = new_test_user(cls.env, login='fru', groups='base.group_user,hr_attendance.group_hr_attendance_manager', company_id=cls.company.id).with_company(cls.company)
        cls.employee = cls.env['hr.employee'].create({
            'name': "Marie-Edouard De La Court",
            'user_id': cls.user.id,
            'company_id': cls.company.id,
            'tz': 'UTC',
            'date_version': date(2020, 1, 1),
            'contract_date_start': date(2020, 1, 1),
            'resource_calendar_id': cls.company.resource_calendar_id.id,
            'ruleset_id': cls.ruleset.id
        })
        cls.other_employee = cls.env['hr.employee'].create({
            'name': 'Yolanda',
            'company_id': cls.company.id,
            'tz': 'UTC',
            'date_version': date(2020, 1, 1),
            'contract_date_start': date(2020, 1, 1),
            'resource_calendar_id': cls.company.resource_calendar_id.id,
            'ruleset_id': cls.ruleset.id
        })
        cls.jpn_employee = cls.env['hr.employee'].create({
            'name': 'Sacha',
            'company_id': cls.company.id,
            'tz': 'Asia/Tokyo',
            'date_version': date(2020, 1, 1),
            'contract_date_start': date(2020, 1, 1),
            'resource_calendar_id': cls.company.resource_calendar_id.id,
            'ruleset_id': cls.ruleset.id
        })
        set_calendar_and_tz(cls.jpn_employee, 'Asia/Tokyo')

        cls.honolulu_employee = cls.env['hr.employee'].create({
            'name': 'Susan',
            'company_id': cls.company.id,
            'tz': 'Pacific/Honolulu',
            'date_version': date(2020, 1, 1),
            'contract_date_start': date(2020, 1, 1),
            'resource_calendar_id': cls.company.resource_calendar_id.id,
            'ruleset_id': cls.ruleset.id
        })
        set_calendar_and_tz(cls.honolulu_employee, 'Pacific/Honolulu')

        cls.europe_employee = cls.env['hr.employee'].with_company(cls.company_1).create({
            'name': 'Schmitt',
            'company_id': cls.company_1.id,
            'tz': 'Europe/Brussels',
            'date_version': date(2020, 1, 1),
            'contract_date_start': date(2020, 1, 1),
            'resource_calendar_id': cls.company_1.resource_calendar_id.id,
            'ruleset_id': cls.ruleset.id
        })
        set_calendar_and_tz(cls.europe_employee, 'Europe/Brussels')

        cls.no_contract_employee = cls.env['hr.employee'].create({
            'name': 'No Contract',
            'company_id': cls.company.id,
            'tz': 'UTC',
            'resource_calendar_id': cls.company.resource_calendar_id.id,
            'date_version': date(2020, 1, 1),
            'contract_date_start': False,
        })
        cls.future_contract_employee = cls.env['hr.employee'].create({
            'name': 'Future contract',
            'company_id': cls.company.id,
            'tz': 'UTC',
            'resource_calendar_id': cls.company.resource_calendar_id.id,
            'date_version': date(2020, 1, 1),
            'contract_date_start': date(2030, 1, 1),
        })

        cls.calendar_flex_40h = cls.env['resource.calendar'].create({
            'name': 'Flexible 40 hours/week',
            'company_id': cls.company.id,
            'hours_per_day': 8,
            'hours_per_week': 40,
            'flexible_hours': True,
            'full_time_required_hours': 40,
        })

        cls.flexible_employee = cls.env['hr.employee'].create({
            'name': 'Flexi',
            'company_id': cls.company.id,
            'tz': 'UTC',
            'resource_calendar_id': cls.calendar_flex_40h.id,
            'date_version': date(2020, 1, 1),
            'contract_date_start': date(2020, 1, 1),
            'ruleset_id': cls.ruleset.id
        })

    def test_overtime_company_settings(self):
        self.company.write({
            "attendance_overtime_validation": "by_manager"
        })

        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, 20, 0)
        })

        self.assertEqual(attendance.overtime_status, 'to_approve')
        # self.assertAlmostEqual(attendance.validated_overtime_hours, 3, 2) -- TODO naja: seems wrong: why did we want that?
        self.assertAlmostEqual(attendance.validated_overtime_hours, 0, 2)
        self.assertEqual(attendance.employee_id.total_overtime, 0)

        attendance.action_approve_overtime()

        self.assertEqual(attendance.overtime_status, 'approved')
        self.assertAlmostEqual(attendance.validated_overtime_hours, 3, 2)
        self.assertAlmostEqual(attendance.employee_id.total_overtime, 3, 2)

        attendance.action_refuse_overtime()
        self.assertEqual(attendance.employee_id.total_overtime, 0, 0)

    def test_simple_overtime(self):
        checkin_am = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2021, 1, 4, 8, 0)
        })
        self.env['hr.attendance'].create({
            'employee_id': self.other_employee.id,
            'check_in': datetime(2021, 1, 4, 8, 0),
            'check_out': datetime(2021, 1, 4, 22, 0)
        })

        overtime = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id), ('date', '=', date(2021, 1, 4))])
        self.assertFalse(overtime, 'No overtime record should exist for that employee')

        checkin_am.write({'check_out': datetime(2021, 1, 4, 12, 0)})

        checkin_pm = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2021, 1, 4, 13, 0)
        })
        self.assertEqual(overtime.duration, 0, 'Overtime duration should be 0 when an attendance has not been checked out.')
        checkin_pm.write({'check_out': datetime(2021, 1, 4, 18, 0)})
        # self.assertTrue(overtime.exists(), 'Overtime should not be deleted')
        overtime = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id), ('date', '=', date(2021, 1, 4))])
        self.assertAlmostEqual(overtime.duration, 1)
        self.assertAlmostEqual(self.employee.total_overtime, 1)

    def test_overtime_weekend(self):
        self.env['hr.attendance.overtime.rule'].create({
            'name': "Rule non working days",
            'base_off': 'timing',
            'timing_type': 'non_work_days',
            'ruleset_id': self.ruleset.id,
        })

        self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2021, 1, 2, 8, 0),
            'check_out': datetime(2021, 1, 2, 11, 0)
        })

        overtime = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id), ('date', '=', date(2021, 1, 2))])
        self.assertTrue(overtime, 'Overtime should be created')
        self.assertEqual(overtime.duration, 3, 'Should have 3 hours of overtime')
        self.assertEqual(self.employee.total_overtime, 3, 'Should still have 3 hours of overtime')

    def test_overtime_multiple(self):
        self.env['hr.attendance.overtime.rule'].create({
            'name': "Rule non working days",
            'base_off': 'timing',
            'timing_type': 'non_work_days',
            'ruleset_id': self.ruleset.id,
        })
        attendance = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2021, 1, 2, 8, 0),
            'check_out': datetime(2021, 1, 2, 19, 0)
        })
        self.assertEqual(self.employee.total_overtime, 11)
        # self.assertEqual(self.employee.total_overtime, 3)

        self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2021, 1, 4, 7, 0),
            'check_out': datetime(2021, 1, 4, 17, 0)
        })
        self.assertEqual(self.employee.total_overtime, 12)

        attendance.unlink()
        self.assertAlmostEqual(self.employee.total_overtime, 1, 2)

    def test_overtime_change_employee(self):
        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, 2)
        self.assertEqual(self.other_employee.total_overtime, 0)

        self.other_employee.ruleset_id = self.ruleset
        Attendance.create({
            'employee_id': self.other_employee.id,
            'check_in': datetime(2021, 1, 4, 7, 0),
            'check_out': datetime(2021, 1, 4, 18, 0)
        })
        attendance.unlink()
        self.assertEqual(self.other_employee.total_overtime, 2)
        self.assertEqual(self.employee.total_overtime, 0)

    def test_overtime_far_timezones(self):
        (self.jpn_employee | self.honolulu_employee).ruleset_id = self.ruleset
        # attendance from 10 to 21(japan time)
        att = self.env['hr.attendance'].create({
            'employee_id': self.jpn_employee.id,
            'check_in': datetime(2021, 1, 4, 1, 0),
            'check_out': datetime(2021, 1, 4, 12, 0),
        })
        self.assertEqual(att.linked_overtime_ids.date, date(2021, 1, 4))

        # attendance from 7 to 18 (honolulu time)
        self.env['hr.attendance'].create({
            'employee_id': self.honolulu_employee.id,
            'check_in': datetime(2021, 1, 4, 17, 0),
            'check_out': datetime(2021, 1, 5, 4, 0),
        })
        self.assertAlmostEqual(self.jpn_employee.total_overtime, 2, 2)
        self.assertAlmostEqual(self.honolulu_employee.total_overtime, 2, 2)

    def test_overtime_unclosed(self):
        attendance = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2021, 1, 4, 8, 0),
        })
        overtime = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id)])
        self.assertFalse(overtime, 'Overtime entry should not exist at this point.')
        # Employees goes to eat
        attendance.write({
            'check_out': datetime(2021, 1, 4, 20, 0),
        })
        overtime = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id)])
        self.assertTrue(overtime, 'An overtime entry should have been created.')
        self.assertEqual(overtime.duration, 3, 'User should have 3 hours of overtime.')

    def test_overtime_company_threshold(self):
        self.ruleset.rule_ids[0].employer_tolerance = 10 / 60  # 10 minutes
        self.env['hr.attendance'].create([
            {
                'employee_id': self.employee.id,
                'check_in': datetime(2021, 1, 4, 7, 55),
                'check_out': datetime(2021, 1, 4, 12, 0),
            },
            {
                'employee_id': self.employee.id,
                'check_in': datetime(2021, 1, 4, 13, 0),
                'check_out': datetime(2021, 1, 4, 17, 5),
            }
        ])
        overtime = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id)])
        self.assertFalse(overtime, 'No overtime should be counted because of the threshold.')

        self.ruleset.rule_ids[0].employer_tolerance = 4 / 60  # 4 minutes
        self.ruleset.action_regenerate_overtimes()

        overtime = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id)])
        self.assertTrue(overtime, 'Overtime entry should exist since the threshold has been lowered.')
        self.assertAlmostEqual(overtime.duration, 10 / 60, places=2, msg='Overtime should be equal to 10 minutes.')

    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.')

    def test_overtime_hours_inside_attendance(self):
        # 1 Attendance case
        attendance = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 2, 8, 0),
            'check_out': datetime(2023, 1, 2, 21, 0)
        })

        # 8:00 -> 21:00 should contain 4 hours of overtime
        self.assertAlmostEqual(attendance.overtime_hours, 4, 2)

        # Total overtime for that day : 4 hours
        overtime_1 = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id),
                                                              ('date', '=', datetime(2023, 1, 2))])
        self.assertAlmostEqual(overtime_1.duration, 4, 2)

        # Multi attendance case

        m_attendance_1 = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 3, 8, 0),
            'check_out': datetime(2023, 1, 3, 19, 0)
        })
        # 8:00 -> 19:00 should contain 2 hours of overtime
        self.assertAlmostEqual(m_attendance_1.overtime_hours, 2, 2)

        m_attendance_2 = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 3, 19, 0),
            'check_out': datetime(2023, 1, 3, 20, 0)
        })
        # 19:00 -> 20:00 should contain 1 hour of overtime
        self.assertAlmostEqual(m_attendance_2.overtime_hours, 1, 2)

        m_attendance_3 = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 3, 21, 0),
            'check_out': datetime(2023, 1, 3, 23, 0)
        })
        # 21:00 -> 23:00 should contain 2 hours of overtime
        self.assertAlmostEqual(m_attendance_3.overtime_hours, 2, 2)

        overtime_2 = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id),
                                                                ('date', '=', datetime(2023, 1, 3))])
        # Total overtime for that day : 5 hours
        self.assertEqual(sum(overtime_2.mapped('duration')), 5)

        # Attendance Modification case

        m_attendance_3.write({
            'check_out': datetime(2023, 1, 3, 22, 30)
        })

        self.assertEqual(m_attendance_3.overtime_hours, 1.5)

        # Deleting previous attendances should update correctly the overtime hours in other attendances
        m_attendance_2.unlink()
        m_attendance_1.write({
            'check_out': datetime(2023, 1, 3, 17, 0)
        })
        m_attendance_3.write({
            'check_out': datetime(2023, 1, 3, 21, 30)
        })
        self.assertEqual(m_attendance_3.overtime_hours, 0.5)

        self.europe_employee.ruleset_id = self.ruleset
        # Create an attendance record for early check-in
        early_attendance = self.env['hr.attendance'].create({
            'employee_id': self.europe_employee.id,
            'check_in': datetime(2024, 5, 27, 23, 30),
            'check_out': datetime(2024, 5, 28, 13, 30)
        })

        # 5:00 -> 19:00[in emp tz] should contain 5 hours of overtime
        self.assertAlmostEqual(early_attendance.overtime_hours, 5)

        # Total overtime for that day : 5 hours
        overtime_record = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.europe_employee.id),
                                                              ('date', '=', datetime(2024, 5, 28))])
        self.assertAlmostEqual(overtime_record.duration, 5)

        # Check that the calendar's timezones take priority and that overtimes and attendances dates are consistent
        self.europe_employee.tz = 'America/New_York'
        early_attendance2 = self.env['hr.attendance'].create({
            'employee_id': self.europe_employee.id,
            'check_in': datetime(2024, 5, 30, 3, 0),  # 23:00 NY prev day -- attendance should be for the 29th
            'check_out': datetime(2024, 5, 30, 16, 0)  # 12:00 NY
        })
        # as his ruleset is per day; this employee works from 23h to 0h the 19th
        # and from 0h to 12h the 30th -> so he did 4h of overtime for this day

        self.assertAlmostEqual(early_attendance2.overtime_hours, 4)
        overtime_record2 = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.europe_employee.id),
                                                              ('date', '=', datetime(2024, 5, 30))])
        self.assertAlmostEqual(overtime_record2.duration, 4)

    @freeze_time("2024-02-01 23:00:00")
    def test_auto_check_out(self):
        self.company.write({
            'auto_check_out': True,
            'auto_check_out_tolerance': 1
        })
        self.env['hr.attendance'].create([{
            'employee_id': self.employee.id,
            'check_in': datetime(2024, 2, 1, 8, 0),
            'check_out': datetime(2024, 2, 1, 11, 0)
        },
        {
            'employee_id': self.employee.id,
            'check_in': datetime(2024, 2, 1, 11, 0),
            'check_out': datetime(2024, 2, 1, 13, 0)
        }
        ])

        attendance_utc_pending = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2024, 2, 1, 14, 0)
        })

        # Based on the employee's working calendar, they should be within the allotted hours.
        attendance_utc_pending_within_allotted_hours = self.env['hr.attendance'].create({
            'employee_id': self.europe_employee.id,
            'check_in': datetime(2024, 2, 1, 20, 0, 0)
        })

        attendance_utc_done = self.env['hr.attendance'].create({
            'employee_id': self.other_employee.id,
            'check_in': datetime(2024, 2, 1, 8, 0),
            'check_out': datetime(2024, 2, 1, 17, 0)
        })

        attendance_jpn_pending = self.env['hr.attendance'].create({
            'employee_id': self.jpn_employee.id,
            'check_in': datetime(2024, 2, 1, 12, 0)
        })

        attendance_flexible_pending = self.env['hr.attendance'].create({
            'employee_id': self.flexible_employee.id,
            'check_in': datetime(2024, 2, 1, 12, 0)
        })

        self.assertEqual(attendance_utc_pending.check_out, False)
        self.assertEqual(attendance_utc_pending_within_allotted_hours.check_out, False)
        self.assertEqual(attendance_utc_done.check_out, datetime(2024, 2, 1, 17, 0))
        self.assertEqual(attendance_jpn_pending.check_out, False)
        self.assertEqual(attendance_flexible_pending.check_out, False)

        self.env['hr.attendance']._cron_auto_check_out()

        self.assertEqual(attendance_utc_pending.check_out, datetime(2024, 2, 1, 19, 0))
        self.assertEqual(attendance_utc_pending_within_allotted_hours.check_out, False)
        self.assertEqual(attendance_utc_done.check_out, datetime(2024, 2, 1, 17, 0))
        self.assertEqual(attendance_jpn_pending.check_out, datetime(2024, 2, 1, 21, 0))

        # Employee with flexible working schedule should not be checked out
        self.assertEqual(attendance_flexible_pending.check_out, False)

    @freeze_time("2024-02-1 23:00:00")
    def test_auto_check_out_more_one_day_delta(self):
        """ Test that the checkout is correct if the delta between the check in and now is > 24 hours"""
        self.company.write({
            'auto_check_out': True,
            'auto_check_out_tolerance': 1
        })

        attendance_utc_pending = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2024, 1, 30, 8, 0)
        })

        self.assertEqual(attendance_utc_pending.check_out, False)
        self.env['hr.attendance']._cron_auto_check_out()
        self.assertEqual(attendance_utc_pending.check_out, datetime(2024, 1, 30, 18, 0))

    @freeze_time("2024-02-05 23:00:00")
    def test_auto_checkout_past_day(self):
        self.company.write({
            'auto_check_out': True,
            'auto_check_out_tolerance': 1,
        })
        attendance_utc_pending_7th_day = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2024, 2, 1, 14, 0),
        })
        self.assertEqual(attendance_utc_pending_7th_day.check_out, False)
        self.env['hr.attendance']._cron_auto_check_out()
        self.assertEqual(attendance_utc_pending_7th_day.check_out, datetime(2024, 2, 1, 23, 0))

    @freeze_time("2024-02-2 20:00:00")
    def test_auto_check_out_calendar_tz(self):
        """Check expected working hours and previously worked hours are from the correct day when
        using a calendar with a different timezone."""
        self.company.write({
            'auto_check_out': True,
            'auto_check_out_tolerance': 1
        })
        self.jpn_employee.resource_calendar_id.tz = 'Asia/Tokyo'  # UTC+9
        self.jpn_employee.resource_calendar_id.attendance_ids.filtered(lambda a: a.dayofweek == "4" and a.day_period in ["lunch", "afternoon"]).unlink()

        attendances_jpn = self.env['hr.attendance'].create([
            {
                'employee_id': self.jpn_employee.id,
                'check_in': datetime(2024, 2, 1, 6, 0),
                'check_out': datetime(2024, 2, 1, 7, 0)
            },
            {
                'employee_id': self.jpn_employee.id,
                'check_in': datetime(2024, 2, 1, 21, 0),
                'check_out': datetime(2024, 2, 1, 22, 0)
            },
            {
                'employee_id': self.jpn_employee.id,
                'check_in': datetime(2024, 2, 1, 23, 0)
            }
        ])

        self.env['hr.attendance']._cron_auto_check_out()
        self.assertEqual(attendances_jpn[2].check_out, datetime(2024, 2, 2, 3, 0), "Check-out after 4 hours (4 hours expected from calendar + 1 hours tolerance - 1 hour previous attendance)")

    def test_auto_check_out_lunch_period(self):
        Attendance = self.env['hr.attendance']
        self.company.write({
            'auto_check_out': True,
            'auto_check_out_tolerance': 1
        })
        morning, afternoon = Attendance.create([{
            'employee_id': self.employee.id,
            'check_in': datetime(2024, 1, 1, 8, 0),
            'check_out': datetime(2024, 1, 1, 12, 0)
        },
        {
            'employee_id': self.employee.id,
            'check_in': datetime(2024, 1, 1, 13, 0)
        }])

        with freeze_time("2024-01-01 22:00:00"):
            Attendance._cron_auto_check_out()
            self.assertEqual(morning.worked_hours + afternoon.worked_hours, 9)  # 8 hours from calendar's attendances + 1 hour of tolerance
            self.assertEqual(afternoon.check_out, datetime(2024, 1, 1, 18, 0))

    def test_auto_check_out_two_weeks_calendar(self):
        """Test case: two weeks calendar with different attendances depending on the week. No morning attendance on
        wednesday of the first week."""
        Attendance = self.env['hr.attendance']
        self.company.write({
            'auto_check_out': True,
            'auto_check_out_tolerance': 0
        })
        self.employee.resource_calendar_id.switch_calendar_type()
        self.employee.resource_calendar_id.attendance_ids.search([("dayofweek", "=", "2"), ("week_type", '=', '0'), ("day_period", "in", ["morning", "lunch"])]).unlink()

        with freeze_time("2025-03-05 22:00:00"):
            att = Attendance.create({
                'employee_id': self.employee.id,
                'check_in': datetime(2025, 3, 5, 8, 0)
            })
            Attendance._cron_auto_check_out()
            self.assertEqual(att.worked_hours, 4)
            self.assertEqual(att.check_out, datetime(2025, 3, 5, 12, 0))

        with freeze_time("2025-03-12 22:00:00"):
            att = Attendance.create({
                'employee_id': self.employee.id,
                'check_in': datetime(2025, 3, 12, 8, 0),
            })
            Attendance._cron_auto_check_out()
            self.assertEqual(att.worked_hours, 8)
            self.assertEqual(att.check_out, datetime(2025, 3, 12, 17, 0))

    # @freeze_time("2024-02-01 14:00:00")
    # def test_absence_management(self):
    # TODO no more absence management
    #     self.company.write({
    #         'absence_management': True,
    #     })

    #     self.env['hr.attendance'].create({
    #         'employee_id': self.employee.id,
    #         'check_in': datetime(2024, 1, 31, 8, 0),
    #         'check_out': datetime(2024, 1, 31, 17, 0)
    #     })

    #     self.env['hr.attendance'].create({
    #         'employee_id': self.employee.id,
    #         'check_in': datetime(2024, 2, 1, 8, 0),
    #         'check_out': datetime(2024, 2, 1, 17, 0)
    #     })

    #     self.env['hr.attendance'].create({
    #         'employee_id': self.other_employee.id,
    #         'check_in': datetime(2024, 2, 1, 8, 0),
    #         'check_out': datetime(2024, 2, 1, 17, 0)
    #     })

    #     self.env['hr.attendance'].create({
    #         'employee_id': self.jpn_employee.id,
    #         'check_in': datetime(2024, 2, 1, 1, 0),
    #         'check_out': datetime(2024, 2, 1, 10, 0)

    #     })

    #     self.env['hr.attendance'].create({
    #         'employee_id': self.honolulu_employee.id,
    #         'check_in': datetime(2024, 2, 1, 17, 0),
    #         'check_out': datetime(2024, 2, 2, 2, 0)
    #     })

    #     self.env['hr.attendance'].create({
    #         'employee_id': self.europe_employee.id,
    #         'check_in': datetime(2024, 2, 1, 8, 0),
    #         'check_out': datetime(2024, 2, 1, 17, 0)
    #     })

    #     self.env['hr.attendance'].create({
    #         'employee_id': self.flexible_employee.id,
    #         'check_in': datetime(2024, 2, 1, 8, 0),
    #         'check_out': datetime(2024, 2, 1, 16, 0)
    #     })

    #     self.assertAlmostEqual(self.employee.total_overtime, 0, 2)
    #     self.assertAlmostEqual(self.other_employee.total_overtime, 0, 2)
    #     self.assertAlmostEqual(self.jpn_employee.total_overtime, 0, 2)
    #     self.assertAlmostEqual(self.honolulu_employee.total_overtime, 0, 2)
    #     self.assertAlmostEqual(self.europe_employee.total_overtime, 0, 2)
    #     self.assertAlmostEqual(self.flexible_employee.total_overtime, 0, 2)

    #     self.env['hr.attendance']._cron_absence_detection()

    #     # Check that absences were correctly attributed
    #     self.assertAlmostEqual(self.other_employee.total_overtime, -8, 2)
    #     self.assertAlmostEqual(self.jpn_employee.total_overtime, -8, 2)
    #     self.assertAlmostEqual(self.honolulu_employee.total_overtime, -8, 2)

    #     # Employee Checked in yesterday, no absence found
    #     self.assertAlmostEqual(self.employee.total_overtime, 0, 2)

    #     # Flexible schedule employee, no absence found
    #     self.assertAlmostEqual(self.flexible_employee.total_overtime, 0, 2)

    #     # Other company with setting disabled
    #     self.assertAlmostEqual(self.europe_employee.total_overtime, 0, 2)

    #     # Employee with no contract or future contract
    #     # self.assertAlmostEqual(self.no_contract_employee.total_overtime, 0, 2)
    #     # self.assertAlmostEqual(self.future_contract_employee.total_overtime, 0, 2)

    def test_overtime_hours_flexible_resource(self):
        """ Test the computation of overtime hours for a single flexible resource with 8 hours_per_day.
        =========
        Test Case
        1) | 8:00  | 16:00 | -> No overtime
        2) | 12:00 | 18:00 | -> -2 hours of overtime
        3) | 10:00 | 22:00 | -> 4 hours of overtime
        """
        self.flexible_employee.ruleset_id = self.ruleset
        # 1) 8:00 - 16:00 should contain 0 hours of overtime
        attendance = self.env['hr.attendance'].create({
            'employee_id': self.flexible_employee.id,
            'check_in': datetime(2023, 1, 2, 8, 0),
            'check_out': datetime(2023, 1, 2, 16, 0)
        })
        self.assertEqual(attendance.overtime_hours, 0, 'There should be no overtime for the flexible resource.')

        # 2) 12:00 - 18:00 should contain -2 hours of overtime
        # as we expect the employee to work 8 hours per day
        attendance.write({
            'check_in': datetime(2023, 1, 3, 12, 0),
            'check_out': datetime(2023, 1, 3, 18, 0)
        })
        self.assertAlmostEqual(attendance.overtime_hours, 0, 2, 'There should be 0 hours of overtime for the flexible resource.')

        # 3) 10:00 - 22:00 should contain 4 hours of overtime
        attendance.write({
            'check_in': datetime(2023, 1, 4, 10, 0),
            'check_out': datetime(2023, 1, 4, 22, 0)
        })
        self.assertAlmostEqual(attendance.overtime_hours, 4, 2, 'There should be 4 hours of overtime for the flexible resource.')

    def test_overtime_hours_multiple_flexible_resources(self):
        """ Test the computation of overtime hours for multiple flexible resources on a single workday with 8 hours_per_day.
        =========

        We should see that the overtime hours are recomputed correctly when new attendance records are created.

        Test Case
        1) | 8:00  | 12:00 | -> -4 hours of overtime
        2) (| 8:00 | 12:00 |, | 13:00 | 15:00 |) -> (0, -2) hours of overtime
        3) (| 8:00 | 12:00 |, | 13:00 | 15:00 |, | 16:00 | 18:00 |) -> (0, 0, 0) hours of overtime
        """
        self.flexible_employee.ruleset_id = self.ruleset

        # 1) 8:00 - 12:00 should contain -4 hours of overtime
        attendance_1 = self.env['hr.attendance'].create({
            'employee_id': self.flexible_employee.id,
            'check_in': datetime(2023, 1, 2, 8, 0),
            'check_out': datetime(2023, 1, 2, 12, 0)
        })
        self.assertAlmostEqual(attendance_1.overtime_hours, 0, 2, 'There should be -4 hours of overtime for the flexible resource.')

        # 2) 8:00 - 12:00 and 13:00 - 15:00 should contain 0 and -2 hours of overtime
        attendance_2 = self.env['hr.attendance'].create({
            'employee_id': self.flexible_employee.id,
            'check_in': datetime(2023, 1, 2, 13, 0),
            'check_out': datetime(2023, 1, 2, 15, 0)
        })
        self.assertEqual(attendance_1.overtime_hours, 0, 'There should be no overtime for the flexible resource.')
        self.assertAlmostEqual(attendance_2.overtime_hours, 0, 2, 'There should be 0 hours of overtime for the flexible resource.')

        # 3) 8:00 - 12:00, 13:00 - 15:00 and 16:00 - 18:00 should contain 0, 0 and 0 hours of overtime
        attendance_3 = self.env['hr.attendance'].create({
            'employee_id': self.flexible_employee.id,
            'check_in': datetime(2023, 1, 2, 16, 0),
            'check_out': datetime(2023, 1, 2, 18, 0),
        })
        self.assertEqual(attendance_1.overtime_hours, 0, 'There should be no overtime for the flexible resource.')
        self.assertEqual(attendance_2.overtime_hours, 0, 'There should be no overtime for the flexible resource.')
        self.assertEqual(attendance_3.overtime_hours, 0, 'There should be no overtime for the flexible resource.')

    def test_overtime_hours_fully_flexible_resource(self):
        """ Test the computation of overtime hours for a fully flexible resource.
        Fully flexible resources should not have any overtime. """

        # take the flexible resource and set the resource calendar to a fully flexible one
        self.flexible_employee.resource_calendar_id = False

        # 1) 8:00 - 16:00 should contain 0 hours of overtime
        attendance = self.env['hr.attendance'].create({
            'employee_id': self.flexible_employee.id,
            'check_in': datetime(2023, 1, 2, 8, 0),
            'check_out': datetime(2023, 1, 2, 16, 0)
        })
        self.assertEqual(attendance.overtime_hours, 0, 'There should be no overtime for the fully flexible resource.')

        # 2) 16:00 - 09:00 (next day) should contain 0 hours of overtime
        attendance.write({
            'check_in': datetime(2023, 1, 3, 16, 0),
            'check_out': datetime(2023, 1, 4, 9, 0)
        })
        self.assertEqual(attendance.overtime_hours, 0, 'There should be no overtime for the fully flexible resource.')

    def test_refuse_timeoff(self):
        self.company.write({
            "attendance_overtime_validation": "by_manager"
        })
        self.employee.tz = 'Europe/Brussels'
        # employee_tz and calendar_tz should be the same.
        # Currently due to this mismatch one hour in added
        # (because the employee stop working at 13h brussels so 12h UTC so before the lunch period)
        attendance = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 2, 8, 0),
            'check_out': datetime(2023, 1, 3, 12, 0)
        })
        # hours are with the Europe/Brussels timezone
        # This employee works from 9h -> 0h (the 2nd)
        # he should works 8h (+1h because he works on lunch time)
        # so 15h - 1h = 14h of working attendance
        # 14 - 8 = 6h of overtime
        # and from 0h -> 13h (the 3rd)
        # he should works 8h (+1h because he works on lunch time)
        # so 13h - 1h = 12h of working attendance
        # 12 - 8 = 4h of overtime
        # so he should have 10 hours of overtime this day
        overtime = self.env['hr.attendance.overtime.line'].search([
            ('employee_id', '=', self.employee.id),
        ])
        self.assertItemsEqual(overtime.mapped('duration'), [6, 4])
        self.assertEqual(attendance.validated_overtime_hours, 0)
        overtime.action_approve()
        self.assertEqual(attendance.validated_overtime_hours, 10)
        self.assertEqual(attendance.overtime_hours, attendance.validated_overtime_hours)

        attendance.action_refuse_overtime()
        self.assertEqual(attendance.validated_overtime_hours, 0)

        # Create 2 attendance to avoid to work during lunch period; the overtime duration should be the same
        attendances = self.env['hr.attendance'].create([
            {
                'employee_id': self.employee.id,
                'check_in': datetime(2025, 12, 18, 8, 0),
                'check_out': datetime(2025, 12, 18, 11, 0)  # == 12h Europe/Bussels
            }, {
                'employee_id': self.employee.id,
                'check_in': datetime(2025, 12, 18, 12, 0),  # == 13h Europe/Bussels
                'check_out': datetime(2025, 12, 19, 12, 0)
            },
        ])
        overtimes = self.env['hr.attendance.overtime.line'].search([
            ('employee_id', '=', self.employee.id),
            ('date', '>=', datetime(2025, 12, 18).date())
        ])
        self.assertItemsEqual(overtimes.mapped('duration'), [6, 4])
        self.assertEqual(sum(attendances.mapped('validated_overtime_hours')), 0)
        overtimes.action_approve()
        self.assertEqual(sum(attendances.mapped('validated_overtime_hours')), 10)
        self.assertEqual(sum(attendances.mapped('overtime_hours')), sum(attendances.mapped('validated_overtime_hours')))

        attendances.action_refuse_overtime()
        self.assertEqual(sum(attendances.mapped('validated_overtime_hours')), 0)

    def test_no_validation_extra_hours_change(self):
        """
         Check that manual edits are recomputed when updating another attendance,
         but flags the record as 'to_approve'
        """
        self.company.attendance_overtime_validation = "no_validation"

        attendance = self.env['hr.attendance']
        # Form is used here as it will send a `validated_overtime_hours` value of 0 when saved.
        # This should not be considered as a manual edition of the field by the user.
        with Form(attendance) as attendance_form:
            attendance_form.employee_id = self.employee
            attendance_form.check_in = datetime(2023, 1, 2, 8, 0)
            attendance_form.check_out = datetime(2023, 1, 2, 18, 0)
        attendance = attendance_form.save()

        self.assertAlmostEqual(attendance.overtime_hours, 1, 2)
        self.assertAlmostEqual(attendance.validated_overtime_hours, 1, 2)

        attendance.linked_overtime_ids.manual_duration = 0.5
        self.assertNotEqual(attendance.validated_overtime_hours, attendance.overtime_hours)

        # Create another attendance for the same employee
        self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 4, 8, 0),
            'check_out': datetime(2023, 1, 4, 18, 0)
        })
        # The hours will now be recomputed
        # But they should have the 'to_approve' status
        self.assertEqual(attendance.linked_overtime_ids.status, 'to_approve', "Record should be flagged for approval")
        self.assertAlmostEqual(attendance.linked_overtime_ids.duration, 1.0, 2, "Math should be reset to 1.0")
        self.assertEqual(attendance.validated_overtime_hours, 0.0, "Validated hours should be 0 until approved")

    def _check_overtimes(self, overtimes, vals_list):
        self.assertEqual(len(overtimes), len(vals_list), "Wrong number of overtimes")
        for overtime, vals in zip(overtimes, vals_list):
            for k, v in vals.items():
                self.assertEqual(overtime[k], v)

    def test_overtime_rule_timing(self):
        version = self.employee._get_version(date(2025, 8, 20))
        ruleset = self.env['hr.attendance.overtime.ruleset'].create({
            'name': 'Test Timing Ruleset',
            'rule_ids': [
                Command.create({
                    'name': "Company Schedule",
                    'base_off': 'timing',
                    'timing_type': 'schedule',
                    'resource_calendar_id': self.company.resource_calendar_id.id,
                }),
                Command.create({
                    'name': "Naptime",
                    'base_off': 'timing',
                    'timing_type': 'work_days',
                    'timing_start': 14,
                    'timing_stop': 15,
                }),
            ],
        })

        version.ruleset_id = ruleset
        self.europe_employee.ruleset_id = ruleset

        self.env['hr.attendance'].create({
            'employee_id': self.europe_employee.id,
            'check_in': datetime(2025, 8, 20, 5, 0),  # 7h Europe/Brussels
            'check_out': datetime(2025, 8, 20, 14, 0),  # 16h Europe/Brussels
        })
        self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2025, 8, 20, 7, 0),
            'check_out': datetime(2025, 8, 20, 16, 0),
        })
        overtimes_by_employee = self.env['hr.attendance.overtime.line'].search([
            ('employee_id', 'in', [self.employee.id, self.europe_employee.id]),
        ]).grouped('employee_id')
        self._check_overtimes(overtimes_by_employee.get(self.employee), [
            {  # 7:00 -> 8:00
                'date': date(2025, 8, 20),
                'duration': 1,
            },
            {  # 14:00 -> 15:00
                'date': date(2025, 8, 20),
                'duration': 1,
            },
        ])

        self._check_overtimes(overtimes_by_employee.get(self.europe_employee), [
            {  # 7:00 -> 8:00
                'date': date(2025, 8, 20),
                'duration': 1,
            },
            {  # 14:00 -> 15:00
                'date': date(2025, 8, 20),
                'duration': 1,
            },
        ])

    def test_overtime_rule_quantity(self):
        version = self.employee._get_version(date(2025, 8, 20))
        ruleset = self.env['hr.attendance.overtime.ruleset'].create({
            'name': 'Test Qty Ruleset',
            'rule_ids': [
                Command.create({
                    'name': "> 9h/d",
                    'base_off': 'quantity',
                    'quantity_period': 'day',
                    'expected_hours_from_contract': False,
                    'expected_hours': 9,
                }),
                Command.create({
                    'name': "Weekly Overtime",
                    'base_off': 'quantity',
                    'quantity_period': 'week',
                    'expected_hours_from_contract': False,
                    'expected_hours': 40,
                }),
            ],
        })

        version.ruleset_id = ruleset

        # 10h on monday: 1h daily ot
        # 8h on tue-thu (24h)
        # 10h on friday: 1h daily ot
        # 10 + 24 + 10 - 40 = 4 weekly ot
        # Expected result:
        # * monday, friday: 1 h daily ot each on the end of the day, that are also weekly
        # * friday: 4h weekly, 1 overlaps with the hours
        # * total = 1 + 4 = 5
        self.env['hr.attendance'].create([
            # monday 8-19 (10h bc 1 hours lunch)
            {
                'employee_id': self.employee.id,
                'check_in': datetime(2025, 8, 18, 6, 0),
                'check_out': datetime(2025, 8, 18, 17, 0),
            },
            # friday 8-19: 10h
            {
                'employee_id': self.employee.id,
                'check_in': datetime(2025, 8, 22, 6, 0),
                'check_out': datetime(2025, 8, 22, 17, 0),
            },
            # tue-thu 8-17: 8h each
            *[{
                'employee_id': self.employee.id,
                'check_in': datetime(2025, 8, day, 6, 0),
                'check_out': datetime(2025, 8, day, 15, 0),
            } for day in range(19, 22)]
        ])

        overtimes = self.env['hr.attendance.overtime.line'].search([
            ('employee_id', '=', self.employee.id),
        ])

        self.assertAlmostEqual(sum(ot.duration for ot in overtimes), 5.0, 2)
        self._check_overtimes(overtimes, [
            {  # monday 18:00->19:00 (weekly + monday daily)
                'date': date(2025, 8, 18),
                'duration': 1,
            },
            {  # friday 15:00->18:00 (weekly)
                'date': date(2025, 8, 22),
                'duration': 3,
            },
            {  # friday 18:00->19:00 (weekly + friday daily)
                'date': date(2025, 8, 22),
                'duration': 1,
            },
        ])

    def test_overtime_rule_combined(self):
        # TODO
        pass

    def test_overtime_rule_timing_type_not_set(self):
        ruleset = self.env['hr.attendance.overtime.ruleset'].create({
            'name': 'Test Timing Ruleset',
            'rule_ids': [
                Command.create({
                    'name': "Company Schedule",
                    'base_off': 'timing',
                }),
            ],
        })

        self.assertEqual(ruleset.rule_ids.timing_type, 'work_days',
                 "Employee work Timing type should default to 'work_days' when not set.")

    def test_employee_overtime_with_multiple_attendance_lines(self):
        """Validate that multiple overtime lines for today are summed correctly
        and that the entire attendance_employee_data response is consistent.
        """
        for _ in range(2):
            self.env['hr.attendance.overtime.line'].create({
                'employee_id': self.employee.id,
                'date': date.today(),
                'duration': 5,
            })
        token = self.employee.company_id.attendance_kiosk_key
        response = self.make_jsonrpc_request(
            '/hr_attendance/attendance_employee_data',
            {'token': token, 'employee_id': self.employee.id},
        )
        self.assertEqual(response.get('hours_previously_today'), 0)
        self.assertEqual(response.get('hours_today'), 0)
        self.assertEqual(response.get('last_attendance_worked_hours'), 0)
        self.assertEqual(response.get('overtime_today'), 10)
        self.assertEqual(response.get('total_overtime'), 10)

    def test_overtime_with_public_holidays(self):
        """ Comapny 1 has a public holiday, while Company 2 does not.
            Employee from Company 2 should not get overtime for working that day.
        """
        with freeze_time("2025-11-11 12:00:00"):
            self.env.user.tz = 'UTC'  # to avoid to shift the public holidays hours
            company_be = self.env['res.company'].create({'name': 'Odoo BE'})
            company_de = self.env['res.company'].create({'name': 'Odoo DE'})

            with Form(self.env['resource.calendar.leaves'].with_company(company_be)) as holiday_form:
                holiday_form.name = 'Armistice Day'
                holiday_form.date_from = datetime(2025, 11, 11, 0, 0)
                holiday_form.save()

            ruleset_be = self.env['hr.attendance.overtime.ruleset'].with_company(company_be).create({
                'name': 'Ruleset schedule timing',
                'rule_ids': [Command.create({
                        'name': 'Rule schedule timing',
                        'base_off': 'timing',
                        'timing_type': 'non_work_days',
                        'timing_start': 0,
                        'timing_stop': 24,
                    })],
            })
            ruleset_de = self.env['hr.attendance.overtime.ruleset'].with_company(company_de).create({
                'name': 'Ruleset schedule timing',
                'rule_ids': [Command.create({
                        'name': 'Rule schedule timing',
                        'base_off': 'timing',
                        'timing_type': 'non_work_days',
                        'timing_start': 0,
                        'timing_stop': 24,
                    })],
            })

            employee_be = self.env['hr.employee'].with_company(company_be).create({
                'name': 'Hans Belgian',
                'ruleset_id': ruleset_be.id,
            })
            employee_de = self.env['hr.employee'].with_company(company_de).create({
                'name': 'Henry German',
                'ruleset_id': ruleset_de.id,
            })

            attendance_company_be = self.env['hr.attendance'].create({
                'employee_id': employee_be.id,
                'check_in': datetime(2025, 11, 11, 8, 0),
                'check_out': datetime(2025, 11, 11, 17, 0),
            })
            attendance_company_de = self.env['hr.attendance'].create({
                'employee_id': employee_de.id,
                'check_in': datetime(2025, 11, 11, 8, 0),
                'check_out': datetime(2025, 11, 11, 17, 0),
            })

            self.assertAlmostEqual(attendance_company_be.overtime_hours, 9, 2, "Employee from Company 1 should have overtime for working on a public holiday.")
            self.assertAlmostEqual(attendance_company_de.overtime_hours, 0, 2, "Employee from Company 2 should not have overtime for working on a non-holiday day.")

    def test_officer_access_on_overtime_records(self):
        user1 = new_test_user(self.env, login='user1', groups='hr_attendance.group_hr_attendance_officer', company_id=self.company.id).with_company(self.company)
        self.other_employee.attendance_manager_id = user1.id
        attendance = self.env['hr.attendance'].create({
            'employee_id': self.other_employee.id,
            'check_in': datetime(2021, 1, 4, 8, 0),
            'check_out': datetime(2021, 1, 4, 20, 0)
        })
        self.assertTrue(attendance.with_user(user1).linked_overtime_ids.rule_ids.has_access("read"))

    def test_attendance_overtime_with_timing_rule_cross_midnight(self):
        """Test attendance creation when the overtime timing rule crosses midnight."""
        self.employee.ruleset_id.rule_ids.base_off = 'timing'
        self.employee.ruleset_id.rule_ids.timing_start = 14
        self.employee.ruleset_id.rule_ids.timing_stop = 5
        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, 18, 0)
        })

        self.assertEqual(attendance.worked_hours, 9.0)
        self.assertEqual(attendance.overtime_hours, 4.0)
        self.assertEqual(attendance.expected_hours, 5.0)

    def test_company_tolerance_multiple_attendances(self):
        """
        This test checks that the company tolerance is correct in case of multiple attendances registered
        for a same day.
        """
        self.employee.ruleset_id.rule_ids.employer_tolerance = 0.25
        attendance_1, attendance_2, attendance_3, attendance_4, attendance_5, attendance_6 = self.env['hr.attendance'].create([{
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 4, 7, 0),
            'check_out': datetime(2023, 1, 4, 8, 0)
        }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 4, 12, 0),
            'check_out': datetime(2023, 1, 4, 20, 30)
        }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 5, 7, 0),
            'check_out': datetime(2023, 1, 5, 8, 0)
        }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 5, 12, 0),
            'check_out': datetime(2023, 1, 5, 20, 14)
        }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 6, 7, 44),
            'check_out': datetime(2023, 1, 6, 12, 00)
        }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 6, 13, 30),
            'check_out': datetime(2023, 1, 6, 17, 44)
        }])
        expected = (0.0, 0.5, 0.0, 0.0, 0.0, 0.5)
        actual = (
            attendance_1.overtime_hours,
            attendance_2.overtime_hours,
            attendance_3.overtime_hours,
            attendance_4.overtime_hours,
            attendance_5.overtime_hours,
            attendance_6.overtime_hours,
        )

        for a, e in zip(actual, expected):
            self.assertAlmostEqual(a, e)

    def test_employee_tolerance_multiple_attendances(self):
        """
        This test checks that the employee tolerance is correct in case of multiple attendances registered
        for a same day.
        """
        self.employee.ruleset_id.rule_ids.employee_tolerance = 0.25
        self.employee.company_id.absence_management = True
        self.employee.ruleset_id.rule_ids.company_id = self.employee.company_id
        attendance_1, attendance_2, attendance_3, attendance_4, attendance_5, attendance_6 = self.env['hr.attendance'].create([{
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 4, 7, 0),
            'check_out': datetime(2023, 1, 4, 8, 0)
        }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 4, 12, 0),
            'check_out': datetime(2023, 1, 4, 19, 30)
        }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 5, 7, 0),
            'check_out': datetime(2023, 1, 5, 8, 0)
        }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 5, 12, 0),
            'check_out': datetime(2023, 1, 5, 19, 54)
        }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 6, 7, 44),
            'check_out': datetime(2023, 1, 6, 12, 00)
        }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 6, 13, 30),
            'check_out': datetime(2023, 1, 6, 16, 44)
        }])

        expected = (0.0, -0.5, 0.0, 0.0, 0.0, -0.5)
        actual = (
            attendance_1.overtime_hours,
            attendance_2.overtime_hours,
            attendance_3.overtime_hours,
            attendance_4.overtime_hours,
            attendance_5.overtime_hours,
            attendance_6.overtime_hours,
        )

        for a, e in zip(actual, expected):
            self.assertAlmostEqual(a, e)

    def test_check_linked_overtime_to_attendance(self):
        morning_att, afternoon_att = self.env['hr.attendance'].create([{
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 4, 7, 0),
            'check_out': datetime(2023, 1, 4, 11, 0)
        }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 4, 12, 0),
            'check_out': datetime(2023, 1, 4, 19, 30)
        }])
        overtime_lines = (morning_att + afternoon_att).linked_overtime_ids
        self.assertFalse(morning_att.linked_overtime_ids)
        # The overtime line is linked to the afternoon attendance
        self.assertTrue(afternoon_att.linked_overtime_ids)
        # Should be the same as it's the reverse checking
        self.assertEqual(overtime_lines._linked_attendances(), afternoon_att)

    def test_regenerate_overtime_employee_multiple_versions(self):
        """ Checks that regenerating overtimes succeeds when an employee who has entries in attendances
         has more than one version (contract) and each version has a different ruleset. """
        ruleset_1, ruleset_2 = self.env['hr.attendance.overtime.ruleset'].create([{
            'name': 'Test Ruleset Version 1',
            'rule_ids': [
                Command.create({
                    'name': "> 8h/d",
                    'base_off': 'quantity',
                    'quantity_period': 'day',
                    'expected_hours_from_contract': False,
                    'expected_hours': 9,
                    'paid': True
                }),
            ],
        }, {
            'name': 'Test Ruleset Version 2',
            'rule_ids': [
                Command.create({
                    'name': "> 8h/d",
                    'base_off': 'quantity',
                    'quantity_period': 'day',
                    'expected_hours_from_contract': False,
                    'expected_hours': 9,
                    'paid': True
                }),
            ],
        }])
        self.env['hr.version'].create([{
            'name': 'version old',
            'employee_id': self.employee.id,
            'structure_type_id': self.employee.version_id.structure_type_id.id,
            'date_version': date(2020, 3, 1),
            'date_start': datetime(2020, 3, 1, 0, 0),
            'date_end': datetime(2020, 4, 1, 23, 59, 59),
            'ruleset_id': ruleset_1.id,
            'resource_calendar_id':  self.company.resource_calendar_id.id,
        }, {
            'name': 'version new',
            'employee_id': self.employee.id,
            'structure_type_id': self.employee.version_id.structure_type_id.id,
            'date_version': date(2020, 4, 1),
            'date_start': datetime(2020, 4, 2, 0, 0),
            'date_end': False,
            'ruleset_id': ruleset_2.id,
            'resource_calendar_id':  self.company.resource_calendar_id.id,
        }])

        attendance_1, attendance_2 = self.env['hr.attendance'].create([{
            'employee_id': self.employee.id,
            'check_in': datetime(2020, 3, 4, 7, 0),
            'check_out': datetime(2020, 3, 4, 18, 0)
            }, {
            'employee_id': self.employee.id,
            'check_in': datetime(2020, 4, 4, 10, 0),
            'check_out': datetime(2020, 4, 4, 19, 30)
        }])
        ruleset_1.action_regenerate_overtimes()
        self.assertEqual(attendance_1.overtime_hours, 1)
        self.assertEqual(attendance_2.overtime_hours, 0.5)

    def test_attendance_expected_hours_compute(self):
        att = self.env['hr.attendance'].create({
            'employee_id': self.employee.id,
            'check_in': datetime(2023, 1, 4, 7, 0),
            'check_out': datetime(2023, 1, 4, 18, 0)
        })
        self.assertEqual(att.expected_hours, 8)
        att.check_out = datetime(2023, 1, 4, 8, 0)
        self.assertEqual(att.expected_hours, 1)  # This is because absence management is off

        self.employee.ruleset_id.company_id.absence_management = True
        att.check_out = datetime(2023, 1, 4, 9, 0)
        self.assertEqual(att.expected_hours, 8)

        self.assertEqual(att.overtime_hours, -6)
        self.assertEqual(att.validated_overtime_hours, -6)
        self.assertEqual(att.expected_hours, 8)

        att.linked_overtime_ids.manual_duration = 10
        self.assertEqual(att.overtime_hours, -6)
        self.assertEqual(att.validated_overtime_hours, 10)
        self.assertEqual(att.expected_hours, 8)

    def test_overtime_line_access_rules(self):
        """Test ir.rules on hr.attendance.overtime.line per access level:
        - own_reader (base user): can only read their own overtime lines.
        - officer: can read overtime lines of managed employees and their own.
        - admin (group_hr_attendance_user): can read all overtime lines.
        """
        officer_user = new_test_user(self.env, login='officer', groups='base.group_user,hr_attendance.group_hr_attendance_officer', company_id=self.company.id).with_company(self.company)
        own_reader_user = new_test_user(self.env, login='own_reader', groups='hr_attendance.group_hr_attendance_own_reader', company_id=self.company.id).with_company(self.company)
        officer_employee, own_reader_employee = self.env['hr.employee'].create([{
            'name': 'Officer Employee',
            'user_id': officer_user.id,
            'company_id': self.company.id,
            'tz': 'UTC',
            'date_version': date(2020, 1, 1),
            'contract_date_start': date(2020, 1, 1),
            'resource_calendar_id': self.company.resource_calendar_id.id,
            'ruleset_id': self.ruleset.id,
        }, {
            'name': 'Own Reader Employee',
            'user_id': own_reader_user.id,
            'company_id': self.company.id,
            'tz': 'UTC',
            'date_version': date(2020, 1, 1),
            'contract_date_start': date(2020, 1, 1),
            'resource_calendar_id': self.company.resource_calendar_id.id,
            'ruleset_id': self.ruleset.id,
        }])
        self.other_employee.attendance_manager_id = officer_user.id
        self.env['hr.attendance'].create([{
            'employee_id': own_reader_employee.id,
            'check_in': datetime(2021, 1, 4, 8, 0),
            'check_out': datetime(2021, 1, 4, 20, 0),
        }, {
            'employee_id': officer_employee.id,
            'check_in': datetime(2021, 1, 4, 8, 0),
            'check_out': datetime(2021, 1, 4, 20, 0),
        }, {
            'employee_id': self.other_employee.id,
            'check_in': datetime(2021, 1, 4, 8, 0),
            'check_out': datetime(2021, 1, 4, 20, 0),
        }])
        overtime_line = self.env['hr.attendance.overtime.line']
        all_lines = overtime_line.search([('employee_id', 'in', (own_reader_employee | officer_employee | self.other_employee).ids)])
        self.assertEqual(len(all_lines.employee_id), 3)

        own_reader_lines = overtime_line.with_user(own_reader_user).search([])
        self.assertEqual(own_reader_lines.employee_id, own_reader_employee)

        officer_lines = overtime_line.with_user(officer_user).search([])
        self.assertEqual(officer_lines.mapped('employee_id'), officer_employee | self.other_employee)

        admin_lines = overtime_line.with_user(self.user).search([])
        self.assertIn(own_reader_employee, admin_lines.employee_id)
        self.assertIn(officer_employee, admin_lines.employee_id)
        self.assertIn(self.other_employee, admin_lines.employee_id)
