19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:00 +01:00
parent a1137a1456
commit e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions

View file

@ -1,5 +1,10 @@
# -*- coding: utf-8 -*-
from . import test_hr_attendance_constraints
from . import test_hr_attendance_overtime
from . import test_hr_attendance_undertime
from . import test_hr_attendance_process
from . import test_hr_attendance_domain_translation
from . import test_load_scenario
from . import test_hr_attendance_kiosk
from . import test_hr_attendance_rulesets
from . import test_performance
from . import test_hr_attendance_manager

View file

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from freezegun import freeze_time
import time
from odoo.tests.common import tagged, TransactionCase
@ -13,7 +14,7 @@ class TestHrAttendance(TransactionCase):
def setUpClass(cls):
super(TestHrAttendance, cls).setUpClass()
cls.attendance = cls.env['hr.attendance']
cls.test_employee = cls.env['hr.employee'].create({'name': "Jacky"})
cls.test_employee = cls.env['hr.employee'].create({'name': "Jacky", 'ruleset_id': False})
# demo data contains set up for cls.test_employee
cls.open_attendance = cls.attendance.create({
'employee_id': cls.test_employee.id,
@ -62,3 +63,39 @@ class TestHrAttendance(TransactionCase):
self.open_attendance.write({
'check_out': time.strftime('%Y-%m-10 11:30'),
})
@freeze_time("2024-02-05 11:00:00")
def test_attendance_in_the_future(self):
employee = self.env['hr.employee'].create({'name': "Test"})
self.attendance.create({
'employee_id': employee.id,
'check_in': time.strftime('2024-02-10 11:00'),
'check_out': time.strftime('2024-02-10 12:00'),
})
open_attendance = self.env['hr.attendance'].create({
'employee_id': employee.id,
'check_in': time.strftime('2024-02-05 10:00'),
})
self.assertEqual(employee.attendance_state, 'checked_in')
open_attendance.write({
'check_out': time.strftime('2024-02-05 11:30'),
})
self.assertEqual(employee.attendance_state, 'checked_out')
def test_time_format_attendance(self):
self.env.user.tz = 'UTC'
self.env['res.lang']._activate_lang('en_US')
lang = self.env['res.lang']._lang_get(self.env.user.lang)
lang.time_format = "%I:%M:%S %p" # here "%I:%M:%S %p" represents AM:PM format
attendance_id = self.attendance.create({
'employee_id': self.test_employee.id,
'check_in': time.strftime('%Y-%m-28 08:00'),
'check_out': time.strftime('%Y-%m-28 09:00'),
})
self.assertEqual(attendance_id.display_name, "01:00 (08:00:00 AM-09:00:00 AM)")
lang.time_format = "%H:%M:%S"
attendance_id._compute_display_name()
self.assertEqual(attendance_id.display_name, "01:00 (08:00:00-09:00:00)")

View file

@ -0,0 +1,53 @@
import time
from odoo.tests.common import tagged, TransactionCase
@tagged('attendance_searchbar_user_domain')
class TestHrAttendanceDomainTranslation(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.hr_attendance = cls.env['hr.attendance']
cls.hr_employee = cls.env['hr.employee']
cls.employee_musa, employee_tecna = cls.hr_employee.create([{'name': 'Musa'}, {'name': 'Tecna'}])
cls.hr_attendance.create({
'employee_id': employee_tecna.id,
'check_in': time.strftime('%Y-%m-10 10:00'),
})
def test_searchbar_with_user_domain(self):
companies_ids = self.env['res.company'].search([]).ids
# Checks that this domain returns no attendance
self.assertEqual(
self.hr_attendance.search([
'&',
('check_out', "!=", False),
'|',
('employee_id', 'ilike', 'Musa'),
('employee_id', 'ilike', 'Flora')
]),
self.hr_attendance
)
# Ensure that if an employee is searched with the search bar even if he doesn't have any attendance,
# he will be returned.
self.assertEqual(
self.hr_attendance.with_context(
allowed_company_ids=companies_ids,
user_domain=[
'|',
('employee_id', 'ilike', 'Musa'),
('employee_id', 'ilike', 'Flora')
])._read_group_employee_id(self.hr_employee, [
'&',
('check_out', "!=", False),
'|',
('employee_id', 'ilike', 'Musa'),
('employee_id', 'ilike', 'Flora')
]),
self.employee_musa
)

View file

@ -0,0 +1,39 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import tagged, HttpCase
from unittest.mock import patch
from odoo.http import Request
@tagged('post_install', '-at_install', 'hr_attendance_overtime')
class TestHrAttendanceKiosk(HttpCase):
""" Tests for kiosk """
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.company_A = cls.env['res.company'].create({'name': 'company_A'})
cls.company_B = cls.env['res.company'].create({'name': 'company_B'})
cls.department_A = cls.env['hr.department'].create({'name': 'department_A', 'company_id': cls.company_B.id})
cls.employee_A = cls.env['hr.employee'].create({
'name': 'employee_A',
'company_id': cls.company_B.id,
'department_id': cls.department_A.id,
})
cls.employee_B = cls.env['hr.employee'].create({
'name': 'employee_B',
'company_id': cls.company_A.id,
'department_id': cls.department_A.id,
})
def test_employee_count_kiosk(self):
# the mock need to return a None value which can be converted into a Reponse object
with patch.object(Request, "render", return_value=None) as render:
self.url_open(self.company_B.attendance_kiosk_url)
render.assert_called_once()
_template, kiosk_info = render.call_args[0]
kiosk_info = kiosk_info['kiosk_backend_info']
self.assertEqual(kiosk_info['company_name'], 'company_B')
self.assertEqual(kiosk_info['departments'][0]['count'], 1)

View file

@ -0,0 +1,68 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.exceptions import AccessError
from odoo.tests import new_test_user
from odoo.tests.common import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestAttendanceManager(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Create an attendance manager
cls.luisa = new_test_user(cls.env, login='luisa', groups='hr_attendance.group_hr_attendance_manager')
# Create a normal user
cls.marc = new_test_user(cls.env, login='marc', groups='base.group_user')
cls.marc_employee = cls.env['hr.employee'].create({
'name': 'Marc Employee',
'user_id': cls.marc.id,
})
cls.marc_employee.attendance_manager_id = cls.marc
# Create another employee
cls.abigail_employee, cls.ryan_employee = cls.env['hr.employee'].create([
{
'name': 'Abigail Employee',
'attendance_manager_id': cls.marc.id,
},
{
'name': 'Ryan Employee',
'attendance_manager_id': cls.luisa.id,
},
])
# Create an attendance for Marc Demo's employee
cls.attendance = cls.env['hr.attendance'].create({
'employee_id': cls.marc_employee.id,
'check_in': '2025-09-09 08:00:00',
'check_out': '2025-09-09 12:00:00',
})
def test_attendance_officer_rights(self):
"""Marc Demo should NOT be able to change the employee on his attendance
if he is not assigned as attendance manager of that employee.
"""
attendance_as_marc = self.attendance.with_user(self.marc)
# Marc can change the employee to Abigail
attendance_as_marc.write({'employee_id': self.abigail_employee.id})
self.assertEqual(self.attendance.employee_id, self.abigail_employee)
# Marc cannot change the employee to Ryan
with self.assertRaises(AccessError):
attendance_as_marc.write({'employee_id': self.ryan_employee.id})
def test_attendance_manager_rights(self):
"""Luisa should be able to change the employee on attendance without the need
of being set as attendance_manager since she has the attendance_manager group.
"""
attendance_as_luisa = self.attendance.with_user(self.luisa)
attendance_as_luisa.write({'employee_id': self.abigail_employee.id})
self.assertEqual(self.attendance.employee_id, self.abigail_employee)
attendance_as_luisa.write({'employee_id': self.ryan_employee.id})
self.assertEqual(self.attendance.employee_id, self.ryan_employee)

View file

@ -5,8 +5,8 @@ from datetime import datetime
from unittest.mock import patch
from odoo import fields
from odoo.tests import new_test_user
from odoo.tests.common import tagged, TransactionCase
from odoo.tests import Form, new_test_user
from odoo.tests.common import tagged, TransactionCase, freeze_time
@tagged('attendance_process')
@ -16,12 +16,13 @@ class TestHrAttendance(TransactionCase):
@classmethod
def setUpClass(cls):
super(TestHrAttendance, cls).setUpClass()
cls.user = new_test_user(cls.env, login='fru', groups='base.group_user,hr_attendance.group_hr_attendance_use_pin')
cls.user = new_test_user(cls.env, login='fru', groups='base.group_user')
cls.user_no_pin = new_test_user(cls.env, login='gru', groups='base.group_user')
cls.test_employee = cls.env['hr.employee'].create({
'name': "François Russie",
'user_id': cls.user.id,
'pin': '1234',
'ruleset_id': False,
})
cls.employee_kiosk = cls.env['hr.employee'].create({
'name': "Machiavel",
@ -41,56 +42,42 @@ class TestHrAttendance(TransactionCase):
self.test_employee._attendance_action_change()
assert self.test_employee.attendance_state == 'checked_out'
def test_checkin_self_without_pin(self):
""" Employee can check in/out without pin with his own account """
employee = self.test_employee.with_user(self.user)
employee.with_user(self.user).attendance_manual({}, entered_pin=None)
self.assertEqual(employee.attendance_state, 'checked_in', "He should be able to check in without pin")
employee.attendance_manual({}, entered_pin=None)
self.assertEqual(employee.attendance_state, 'checked_out', "He should be able to check out without pin")
def test_employee_group_id(self):
# Create attendance for one of them
self.env['hr.attendance'].create({
'employee_id': self.employee_kiosk.id,
'check_in': '2025-08-01 08:00:00',
'check_out': '2025-08-01 17:00:00',
})
context = self.env.context.copy()
context['read_group_expand'] = True
def test_checkin_self_with_pin(self):
""" Employee can check in/out with pin with his own account """
employee = self.test_employee.with_user(self.user)
employee.attendance_manual({}, entered_pin='1234')
self.assertEqual(employee.attendance_state, 'checked_in', "He should be able to check in with his pin")
employee.attendance_manual({}, entered_pin='1234')
self.assertEqual(employee.attendance_state, 'checked_out', "He should be able to check out with his pin")
groups = self.env['hr.attendance'].with_context(**context).web_read_group(
domain=[],
groupby=['employee_id']
)
groups = groups['groups']
def test_checkin_self_wrong_pin(self):
""" Employee cannot check in/out with wrong pin with his own account """
employee = self.test_employee.with_user(self.user)
action = employee.attendance_manual({}, entered_pin='9999')
self.assertNotEqual(employee.attendance_state, 'checked_in', "He should not be able to check in with a wrong pin")
self.assertTrue(action.get('warning'))
grouped_employee_ids = [g['employee_id'][0] for g in groups]
def test_checkin_kiosk_with_pin(self):
""" Employee can check in/out with his pin in kiosk """
employee = self.employee_kiosk.with_user(self.user)
employee.attendance_manual({}, entered_pin='5678')
self.assertEqual(employee.attendance_state, 'checked_in', "He should be able to check in with his pin")
employee.attendance_manual({}, entered_pin='5678')
self.assertEqual(employee.attendance_state, 'checked_out', "He should be able to check out with his pin")
self.assertNotIn(self.test_employee.id, grouped_employee_ids)
self.assertIn(self.employee_kiosk.id, grouped_employee_ids)
def test_checkin_kiosk_with_wrong_pin(self):
""" Employee cannot check in/out with wrong pin in kiosk """
employee = self.employee_kiosk.with_user(self.user)
action = employee.attendance_manual({}, entered_pin='8888')
self.assertNotEqual(employee.attendance_state, 'checked_in', "He should not be able to check in with a wrong pin")
self.assertTrue(action.get('warning'))
# Specific to gantt view.
context['gantt_start_date'] = fields.Datetime.now()
context['allowed_company_ids'] = [self.env.company.id]
def test_checkin_kiosk_without_pin(self):
""" Employee cannot check in/out without his pin in kiosk """
employee = self.employee_kiosk.with_user(self.user)
action = employee.attendance_manual({}, entered_pin=None)
self.assertNotEqual(employee.attendance_state, 'checked_in', "He should not be able to check in with no pin")
self.assertTrue(action.get('warning'))
groups = self.env['hr.attendance'].with_context(**context).web_read_group(
domain=[],
groupby=['employee_id']
)
groups = groups['groups']
def test_checkin_kiosk_no_pin_mode(self):
""" Employee cannot check in/out without pin in kiosk when user has not group `use_pin` """
employee = self.employee_kiosk.with_user(self.user_no_pin)
employee.attendance_manual({}, entered_pin=None)
self.assertEqual(employee.attendance_state, 'checked_out', "He shouldn't be able to check in without")
grouped_employee_ids = [g['employee_id'][0] for g in groups]
# Check that both employees appears
self.assertIn(self.test_employee.id, grouped_employee_ids)
self.assertIn(self.employee_kiosk.id, grouped_employee_ids)
def test_hours_today(self):
""" Test day start is correctly computed according to the employee's timezone """
@ -113,3 +100,43 @@ class TestHrAttendance(TransactionCase):
# now = 2019/3/2 14:00 in the employee's timezone
with patch.object(fields.Datetime, 'now', lambda: tz_datetime(2019, 3, 2, 14, 0).astimezone(pytz.utc).replace(tzinfo=None)):
self.assertEqual(employee.hours_today, 5, "It should have counted 5 hours")
def test_remove_check_in_value_from_attendance(self):
attendance_form = Form(self.env['hr.attendance'])
attendance_form.employee_id = self.test_employee
attendance_form.check_in = False
with self.assertRaises(AssertionError):
attendance_form.save()
# @freeze_time("2024-02-1")
# def test_change_in_out_mode_when_manual_modification(self):
# TODO naja: cron should work eventually when the adjustment feature is back
# company = self.env['res.company'].create({
# 'name': 'Monsters, Inc.',
# 'absence_management': True,
# })
# employee = self.env['hr.employee'].create({
# 'name': "James P. Sullivan",
# 'company_id': company.id,
# 'date_version': date(2021, 1, 1),
# 'contract_date_start': date(2021, 1, 1),
# })
# breakpoint()
# self.env['hr.attendance']._cron_absence_detection()
# attendance = self.env['hr.attendance'].search([('employee_id', '=', employee.id)])
# self.assertEqual(attendance.in_mode, 'technical')
# self.assertEqual(attendance.out_mode, 'technical')
# self.assertEqual(attendance.color, 1)
# attendance.write({
# 'check_in': datetime(2021, 1, 4, 8, 0),
# 'check_out': datetime(2021, 1, 4, 17, 0),
# })
# self.assertEqual(attendance.in_mode, 'manual')
# self.assertEqual(attendance.out_mode, 'manual')
# self.assertEqual(attendance.color, 0)

View file

@ -0,0 +1,184 @@
# 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 new_test_user, Form
from odoo.tests.common import tagged, TransactionCase
@tagged('hr_attendance_overtime_ruleset')
class TestHrAttendanceOvertime(TransactionCase):
""" Tests for overtime """
@classmethod
def setUpClass(cls):
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',
'quantity_period': 'day',
'expected_hours': 8,
'paid': True,
'amount_rate': 150,
}),
Command.create({
'name': 'Rule schedule quantity',
'base_off': 'quantity',
'quantity_period': 'day',
'expected_hours': 10,
'paid': True,
'amount_rate': 200,
}),
Command.create({
'name': 'Rule schedule quantity',
'base_off': 'quantity',
'quantity_period': 'week',
'expected_hours': 40,
'paid': True,
'amount_rate': 150,
}),
],
})
cls.company = cls.env['res.company'].create({
'name': 'SweatChipChop Inc.',
'attendance_overtime_validation': 'no_validation',
})
cls.company.resource_calendar_id = cls.env.company.resource_calendar_id = cls.env['resource.calendar'].create({
'name': 'Standard 40 hours/week (No Lunch)',
'company_id': cls.env.company.id,
'hours_per_day': 7.6,
'full_time_required_hours': 38,
'attendance_ids': [
(5, 0, 0), # Clear existing attendances
(0, 0, {'name': 'Monday', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 16, 'day_period': 'morning'}),
(0, 0, {'name': 'Tuesday', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 16, 'day_period': 'morning'}),
(0, 0, {'name': 'Wednesday', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 16, 'day_period': 'morning'}),
(0, 0, {'name': 'Thursday', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 16, 'day_period': 'morning'}),
(0, 0, {'name': 'Friday', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 16, 'day_period': 'morning'}),
],
})
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
})
def test_daily_overtime_8_hours_rule(self):
with freeze_time("2021-01-04"):
# Attendance: 10 hours (8 expected + 2 overtime at 150%)
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.assertAlmostEqual(attendance.employee_id.total_overtime, 2, 2, msg="2 hours overtime at 150% should yield 2 hours total overtime")
def test_daily_overtime_10_hours_rule(self):
""" Test daily overtime for the 10-hour rule """
with freeze_time("2021-01-04"):
# Attendance: 12 hours (10 expected + 2 overtime at 200%)
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.assertAlmostEqual(attendance.employee_id.total_overtime, 4.0, 2, msg="2 hours overtime at 200% should yield 4 hours total overtime")
def test_no_overtime(self):
""" Test no overtime when working expected hours or less """
with freeze_time("2021-01-04"):
# Attendance: 8 hours (exactly 8 expected, no overtime)
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, 16, 0)
})
self.assertAlmostEqual(attendance.employee_id.total_overtime, 0.0, 2, msg="No overtime should be recorded for 8 hours or less")
def test_weekly_overtime(self):
""" Test weekly overtime for the 40-hour rule """
with freeze_time("2021-01-04"):
# Week: Mon-Fri, 10 hours/day = 50 hours total (40 expected + 10 overtime at 200%)
[
self.env['hr.attendance'].create({
'employee_id': self.employee.id, # This employee have a calendar with no lunch
'check_in': datetime(2021, 1, day, 8, 0),
'check_out': datetime(2021, 1, day, 18, 0)
}) for day in range(4, 9) # Monday to Friday
]
self.assertAlmostEqual(self.employee.total_overtime, 10, 2, msg="He should work from 8-16h so each day he did 2 hours of overtime")
def test_multiple_attendances_same_day(self):
""" Test multiple attendances in one day """
with freeze_time("2021-01-04"):
# Two attendances: 6 hours + 6 hours = 12 hours (10 expected + 2 overtime at 200%)
self.env['hr.attendance'].create([
{
'employee_id': self.employee.id,
'check_in': datetime(2021, 1, 4, 8, 0),
'check_out': datetime(2021, 1, 4, 14, 0)
},
{
'employee_id': self.employee.id,
'check_in': datetime(2021, 1, 4, 14, 0),
'check_out': datetime(2021, 1, 4, 20, 0)
}
])
self.assertAlmostEqual(self.employee.total_overtime, 4.0, 2, msg="2 hours overtime at 200% should yield 4 hours total overtime")
def test_partial_week(self):
""" Test partial week with overtime """
with freeze_time("2021-01-04"):
# Week: Mon-Wed, 12 hours/day = 36 hours total (no weekly overtime, daily overtime applies)
[
self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2021, 1, day, 8, 0),
'check_out': datetime(2021, 1, day, 20, 0)
}) for day in range(4, 7) # Monday to Wednesday
]
self.assertAlmostEqual(self.employee.total_overtime, 12.0, 2, msg="3 days of 2 hours overtime at 200% should yield 12 hours total overtime")
def test_access_ruleset_on_employee(self):
"""
Test the access rights of the ruleset on the employee
Only the employee admin should be able to see and change the ruleset on the employee
"""
user = new_test_user(self.env, login='usr', groups='hr.group_hr_user', company_id=self.company.id).with_company(self.company)
employee = self.env['hr.employee'].with_company(self.company).create({'name': "Employee Test"})
with Form(employee.with_user(user)) as employee_form:
self.assertFalse("ruleset_id" in employee_form._view['fields'])
# HR Mangers should be able to see the ruleset on the employee
user.group_ids |= self.env.ref('hr.group_hr_manager')
# fix le truc chelou de pas pouvoir ecrire la surrement les access rule
with Form(employee.with_user(user)) as employee_form:
self.assertTrue("ruleset_id" in employee_form._view['fields'])
employee_form.record.ruleset_id = self.ruleset.id
def test_is_manager_with_overtime(self):
""" Test the computation of is_manager with overtime """
user = new_test_user(self.env, login='usr', groups='hr_attendance.group_hr_attendance_officer', company_id=self.company.id).with_company(self.company)
self.employee.attendance_manager_id = user.id
attendance = self.env['hr.attendance'].with_company(self.company).create({
'employee_id': self.employee.id,
'check_in': datetime(2021, 1, 4, 8, 0),
'check_out': datetime(2021, 1, 4, 20, 0)
})
self.assertTrue(attendance.with_user(user).linked_overtime_ids.is_manager)

View file

@ -0,0 +1,553 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import date, datetime
from odoo import Command
from odoo.tests import Form, HttpCase, new_test_user
from odoo.tests.common import tagged
@tagged('hr_attendance_overtime')
class TestHrAttendanceUndertime(HttpCase):
""" Tests for undertime """
@classmethod
def setUpClass(cls):
def set_calendar_and_tz(employee, tz):
employee.resource_calendar_id = cls.env['resource.calendar'].create({
'name': f'Default Calendar ({tz})',
'tz': tz,
})
super().setUpClass()
cls.company = cls.env['res.company'].create({
'name': 'SweatChipChop Inc.',
'attendance_overtime_validation': 'no_validation',
'absence_management': True,
})
cls.company.resource_calendar_id.tz = 'Europe/Brussels'
cls.ruleset = cls.env['hr.attendance.overtime.ruleset'].with_company(cls.company).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_1 = cls.env['res.company'].create({
'name': 'Overtime Inc.',
'absence_management': True,
})
cls.company_1.resource_calendar_id.tz = 'Europe/Brussels'
cls.ruleset_1 = cls.env['hr.attendance.overtime.ruleset'].with_company(cls.company_1).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.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_1.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, 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_undertime(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, 15, 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)})
overtime = checkin_am._linked_overtimes()
self.assertTrue(overtime, 'An overtime record should be created')
self.assertEqual(overtime.duration, -4)
checkin_pm = self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2021, 1, 4, 13, 0),
})
overtime = checkin_pm._linked_overtimes()
self.assertFalse(overtime.exists(), 'Overtime duration should not exist when an attendance has not been checked out.')
checkin_pm.write({'check_out': datetime(2021, 1, 4, 18, 0)})
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_simple_undertime_multiple_rules(self):
""" Checks that only the least consequent undertime of the rules is considered."""
ruleset = self.env['hr.attendance.overtime.ruleset'].with_company(self.company).create({
'name': 'Ruleset schedule quantity',
'rule_ids': [Command.create({
'name': 'Rule schedule quantity',
'base_off': 'quantity',
'expected_hours_from_contract': False,
'expected_hours': 8.0,
'quantity_period': 'day',
}),
Command.create({
'name': 'Rule schedule quantity',
'base_off': 'quantity',
'expected_hours_from_contract': False,
'expected_hours': 10.0,
'quantity_period': 'day',
})],
})
self.employee.ruleset_id = ruleset
attendance = self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2021, 1, 4, 13, 0),
'check_out': datetime(2021, 1, 4, 18, 0),
})
self.assertEqual(attendance.overtime_hours, -3.0)
overtime = attendance._linked_overtimes()
self.assertEqual(len(overtime), 1, 'Only one overtime record should be created')
self.assertEqual(overtime.duration, -3.0)
def test_simple_undertime_multiple_rules_on_several_periods(self):
"""Whatever the period type, only the least consequent undertime of the rules is considered.
"""
ruleset = self.env['hr.attendance.overtime.ruleset'].with_company(self.company).create({
'name': 'Ruleset schedule quantity',
'rule_ids': [Command.create({
'name': 'Rule schedule quantity',
'base_off': 'quantity',
'expected_hours_from_contract': False,
'expected_hours': 8.0,
'quantity_period': 'day',
}),
Command.create({
'name': 'Rule schedule quantity',
'base_off': 'quantity',
'expected_hours_from_contract': False,
'expected_hours': 40.0,
'quantity_period': 'week',
})],
})
self.employee.ruleset_id = ruleset
attendance = self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2021, 1, 4, 13, 0),
'check_out': datetime(2021, 1, 4, 18, 0),
})
self.assertEqual(attendance.overtime_hours, -3.0)
overtime = attendance._linked_overtimes()
self.assertEqual(len(overtime), 1, 'Only one overtime record should be created')
self.assertEqual(overtime.duration, -3.0)
def test_undertime_change_employee(self):
attendance = self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2021, 1, 4, 7, 0),
'check_out': datetime(2021, 1, 4, 15, 0),
})
self.assertEqual(self.employee.total_overtime, -1)
self.assertEqual(self.other_employee.total_overtime, 0)
self.other_employee.ruleset_id = self.ruleset
self.env['hr.attendance'].create({
'employee_id': self.other_employee.id,
'check_in': datetime(2021, 1, 4, 7, 0),
'check_out': datetime(2021, 1, 4, 15, 0),
})
attendance.unlink()
self.assertEqual(self.other_employee.total_overtime, -1)
self.assertEqual(self.employee.total_overtime, 0)
def test_undertime_far_timezones(self):
# Since dates have to be stored in utc these are the tokyo timezone times for 7-12 / 13-18 (UTC+9)
(self.jpn_employee | self.honolulu_employee).ruleset_id = self.ruleset
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, 4, 0),
})
# Lunch time is at 3-4 UTC in Tokyo, and we don't work during lunch.
# Same but for alaskan times (UTC-10)
self.env['hr.attendance'].create({
'employee_id': self.honolulu_employee.id,
'check_in': datetime(2021, 1, 4, 17, 0),
'check_out': datetime(2021, 1, 4, 20, 0),
})
self.assertAlmostEqual(self.jpn_employee.total_overtime, -6, 2)
self.assertAlmostEqual(self.honolulu_employee.total_overtime, -5, 2)
def test_undertime_lunch(self):
self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2021, 1, 4, 8, 0),
'check_out': datetime(2021, 1, 4, 13, 0),
})
self.assertEqual(self.employee.total_overtime, -4, 'There should be only -4 since the employee did not work through the lunch period.')
def test_undertime_hours_with_multiple_attendance(self):
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, 12, 0),
})
self.assertAlmostEqual(m_attendance_1.overtime_hours, -4, 2)
m_attendance_2 = self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2023, 1, 3, 13, 0),
'check_out': datetime(2023, 1, 3, 17, 0),
})
self.assertAlmostEqual(m_attendance_1.overtime_hours, 0, 2)
self.assertAlmostEqual(m_attendance_2.overtime_hours, 0, 2)
m_attendance_3 = self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2023, 1, 3, 18, 0),
'check_out': datetime(2023, 1, 3, 19, 0),
})
self.assertAlmostEqual(m_attendance_1.overtime_hours, 0, 2)
self.assertAlmostEqual(m_attendance_2.overtime_hours, 0, 2)
self.assertAlmostEqual(m_attendance_3.overtime_hours, 1, 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(len(overtime_2), 1, "Only one overtime record should be created for that day.")
self.assertEqual(overtime_2.duration, 1)
# Attendance Modification case
m_attendance_3.write({
'check_out': datetime(2023, 1, 3, 20, 00),
})
self.assertAlmostEqual(m_attendance_3.overtime_hours, 2, 2)
# Deleting previous attendances should update correctly the overtime hours in other attendances
m_attendance_2.unlink()
self.assertAlmostEqual(m_attendance_3.overtime_hours, -2, 2)
def test_undertime_across_days_timezones(self):
self.europe_employee.ruleset_id = self.ruleset
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, 4, 30),
})
self.assertAlmostEqual(early_attendance.overtime_hours, -3, 2)
# Total overtime for that day : -3 hours
overtime_record = early_attendance.linked_overtime_ids
self.assertAlmostEqual(overtime_record.duration, -3, 2)
# Check that the calendar's timezones take priority and that overtimes and attendances dates are consistent
self.europe_employee.resource_calendar_id.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
'check_out': datetime(2024, 5, 30, 10, 0), # 6:00 NY
})
# First day you only work 1 hour and second day you work 6 hours, that's -1 hours of overtime
self.assertAlmostEqual(early_attendance2.overtime_hours, -1, 2)
overtime_record2 = early_attendance2.linked_overtime_ids
self.assertEqual(len(overtime_record2), 1, "One undertime records should be created for that attendance.")
self.assertAlmostEqual(overtime_record2.duration, -1, 2)
early_attendance3 = self.env['hr.attendance'].create({
'employee_id': self.europe_employee.id,
'check_in': datetime(2024, 5, 31, 4, 0), # 00:00 NY
'check_out': datetime(2024, 5, 31, 10, 0), # 6:00 NY
})
self.assertAlmostEqual(early_attendance3.overtime_hours, -2, 2)
def test_undertime_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, -2, 2, 'There should be -2 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_undertime_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, -4, 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, -2, 2, 'There should be -2 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_refuse_overtime(self):
self.company.write({
"attendance_overtime_validation": "by_manager",
})
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, 12, 0),
})
overtime = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id)])
overtime.action_approve()
self.assertEqual(attendance.validated_overtime_hours, -4)
self.assertEqual(attendance.overtime_hours, attendance.validated_overtime_hours)
attendance.action_refuse_overtime()
self.assertEqual(attendance.validated_overtime_hours, 0)
def test_no_validation_extra_hours_change(self):
"""
In case of attendances requiring no validation, check that extra hours are not recomputed
if the value is different from `validated_hours` (meaning it has been modified by the user).
"""
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, 15, 0)
attendance = attendance_form.save()
self.assertAlmostEqual(attendance.overtime_hours, -2, 2)
self.assertAlmostEqual(attendance.validated_overtime_hours, -2, 2)
attendance.linked_overtime_ids.manual_duration = previous = -1.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),
})
self.assertEqual(attendance.validated_overtime_hours, previous, "Extra hours shouldn't be recomputed")
def test_overtime_employee_tolerance(self):
self.ruleset.rule_ids[0].employee_tolerance = 10 / 60
self.env['hr.attendance'].create([
{
'employee_id': self.employee.id,
'check_in': datetime(2021, 1, 4, 8, 5),
'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, 16, 55),
}
])
overtime = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id)])
self.assertFalse(overtime, 'No overtime should be counted because of the tolerance.')
self.ruleset.rule_ids[0].employee_tolerance = 4 / 60
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 tolerance has been lowered.')
self.assertAlmostEqual(overtime.duration, -(10 / 60), places=2, msg='Overtime should be equal to -10 minutes.')
def test_overtime_on_multiple_days(self):
attendance = self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2021, 1, 8, 8, 0), # Friday 8 AM - 17 PM work, 17 - 24 overtime (7 hours)
'check_out': datetime(2021, 1, 9, 3, 0), # Saturday 0-3 AM overtime (3 hours)
})
overtime = attendance._linked_overtimes()
self.assertEqual(len(overtime), 2, 'There should be 2 overtime records for that attendance.')
self.assertEqual(sum(overtime.mapped('duration')), 10, 'There should be a total of 10 hours of overtime for that attendance.')
attendance.write({
'check_out': datetime(2021, 1, 8, 20, 0),
})
overtime = attendance._linked_overtimes()
self.assertEqual(len(overtime), 1, 'There should have only 1 overtime for that attendance after modification.')
self.assertEqual(sum(overtime.mapped('duration')), 3, 'There should be a total of 3 hours of overtime for that attendance after modification.')
all_overtimes = self.env['hr.attendance.overtime.line'].search([('employee_id', '=', self.employee.id)])
self.assertEqual(len(all_overtimes), 1, 'There should be only 1 overtime record in total for that employee.')

View file

@ -0,0 +1,8 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import TransactionCase
class TestHrAttendanceScenario(TransactionCase):
def test_load_scenario(self):
self.env['hr.attendance']._load_demo_data()

View file

@ -0,0 +1,92 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import date
from dateutil.relativedelta import relativedelta
from dateutil.rrule import DAILY, rrule
import logging
import time
from odoo import Command
from odoo.tests.common import tagged
from odoo.tests.common import TransactionCase
_logger = logging.getLogger(__name__)
@tagged('post_install', '-at_install', 'hr_attendance_perf')
class TestHrAttendancePerformance(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env.user.company_id = cls.env['res.company'].create({'name': 'Flower Corporation'})
cls.calendar_38h = cls.env['resource.calendar'].create({
'name': 'Standard 38 hours/week',
'tz': 'Europe/Brussels',
'company_id': False,
'hours_per_day': 7.6,
'attendance_ids': [(5, 0, 0),
(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': 16.6, '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': 16.6, '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': 16.6, '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': 16.6, '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': 16.6, 'day_period': 'afternoon'})
],
})
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',
}),
],
})
employees = cls.env['hr.employee'].create([{
'name': f'Employee {i}',
'sex': 'male',
'birthday': '1982-08-01',
'country_id': cls.env.ref('base.us').id,
'wage': 5000.0,
'date_version': date.today() - relativedelta(months=2),
'contract_date_start': date.today() - relativedelta(months=2),
'contract_date_end': False,
'resource_calendar_id': cls.calendar_38h.id,
'ruleset_id': cls.ruleset.id,
} for i in range(100)])
for employee in employees:
employee.create_version({'date_version': date.today() - relativedelta(months=1, days=15), 'wage': 5500})
employee.create_version({'date_version': date.today() - relativedelta(months=1), 'wage': 6000})
vals = []
for employee in employees:
for day in rrule(DAILY, dtstart=date.today() - relativedelta(months=2), until=date.today()):
vals.append({
'employee_id': employee.id,
'check_in': day.replace(hour=8, minute=0),
'check_out': day.replace(hour=17, minute=36),
})
cls.attendances = cls.env['hr.attendance'].create(vals)
def test_regenerate_overtime_line(self):
t0 = time.time()
with self.assertQueryCount(1700):
self.ruleset.action_regenerate_overtimes()
t1 = time.time()
_logger.info("Regenerated overtime for %s hr.attendance records in %s seconds.",
len(self.attendances.ids), t1 - t0)