Initial commit: Project packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:52 +02:00
commit 89613c97b0
753 changed files with 496325 additions and 0 deletions

View file

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_cancel_time_off
from . import test_employee
from . import test_timesheet_holidays
from . import test_timesheet_global_time_off

View file

@ -0,0 +1,67 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from freezegun import freeze_time
from odoo.tests import TransactionCase, tagged, new_test_user
@tagged('post_install', '-at_install')
class TestCancelTimeOff(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.company = cls.env['res.company'].create({
'name': 'Test Company',
})
cls.global_leave = cls.env['resource.calendar.leaves'].create({
'name': 'Test Global Leave',
'date_from': '2020-01-08 00:00:00',
'date_to': '2020-01-08 23:59:59',
'calendar_id': cls.company.resource_calendar_id.id,
'company_id': cls.company.id,
})
cls.employee_user = new_test_user(
cls.env,
login='test_user',
name='Test User',
company_id=cls.company.id,
groups='base.group_user,hr_timesheet.group_hr_timesheet_user',
)
cls.employee = cls.env['hr.employee'].create({
'name': 'Test Employee',
'user_id': cls.employee_user.id,
'resource_calendar_id': cls.company.resource_calendar_id.id,
'company_id': cls.company.id,
})
cls.generic_time_off_type = cls.env['hr.leave.type'].create({
'name': 'Generic Time Off',
'requires_allocation': 'no',
'leave_validation_type': 'both',
'company_id': cls.company.id,
})
@freeze_time('2020-01-01')
def test_cancel_time_off(self):
""" Test that an employee can cancel a future time off, that crosses a global leave,
if the employee is not in the group_hr_holidays_user.
Test Case:
=========
1) Create a time off in the future and that crosses a global leave
2) Approve the time off with the admin
3) Cancel the time off with the user that is not in the group_hr_holidays_user
4) No read error on employee_ids should be raised
"""
time_off = self.env['hr.leave'].create({
'name': 'Test Time Off',
'holiday_type': 'employee',
'holiday_status_id': self.generic_time_off_type.id,
'employee_id': self.employee.id,
'date_from': '2020-01-07 08:00:00',
'date_to': '2020-01-09 17:00:00',
})
time_off.action_validate()
HrHolidaysCancelLeave = self.env[
'hr.holidays.cancel.leave'].with_user(self.employee_user).with_company(self.company.id)
HrHolidaysCancelLeave.create({
'leave_id': time_off.id, 'reason': 'Test Reason'}).action_cancel_leave()

View file

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from freezegun import freeze_time
from odoo.tests import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestEmployee(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.company = cls.env['res.company'].create({
'name': 'Test Company',
})
cls.global_leave = cls.env['resource.calendar.leaves'].create({
'name': 'Test Global Leave',
'date_from': '2020-01-01 00:00:00',
'date_to': '2020-01-01 23:59:59',
'calendar_id': cls.company.resource_calendar_id.id,
'company_id': cls.company.id,
})
@freeze_time('2020-01-01')
def test_create_employee(self):
""" Test the timesheets representing the time off of this new employee
is correctly generated once the employee is created
Test Case:
=========
1) Create a new employee
2) Check the timesheets representing the time off of this new employee
is correctly generated
"""
employee = self.env['hr.employee'].create({
'name': 'Test Employee',
'company_id': self.company.id,
'resource_calendar_id': self.company.resource_calendar_id.id,
})
timesheet = self.env['account.analytic.line'].search([
('employee_id', '=', employee.id),
('global_leave_id', '=', self.global_leave.id),
])
self.assertEqual(len(timesheet), 1, 'A timesheet should have been created for the global leave of the employee')
self.assertEqual(str(timesheet.date), '2020-01-01', 'The timesheet should be created for the correct date')
self.assertEqual(timesheet.unit_amount, 8, 'The timesheet should be created for the correct duration')
# simulate the company of the employee updated is not in the allowed_company_ids of the current user
employee2 = self.env['hr.employee'].with_company(self.env.company).create({
'name': 'Test Employee',
'company_id': self.company.id,
'resource_calendar_id': self.company.resource_calendar_id.id,
})
timesheet = self.env['account.analytic.line'].search([
('employee_id', '=', employee2.id),
('global_leave_id', '=', self.global_leave.id),
])
self.assertEqual(len(timesheet), 1, 'A timesheet should have been created for the global leave of the employee')
self.assertEqual(str(timesheet.date), '2020-01-01', 'The timesheet should be created for the correct date')
self.assertEqual(timesheet.unit_amount, 8, 'The timesheet should be created for the correct duration')
@freeze_time('2020-01-01')
def test_write_employee(self):
""" Test the timesheets representing the time off of this employee
is correctly generated once the employee is updated
Test Case:
=========
1) Create a new employee
2) Check the timesheets representing the time off of this new employee
is correctly generated
3) Update the employee
4) Check the timesheets representing the time off of this employee
is correctly updated
"""
employee = self.env['hr.employee'].create({
'name': 'Test Employee',
'company_id': self.company.id,
})
employee.write({'resource_calendar_id': self.company.resource_calendar_id.id})
timesheet = self.env['account.analytic.line'].search([
('employee_id', '=', employee.id),
('global_leave_id', '=', self.global_leave.id),
])
self.assertEqual(len(timesheet), 1, 'A timesheet should have been created for the global leave of the employee')
self.assertEqual(str(timesheet.date), '2020-01-01', 'The timesheet should be created for the correct date')
self.assertEqual(timesheet.unit_amount, 8, 'The timesheet should be created for the correct duration')
employee.write({'active': False})
timesheet = self.env['account.analytic.line'].search([
('employee_id', '=', employee.id),
('global_leave_id', '=', self.global_leave.id),
])
self.assertFalse(timesheet, 'The timesheet should have been deleted when the employee was archived')
employee.write({'active': True})
timesheet = self.env['account.analytic.line'].search([
('employee_id', '=', employee.id),
('global_leave_id', '=', self.global_leave.id),
])
self.assertEqual(len(timesheet), 1, 'A timesheet should have been created for the global leave of the employee')
self.assertEqual(str(timesheet.date), '2020-01-01', 'The timesheet should be created for the correct date')
self.assertEqual(timesheet.unit_amount, 8, 'The timesheet should be created for the correct duration')
# simulate the company of the employee updated is not in the allowed_company_ids of the current user
employee.with_company(self.env.company).write({'resource_calendar_id': self.company.resource_calendar_id.id})
timesheet = self.env['account.analytic.line'].search([
('employee_id', '=', employee.id),
('global_leave_id', '=', self.global_leave.id),
])
self.assertEqual(len(timesheet), 1, 'A timesheet should have been created for the global leave of the employee')
self.assertEqual(str(timesheet.date), '2020-01-01', 'The timesheet should be created for the correct date')
self.assertEqual(timesheet.unit_amount, 8, 'The timesheet should be created for the correct duration')

View file

@ -0,0 +1,419 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from datetime import datetime, timedelta
from freezegun import freeze_time
from odoo import Command
from odoo.tests import common
class TestTimesheetGlobalTimeOff(common.TransactionCase):
def setUp(self):
super(TestTimesheetGlobalTimeOff, self).setUp()
# Creates 1 test company and a calendar for employees that
# work part time. Then creates an employee per calendar (one
# for the standard calendar and one for the one we created)
self.test_company = self.env['res.company'].create({
'name': 'My Test Company',
})
attendance_ids = [
Command.create({'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 9, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Monday Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
Command.create({'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 9, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
Command.create({'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 9, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
Command.create({'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 9, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'})
]
self.part_time_calendar = self.env['resource.calendar'].create({
'name': 'Part Time Calendar',
'company_id': self.test_company.id,
'hours_per_day': 6,
'attendance_ids': attendance_ids,
})
self.full_time_employee = self.env['hr.employee'].create({
'name': 'John Doe',
'company_id': self.test_company.id,
'resource_calendar_id': self.test_company.resource_calendar_id.id,
})
self.full_time_employee_2 = self.env['hr.employee'].create({
'name': 'John Smith',
'company_id': self.test_company.id,
'resource_calendar_id': self.test_company.resource_calendar_id.id,
})
self.part_time_employee = self.env['hr.employee'].create({
'name': 'Jane Doe',
'company_id': self.test_company.id,
'resource_calendar_id': self.part_time_calendar.id,
})
# This tests that timesheets are created for every employee with the same calendar
# when a global time off is created.
# This also tests that timesheets are deleted when global time off is deleted.
def test_timesheet_creation_and_deletion_for_time_off(self):
leave_start_datetime = datetime(2021, 1, 4, 7, 0, 0, 0) # This is a monday
leave_end_datetime = datetime(2021, 1, 8, 18, 0, 0, 0) # This is a friday
global_time_off = self.env['resource.calendar.leaves'].create({
'name': 'Test',
'calendar_id': self.test_company.resource_calendar_id.id,
'date_from': leave_start_datetime,
'date_to': leave_end_datetime,
})
# 5 Timesheets should have been created for full_time_employee and full_time_employee_2
# but none for part_time_employee
leave_task = self.test_company.leave_timesheet_task_id
timesheets_by_employee = defaultdict(lambda: self.env['account.analytic.line'])
for timesheet in leave_task.timesheet_ids:
timesheets_by_employee[timesheet.employee_id] |= timesheet
self.assertFalse(timesheets_by_employee.get(self.part_time_employee, False))
self.assertEqual(len(timesheets_by_employee.get(self.full_time_employee)), 5)
self.assertEqual(len(timesheets_by_employee.get(self.full_time_employee_2)), 5)
# The standard calendar is for 8 hours/day from 8 to 12 and from 13 to 17.
# So we need to check that the timesheets don't have more than 8 hours per day.
self.assertEqual(leave_task.effective_hours, 80)
# Now we delete the global time off. The timesheets should be deleted too.
global_time_off.unlink()
self.assertFalse(leave_task.timesheet_ids.ids)
@freeze_time('2022-01-01 08:00:00')
def test_timesheet_creation_and_deletion_on_employee_archive(self):
""" Test the timesheets linked to the global time off in the future when the employee is archived """
today = datetime.today()
leave_start_datetime = today + timedelta(days=-today.weekday(), weeks=1) # Next monday
leave_end_datetime = leave_start_datetime + timedelta(days=5) # Next friday
self.env['resource.calendar.leaves'].create({
'name': 'Test',
'calendar_id': self.test_company.resource_calendar_id.id,
'date_from': leave_start_datetime,
'date_to': leave_end_datetime,
})
# 5 Timesheets should have been created for full_time_employee
timesheets_full_time_employee = self.env['account.analytic.line'].search([('employee_id', '=', self.full_time_employee.id)])
self.assertEqual(len(timesheets_full_time_employee), 5)
# All timesheets should have been deleted for full_time_employee when he is archived
self.full_time_employee.active = False
timesheets_full_time_employee = self.env['account.analytic.line'].search([('employee_id', '=', self.full_time_employee.id)])
self.assertEqual(len(timesheets_full_time_employee), 0)
# 5 Timesheets should have been created for full_time_employee when he is unarchived
self.full_time_employee.active = True
timesheets_full_time_employee = self.env['account.analytic.line'].search([('employee_id', '=', self.full_time_employee.id)])
self.assertEqual(len(timesheets_full_time_employee), 5)
# This tests that no timesheet are created for days when the employee is not supposed to work
def test_no_timesheet_on_off_days(self):
leave_start_datetime = datetime(2021, 1, 4, 7, 0, 0, 0) # This is a monday
leave_end_datetime = datetime(2021, 1, 8, 18, 0, 0, 0) # This is a friday
day_off = datetime(2021, 1, 6, 0, 0, 0) # part_time_employee does not work on wednesday
self.env['resource.calendar.leaves'].create({
'name': 'Test',
'calendar_id': self.part_time_calendar.id,
'date_from': leave_start_datetime,
'date_to': leave_end_datetime,
})
# The total number of hours for the timesheet created should be equal to the
# hours_per_day of the calendar
leave_task = self.test_company.leave_timesheet_task_id
self.assertEqual(leave_task.effective_hours, 4 * self.part_time_calendar.hours_per_day)
# No timesheet should have been created on the day off
timesheet = self.env['account.analytic.line'].search([('date', '=', day_off), ('task_id', '=', leave_task.id)])
self.assertFalse(timesheet.id)
# This tests that timesheets are created/deleted for every employee with the same calendar
# when a global time off has a calendar_id set/remove
def test_timesheet_creation_and_deletion_for_calendar_set_and_remove(self):
leave_start_datetime = datetime(2021, 1, 4, 7, 0, 0, 0) # This is a monday
leave_end_datetime = datetime(2021, 1, 8, 18, 0, 0, 0) # This is a friday
global_time_off = self.env['resource.calendar.leaves'].create({
'name': 'Test',
'calendar_id': self.test_company.resource_calendar_id.id,
'date_from': leave_start_datetime,
'date_to': leave_end_datetime,
})
# 5 Timesheets should have been created for full_time_employee and full_time_employee_2
# but none for part_time_employee
leave_task = self.test_company.leave_timesheet_task_id
# Now we delete the calendar_id. The timesheets should be deleted too.
global_time_off.calendar_id = False
self.assertFalse(leave_task.timesheet_ids.ids)
# Now we reset the calendar_id. The timesheets should be created and have the right value.
global_time_off.calendar_id = self.test_company.resource_calendar_id.id
timesheets_by_employee = defaultdict(lambda: self.env['account.analytic.line'])
for timesheet in leave_task.timesheet_ids:
timesheets_by_employee[timesheet.employee_id] |= timesheet
self.assertFalse(timesheets_by_employee.get(self.part_time_employee, False))
self.assertEqual(len(timesheets_by_employee.get(self.full_time_employee)), 5)
self.assertEqual(len(timesheets_by_employee.get(self.full_time_employee_2)), 5)
# The standard calendar is for 8 hours/day from 8 to 12 and from 13 to 17.
# So we need to check that the timesheets don't have more than 8 hours per day.
self.assertEqual(leave_task.effective_hours, 80)
def test_search_is_timeoff_task(self):
""" Test the search method on is_timeoff_task
with and without any hr.leave.type with timesheet_task_id defined"""
leaves_types_with_task_id = self.env['hr.leave.type'].search([('timesheet_task_id', '!=', False)])
self.env['project.task'].search([('is_timeoff_task', '!=', False)])
leaves_types_with_task_id.write({'timesheet_task_id': False})
self.env['project.task'].search([('is_timeoff_task', '!=', False)])
def test_timesheet_creation_and_deletion_for_calendar_update(self):
"""
Check that employee's timesheets are correctly updated when the employee's calendar
is modified for public holidays after today's date.
"""
attendance_ids_40h = [
Command.create({'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Monday Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
Command.create({'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
Command.create({'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
Command.create({'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
Command.create({'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'})
]
attendance_ids_35h = [
Command.create({'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Monday Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
Command.create({'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
Command.create({'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
Command.create({'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
Command.create({'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
Command.create({'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'})
]
calendar_40h, calendar_35h = self.env['resource.calendar'].create([
{
'name': 'Calendar 40h',
'company_id': self.test_company.id,
'hours_per_day': 8,
'attendance_ids': attendance_ids_40h,
},
{
'name': 'Calendar 35h',
'company_id': self.test_company.id,
'hours_per_day': 8,
'attendance_ids': attendance_ids_35h,
}
])
gto_09_04, gto_09_11, gto_11_06, gto_11_13 = self.env['resource.calendar.leaves'].create([
{
'name': 'Global Time Off 4 Setpember',
'date_from': datetime(2023, 9, 4, 7, 0, 0, 0),
'date_to': datetime(2023, 9, 4, 18, 0, 0, 0),
'calendar_id': calendar_40h.id,
},
{
'name': 'Global Time Off 11 Setpember',
'date_from': datetime(2023, 9, 11, 7, 0, 0, 0),
'date_to': datetime(2023, 9, 11, 18, 0, 0, 0),
'calendar_id': calendar_35h.id,
},
{
'name': 'Global Time Off 6 November',
'date_from': datetime(2023, 11, 6, 7, 0, 0, 0),
'date_to': datetime(2023, 11, 6, 18, 0, 0, 0),
'calendar_id': calendar_40h.id,
},
{
'name': 'Global Time Off 13 November',
'date_from': datetime(2023, 11, 13, 7, 0, 0, 0),
'date_to': datetime(2023, 11, 13, 18, 0, 0, 0),
'calendar_id': calendar_35h.id,
}
])
with freeze_time('2023-08-10'):
self.full_time_employee.resource_calendar_id = calendar_40h.id
timesheets_employee_40h = self.env['account.analytic.line'].search([('employee_id', '=', self.full_time_employee.id)])
global_leaves_ids_40h = timesheets_employee_40h.global_leave_id
self.assertEqual(len(global_leaves_ids_40h), 2)
self.assertIn(gto_09_04, global_leaves_ids_40h)
self.assertIn(gto_11_06, global_leaves_ids_40h)
with freeze_time('2023-10-10'):
self.full_time_employee.resource_calendar_id = calendar_35h.id
timesheets_employee_35h = self.env['account.analytic.line'].search([('employee_id', '=', self.full_time_employee.id)])
global_leaves_ids_35h = timesheets_employee_35h.global_leave_id
self.assertEqual(len(global_leaves_ids_35h), 2)
self.assertIn(gto_09_04, global_leaves_ids_35h)
self.assertIn(gto_11_13, global_leaves_ids_35h)
self.assertNotIn(gto_09_11, global_leaves_ids_35h)
def test_global_time_off_timesheet_creation_after_leave_refusal(self):
""" When a global time off is created and an employee already has a
validated leave at that date, a timesheet is not created for the
global time off.
We make sure that the global time off timesheet is restored if the
leave is refused.
"""
test_user = self.env['res.users'].with_company(self.test_company).create({
'name': 'Jonathan Doe',
'login': 'jdoe@example.com',
})
test_user.with_company(self.test_company).action_create_employee()
test_user.employee_id.write({
'resource_calendar_id': self.test_company.resource_calendar_id.id,
})
# needed for cancelled leave chatter message
test_user.partner_id.write({
'email': 'jdoe@example.com',
})
# employee leave dates: from monday to friday
today = datetime.today()
next_monday = today.date() + timedelta(days=-today.weekday(), weeks=1)
hr_leave_start_datetime = datetime(next_monday.year, next_monday.month, next_monday.day, 8, 0, 0) # monday next week
hr_leave_end_datetime = hr_leave_start_datetime + timedelta(days=4, hours=9) # friday next week
self.env.company = self.test_company
internal_project = self.test_company.internal_project_id
internal_task_leaves = self.test_company.leave_timesheet_task_id
hr_leave_type_with_ts = self.env['hr.leave.type'].create({
'name': 'Leave Type with timesheet generation',
'requires_allocation': 'no',
'timesheet_generate': True,
'timesheet_project_id': internal_project.id,
'timesheet_task_id': internal_task_leaves.id,
})
# create and validate a leave for full time employee
HrLeave = self.env['hr.leave'].with_context(mail_create_nolog=True, mail_notrack=True)
holiday = HrLeave.with_user(test_user).create({
'name': 'Leave 1',
'employee_id': test_user.employee_id.id,
'holiday_status_id': hr_leave_type_with_ts.id,
'date_from': hr_leave_start_datetime,
'date_to': hr_leave_end_datetime,
})
holiday.sudo().action_validate()
self.assertEqual(len(holiday.timesheet_ids), 5)
# create overlapping global time off
global_leave_start_datetime = hr_leave_start_datetime + timedelta(days=2)
global_leave_end_datetime = global_leave_start_datetime + timedelta(hours=9)
global_time_off = self.env['resource.calendar.leaves'].create({
'name': 'Public Holiday',
'calendar_id': self.test_company.resource_calendar_id.id,
'date_from': global_leave_start_datetime,
'date_to': global_leave_end_datetime,
})
# timesheet linked to global time off should not exist as one already exists for the leave
self.assertFalse(global_time_off.timesheet_ids.filtered(lambda r: r.employee_id == test_user.employee_id))
# refuse original leave
holiday.sudo().action_refuse()
self.assertFalse(holiday.timesheet_ids)
# timesheet linked to global time off should be restored as the existing leave timesheets were unlinked after refusal
self.assertTrue(global_time_off.timesheet_ids.filtered(lambda r: r.employee_id == test_user.employee_id))
# remove global time off to remove the timesheet so we can test cancelling the leave
global_time_off.unlink()
# restore the leave to validated to re-create the associated timesheets
holiday.sudo().action_draft()
holiday.sudo().action_confirm()
holiday.sudo().action_validate()
# recreate the global time off
global_time_off = self.env['resource.calendar.leaves'].create({
'name': 'Public Holiday',
'calendar_id': self.test_company.resource_calendar_id.id,
'date_from': global_leave_start_datetime,
'date_to': global_leave_end_datetime,
})
# timesheet linked to global time off should not exist as one already exists for the leave
self.assertFalse(global_time_off.timesheet_ids.filtered(lambda r: r.employee_id == test_user.employee_id))
# cancel the leave
holiday.with_user(test_user)._action_user_cancel('User cancelled leave')
self.assertFalse(holiday.timesheet_ids)
self.assertTrue(global_time_off.timesheet_ids.filtered(lambda r: r.employee_id == test_user.employee_id))
def test_global_time_off_timesheet_creation_without_calendar(self):
""" Ensure that a global time off without a calendar does not get restored if it overlaps with a refused leave. """
# 5 day leave
hr_leave_start_datetime = datetime(2023, 12, 25, 7, 0, 0, 0)
hr_leave_end_datetime = datetime(2023, 12, 29, 18, 0, 0, 0)
self.env.company = self.test_company
internal_project = self.test_company.internal_project_id
internal_task_leaves = self.test_company.leave_timesheet_task_id
hr_leave_type_with_ts = self.env['hr.leave.type'].create({
'name': 'Leave Type with timesheet generation',
'requires_allocation': 'no',
'timesheet_generate': True,
'timesheet_project_id': internal_project.id,
'timesheet_task_id': internal_task_leaves.id,
})
# create and validate a leave for full time employee
HrLeave = self.env['hr.leave'].with_context(mail_create_nolog=True, mail_notrack=True)
holiday = HrLeave.with_user(self.full_time_employee.user_id).create({
'name': 'Leave 1',
'employee_id': self.full_time_employee.id,
'holiday_status_id': hr_leave_type_with_ts.id,
'date_from': hr_leave_start_datetime,
'date_to': hr_leave_end_datetime,
})
holiday.sudo().action_validate()
self.assertEqual(len(holiday.timesheet_ids), 5)
# overlapping leave without calendar
global_leave_start_datetime = datetime(2023, 12, 27, 7, 0, 0, 0)
global_leave_end_datetime = datetime(2023, 12, 27, 18, 0, 0, 0)
gto_without_calendar = self.env['resource.calendar.leaves'].create({
'name': '2 days afer Christmas',
'date_from': global_leave_start_datetime,
'date_to': global_leave_end_datetime,
})
# ensure timesheets are not created for a global time off without a calendar
self.assertFalse(gto_without_calendar.timesheet_ids)
# refuse the leave
holiday.sudo().action_refuse()
self.assertFalse(holiday.timesheet_ids)
# timesheets should not be restored for a global time off without a calendar
self.assertFalse(gto_without_calendar.timesheet_ids)

View file

@ -0,0 +1,227 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from dateutil.relativedelta import relativedelta
from freezegun import freeze_time
from odoo import fields, SUPERUSER_ID
from odoo.exceptions import UserError
from odoo.tests import common, new_test_user
from odoo.addons.hr_timesheet.tests.test_timesheet import TestCommonTimesheet
import time
class TestTimesheetHolidaysCreate(common.TransactionCase):
def test_status_create(self):
"""Ensure that when a status is created, it fullfills the project and task constrains"""
status = self.env['hr.leave.type'].create({
'name': 'A nice Leave Type',
'requires_allocation': 'no'
})
company = self.env.company
self.assertEqual(status.timesheet_project_id, company.internal_project_id, 'The default project linked to the status should be the same as the company')
self.assertEqual(status.timesheet_task_id, company.leave_timesheet_task_id, 'The default task linked to the status should be the same as the company')
def test_company_create(self):
main_company = self.env.ref('base.main_company')
user = new_test_user(self.env, login='fru',
groups='base.group_user,base.group_erp_manager,base.group_partner_manager',
company_id=main_company.id,
company_ids=[(6, 0, main_company.ids)])
Company = self.env['res.company']
Company = Company.with_user(user)
Company = Company.with_company(main_company)
company = Company.create({'name': "Wall Company"})
self.assertEqual(company.internal_project_id.sudo().company_id, company, "It should have created a project for the company")
class TestTimesheetHolidays(TestCommonTimesheet):
def setUp(self):
super(TestTimesheetHolidays, self).setUp()
self.employee_working_calendar = self.empl_employee.resource_calendar_id
# leave dates : from next monday to next wednesday (to avoid crashing tests on weekend, when
# there is no work days in working calendar)
# NOTE: second and millisecond can add a working days
self.leave_start_datetime = datetime(2018, 2, 5, 7, 0, 0, 0) # this is monday
self.leave_end_datetime = self.leave_start_datetime + relativedelta(days=3)
# all company have those internal project/task (created by default)
self.internal_project = self.env.company.internal_project_id
self.internal_task_leaves = self.env.company.leave_timesheet_task_id
self.hr_leave_type_with_ts = self.env['hr.leave.type'].create({
'name': 'Leave Type with timesheet generation',
'requires_allocation': 'no',
'timesheet_generate': True,
'timesheet_project_id': self.internal_project.id,
'timesheet_task_id': self.internal_task_leaves.id,
})
self.hr_leave_type_no_ts = self.env['hr.leave.type'].create({
'name': 'Leave Type without timesheet generation',
'requires_allocation': 'no',
'timesheet_generate': False,
'timesheet_project_id': False,
'timesheet_task_id': False,
})
# HR Officer allocates some leaves to the employee 1
self.Requests = self.env['hr.leave'].with_context(mail_create_nolog=True, mail_notrack=True)
self.Allocations = self.env['hr.leave.allocation'].with_context(mail_create_nolog=True, mail_notrack=True)
self.hr_leave_allocation_with_ts = self.Allocations.sudo().create({
'name': 'Days for limited category with timesheet',
'employee_id': self.empl_employee.id,
'holiday_status_id': self.hr_leave_type_with_ts.id,
'number_of_days': 10,
'state': 'confirm',
'date_from': time.strftime('%Y-01-01'),
'date_to': time.strftime('%Y-12-31'),
})
self.hr_leave_allocation_with_ts.action_validate()
self.hr_leave_allocation_no_ts = self.Allocations.sudo().create({
'name': 'Days for limited category without timesheet',
'employee_id': self.empl_employee.id,
'holiday_status_id': self.hr_leave_type_no_ts.id,
'number_of_days': 10,
'state': 'confirm',
'date_from': time.strftime('%Y-01-01'),
'date_to': time.strftime('%Y-12-31'),
})
self.hr_leave_allocation_no_ts.action_validate()
def test_validate_with_timesheet(self):
# employee creates a leave request
number_of_days = (self.leave_end_datetime - self.leave_start_datetime).days
holiday = self.Requests.with_user(self.user_employee).create({
'name': 'Leave 1',
'employee_id': self.empl_employee.id,
'holiday_status_id': self.hr_leave_type_with_ts.id,
'date_from': self.leave_start_datetime,
'date_to': self.leave_end_datetime,
'number_of_days': number_of_days,
})
holiday.with_user(SUPERUSER_ID).action_validate()
# The leave type and timesheet are linked to the same project and task'
self.assertEqual(holiday.timesheet_ids.project_id.id, self.internal_project.id)
self.assertEqual(holiday.timesheet_ids.task_id.id, self.internal_task_leaves.id)
self.assertEqual(len(holiday.timesheet_ids), number_of_days, 'Number of generated timesheets should be the same as the leave duration (1 per day between %s and %s)' % (fields.Datetime.to_string(self.leave_start_datetime), fields.Datetime.to_string(self.leave_end_datetime)))
# manager refuse the leave
holiday.with_user(SUPERUSER_ID).action_refuse()
self.assertEqual(len(holiday.timesheet_ids), 0, 'Number of linked timesheets should be zero, since the leave is refused.')
def test_validate_without_timesheet(self):
# employee creates a leave request
number_of_days = (self.leave_end_datetime - self.leave_start_datetime).days
holiday = self.Requests.with_user(self.user_employee).create({
'name': 'Leave 1',
'employee_id': self.empl_employee.id,
'holiday_status_id': self.hr_leave_type_no_ts.id,
'date_from': self.leave_start_datetime,
'date_to': self.leave_end_datetime,
'number_of_days': number_of_days,
})
holiday.with_user(SUPERUSER_ID).action_validate()
self.assertEqual(len(holiday.timesheet_ids), 0, 'Number of generated timesheets should be zero since the leave type does not generate timesheet')
@freeze_time('2018-02-05') # useful to be able to cancel the validated time off
def test_cancel_validate_holidays(self):
number_of_days = (self.leave_end_datetime - self.leave_start_datetime).days
holiday = self.Requests.with_user(self.user_employee).create({
'name': 'Leave 1',
'employee_id': self.empl_employee.id,
'holiday_status_id': self.hr_leave_type_with_ts.id,
'date_from': self.leave_start_datetime,
'date_to': self.leave_end_datetime,
'number_of_days': number_of_days,
})
holiday.with_user(self.env.user).action_validate()
self.assertEqual(len(holiday.timesheet_ids), number_of_days, 'Number of generated timesheets should be the same as the leave duration (1 per day between %s and %s)' % (fields.Datetime.to_string(self.leave_start_datetime), fields.Datetime.to_string(self.leave_end_datetime)))
self.env['hr.holidays.cancel.leave'].with_user(self.user_employee).with_context(default_leave_id=holiday.id) \
.new({'reason': 'Test remove holiday'}) \
.action_cancel_leave()
self.assertFalse(holiday.active, 'The time off should be archived')
self.assertEqual(len(holiday.timesheet_ids), 0, 'The timesheets generated should be unlink.')
def test_timesheet_time_off_including_public_holiday(self):
""" Generate one timesheet for the public holiday and 4 timesheets for the time off.
Test Case:
=========
1) Create a public time off on Wednesday
2) In the same week, create a time off during one week for an employee
3) Check if there are five timesheets generated for time off and public
holiday.4 timesheets should be linked to the time off and 1 for
the public one.
"""
leave_start_datetime = datetime(2022, 1, 24, 7, 0, 0, 0) # Monday
leave_end_datetime = datetime(2022, 1, 28, 18, 0, 0, 0)
# Create a public holiday
self.env['resource.calendar.leaves'].create({
'name': 'Test',
'calendar_id': self.employee_working_calendar.id,
'date_from': datetime(2022, 1, 26, 7, 0, 0, 0), # This is Wednesday and India Independence
'date_to': datetime(2022, 1, 26, 18, 0, 0, 0),
})
holiday = self.Requests.with_user(self.user_employee).create({
'name': 'Leave 1',
'employee_id': self.empl_employee.id,
'holiday_status_id': self.hr_leave_type_with_ts.id,
'date_from': leave_start_datetime,
'date_to': leave_end_datetime,
})
holiday.with_user(SUPERUSER_ID).action_validate()
self.assertEqual(len(holiday.timesheet_ids), 4, '4 timesheets should be generated for this time off.')
timesheets = self.env['account.analytic.line'].search([
('date', '>=', leave_start_datetime),
('date', '<=', leave_end_datetime),
('employee_id', '=', self.empl_employee.id),
])
# should not able to update timeoff timesheets
with self.assertRaises(UserError):
timesheets.with_user(self.empl_employee).write({'task_id': 4})
# should not able to create timesheet in timeoff task
with self.assertRaises(UserError):
self.env['account.analytic.line'].with_user(self.empl_employee).create({
'name': "my timesheet",
'project_id': self.internal_project.id,
'task_id': self.internal_task_leaves.id,
'date': '2021-10-04',
'unit_amount': 8.0,
})
self.assertEqual(len(timesheets.filtered('holiday_id')), 4, "4 timesheet should be linked to employee's timeoff")
self.assertEqual(len(timesheets.filtered('global_leave_id')), 1, '1 timesheet should be linked to global leave')
@freeze_time('2018-02-01 08:00:00')
def test_timesheet_when_archiving_employee(self):
number_of_days = (self.leave_end_datetime - self.leave_start_datetime).days
holiday = self.Requests.with_user(self.user_employee).create({
'name': 'Leave 1',
'employee_id': self.empl_employee.id,
'holiday_status_id': self.hr_leave_type_with_ts.id,
'date_from': self.leave_start_datetime,
'date_to': self.leave_end_datetime,
'number_of_days': number_of_days,
})
holiday.with_user(SUPERUSER_ID).action_validate()
wizard = self.env['hr.departure.wizard'].create({
'employee_id': self.empl_employee.id,
'departure_date': datetime(2018, 2, 1, 12),
'archive_allocation': False,
})
wizard.action_register_departure()
self.assertEqual(len(holiday.timesheet_ids), 0, 'Timesheets related to the archived employee should have been deleted')