mirror of
https://github.com/bringout/oca-ocb-hr.git
synced 2026-04-26 12:12:02 +02:00
19.0 vanilla
This commit is contained in:
parent
a1137a1456
commit
e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions
|
|
@ -2,10 +2,12 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import etree
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import fields
|
||||
from odoo.fields import Command
|
||||
from odoo.tests.common import TransactionCase, Form
|
||||
from odoo.exceptions import AccessError, UserError, ValidationError
|
||||
from odoo.tests import Form, TransactionCase, new_test_user
|
||||
from odoo.exceptions import AccessError, RedirectWarning, UserError, ValidationError
|
||||
|
||||
|
||||
class TestCommonTimesheet(TransactionCase):
|
||||
|
|
@ -13,8 +15,7 @@ class TestCommonTimesheet(TransactionCase):
|
|||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCommonTimesheet, cls).setUpClass()
|
||||
cls.env.user.tz = "Europe/Brussels"
|
||||
cls.env.company.resource_calendar_id.tz = "Europe/Brussels"
|
||||
|
||||
# Crappy hack to disable the rule from timesheet grid, if it exists
|
||||
# The registry doesn't contain the field timesheet_manager_id.
|
||||
# but there is an ir.rule about it, crashing during its evaluation
|
||||
|
|
@ -30,8 +31,7 @@ class TestCommonTimesheet(TransactionCase):
|
|||
})
|
||||
|
||||
cls.analytic_plan = cls.env['account.analytic.plan'].create({
|
||||
'name': 'Plan Test',
|
||||
'company_id': False,
|
||||
'name': 'Timesheet Plan Test',
|
||||
})
|
||||
cls.analytic_account = cls.env['account.analytic.account'].create({
|
||||
'name': 'Analytic Account for Test Customer',
|
||||
|
|
@ -45,19 +45,19 @@ class TestCommonTimesheet(TransactionCase):
|
|||
'name': 'Project X',
|
||||
'allow_timesheets': True,
|
||||
'partner_id': cls.partner.id,
|
||||
'analytic_account_id': cls.analytic_account.id,
|
||||
'account_id': cls.analytic_account.id,
|
||||
})
|
||||
cls.task1 = cls.env['project.task'].create({
|
||||
'name': 'Task One',
|
||||
'priority': '0',
|
||||
'kanban_state': 'normal',
|
||||
'state': '01_in_progress',
|
||||
'project_id': cls.project_customer.id,
|
||||
'partner_id': cls.partner.id,
|
||||
})
|
||||
cls.task2 = cls.env['project.task'].create({
|
||||
'name': 'Task Two',
|
||||
'priority': '1',
|
||||
'kanban_state': 'done',
|
||||
'state': '1_done',
|
||||
'project_id': cls.project_customer.id,
|
||||
})
|
||||
# users
|
||||
|
|
@ -65,52 +65,62 @@ class TestCommonTimesheet(TransactionCase):
|
|||
'name': 'User Employee',
|
||||
'login': 'user_employee',
|
||||
'email': 'useremployee@test.com',
|
||||
'groups_id': [(6, 0, [cls.env.ref('hr_timesheet.group_hr_timesheet_user').id])],
|
||||
'group_ids': [(6, 0, [cls.env.ref('hr_timesheet.group_hr_timesheet_user').id])],
|
||||
})
|
||||
cls.user_employee2 = cls.env['res.users'].create({
|
||||
'name': 'User Employee 2',
|
||||
'login': 'user_employee2',
|
||||
'email': 'useremployee2@test.com',
|
||||
'groups_id': [(6, 0, [cls.env.ref('hr_timesheet.group_hr_timesheet_user').id])],
|
||||
'group_ids': [(6, 0, [cls.env.ref('hr_timesheet.group_hr_timesheet_user').id])],
|
||||
})
|
||||
cls.user_manager = cls.env['res.users'].create({
|
||||
'name': 'User Officer',
|
||||
'login': 'user_manager',
|
||||
'email': 'usermanager@test.com',
|
||||
'groups_id': [(6, 0, [cls.env.ref('hr_timesheet.group_timesheet_manager').id])],
|
||||
'group_ids': [(6, 0, [cls.env.ref('hr_timesheet.group_timesheet_manager').id])],
|
||||
})
|
||||
# employees
|
||||
cls.empl_employee = cls.env['hr.employee'].create({
|
||||
'name': 'User Empl Employee',
|
||||
'user_id': cls.user_employee.id,
|
||||
'employee_type': 'freelance', # Avoid searching the contract if hr_contract module is installed before this module.
|
||||
})
|
||||
cls.empl_employee2 = cls.env['hr.employee'].create({
|
||||
'name': 'User Empl Employee 2',
|
||||
'user_id': cls.user_employee2.id,
|
||||
'employee_type': 'freelance',
|
||||
})
|
||||
cls.empl_manager = cls.env['hr.employee'].create({
|
||||
'name': 'User Empl Officer',
|
||||
'user_id': cls.user_manager.id,
|
||||
'employee_type': 'freelance',
|
||||
})
|
||||
|
||||
def assert_get_view_timesheet_encode_uom(self, expected):
|
||||
companies = self.env['res.company'].create([
|
||||
{'name': 'foo', 'timesheet_encode_uom_id': self.env.ref('uom.product_uom_hour').id},
|
||||
{'name': 'bar', 'timesheet_encode_uom_id': self.env.ref('uom.product_uom_day').id},
|
||||
])
|
||||
for view_xml_id, xpath_expr, expected_labels in expected:
|
||||
for company, expected_label in zip(companies, expected_labels):
|
||||
view = self.env.ref(view_xml_id)
|
||||
view = self.env[view.model].with_company(company).get_view(view.id, view.type)
|
||||
tree = etree.fromstring(view['arch'])
|
||||
field_node = tree.xpath(xpath_expr)[0]
|
||||
self.assertEqual(field_node.get('string'), expected_label)
|
||||
cls.project = cls.env['project.project'].create({
|
||||
'name': 'Test Project',
|
||||
'privacy_visibility': 'followers',
|
||||
'task_ids': [Command.create({
|
||||
'name': 'Test Task',
|
||||
})],
|
||||
})
|
||||
cls.timesheet = cls.env['account.analytic.line'].create({
|
||||
'name': 'Test Timesheet',
|
||||
'project_id': cls.project.id,
|
||||
'task_id': cls.project.task_ids[0].id,
|
||||
'employee_id': cls.empl_employee.id,
|
||||
})
|
||||
cls.timesheet_manager_no_project_user = new_test_user(
|
||||
cls.env,
|
||||
login='no_project_user',
|
||||
groups='hr_timesheet.group_timesheet_manager'
|
||||
)
|
||||
|
||||
|
||||
class TestTimesheet(TestCommonTimesheet):
|
||||
|
||||
def setUp(self):
|
||||
super(TestTimesheet, self).setUp()
|
||||
super().setUp()
|
||||
# Make sure to clean the plan fields
|
||||
self.env.registry._setup_models__(self.env.cr)
|
||||
|
||||
# Crappy hack to disable the rule from timesheet grid, if it exists
|
||||
# The registry doesn't contain the field timesheet_manager_id.
|
||||
|
|
@ -120,9 +130,9 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
rule.active = False
|
||||
|
||||
def test_log_timesheet(self):
|
||||
""" Test when log timesheet : check analytic account, user and employee are correctly set. """
|
||||
""" Test when log timesheet: check analytic account, user and employee are correctly set. """
|
||||
Timesheet = self.env['account.analytic.line']
|
||||
timesheet_uom = self.project_customer.analytic_account_id.company_id.project_time_mode_id
|
||||
timesheet_uom = self.project_customer.account_id.company_id.project_time_mode_id
|
||||
# employee 1 log some timesheet on task 1
|
||||
timesheet1 = Timesheet.with_user(self.user_employee).create({
|
||||
'project_id': self.project_customer.id,
|
||||
|
|
@ -130,7 +140,7 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
'name': 'my first timesheet',
|
||||
'unit_amount': 4,
|
||||
})
|
||||
self.assertEqual(timesheet1.account_id, self.project_customer.analytic_account_id, 'Analytic account should be the same as the project')
|
||||
self.assertEqual(timesheet1.account_id, self.project_customer.account_id, 'Analytic account should be the same as the project')
|
||||
self.assertEqual(timesheet1.employee_id, self.empl_employee, 'Employee should be the one of the current user')
|
||||
self.assertEqual(timesheet1.partner_id, self.task1.partner_id, 'Customer of task should be the same of the one set on new timesheet')
|
||||
self.assertEqual(timesheet1.product_uom_id, timesheet_uom, "The UoM of the timesheet should be the one set on the company of the analytic account.")
|
||||
|
|
@ -165,7 +175,7 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
self.assertEqual(timesheet4.partner_id, self.project_customer.partner_id, 'Customer of new timesheet should be the same of the one set project (since no task on timesheet)')
|
||||
|
||||
def test_log_access_rights(self):
|
||||
""" Test access rights : user can update its own timesheets only, and manager can change all """
|
||||
""" Test access rights: user can update its own timesheets only, and manager can change all """
|
||||
# employee 1 log some timesheet on task 1
|
||||
Timesheet = self.env['account.analytic.line']
|
||||
timesheet1 = Timesheet.with_user(self.user_employee).create({
|
||||
|
|
@ -195,7 +205,7 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
'allow_timesheets': False,
|
||||
'partner_id': self.partner.id,
|
||||
})
|
||||
self.assertFalse(non_tracked_project.analytic_account_id, "A non time-tracked project shouldn't generate an analytic account")
|
||||
self.assertFalse(non_tracked_project.account_id, "A non time-tracked project shouldn't generate an analytic account")
|
||||
|
||||
# create a project tracking time
|
||||
tracked_project = self.env['project.project'].create({
|
||||
|
|
@ -203,21 +213,21 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
'allow_timesheets': True,
|
||||
'partner_id': self.partner.id,
|
||||
})
|
||||
self.assertTrue(tracked_project.analytic_account_id, "A time-tracked project should generate an analytic account")
|
||||
self.assertTrue(tracked_project.analytic_account_id.active, "A time-tracked project should generate an active analytic account")
|
||||
self.assertEqual(tracked_project.partner_id, tracked_project.analytic_account_id.partner_id, "The generated AA should have the same partner as the project")
|
||||
self.assertEqual(tracked_project.name, tracked_project.analytic_account_id.name, "The generated AA should have the same name as the project")
|
||||
self.assertEqual(tracked_project.analytic_account_id.project_count, 1, "The generated AA should be linked to the project")
|
||||
self.assertTrue(tracked_project.account_id, "A time-tracked project should generate an analytic account")
|
||||
self.assertTrue(tracked_project.account_id.active, "A time-tracked project should generate an active analytic account")
|
||||
self.assertEqual(tracked_project.partner_id, tracked_project.account_id.partner_id, "The generated AA should have the same partner as the project")
|
||||
self.assertEqual(tracked_project.name, tracked_project.account_id.name, "The generated AA should have the same name as the project")
|
||||
self.assertEqual(tracked_project.account_id.project_count, 1, "The generated AA should be linked to the project")
|
||||
|
||||
# create a project without tracking time, but with analytic account
|
||||
analytic_project = self.env['project.project'].create({
|
||||
'name': 'Project without timesheet but with AA',
|
||||
'allow_timesheets': True,
|
||||
'partner_id': self.partner.id,
|
||||
'analytic_account_id': tracked_project.analytic_account_id.id,
|
||||
'account_id': tracked_project.account_id.id,
|
||||
})
|
||||
self.assertNotEqual(analytic_project.name, tracked_project.analytic_account_id.name, "The name of the associated AA can be different from the project")
|
||||
self.assertEqual(tracked_project.analytic_account_id.project_count, 2, "The AA should be linked to 2 project")
|
||||
self.assertNotEqual(analytic_project.name, tracked_project.account_id.name, "The name of the associated AA can be different from the project")
|
||||
self.assertEqual(tracked_project.account_id.project_count, 2, "The AA should be linked to 2 project")
|
||||
|
||||
# analytic linked to projects containing tasks can not be removed
|
||||
task = self.env['project.task'].create({
|
||||
|
|
@ -225,25 +235,26 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
'project_id': tracked_project.id,
|
||||
})
|
||||
with self.assertRaises(UserError):
|
||||
tracked_project.analytic_account_id.unlink()
|
||||
tracked_project.account_id.unlink()
|
||||
|
||||
# task can be removed, as there is no timesheet
|
||||
task.unlink()
|
||||
|
||||
# since both projects linked to the same analytic account are empty (no task), it can be removed
|
||||
tracked_project.analytic_account_id.unlink()
|
||||
tracked_project.account_id.unlink()
|
||||
|
||||
def test_transfert_project(self):
|
||||
""" Transfert task with timesheet to another project. """
|
||||
Timesheet = self.env['account.analytic.line']
|
||||
Task = self.env['project.task'].with_context(default_project_id=self.task1.project_id.id)
|
||||
|
||||
# create nested subtasks
|
||||
task_child = self.env['project.task'].create({
|
||||
task_child = Task.create({
|
||||
'name': 'Task Child',
|
||||
'parent_id': self.task1.id,
|
||||
})
|
||||
|
||||
task_grandchild = self.env['project.task'].create({
|
||||
task_grandchild = Task.create({
|
||||
'name': 'Task Grandchild',
|
||||
'parent_id': task_child.id,
|
||||
})
|
||||
|
|
@ -288,18 +299,28 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
|
||||
timesheet_count1 = Timesheet.search_count([('project_id', '=', self.project_customer.id)])
|
||||
timesheet_count2 = Timesheet.search_count([('project_id', '=', self.project_customer2.id)])
|
||||
self.assertEqual(timesheet_count1, 0, "There are still timesheets linked to Project1")
|
||||
self.assertEqual(timesheet_count1, 0, "No timesheets should be linked to Project1")
|
||||
self.assertEqual(timesheet_count2, 3, "3 timesheets should be linked to Project2")
|
||||
self.assertEqual(len(self.task1.timesheet_ids), 1, "The timesheet still should be linked to task1")
|
||||
self.assertEqual(len(task_child.timesheet_ids), 1, "The timesheet still should be linked to task_child")
|
||||
self.assertEqual(len(task_grandchild.timesheet_ids), 1, "The timesheet still should be linked to task_grandchild")
|
||||
|
||||
# it is forbidden to set a task with timesheet without project
|
||||
# It is forbidden to unset the project of a task with timesheet
|
||||
with self.assertRaises(UserError):
|
||||
self.task1.write({
|
||||
'project_id': False
|
||||
})
|
||||
|
||||
def test_favorite_project_id(self):
|
||||
""" Test that user without previous timesheets and without
|
||||
access to the internal project has no favorite project. """
|
||||
|
||||
# make internal project accessible to invited internal users only
|
||||
self.env.company.internal_project_id.privacy_visibility = 'followers'
|
||||
|
||||
favorite_project = self.env['account.analytic.line'].with_user(self.user_employee)._get_favorite_project_id()
|
||||
self.assertFalse(favorite_project, "A user without timesheet and without access to the internal project should have no favorite project.")
|
||||
|
||||
def test_recompute_amount_for_multiple_timesheets(self):
|
||||
""" Check that amount is recomputed correctly when setting unit_amount for multiple timesheets at once. """
|
||||
Timesheet = self.env['account.analytic.line']
|
||||
|
|
@ -360,13 +381,12 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
|
||||
def test_task_with_timesheet_project_change(self):
|
||||
'''This test checks that no error is raised when moving a task that contains timesheet to another project.
|
||||
This move implying writing on the account.analytic.line.
|
||||
'''
|
||||
|
||||
project_manager = self.env['res.users'].create({
|
||||
'name': 'user_project_manager',
|
||||
'login': 'user_project_manager',
|
||||
'groups_id': [(6, 0, [self.ref('project.group_project_manager')])],
|
||||
'group_ids': [(6, 0, [self.ref('project.group_project_manager')])],
|
||||
})
|
||||
|
||||
project = self.env['project.project'].create({
|
||||
|
|
@ -400,7 +420,15 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
'project_id': second_project.id
|
||||
})
|
||||
|
||||
self.assertEqual(timesheet.project_id, second_project, 'The project_id of timesheet should be second_project')
|
||||
self.assertEqual(timesheet.project_id, second_project, 'The project_id of non-validated timesheet should have changed')
|
||||
|
||||
def test_compute_display_name(self):
|
||||
self.timesheet.with_user(self.timesheet_manager_no_project_user)._compute_display_name()
|
||||
self.assertEqual(
|
||||
self.timesheet.display_name,
|
||||
"Test Project - Test Task",
|
||||
"Display name should be correctly computed without raising AccessError."
|
||||
)
|
||||
|
||||
def test_create_timesheet_employee_not_in_company(self):
|
||||
''' ts.employee_id only if the user has an employee in the company or one employee for all companies.
|
||||
|
|
@ -410,7 +438,6 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
|
||||
analytic_plan = self.env['account.analytic.plan'].create({
|
||||
'name': 'Plan Test',
|
||||
'company_id': company_3.id
|
||||
})
|
||||
analytic_account = self.env['account.analytic.account'].create({
|
||||
'name': 'Aa Aa',
|
||||
|
|
@ -420,7 +447,7 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
project = self.env['project.project'].create({
|
||||
'name': 'Aa Project',
|
||||
'company_id': company_3.id,
|
||||
'analytic_account_id': analytic_account.id,
|
||||
'account_id': analytic_account.id,
|
||||
})
|
||||
task = self.env['project.task'].create({
|
||||
'name': 'Aa Task',
|
||||
|
|
@ -490,16 +517,46 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
3) Enter the 8 hour timesheet in the child task
|
||||
4) Check subtask Effective hours in parent task
|
||||
"""
|
||||
self.task1.child_ids = [Command.set([self.task2.id])]
|
||||
self.env['account.analytic.line'].create({
|
||||
subtask_1, subtask_2 = self.env['project.task'].create([
|
||||
{
|
||||
'name': 'Subtask 1',
|
||||
'project_id': self.project_customer.id,
|
||||
},
|
||||
{
|
||||
'name': 'Subtask 2',
|
||||
'project_id': self.project_customer.id,
|
||||
'child_ids': [Command.create({'name': 'Subsubtask', 'project_id': self.project_customer.id})],
|
||||
},
|
||||
])
|
||||
subsubtask = subtask_2.child_ids
|
||||
self.task1.child_ids = subtask_1 + subtask_2
|
||||
self.assertTrue(self.project_customer.allow_timesheets, 'The project should be timesheetable')
|
||||
self.assertEqual(subtask_1.allow_timesheets, self.project_customer.allow_timesheets, 'The subtask should follow the settings of its project linked.')
|
||||
Timesheet = self.env['account.analytic.line']
|
||||
Timesheet.create({
|
||||
'name': 'FirstTimeSheet',
|
||||
'project_id': self.project_customer.id,
|
||||
'task_id': self.task2.id,
|
||||
'task_id': subtask_1.id,
|
||||
'unit_amount': 8.0,
|
||||
'employee_id': self.empl_employee2.id,
|
||||
})
|
||||
self.assertEqual(self.task1.subtask_effective_hours, 8, 'Hours Spent on Sub-tasks should be 8 hours in Parent Task')
|
||||
self.task1.child_ids = [Command.clear()]
|
||||
|
||||
Timesheet.create([
|
||||
{
|
||||
'name': '/',
|
||||
'task_id': subtask_2.id,
|
||||
'unit_amount': 1.0,
|
||||
'employee_id': self.empl_employee2.id,
|
||||
},
|
||||
{
|
||||
'name': '/',
|
||||
'task_id': subsubtask.id,
|
||||
'unit_amount': 1.0,
|
||||
'employee_id': self.empl_employee2.id,
|
||||
},
|
||||
])
|
||||
self.assertEqual(self.task1.subtask_effective_hours, 10)
|
||||
|
||||
def test_ensure_product_uom_set_in_timesheet(self):
|
||||
self.assertFalse(self.project_customer.timesheet_ids, 'No timesheet should be recorded in this project')
|
||||
|
|
@ -511,12 +568,12 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
])
|
||||
self.assertEqual(
|
||||
timesheet1.product_uom_id,
|
||||
self.project_customer.analytic_account_id.company_id.timesheet_encode_uom_id,
|
||||
self.project_customer.account_id.company_id.timesheet_encode_uom_id,
|
||||
'The default UoM set on the timesheet should be the one set on the company of AA.'
|
||||
)
|
||||
self.assertEqual(
|
||||
timesheet2.product_uom_id,
|
||||
self.project_customer.analytic_account_id.company_id.timesheet_encode_uom_id,
|
||||
self.project_customer.account_id.company_id.timesheet_encode_uom_id,
|
||||
'Even if the product_uom_id field is empty in the vals, the product_uom_id should have a UoM by default,'
|
||||
' otherwise the `total_timesheet_time` in project should not included the timesheet.'
|
||||
)
|
||||
|
|
@ -546,16 +603,37 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
with self.assertRaises(UserError):
|
||||
timesheet.employee_id = self.empl_employee2
|
||||
|
||||
def test_get_view_timesheet_encode_uom(self):
|
||||
""" Test the label of timesheet time spent fields according to the company encoding timesheet uom """
|
||||
self.assert_get_view_timesheet_encode_uom([
|
||||
('hr_timesheet.hr_timesheet_line_form', '//field[@name="unit_amount"]', ['Hours Spent', 'Days Spent']),
|
||||
('hr_timesheet.project_invoice_form', '//field[@name="allocated_hours"]', [None, 'Allocated Days']),
|
||||
('hr_timesheet.view_task_form2_inherited', '//field[@name="unit_amount"]', ['Hours Spent', 'Days Spent']),
|
||||
('hr_timesheet.view_task_tree2_inherited', '//field[@name="planned_hours"]', [None, 'Initially Planned Days']),
|
||||
('hr_timesheet.view_task_project_user_graph_inherited', '//field[@name="hours_planned"]', [None, 'Planned Days']),
|
||||
('hr_timesheet.timesheets_analysis_report_pivot_employee', '//field[@name="unit_amount"]', [None, 'Days Spent']),
|
||||
])
|
||||
def test_create_timesheet_with_companyless_analytic_account(self):
|
||||
""" This test ensures that a timesheet can be created on an analytic account whose company_id is set to False"""
|
||||
self.project_customer.account_id.company_id = False
|
||||
timesheet_with_project = self.env['account.analytic.line'].with_user(self.user_employee).create(
|
||||
{'unit_amount': 1.0, 'project_id': self.project_customer.id})
|
||||
self.assertEqual(timesheet_with_project.product_uom_id, self.project_customer.company_id.project_time_mode_id,
|
||||
"The product_uom_id of the timesheet should be equal to the project's company uom "
|
||||
"if the project's analytic account has no company_id and no task_id is defined in the vals")
|
||||
timesheet_with_task = self.env['account.analytic.line'].with_user(self.user_employee).create({
|
||||
'unit_amount': 1.0, 'task_id': self.task1.id
|
||||
})
|
||||
self.assertEqual(timesheet_with_task.product_uom_id, self.task1.company_id.project_time_mode_id,
|
||||
"The product_uom_id of the timesheet should be equal to the task's company uom "
|
||||
"if the project's analytic account has no company_id")
|
||||
# Remove the company also on the project to be sure we find a UoM
|
||||
self.project_customer.company_id = False
|
||||
timesheet_with_project.with_user(self.user_employee).write(
|
||||
{'unit_amount': 2.0, 'project_id': self.project_customer.id})
|
||||
self.assertEqual(timesheet_with_project.product_uom_id, self.env.company.project_time_mode_id,
|
||||
"The product_uom_id of the timesheet should be equal to the company uom "
|
||||
"if the project's analytic account and the project have no company_id")
|
||||
|
||||
|
||||
def test_create_timesheet_with_default_employee_in_context(self):
|
||||
timesheet = self.env['account.analytic.line'].with_context(default_employee_id=self.empl_employee.id).create({
|
||||
'project_id': self.project_customer.id,
|
||||
'task_id': self.task1.id,
|
||||
'name': 'Timesheet with default employee in context',
|
||||
'unit_amount': 3,
|
||||
})
|
||||
self.assertEqual(timesheet.employee_id, self.empl_employee)
|
||||
|
||||
def test_uom_change_timesheet(self):
|
||||
"""
|
||||
|
|
@ -577,22 +655,59 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
'unit_amount': 8,
|
||||
'employee_id': self.empl_employee2.id
|
||||
})
|
||||
project_update_hrs = self.env['project.update'].create({
|
||||
'name': 'Project update 1',
|
||||
'project_id': project.id,
|
||||
'status': 'on_track',
|
||||
})
|
||||
self.assertEqual(project_update_hrs.timesheet_time, 8, "Timesheet time should be 8 hours for new project update")
|
||||
# Clear cached computed project values before the UoM change
|
||||
self.env['project.project'].invalidate_model()
|
||||
self.env.company.timesheet_encode_uom_id = self.env.ref('uom.product_uom_day')
|
||||
self.assertEqual(project.total_timesheet_time, 8, "Total timesheet time should be 8 hours")
|
||||
self.assertEqual(project.timesheet_encode_uom_id.name, 'Days', "Timesheet encode uom should be 'Days'")
|
||||
self.assertEqual(project.total_timesheet_time, 1, "Total timesheet time should be 1 day")
|
||||
project.allocated_hours = 0.0
|
||||
panel_data = project.get_panel_data()
|
||||
self.assertEqual(panel_data['buttons'][1]['number'], "1 Days", "Should display '1 Days'")
|
||||
self.assertEqual(project.timesheet_encode_uom_id, self.env.company.timesheet_encode_uom_id, "Timesheet encode uom should be the one from the company of the env, since the project has no company.")
|
||||
project_update_days = self.env['project.update'].create({
|
||||
'name': 'Project update 2',
|
||||
'project_id': project.id,
|
||||
'status': 'on_track',
|
||||
})
|
||||
self.assertEqual(project_update_days.timesheet_time, 1, "Timesheet time should be 1 day for new project update")
|
||||
|
||||
def test_create_timesheet_with_companyless_analytic_account(self):
|
||||
""" This test ensures that a timesheet can be created on an analytic account whose company_id is set to False"""
|
||||
self.project_customer.analytic_account_id.company_id = False
|
||||
timesheet = self.env['account.analytic.line'].with_user(self.user_employee).create(
|
||||
{'unit_amount': 1.0, 'project_id': self.project_customer.id})
|
||||
self.assertEqual(timesheet.product_uom_id, self.project_customer.company_id.project_time_mode_id,
|
||||
"The product_uom_id of the timesheet should be equal to the project's company uom "
|
||||
"if the project's analytic account has no company_id")
|
||||
def test_unlink_task_with_timesheet(self):
|
||||
self.env['account.analytic.line'].create({
|
||||
'project_id': self.project_customer.id,
|
||||
'task_id': self.task1.id,
|
||||
'name': 'timesheet',
|
||||
'unit_amount': 4,
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
self.task2.unlink()
|
||||
with self.assertRaises(RedirectWarning):
|
||||
self.task1.unlink()
|
||||
|
||||
def test_percentage_of_planned_hours(self):
|
||||
""" Test the percentage of planned hours on a task. """
|
||||
self.task1.planned_hours = round(11/60, 2)
|
||||
def test_cannot_convert_task_with_timesheets_in_private_task(self):
|
||||
self.env['account.analytic.line'].create({
|
||||
'name': '/',
|
||||
'unit_amount': 1,
|
||||
'project_id': self.project_customer.id,
|
||||
'task_id': self.task1.id,
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
with self.assertRaises(UserError):
|
||||
self.task1.project_id = False
|
||||
|
||||
self.task1.parent_id = self.task2
|
||||
self.task1.project_id = self.project_customer
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
self.task1.project_id = False
|
||||
|
||||
def test_percentage_of_allocated_hours(self):
|
||||
""" Test the percentage of allocated hours on a task. """
|
||||
self.task1.allocated_hours = 11/60
|
||||
self.assertEqual(self.task1.effective_hours, 0, 'No timesheet should be created yet.')
|
||||
self.assertEqual(self.task1.progress, 0, 'No timesheet should be created yet.')
|
||||
self.env['account.analytic.line'].create([
|
||||
|
|
@ -616,4 +731,319 @@ class TestTimesheet(TestCommonTimesheet):
|
|||
'employee_id': self.empl_employee.id,
|
||||
},
|
||||
])
|
||||
self.assertEqual(self.task1.progress, 100, 'The percentage of planned hours should be 100%.')
|
||||
self.assertEqual(self.task1.progress, 1, 'The progress of allocated hours should be 1.')
|
||||
|
||||
def test_timesheet_update_user_on_employee(self):
|
||||
timesheet = self.env['account.analytic.line'].create({
|
||||
'project_id': self.project_customer.id,
|
||||
'task_id': self.task1.id,
|
||||
'name': 'my first timesheet',
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
self.assertEqual(timesheet.user_id, self.empl_employee.user_id)
|
||||
new_user = self.env['res.users'].create({
|
||||
'name': 'Test user',
|
||||
'login': 'test',
|
||||
})
|
||||
self.empl_employee.user_id = new_user
|
||||
self.assertEqual(timesheet.user_id, new_user)
|
||||
|
||||
def test_analytic_plan_timesheet_creation(self):
|
||||
child_analytic_plan = self.env['account.analytic.plan'].create({
|
||||
'name': 'Child Analytic Plan',
|
||||
'parent_id': self.analytic_plan.id,
|
||||
})
|
||||
child_analytic_account = self.env['account.analytic.account'].create({
|
||||
'name': 'Analytic Account for Child Analytic Plan',
|
||||
'partner_id': self.partner.id,
|
||||
'plan_id': child_analytic_plan.id,
|
||||
'code': 'TEST',
|
||||
})
|
||||
plan_name = f'{self.analytic_plan._column_name()}'
|
||||
self.project_customer[plan_name] = child_analytic_account
|
||||
timesheet = self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet',
|
||||
'unit_amount': 1,
|
||||
'project_id': self.project_customer.id,
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
self.assertEqual(self.project_customer.account_id, timesheet.account_id)
|
||||
self.assertEqual(self.project_customer[plan_name], timesheet[plan_name])
|
||||
|
||||
def test_analytic_plan_timesheet_change_project(self):
|
||||
timesheet = self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet',
|
||||
'unit_amount': 1,
|
||||
'project_id': self.project_customer.id,
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
analytic_plan = self.env['account.analytic.plan'].create({
|
||||
'name': 'Analytic Plan'
|
||||
})
|
||||
analytic_account = self.env['account.analytic.account'].create({
|
||||
'name': 'Analytic Account',
|
||||
'partner_id': self.partner.id,
|
||||
'plan_id': analytic_plan.id,
|
||||
'code': 'TEST',
|
||||
})
|
||||
plan_name = f'{analytic_plan._column_name()}'
|
||||
other_project = self.env['project.project'].create({
|
||||
'name': 'Other Project',
|
||||
'allow_timesheets': True,
|
||||
plan_name: analytic_account.id,
|
||||
})
|
||||
timesheet.project_id = other_project
|
||||
self.assertEqual(other_project.account_id, timesheet.account_id)
|
||||
self.assertEqual(other_project[plan_name], timesheet[plan_name])
|
||||
|
||||
def test_mandatory_plan_timesheet_applicability(self):
|
||||
self.env['account.analytic.applicability'].create({
|
||||
'business_domain': 'timesheet',
|
||||
'applicability': 'mandatory',
|
||||
'analytic_plan_id': self.analytic_plan.id,
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
# The analytic plan 'self.analytic_plan' is mandatory on the project linked to the timesheet
|
||||
self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet',
|
||||
'unit_amount': 1,
|
||||
'project_id': self.project_customer.id,
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
|
||||
def test_timesheet_unactive_analytic_account(self):
|
||||
self.analytic_account.active = False
|
||||
with self.assertRaises(ValidationError):
|
||||
# The account_id given to the timesheet must be active
|
||||
self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet',
|
||||
'unit_amount': 1,
|
||||
'project_id': self.project_customer.id,
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
|
||||
def test_project_update_reflects_task_allocated_hours_and_timesheet(self):
|
||||
"""
|
||||
Check if the project update is according to the project's allocated hours and timesheet.
|
||||
Step:
|
||||
1) set 10 allocated hours in the project_customer
|
||||
2) add timesheet 2 hour in task1
|
||||
3) create project update and verfity
|
||||
4) repeat step 1 but allocated hour 12
|
||||
5) add timesheet 3 hour in task2
|
||||
6) repeat step 4
|
||||
8) add timesheet 10 hour in task2
|
||||
9) repeat step 4
|
||||
"""
|
||||
def create_timesheet(task, hours):
|
||||
self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet',
|
||||
'task_id': task.id,
|
||||
'unit_amount': hours,
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
|
||||
def create_project_update():
|
||||
update_form = Form(self.env['project.update'].with_context({'default_project_id': self.project_customer.id}))
|
||||
update_form.name = "Test"
|
||||
update_project = update_form.save()
|
||||
|
||||
return [update_project.allocated_time, update_project.timesheet_time, update_project.timesheet_percentage]
|
||||
|
||||
self.project_customer.allocated_hours = 10
|
||||
create_timesheet(self.task1, 2)
|
||||
project_update_vals_list = create_project_update()
|
||||
self.assertListEqual(project_update_vals_list, [10, 2, 20])
|
||||
|
||||
self.project_customer.allocated_hours = 12
|
||||
create_timesheet(self.task2, 3)
|
||||
project_update_vals_list = create_project_update()
|
||||
self.assertListEqual(project_update_vals_list, [12, 5, 42])
|
||||
|
||||
create_timesheet(self.task2, 10)
|
||||
project_update_vals_list = create_project_update()
|
||||
self.assertListEqual(project_update_vals_list, [12, 15, 125])
|
||||
|
||||
def test_calendar_display_name(self):
|
||||
timesheet = self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet',
|
||||
'unit_amount': 1,
|
||||
'project_id': self.project_customer.id,
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
self.assertEqual(timesheet.calendar_display_name, f"{self.project_customer.name} (1h)")
|
||||
timesheet.unit_amount = 1.5
|
||||
timesheet.invalidate_recordset(['calendar_display_name'])
|
||||
self.assertEqual(timesheet.calendar_display_name, f"{self.project_customer.name} (1h30)")
|
||||
timesheet.unit_amount = 8
|
||||
timesheet.company_id.timesheet_encode_uom_id = self.env.ref('uom.product_uom_day')
|
||||
timesheet.invalidate_recordset(['calendar_display_name'])
|
||||
self.assertEqual(timesheet.calendar_display_name, f"{self.project_customer.name} (1d)")
|
||||
timesheet.unit_amount = 12
|
||||
timesheet.invalidate_recordset(['calendar_display_name'])
|
||||
self.assertEqual(timesheet.calendar_display_name, f"{self.project_customer.name} (1.5d)")
|
||||
|
||||
def test_multi_create_timesheets_from_calendar(self):
|
||||
"""
|
||||
Simulate creating timesheets using the multi-create feature in the calendar view
|
||||
"""
|
||||
HrTimesheet = self.env['account.analytic.line'].with_context(timesheet_calendar=True)
|
||||
|
||||
timesheet = HrTimesheet.create({
|
||||
'project_id': self.project_customer.id,
|
||||
'unit_amount': 1,
|
||||
'employee_id': self.empl_employee.id,
|
||||
'date': '2025-05-26',
|
||||
})
|
||||
self.assertTrue(timesheet, "The timesheet should be created without errors")
|
||||
self.assertEqual(timesheet.employee_id, self.empl_employee)
|
||||
self.assertEqual(fields.Date.to_string(timesheet.date), '2025-05-26')
|
||||
|
||||
timesheet = HrTimesheet.create({
|
||||
'project_id': self.project_customer.id,
|
||||
'unit_amount': 1,
|
||||
'employee_id': self.empl_employee.id,
|
||||
'date': '2025-05-25', # Sunday
|
||||
})
|
||||
self.assertFalse(timesheet, "The timesheet should not get created on a weekend")
|
||||
|
||||
calendar = self.env['resource.calendar'].with_company(self.env.company).create({
|
||||
'name': "part time",
|
||||
'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 Evening", 'dayofweek': '0', '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 Evening", '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 Evening", 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
|
||||
],
|
||||
})
|
||||
|
||||
self.empl_employee.resource_calendar_id = calendar
|
||||
self.empl_employee2.resource_calendar_id = calendar
|
||||
with patch.object(self.env.registry['bus.bus'], '_sendone') as mock_send:
|
||||
def create_timesheet_records(days):
|
||||
return HrTimesheet.create([{
|
||||
'project_id': self.project_customer.id,
|
||||
'unit_amount': 1,
|
||||
'date': f'2025-05-{day}',
|
||||
'employee_id': employee.id,
|
||||
} for day in days for employee in (self.empl_employee, self.empl_employee2)])
|
||||
|
||||
def assert_notification(mock_send, notification_type, message):
|
||||
mock_send.assert_called_with(self.env.user.partner_id, 'simple_notification', {
|
||||
'type': notification_type,
|
||||
'message': message,
|
||||
})
|
||||
mock_send.reset_mock()
|
||||
|
||||
timesheets = create_timesheet_records(('25', '26', '27'))
|
||||
self.assertEqual(len(timesheets), 2)
|
||||
self.assertEqual(fields.Date.to_string(timesheets[0].date), '2025-05-26', "The timesheet creation should skip non-working days")
|
||||
self.assertEqual(fields.Date.to_string(timesheets[1].date), '2025-05-26', "The timesheet creation should skip non-working days")
|
||||
assert_notification(mock_send, 'danger', "Some timesheets were not created: employees aren’t working on the selected days")
|
||||
|
||||
timesheets = create_timesheet_records(('18', '25'))
|
||||
self.assertEqual(len(timesheets), 0)
|
||||
assert_notification(mock_send, 'danger', "No timesheets created: employees aren’t working on the selected days")
|
||||
|
||||
timesheets = create_timesheet_records(('8', '9'))
|
||||
self.assertEqual(len(timesheets), 4)
|
||||
assert_notification(mock_send, 'success', "Timesheets successfully created")
|
||||
|
||||
def test_keep_create_account_values_at_timesheet_creation(self):
|
||||
field_name = self.analytic_plan._column_name()
|
||||
analytic_account, another_account = self.env['account.analytic.account'].create([
|
||||
{
|
||||
'name': 'Analytic Account',
|
||||
'partner_id': self.partner.id,
|
||||
'plan_id': self.analytic_plan.id,
|
||||
'code': 'TEST',
|
||||
},
|
||||
{
|
||||
'name': 'Another Analytic Account',
|
||||
'partner_id': self.partner.id,
|
||||
'plan_id': self.analytic_plan.id,
|
||||
'code': 'TEST2',
|
||||
},
|
||||
])
|
||||
|
||||
self.project_customer.write({
|
||||
field_name: another_account.id,
|
||||
})
|
||||
|
||||
line = self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet',
|
||||
'unit_amount': 1,
|
||||
'project_id': self.project_customer.id,
|
||||
field_name: analytic_account.id,
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
self.assertEqual(line[field_name].id, analytic_account.id, f"The value of {field_name} shouldn't get overwritten by the project's account")
|
||||
|
||||
def test_keep_write_account_values_at_timesheet_update(self):
|
||||
field_name = self.analytic_plan._column_name()
|
||||
analytic_account, another_account = self.env['account.analytic.account'].create([
|
||||
{
|
||||
'name': 'Analytic Account',
|
||||
'partner_id': self.partner.id,
|
||||
'plan_id': self.analytic_plan.id,
|
||||
'code': 'TEST',
|
||||
},
|
||||
{
|
||||
'name': 'Another Analytic Account',
|
||||
'partner_id': self.partner.id,
|
||||
'plan_id': self.analytic_plan.id,
|
||||
'code': 'TEST2',
|
||||
},
|
||||
])
|
||||
|
||||
self.project_customer.write({
|
||||
field_name: another_account.id,
|
||||
})
|
||||
|
||||
line = self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet',
|
||||
'unit_amount': 1,
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
|
||||
line.write({
|
||||
'project_id': self.project_customer.id,
|
||||
field_name: analytic_account.id
|
||||
})
|
||||
self.assertEqual(line[field_name].id, analytic_account.id, f"The value of {field_name} shouldn't get overwritten by the project's account")
|
||||
|
||||
def test_split_analytic_dynamic_update(self):
|
||||
self.empl_employee.hourly_cost = 10.0
|
||||
another_account = self.project.account_id.copy()
|
||||
|
||||
line = self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet',
|
||||
'unit_amount': 1,
|
||||
'project_id': self.project_customer.id,
|
||||
'account_id': self.project.account_id.id,
|
||||
'employee_id': self.empl_employee.id,
|
||||
})
|
||||
self.assertEqual(line.amount, -10)
|
||||
line.analytic_distribution = {
|
||||
f"{self.project.account_id.id}": 50,
|
||||
f"{another_account.id}": 50,
|
||||
}
|
||||
self.assertEqual(line.amount, -5) # the line is split in 2
|
||||
|
||||
def test_log_timesheet_with_user_has_two_employees_from_different_companies(self):
|
||||
company_2 = self.env['res.company'].create({'name': 'Company 2'})
|
||||
self.env['hr.employee'].with_company(company_2).create({
|
||||
'name': 'Employee 2',
|
||||
'user_id': self.user_manager.id,
|
||||
})
|
||||
timesheet = self.env['account.analytic.line'].create({
|
||||
'project_id': self.project.id,
|
||||
'user_id': self.user_manager.id,
|
||||
})
|
||||
self.assertEqual(timesheet.company_id, self.env.company)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue