# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from datetime import datetime, date, timedelta
import time
from dateutil.relativedelta import relativedelta
from freezegun import freeze_time
from pytz import timezone

from odoo import fields, Command
from odoo.exceptions import ValidationError
from odoo.tools import mute_logger
from odoo.tests.common import Form
from odoo.tests import tagged

from odoo.exceptions import UserError

from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon

@tagged('leave_requests')
class TestLeaveRequests(TestHrHolidaysCommon):

    def _check_holidays_status(self, holiday_status, employee, ml, lt, rl, vrl):
        result = holiday_status.get_allocation_data(employee)[employee][0][1]
        self.assertEqual(
            result['max_leaves'], ml,
            'hr_holidays: wrong type days computation')
        self.assertEqual(
            result['leaves_taken'], lt,
            'hr_holidays: wrong type days computation')
        self.assertEqual(
            result['remaining_leaves'], rl,
            'hr_holidays: wrong type days computation')
        self.assertEqual(
            result['virtual_remaining_leaves'], vrl,
            'hr_holidays: wrong type days computation')

    @classmethod
    def setUpClass(cls):
        super(TestLeaveRequests, cls).setUpClass()

        # Make sure we have the rights to create, validate and delete the leaves, leave types and allocations
        LeaveType = cls.env['hr.leave.type'].with_user(cls.user_hrmanager_id).with_context(tracking_disable=True)

        cls.holidays_type_1 = LeaveType.create({
            'name': 'NotLimitedHR',
            'requires_allocation': 'no',
            'leave_validation_type': 'hr',
        })
        cls.holidays_type_2 = LeaveType.create({
            'name': 'Limited',
            'requires_allocation': 'yes',
            'employee_requests': 'yes',
            'leave_validation_type': 'hr',
        })
        cls.holidays_type_3 = LeaveType.create({
            'name': 'TimeNotLimited',
            'requires_allocation': 'no',
            'leave_validation_type': 'manager',
        })

        cls.holidays_type_4 = LeaveType.create({
            'name': 'Limited with 2 approvals',
            'requires_allocation': 'yes',
            'employee_requests': 'yes',
            'leave_validation_type': 'both',
        })
        cls.holidays_support_document = LeaveType.create({
            'name': 'Time off with support document',
            'support_document': True,
            'requires_allocation': 'no',
            'leave_validation_type': 'no_validation',
        })

        cls.set_employee_create_date(cls.employee_emp_id, '2010-02-03 00:00:00')
        cls.set_employee_create_date(cls.employee_hruser_id, '2010-02-03 00:00:00')

    def _check_holidays_count(self, holidays_count_result, ml, lt, rl, vrl, vlt):
        self.assertEqual(holidays_count_result['max_leaves'], ml)
        self.assertEqual(holidays_count_result['remaining_leaves'], rl)
        self.assertEqual(holidays_count_result['virtual_remaining_leaves'], vrl)
        self.assertEqual(holidays_count_result['leaves_taken'], lt)
        self.assertEqual(holidays_count_result['virtual_leaves_taken'], vlt)

    @classmethod
    def set_employee_create_date(cls, _id, newdate):
        """ This method is a hack in order to be able to define/redefine the create_date
            of the employees.
            This is done in SQL because ORM does not allow to write onto the create_date field.
        """
        cls.env.cr.execute("""
                       UPDATE
                       hr_employee
                       SET create_date = '%s'
                       WHERE id = %s
                       """ % (newdate, _id))

    @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
    def test_overlapping_requests(self):
        """  Employee cannot create a new leave request at the same time, avoid interlapping  """
        self.env['hr.leave'].with_user(self.user_employee_id).create({
            'name': 'Hol11',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_1.id,
            'request_date_from': (date.today() - relativedelta(days=1)),
            'request_date_to': date.today(),
        })

        with self.assertRaises(ValidationError):
            self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Hol21',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_1.id,
                'request_date_from': (datetime.today() - relativedelta(days=1)),
                'request_date_to': datetime.today(),
            })

    def test_limited_type_not_enough_days(self):
        with freeze_time('2022-01-05'):
            self.env['hr.leave.allocation'].with_user(self.user_hruser_id).create({
                'name': 'Days for limited category',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_2.id,
                'number_of_days': 2,
                'state': 'confirm',
                'date_from': time.strftime('%Y-1-1'),
                'date_to': time.strftime('%Y-12-31'),
            })

            # Employee cannot take a leave longer than the allocation
            with self.assertRaises(ValidationError):
                self.env['hr.leave'].with_user(self.user_employee_id).create({
                    'name': 'Invalid Hol21',
                    'employee_id': self.employee_emp_id,
                    'holiday_status_id': self.holidays_type_2.id,
                    'request_date_from': time.strftime('2022-02-01'),
                    'request_date_to': time.strftime('2022-02-04'),
                })

            # A leave cannot be modified so that it's longer than the allocation
            valid_leave = self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Valid Hol21',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_2.id,
                'request_date_from': time.strftime('2022-02-02'),
                'request_date_to': time.strftime('2022-02-03'),
            })
            with self.assertRaises(ValidationError):
                valid_leave.write({
                    'request_date_from': time.strftime('2022-02-01'),
                    'request_date_to': time.strftime('2022-02-05'),
                })

    @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
    def test_limited_type_days_left(self):
        """  Employee creates a leave request in a limited category and has enough days left  """
        with freeze_time('2022-01-05'):
            self.env['hr.leave.allocation'].with_user(self.user_hruser_id).create({
                'name': 'Days for limited category',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_2.id,
                'number_of_days': 2,
                'state': 'confirm',
                'date_from': time.strftime('%Y-1-1'),
                'date_to': time.strftime('%Y-12-31'),
            })

            holiday_status = self.holidays_type_2.with_user(self.user_employee_id)
            self._check_holidays_status(holiday_status, self.employee_emp, 2.0, 0.0, 2.0, 2.0)

            hol = self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Hol11',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_2.id,
                'request_date_from': (datetime.today() - relativedelta(days=1)),
                'request_date_to': datetime.today(),
            })

            holiday_status.invalidate_model()
            self._check_holidays_status(holiday_status, self.employee_emp, 2.0, 0.0, 2.0, 0.0)

            hol.with_user(self.user_hrmanager_id).action_approve()

            holiday_status.invalidate_model(['max_leaves'])
            self._check_holidays_status(holiday_status, self.employee_emp, 2.0, 2.0, 0.0, 0.0)

    @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
    def test_accrual_validity_time_valid(self):
        """  Employee ask leave during a valid validity time """

        allocation = self.env['hr.leave.allocation'].with_user(self.user_hrmanager_id).create({
            'name': 'Sick Time Off',
            'holiday_status_id': self.holidays_type_2.id,
            'employee_id': self.employee_emp.id,
            'date_from': fields.Datetime.from_string('2017-01-01 00:00:00'),
            'date_to': fields.Datetime.from_string('2017-06-01 00:00:00'),
            'number_of_days': 10,
        })
        allocation.action_validate()

        self.env['hr.leave'].with_user(self.user_employee_id).create({
            'name': 'Valid time period',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_2.id,
            'request_date_from': fields.Date.from_string('2017-03-03'),
            'request_date_to': fields.Date.from_string('2017-03-11'),
        })

    @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
    def test_department_leave(self):
        """ Create a department leave """
        self.employee_hrmanager.write({'department_id': self.hr_dept.id})
        self.assertFalse(self.env['hr.leave'].search([('employee_id', 'in', self.hr_dept.member_ids.ids)]))
        leave_form = Form(self.env['hr.leave'].with_user(self.user_hrmanager), view='hr_holidays.hr_leave_view_form_manager')
        leave_form.holiday_type = 'department'
        leave_form.department_id = self.hr_dept
        leave_form.holiday_status_id = self.holidays_type_1
        leave_form.request_date_from = date(2019, 5, 6)
        leave_form.request_date_to = date(2019, 5, 6)
        leave = leave_form.save()
        leave.action_approve()
        member_ids = self.hr_dept.member_ids.ids
        self.assertEqual(self.env['hr.leave'].search_count([('employee_id', 'in', member_ids)]), len(member_ids), "Time Off should be created for members of department")

    @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
    def test_allocation_request(self):
        """ Create an allocation request """
        # employee should be set to current user
        allocation_form = Form(self.env['hr.leave.allocation'].with_user(self.user_employee))
        allocation_form.holiday_status_id = self.holidays_type_2
        allocation_form.date_from = date(2019, 5, 6)
        allocation_form.date_to = date(2019, 5, 6)
        allocation_form.name = 'New Allocation Request'
        allocation_form.save()

    @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
    def test_employee_is_absent(self):
        """ Only the concerned employee should be considered absent """
        user_employee_leave = self.env['hr.leave'].with_user(self.user_employee_id).create({
            'name': 'Hol11',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_1.id,
            'request_date_from': (date.today() - relativedelta(days=1)),
            'request_date_to': date.today() + relativedelta(days=1),
        })
        (self.employee_emp | self.employee_hrmanager).mapped('is_absent')  # compute in batch
        self.assertFalse(self.employee_emp.is_absent, "He should not be considered absent")
        self.assertFalse(self.employee_hrmanager.is_absent, "He should not be considered absent")

        user_employee_leave.sudo().write({
            'state': 'validate',
        })
        (self.employee_emp | self.employee_hrmanager)._compute_leave_status()
        self.assertTrue(self.employee_emp.is_absent, "He should be considered absent")
        self.assertFalse(self.employee_hrmanager.is_absent, "He should not be considered absent")

    @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
    def test_timezone_employee_leave_request(self):
        """ Create a leave request for an employee in another timezone """
        self.employee_emp.tz = 'NZ'  # GMT+12
        leave = self.env['hr.leave'].new({
            'employee_id': self.employee_emp.id,
            'holiday_status_id': self.holidays_type_1.id,
            'request_unit_hours': True,
            'request_date_from': date(2019, 5, 6),
            'request_date_to': date(2019, 5, 6),
            'request_hour_from': '8',  # 8:00 AM in the employee's timezone
            'request_hour_to': '17',  # 5:00 PM in the employee's timezone
        })
        self.assertEqual(leave.date_from, datetime(2019, 5, 5, 20, 0, 0), "It should have been localized before saving in UTC")
        self.assertEqual(leave.date_to, datetime(2019, 5, 6, 5, 0, 0), "It should have been localized before saving in UTC")

    @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
    def test_timezone_company_leave_request(self):
        """ Create a leave request for a company in another timezone """
        company = self.env['res.company'].create({'name': "Hergé"})
        company.resource_calendar_id.tz = 'NZ'  # GMT+12
        leave = self.env['hr.leave'].new({
            'employee_id': self.employee_emp.id,
            'holiday_status_id': self.holidays_type_1.id,
            'request_unit_hours': True,
            'holiday_type': 'company',
            'mode_company_id': company.id,
            'request_date_from': date(2019, 5, 6),
            'request_date_to': date(2019, 5, 6),
            'request_hour_from': '8',  # 8:00 AM in the company's timezone
            'request_hour_to': '17',  # 5:00 PM in the company's timezone
        })
        self.assertEqual(leave.date_from, datetime(2019, 5, 6, 6, 0, 0), "It should have been localized in the Employee timezone")
        self.assertEqual(leave.date_to, datetime(2019, 5, 6, 15, 0, 0), "It should have been localized in the Employee timezone")

    @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
    def test_timezone_company_validated(self):
        """ Create a leave request for a company in another timezone and validate it """
        self.env.user.tz = 'NZ' # GMT+12
        company = self.env['res.company'].create({'name': "Hergé"})
        employee = self.env['hr.employee'].create({'name': "Remi", 'company_id': company.id})
        leave_form = Form(self.env['hr.leave'], view='hr_holidays.hr_leave_view_form_manager')
        leave_form.holiday_type = 'company'
        leave_form.mode_company_id = company
        leave_form.holiday_status_id = self.holidays_type_1
        leave_form.request_date_from = date(2019, 5, 6)
        leave_form.request_date_to = date(2019, 5, 6)
        leave = leave_form.save()
        leave.state = 'confirm'
        leave.action_validate()
        employee_leave = self.env['hr.leave'].search([('employee_id', '=', employee.id)])
        self.assertEqual(
            employee_leave.request_date_from, date(2019, 5, 6),
            "Timezone should be be adapted on the employee leave"
        )

    @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
    def test_timezone_department_leave_request(self):
        """ Create a leave request for a department in another timezone """
        company = self.env['res.company'].create({'name': "Hergé"})
        company.resource_calendar_id.tz = 'NZ'  # GMT+12
        department = self.env['hr.department'].create({'name': "Museum", 'company_id': company.id})
        leave = self.env['hr.leave'].new({
            'employee_id': self.employee_emp.id,
            'holiday_status_id': self.holidays_type_1.id,
            'request_unit_hours': True,
            'holiday_type': 'department',
            'department_id': department.id,
            'request_date_from': date(2019, 5, 6),
            'request_date_to': date(2019, 5, 6),
            'request_hour_from': '8',  # 8:00 AM in the department's timezone
            'request_hour_to': '17',  # 5:00 PM in the department's timezone
        })
        self.assertEqual(leave.date_from, datetime(2019, 5, 6, 6, 0, 0), "It should have been localized in the Employee timezone")
        self.assertEqual(leave.date_to, datetime(2019, 5, 6, 15, 0, 0), "It should have been localized in the Employee timezone")

    def test_number_of_hours_display(self):
        # Test that the field number_of_hours_dispay doesn't change
        # after time off validation, as it takes the attendances
        # minus the resource leaves to compute that field.
        calendar = self.env['resource.calendar'].create({
            'name': 'Monday Morning Else Full Time 38h/week',
            'hours_per_day': 7.6,
            'attendance_ids': [
                (0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8.5, 'hour_to': 12.5, 'day_period': 'morning'}),
                (0, 0, {'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8.5, 'hour_to': 12.5, 'day_period': 'morning'}),
                (0, 0, {'name': 'Tuesday Lunch', 'dayofweek': '1', 'hour_from': 12.5, 'hour_to': 13, 'day_period': 'lunch'}),
                (0, 0, {'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17.5, 'day_period': 'afternoon'}),
                (0, 0, {'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8.5, 'hour_to': 12.5, 'day_period': 'morning'}),
                (0, 0, {'name': 'Wednesday Lunch', 'dayofweek': '2', 'hour_from': 12.5, 'hour_to': 13, 'day_period': 'lunch'}),
                (0, 0, {'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17.5, 'day_period': 'afternoon'}),
                (0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8.5, 'hour_to': 12.5, 'day_period': 'morning'}),
                (0, 0, {'name': 'Thursday Lunch', 'dayofweek': '3', 'hour_from': 12.5, 'hour_to': 13, 'day_period': 'lunch'}),
                (0, 0, {'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17.5, 'day_period': 'afternoon'}),
                (0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8.5, 'hour_to': 12.5, 'day_period': 'morning'}),
                (0, 0, {'name': 'Friday Lunch', 'dayofweek': '4', 'hour_from': 12.5, 'hour_to': 13, 'day_period': 'lunch'}),
                (0, 0, {'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17.5, 'day_period': 'afternoon'})
            ],
        })
        employee = self.employee_emp
        employee.resource_calendar_id = calendar
        self.env.user.company_id.resource_calendar_id = calendar
        leave_type = self.env['hr.leave.type'].create({
            'name': 'Paid Time Off',
            'request_unit': 'hour',
            'leave_validation_type': 'both',
        })
        self.env['hr.leave.allocation'].create({
            'name': '20 days allocation',
            'holiday_status_id': leave_type.id,
            'number_of_days': 20,
            'employee_id': employee.id,
            'state': 'confirm',
            'date_from': time.strftime('2018-1-1'),
            'date_to': time.strftime('%Y-1-1'),
        })

        leave1 = self.env['hr.leave'].create({
            'name': 'Holiday 1 week',
            'employee_id': employee.id,
            'holiday_status_id': leave_type.id,
            'request_date_from': fields.Date.from_string('2019-12-23'),
            'request_date_to': fields.Date.from_string('2019-12-27'),
        })

        self.assertEqual(leave1.number_of_hours_display, 38)
        leave1.action_approve()
        self.assertEqual(leave1.number_of_hours_display, 38)
        leave1.action_validate()
        self.assertEqual(leave1.number_of_hours_display, 38)

        leave2 = self.env['hr.leave'].create({
            'name': 'Holiday 1 Day',
            'employee_id': employee.id,
            'holiday_status_id': leave_type.id,
            'request_date_from': fields.Datetime.from_string('2019-12-30'),
            'request_date_to': fields.Datetime.from_string('2019-12-30'),
        })

        self.assertEqual(leave2.number_of_hours_display, 4)
        leave2.action_approve()
        self.assertEqual(leave2.number_of_hours_display, 4)
        leave2.action_validate()
        self.assertEqual(leave2.number_of_hours_display, 4)

    def test_number_of_hours_display_global_leave(self):
        # Check that the field number_of_hours_display
        # takes the global leaves into account, even
        # after validation
        calendar = self.env['resource.calendar'].create({
            'name': 'Classic 40h/week',
            'hours_per_day': 8.0,
            'attendance_ids': [
                (0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
                (0, 0, {'name': 'Monday Lunch', 'dayofweek': '0', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'}),
                (0, 0, {'name': 'Monday Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
                (0, 0, {'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
                (0, 0, {'name': 'Tuesday Lunch', 'dayofweek': '1', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'}),
                (0, 0, {'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
                (0, 0, {'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
                (0, 0, {'name': 'Wednesday Lunch', 'dayofweek': '2', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'}),
                (0, 0, {'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
                (0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
                (0, 0, {'name': 'Thursday Lunch', 'dayofweek': '3', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'}),
                (0, 0, {'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
                (0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
                (0, 0, {'name': 'Friday Lunch', 'dayofweek': '4', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'}),
                (0, 0, {'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'})
            ],
            'global_leave_ids': [(0, 0, {
                'name': 'Christmas Time Off',
                'date_from': fields.Datetime.from_string('2019-12-25 00:00:00'),
                'date_to': fields.Datetime.from_string('2019-12-26 23:59:59'),
                'resource_id': False,
                'time_type': 'leave',
            })]
        })
        employee = self.employee_emp
        employee.resource_calendar_id = calendar
        self.env.user.company_id.resource_calendar_id = calendar
        leave_type = self.env['hr.leave.type'].create({
            'name': 'Sick',
            'request_unit': 'hour',
            'leave_validation_type': 'both',
            'requires_allocation': 'no',
        })
        leave1 = self.env['hr.leave'].create({
            'name': 'Sick 1 week during christmas snif',
            'employee_id': employee.id,
            'holiday_status_id': leave_type.id,
            'request_date_from': fields.Date.from_string('2019-12-23'),
            'request_date_to': fields.Date.from_string('2019-12-27'),
        })
        self.assertEqual(leave1.number_of_hours_display, 24)
        leave1.action_approve()
        self.assertEqual(leave1.number_of_hours_display, 24)
        leave1.action_validate()
        self.assertEqual(leave1.number_of_hours_display, 24)

    def _test_leave_with_tz(self, tz, local_date_from, local_date_to, number_of_days):
        self.user_employee.tz = tz
        tz = timezone(tz)

        # We use new instead of create to avoid the leaves generated for the
        # different timezones clashing with each other.
        leave = self.env['hr.leave'].with_user(self.user_employee_id).new({
            'name': 'Test',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_1.id,
            'request_date_from': local_date_from,
            'request_date_to': local_date_to,
        })
        self.assertEqual(leave.number_of_days, number_of_days)

    @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
    def test_leave_defaults_with_timezones(self):
        """ Make sure that leaves start with correct defaults for non-UTC timezones """
        timezones_to_test = ('UTC', 'Pacific/Midway', 'US/Pacific', 'Asia/Taipei', 'Pacific/Kiritimati')  # UTC, UTC -11, UTC -8, UTC +8, UTC +14

        #     January 2020
        # Su Mo Tu We Th Fr Sa
        #           1  2  3  4
        #  5  6  7  8  9 10 11
        # 12 13 14 15 16 17 18
        # 19 20 21 22 23 24 25
        # 26 27 28 29 30 31
        local_date_from = date(2020, 1, 1)
        local_date_to = date(2020, 1, 1)
        for tz in timezones_to_test:
            self._test_leave_with_tz(tz, local_date_from, local_date_to, 1)

        # We, Th, Fr, Mo, Tu, We => 6 days
        local_date_from = date(2020, 1, 2)
        local_date_to = date(2020, 1, 9)
        for tz in timezones_to_test:
            self._test_leave_with_tz(tz, local_date_from, local_date_to, 6)

    def test_expired_allocation(self):
        self.env['hr.leave.allocation'].create({
            'name': 'Expired Allocation',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_2.id,
            'number_of_days': 20,
            'state': 'confirm',
            'date_from': '2020-01-01',
            'date_to': '2020-12-31',
        })

        with self.assertRaises(ValidationError):
            self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Holiday Request',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_2.id,
                'request_date_from': '2021-09-01',
                'request_date_to': '2021-09-01',
            })
        self.env['hr.leave'].with_user(self.user_employee_id).create({
            'name': 'Holiday Request',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_2.id,
            'request_date_from': '2020-09-01',
            'request_date_to': '2020-09-01',
        })

    def test_no_days_expired(self):
        # First expired allocation
        self.env['hr.leave.allocation'].create({
            'name': 'Expired Allocation',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_2.id,
            'number_of_days': 20,
            'state': 'confirm',
            'date_from': '2020-01-01',
            'date_to': '2020-12-31',
        })
        self.env['hr.leave.allocation'].create({
            'name': 'Expired Allocation',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_2.id,
            'number_of_days': 3,
            'state': 'confirm',
            'date_from': '2021-01-01',
            'date_to': '2021-12-31',
        })
        # Try creating a request that could be validated if allocation1 was still valid
        with self.assertRaises(ValidationError):
            self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Holiday Request',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_2.id,
                'request_date_from': '2021-09-06',
                'request_date_to': '2021-09-10',
            })
        # This time we have enough days
        self.env['hr.leave'].with_user(self.user_employee_id).create({
            'name': 'Holiday Request',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_2.id,
            'request_date_from': '2021-09-06',
            'request_date_to': '2021-09-08',
        })

    def test_company_leaves(self):
        # First expired allocation
        self.env['hr.leave.allocation'].create({
            'name': 'Allocation',
            'holiday_type': 'company',
            'mode_company_id': self.env.company.id,
            'holiday_status_id': self.holidays_type_1.id,
            'number_of_days': 20,
            'state': 'confirm',
            'date_from': '2021-01-01',
        })

        req1_form = Form(self.env['hr.leave'].sudo())
        req1_form.employee_ids.add(self.employee_emp)
        req1_form.employee_ids.add(self.employee_hrmanager)
        req1_form.holiday_status_id = self.holidays_type_1
        req1_form.request_date_from = fields.Date.to_date('2021-12-06')
        req1_form.request_date_to = fields.Date.to_date('2021-12-08')

        self.assertEqual(req1_form.number_of_days_display, 3)
        req1_form.save().action_approve()

        req2_form = Form(self.env['hr.leave'].sudo())
        req2_form.employee_ids.add(self.employee_hruser)
        req2_form.holiday_status_id = self.holidays_type_1
        req2_form.request_date_from = fields.Date.to_date('2021-12-06')
        req2_form.request_date_to = fields.Date.to_date('2021-12-08')

        self.assertEqual(req2_form.number_of_days_display, 3)

    def test_leave_with_public_holiday_other_company(self):
        other_company = self.env['res.company'].create({
            'name': 'Test Company',
        })
        # Create a public holiday for the second company
        p_leave = self.env['resource.calendar.leaves'].create({
            'date_from': datetime(2022, 3, 11),
            'date_to': datetime(2022, 3, 11, 23, 59, 59),
        })
        p_leave.company_id = other_company

        leave = self.env['hr.leave'].with_user(self.user_employee_id).create({
            'name': 'Holiday Request',
            'holiday_type': 'employee',
            'employee_id': self.employee_emp.id,
            'holiday_status_id': self.holidays_type_1.id,
            'request_date_from': date(2022, 3, 11),
            'request_date_to': date(2022, 3, 11),
        })
        self.assertEqual(leave.number_of_days, 1)

    def test_several_allocations(self):
        allocation_vals = {
            'name': 'Allocation',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_2.id,
            'number_of_days': 5,
            'state': 'confirm',
            'date_from': '2022-01-01',
            'date_to': '2022-12-31',
        }
        self.env['hr.leave.allocation'].create(allocation_vals)
        self.env['hr.leave.allocation'].create(allocation_vals)

        # Able to create a leave of 10 days with two allocations of 5 days
        self.env['hr.leave'].with_user(self.user_employee_id).create({
            'name': 'Holiday Request',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_2.id,
            'request_date_from': '2022-01-01',
            'request_date_to': '2022-01-15',
        })

    def test_several_allocations_split(self):
        Allocation = self.env['hr.leave.allocation']
        allocation_vals = {
            'name': 'Allocation',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_2.id,
            'state': 'confirm',
            'date_from': '2022-01-01',
            'date_to': '2022-12-31',
        }
        Leave = self.env['hr.leave'].with_user(self.user_employee_id).sudo()
        leave_vals = {
            'name': 'Holiday Request',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_2.id,
        }

        for unit in ['hour', 'day']:
            self.holidays_type_2.request_unit = unit

            allocation_vals.update({'number_of_days': 4})
            allocation_4days = Allocation.create(allocation_vals)
            allocation_vals.update({'number_of_days': 1})
            allocation_1day = Allocation.create(allocation_vals)
            allocations = (allocation_4days + allocation_1day)

            leave_vals.update({
                'request_date_from': '2022-01-03',
                'request_date_to': '2022-01-06',
            })
            leave_draft = Leave.create(leave_vals)
            leave_draft.action_refuse()
            leave_vals.update({
                'request_date_from': '2022-01-03',
                'request_date_to': '2022-01-06',
            })
            leave_4days = Leave.create(leave_vals)
            leave_vals.update({
                'request_date_from': '2022-01-07',
                'request_date_to': '2022-01-07',
            })
            leave_1day = Leave.create(leave_vals)
            leaves = (leave_4days + leave_1day)
            leaves.action_approve()

            allocation_days = self.employee_emp._get_consumed_leaves(self.holidays_type_2)[0]

            self.assertEqual(
                allocation_days[self.employee_emp][self.holidays_type_2][allocation_4days]['leaves_taken'],
                leave_4days['number_of_%ss_display' % unit],
                'As 4 days were available in this allocation, they should have been taken')
            self.assertEqual(
                allocation_days[self.employee_emp][self.holidays_type_2][allocation_1day]['leaves_taken'],
                leave_1day['number_of_%ss_display' % unit],
                'As no days were available in previous allocation, they should have been taken in this one')
            leaves.action_refuse()
            allocations.action_refuse()

    def test_time_off_recovery_on_create(self):
        time_off = self.env['hr.leave'].create([
            {
                'name': 'Holiday Request',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_1.id,
                'request_date_from': '2021-12-06',
                'request_date_to': '2021-12-10',
            },
            {
                'name': 'Holiday Request',
                'employee_id': self.employee_hruser_id,
                'holiday_status_id': self.holidays_type_1.id,
                'request_date_from': '2021-12-06',
                'request_date_to': '2021-12-10',
            }
        ])
        self.assertEqual(time_off[0].number_of_days, 5)
        self.assertEqual(time_off[1].number_of_days, 5)
        self.env['resource.calendar.leaves'].create({
            'name': 'Global Time Off',
            'date_from': '2021-12-07 00:00:00',
            'date_to': '2021-12-07 23:59:59',
        })
        self.assertEqual(time_off[0].number_of_days, 4)
        self.assertEqual(time_off[1].number_of_days, 4)

    def test_time_off_recovery_on_write(self):
        global_time_off = self.env['resource.calendar.leaves'].create({
            'name': 'Global Time Off',
            'date_from': '2021-12-07 00:00:00',
            'date_to': '2021-12-07 23:59:59',
        })

        time_off_1 = self.env['hr.leave'].create({
            'name': 'Holiday Request',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_1.id,
            'request_date_from': '2021-12-06',
            'request_date_to': '2021-12-10',
        })
        self.assertEqual(time_off_1.number_of_days, 4)

        time_off_2 = self.env['hr.leave'].create({
            'name': 'Holiday Request',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_1.id,
            'request_date_from': '2021-12-13',
            'request_date_to': '2021-12-17',
        })
        self.assertEqual(time_off_2.number_of_days, 5)

        # adding 1 day to the global time off
        global_time_off.write({
            'date_to': '2021-12-08 23:59:59',
        })
        self.assertEqual(time_off_1.number_of_days, 3)

        # moving the global time off to the next week
        global_time_off.write({
            'date_from': '2021-12-15 00:00:00',
            'date_to': '2021-12-15 23:59:59',
        })
        self.assertEqual(time_off_1.number_of_days, 5)
        self.assertEqual(time_off_2.number_of_days, 4)

    def test_time_off_recovery_on_unlink(self):
        global_time_off = self.env['resource.calendar.leaves'].create({
            'name': 'Global Time Off',
            'date_from': '2021-12-07 00:00:00',
            'date_to': '2021-12-07 23:59:59',
        })
        time_off = self.env['hr.leave'].create({
            'name': 'Holiday Request',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_1.id,
            'request_date_from': '2021-12-06',
            'request_date_to': '2021-12-10',
        })
        self.assertEqual(time_off.number_of_days, 4)
        global_time_off.unlink()
        self.assertEqual(time_off.number_of_days, 5)

    def test_time_off_duration_zero(self):
        time_off = self.env['hr.leave'].create({
            'name': 'Holiday Request',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': self.holidays_type_1.id,
            'request_date_from': '2021-11-15',
            'request_date_to': '2021-11-19',
        })
        self.assertEqual(time_off.number_of_days, 5)
        self.env['resource.calendar.leaves'].create({
            'name': 'Global Time Off',
            'date_from': '2021-11-15 00:00:00',
            'date_to': '2021-11-19 23:59:59',
        })
        self.assertEqual(time_off.active, True)
        self.assertEqual(time_off.number_of_days, 0)

    def test_holiday_type_requires_no_allocation(self):
        # holiday_type_2 initially requires an allocation
        # Once an allocation is granted and a leave is taken,
        # the holiday type is changed to no longer require an allocation.
        # Leaves taken and available days should be correctly computed.
        with freeze_time('2020-09-15'):
            allocation = self.env['hr.leave.allocation'].create({
                'name': 'Expired Allocation',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_2.id,
                'number_of_days': 5,
                'state': 'confirm',
                'date_from': '2020-01-01',
                'date_to': '2020-12-31',
            })
            leave1 = self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Holiday Request',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_2.id,
                'request_date_from': '2020-09-07',
                'request_date_to': '2020-09-09',
            })

            self._check_holidays_count(
                self.employee_emp._get_consumed_leaves(self.holidays_type_2)[0][self.employee_emp][self.holidays_type_2][allocation],
                ml=5, lt=0, rl=5, vrl=2, vlt=3,
            )

            self.holidays_type_2.requires_allocation = 'no'
            leave2 = self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Holiday Request',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_2.id,
                'request_date_from': '2020-07-06',
                'request_date_to': '2020-07-08',
            })

            # The 5 allocation days are not consumed anymore
            # virtual_remaining_leaves reflect the total number of leave days taken
            self._check_holidays_count(
                self.employee_emp._get_consumed_leaves(self.holidays_type_2)[0][self.employee_emp][self.holidays_type_2][False],
                ml=0, lt=0, rl=0, vrl=0, vlt=6,
            )

            leave1.with_user(self.user_hrmanager_id).action_approve()
            leave2.with_user(self.user_hrmanager_id).action_approve()

            # leaves_taken and virtual_leaves_taken reflect the total number of leave days taken
            self._check_holidays_count(
                self.employee_emp._get_consumed_leaves(self.holidays_type_2)[0][self.employee_emp][self.holidays_type_2][False],
                ml=0, lt=6, rl=0, vrl=0, vlt=6,
            )

    def test_archived_allocation(self):
        with freeze_time('2022-09-15'):
            allocation_2021 = self.env['hr.leave.allocation'].create({
                'name': 'Annual Time Off 2021',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_2.id,
                'number_of_days': 10,
                'state': 'confirm',
                'date_from': '2021-06-01',
                'date_to': '2021-12-31',
            })
            self.env['hr.leave.allocation'].create({
                'name': 'Annual Time Off 2022',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_2.id,
                'number_of_days': 20,
                'state': 'confirm',
                'date_from': '2022-01-01',
                'date_to': '2022-12-31',
            })

            # Leave taken in 2021
            leave_2021 = self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Holiday Request',
                'holiday_type': 'employee',
                'employee_id': self.employee_emp.id,
                'holiday_status_id': self.holidays_type_2.id,
                'request_date_from': datetime(2021, 8, 9),
                'request_date_to': datetime(2021, 8, 13),
            })
            leave_2021.with_user(self.user_hrmanager_id).action_approve()

            # The holidays count only takes into account the valid allocations at that date
            self._check_holidays_count(
                self.holidays_type_2.get_allocation_data(self.employee_emp, target_date=date(2021, 12, 1))[self.employee_emp][0][1],
                ml=10, lt=5, rl=5, vrl=5, vlt=5,
            )

            # Days remaining before the allocation ends is equal to 1 because there is only one day remaining in the allocation based on its validity
            self.assertEqual(
                self.holidays_type_2.get_allocation_data(self.employee_emp, target_date=date(2021, 12, 31))[self.employee_emp][0][1]['closest_allocation_duration'],
                1,
                "Only one day should remain before the allocation expires"
            )

            leave_2022 = self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Holiday Request',
                'holiday_type': 'employee',
                'employee_id': self.employee_emp.id,
                'holiday_status_id': self.holidays_type_2.id,
                'request_date_from': datetime(2022, 8, 9),
                'request_date_to': datetime(2022, 8, 13),
            })
            leave_2022.with_user(self.user_hrmanager_id).action_approve()

            # The holidays count in 2022 is not affected by the first leave taken in 2021
            self._check_holidays_count(
                self.holidays_type_2.get_allocation_data(self.employee_emp)[self.employee_emp][0][1],
                ml=20, lt=4, rl=16, vrl=16, vlt=4,
            )

            # The holidays count in 2021 is not affected by the leave taken in 2022
            self._check_holidays_count(
                self.holidays_type_2.get_allocation_data(self.employee_emp, target_date=date(2021, 12, 1))[self.employee_emp][0][1],
                ml=10, lt=5, rl=5, vrl=5, vlt=5,
            )

            with self.assertRaisesRegex(UserError,
                r'You cannot archive an allocation which is in confirm or validate state.'):

                # The logic of the test is relevant, so we do not remove it.
                # However, the behaviour will change.
                # Indeed, a confirmed or validated allocation cannot be archived

                allocation_2021.active = False

                # If the allocation is archived, the leaves taken are not taken into account anymore
                # As there is no valid allocation, then there is no upcoming one (closest_allocation_to_expire)
                self._check_holidays_count(
                    self.holidays_type_2.get_allocation_data(self.employee_emp, target_date=date(2021, 12, 1))[self.employee_emp][0][1],
                    ml=0, lt=0, rl=0, vrl=0, vlt=0,
                )
                self.assertEqual(
                    self.holidays_type_2.get_allocation_data(self.employee_emp, target_date=date(2021, 12, 1))[self.employee_emp][0][1]['closest_allocation_expire'],
                    False,
                )

                # The holidays count in 2022 is not affected by the archived allocation in 2021
                self._check_holidays_count(
                    self.holidays_type_2.get_allocation_data(self.employee_emp)[self.employee_emp][0][1],
                    ml=20, lt=4, rl=16, vrl=16, vlt=4,
                )

                allocation_2021.active = True

                # The holidays count in 2021 is back to what it was when the allocation was active
                self._check_holidays_count(
                    self.holidays_type_2.get_allocation_data(self.employee_emp, target_date=date(2021, 12, 1))[self.employee_emp][0][1],
                    ml=10, lt=5, rl=5, vrl=5, vlt=5,
                )

                # The holidays count in 2022 is still not affected by the allocation in 2021
                self._check_holidays_count(
                    self.holidays_type_2.get_allocation_data(self.employee_emp)[self.employee_emp][0][1],
                    ml=20, lt=4, rl=16, vrl=16, vlt=4,
                )

    def test_cancel_leave(self):
        with freeze_time('2020-09-15'):
            self.env['hr.leave.allocation'].create({
                'name': 'Annual Time Off',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_4.id,
                'number_of_days': 20,
                'state': 'confirm',
                'date_from': '2020-01-01',
                'date_to': '2020-12-31',
            })

            leave = self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Holiday Request',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_type_4.id,
                'request_date_from': '2020-09-21',
                'request_date_to': '2020-09-23',
            })

            # A meeting is only created once the leave is validated
            self.assertFalse(leave.meeting_id)
            leave.with_user(self.user_hrmanager_id).action_approve()
            self.assertFalse(leave.meeting_id)

            # A meeting is created in the user's calendar when a leave is validated
            leave.with_user(self.user_hrmanager_id).action_validate()
            self.assertTrue(leave.meeting_id.active)

            # The meeting is archived when the leave is cancelled
            leave.with_user(self.user_employee_id)._action_user_cancel('Cancel leave')
            self.assertFalse(leave.meeting_id.active)

    def test_create_support_document_in_the_past(self):
        with freeze_time('2022-10-19'):
            self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Holiday Request',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': self.holidays_support_document.id,
                'request_date_from': '2022-10-17',
                'request_date_to': '2022-10-17',
                'supported_attachment_ids': [(6, 0, [])],  # Sent by webclient
            })

    def test_prevent_misplacement_of_allocations_without_end_date(self):
        """
            The objective is to check that it is not possible to place leaves
            for which the interval does not correspond to the interval of allocations.
        """
        holiday_type_A = self.env['hr.leave.type'].with_user(self.user_hrmanager_id).with_context(tracking_disable=True).create({
            'name': 'Type A',
            'requires_allocation': 'yes',
            'employee_requests': 'yes',
            'leave_validation_type': 'hr',
        })

        # Create allocations with no end date
        allocations = self.env['hr.leave.allocation'].create([
            {
                'name': 'Type A march 1 day without date to',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': holiday_type_A.id,
                'number_of_days': 1,
                'state': 'confirm',
                'date_from': '2023-01-03',
            },
            {
                'name': 'Type A april 5 day without date to',
                'employee_id': self.employee_emp_id,
                'holiday_status_id': holiday_type_A.id,
                'number_of_days': 5,
                'state': 'confirm',
                'date_from': '2023-04-01',
            },
        ])

        allocations.action_validate()

        trigger_error_leave = {
            'name': 'Holiday Request',
            'employee_id': self.employee_emp_id,
            'holiday_status_id': holiday_type_A.id,
            'request_date_from': '2023-03-14',
            'request_date_to': '2023-03-16',
        }

        with self.assertRaises(ValidationError):
            self.env['hr.leave'].with_user(self.user_employee_id).create(trigger_error_leave)

    @freeze_time('2022-06-13 10:00:00')
    def test_current_leave_status(self):
        types = ('no_validation', 'manager', 'hr', 'both')
        employee = self.employee_emp

        def run_validation_flow(leave_validation_type):
            LeaveType = self.env['hr.leave.type'].with_user(self.user_hrmanager_id)
            leave_type = LeaveType.with_context(tracking_disable=True).create({
                'name': leave_validation_type.capitalize(),
                'leave_validation_type': leave_validation_type,
                'requires_allocation': 'no',
                'responsible_ids': [Command.link(self.env.ref('base.user_admin').id)],
            })
            current_leave = self.env['hr.leave'].with_user(self.user_employee_id).create({
                'name': 'Holiday Request',
                'holiday_type': 'employee',
                'employee_id': employee.id,
                'holiday_status_id': leave_type.id,
                'date_from': datetime.today() - timedelta(days=1),
                'date_to': datetime.today() + timedelta(days=1),
            })

            if leave_validation_type in ('manager', 'both'):
                self.assertFalse(employee.is_absent)
                self.assertFalse(employee.current_leave_id)
                self.assertEqual(employee.filtered_domain([('is_absent', '=', False)]), employee)
                self.assertFalse(employee.filtered_domain([('is_absent', '=', True)]))
                current_leave.with_user(self.user_hruser_id).action_approve()

            if leave_validation_type in ('hr', 'both'):
                self.assertFalse(employee.is_absent)
                self.assertFalse(employee.current_leave_id)
                self.assertEqual(employee.filtered_domain([('is_absent', '=', False)]), employee)
                self.assertFalse(employee.filtered_domain([('is_absent', '=', True)]))
                current_leave.with_user(self.user_hrmanager_id).action_validate()

            self.assertTrue(employee.is_absent)
            self.assertEqual(employee.current_leave_id, current_leave.holiday_status_id)
            self.assertFalse(employee.filtered_domain([('is_absent', '=', False)]))
            self.assertEqual(employee.filtered_domain([('is_absent', '=', True)]), employee)

            raise RuntimeError()

        for leave_validation_type in types:
            with self.assertRaises(RuntimeError), self.env.cr.savepoint():
                run_validation_flow(leave_validation_type)

    @freeze_time('2024-01-18')
    def test_undefined_working_hours(self):
        """ Ensure time-off can also be allocated without ResourceCalendar. """
        employee = self.employee_emp
        employee.resource_calendar_id = False
        self.env['hr.leave.allocation'].create({
            'name': 'Annual Time Off',
            'employee_id': employee.id,
            'holiday_status_id': self.holidays_type_4.id,
            'number_of_days': 20,
            'state': 'confirm',
            'date_from': '2024-01-01',
            'date_to': '2024-12-31',
        })
        self.env['hr.leave'].with_user(self.user_employee_id).create({
            'name': 'Holiday Request',
            'employee_id': employee.id,
            'holiday_status_id': self.holidays_type_4.id,
            'request_date_from': '2024-01-23',
            'request_date_to': '2024-01-27',
        })
        holiday_status = self.holidays_type_4.with_user(self.user_employee_id)
        self._check_holidays_status(holiday_status, employee, 20.0, 0.0, 20.0, 16.0)
