# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from lxml import etree from odoo.fields import Command from odoo.tests.common import TransactionCase, Form from odoo.exceptions import AccessError, RedirectWarning, UserError, ValidationError class TestCommonTimesheet(TransactionCase): @classmethod def setUpClass(cls): super(TestCommonTimesheet, cls).setUpClass() # 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 rule = cls.env.ref('timesheet_grid.hr_timesheet_rule_approver_update', raise_if_not_found=False) if rule: rule.active = False # customer partner cls.partner = cls.env['res.partner'].create({ 'name': 'Customer Task', 'email': 'customer@task.com', 'phone': '42', }) cls.analytic_plan = cls.env['account.analytic.plan'].create({ 'name': 'Timesheet Plan Test', }) cls.analytic_account = cls.env['account.analytic.account'].create({ 'name': 'Analytic Account for Test Customer', 'partner_id': cls.partner.id, 'plan_id': cls.analytic_plan.id, 'code': 'TEST' }) # project and tasks cls.project_customer = cls.env['project.project'].create({ 'name': 'Project X', 'allow_timesheets': True, 'partner_id': cls.partner.id, 'analytic_account_id': cls.analytic_account.id, }) cls.task1 = cls.env['project.task'].create({ 'name': 'Task One', 'priority': '0', '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', 'state': '1_done', 'project_id': cls.project_customer.id, }) # users cls.user_employee = cls.env['res.users'].create({ 'name': 'User Employee', 'login': 'user_employee', 'email': 'useremployee@test.com', 'groups_id': [(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])], }) 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])], }) # 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) class TestTimesheet(TestCommonTimesheet): def setUp(self): super(TestTimesheet, self).setUp() # 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 rule = self.env.ref('timesheet_grid.timesheet_line_rule_user_update-unlink', raise_if_not_found=False) if rule: rule.active = False def test_log_timesheet(self): """ 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 # employee 1 log some timesheet on task 1 timesheet1 = Timesheet.with_user(self.user_employee).create({ 'project_id': self.project_customer.id, 'task_id': self.task1.id, '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.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.") # employee 1 cannot log timesheet for employee 2 with self.assertRaises(AccessError): timesheet2 = Timesheet.with_user(self.user_employee).create({ 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'name': 'a second timesheet but for employee 2', 'unit_amount': 3, 'employee_id': self.empl_employee2.id, }) # manager log timesheet for employee 2 timesheet3 = Timesheet.with_user(self.user_manager).create({ 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'name': 'a second timesheet but for employee 2', 'unit_amount': 7, 'employee_id': self.empl_employee2.id, }) self.assertEqual(timesheet3.user_id, self.user_employee2, 'Timesheet user should be the one linked to the given employee') self.assertEqual(timesheet3.product_uom_id, timesheet_uom, "The UoM of the timesheet 3 should be the one set on the company of the analytic account.") # employee 1 log some timesheet on project (no task) timesheet4 = Timesheet.with_user(self.user_employee).create({ 'project_id': self.project_customer.id, 'name': 'my first timesheet', 'unit_amount': 4, }) 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 """ # employee 1 log some timesheet on task 1 Timesheet = self.env['account.analytic.line'] timesheet1 = Timesheet.with_user(self.user_employee).create({ 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'name': 'my first timesheet', 'unit_amount': 4, }) # then employee 2 try to modify it with self.assertRaises(AccessError): timesheet1.with_user(self.user_employee2).write({ 'name': 'i try to update this timesheet', 'unit_amount': 2, }) # manager can modify all timesheet timesheet1.with_user(self.user_manager).write({ 'unit_amount': 8, 'employee_id': self.empl_employee2.id, }) self.assertEqual(timesheet1.user_id, self.user_employee2, 'Changing timesheet employee should change the related user') def test_create_unlink_project(self): """ Check project creation, and if necessary the analytic account generated when project should track time. """ # create project wihtout tracking time, nor provide AA non_tracked_project = self.env['project.project'].create({ 'name': 'Project without timesheet', '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") # create a project tracking time tracked_project = self.env['project.project'].create({ 'name': 'Project with timesheet', '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") # 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, }) 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") # analytic linked to projects containing tasks can not be removed task = self.env['project.task'].create({ 'name': 'task in tracked project', 'project_id': tracked_project.id, }) with self.assertRaises(UserError): tracked_project.analytic_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() 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 = Task.create({ 'name': 'Task Child', 'parent_id': self.task1.id, }) task_grandchild = Task.create({ 'name': 'Task Grandchild', 'parent_id': task_child.id, }) # create a second project self.project_customer2 = self.env['project.project'].create({ 'name': 'Project NUMBER DEUX', 'allow_timesheets': True, }) # employee 1 log some timesheet on task 1 and its subtasks Timesheet.create([{ 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'name': 'my first timesheet', 'unit_amount': 4, 'employee_id': self.empl_employee.id, }, { 'project_id': self.project_customer.id, 'task_id': task_child.id, 'name': 'my second timesheet', 'unit_amount': 4, 'employee_id': self.empl_employee.id, }, { 'project_id': self.project_customer.id, 'task_id': task_grandchild.id, 'name': 'my third timesheet', 'unit_amount': 4, 'employee_id': self.empl_employee.id, }]) 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, 3, "3 timesheets should be linked to Project1") self.assertEqual(timesheet_count2, 0, "No timesheets should be linked to Project2") self.assertEqual(len(self.task1.timesheet_ids), 1, "The timesheet should be linked to task1") self.assertEqual(len(task_child.timesheet_ids), 1, "The timesheet should be linked to task_child") self.assertEqual(len(task_grandchild.timesheet_ids), 1, "The timesheet should be linked to task_grandchild") # change project of task 1 from form to trigger onchange with Form(self.task1) as task_form: task_form.project_id = self.project_customer2 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, 3, "3 timesheets should be linked to Project1") self.assertEqual(timesheet_count2, 0, "No 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 unset the project of a task with timesheet... with self.assertRaises(UserError): self.task1.write({ 'project_id': False }) # ...except if one of its ascendant has one. task_child.write({ 'project_id': False }) 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'] self.empl_employee.hourly_cost = 5.0 self.empl_employee2.hourly_cost = 6.0 # create a timesheet for each employee timesheet_1 = Timesheet.with_user(self.user_employee).create({ 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'name': '/', 'unit_amount': 1, }) timesheet_2 = Timesheet.with_user(self.user_employee2).create({ 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'name': '/', 'unit_amount': 1, }) timesheets = timesheet_1 + timesheet_2 with self.assertRaises(AccessError): # should raise since employee 1 doesn't have the access rights to update employee's 2 timesheet timesheets.with_user(self.empl_employee.user_id).write({ 'unit_amount': 2, }) timesheets.with_user(self.user_manager).write({ 'unit_amount': 2, }) # since timesheet costs are different for both employees, we should get different amounts self.assertRecordValues(timesheets.with_user(self.user_manager), [{ 'amount': -10.0, }, { 'amount': -12.0, }]) def test_recompute_partner_from_task_customer_change(self): partner2 = self.env['res.partner'].create({ 'name': 'Customer Task 2', 'email': 'customer2@task.com', 'phone': '43', }) timesheet_entry = self.env['account.analytic.line'].create({ 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'name': 'my only timesheet', 'unit_amount': 4, 'user_id': self.user_employee.id, }) self.assertEqual(timesheet_entry.partner_id, self.partner, "The timesheet entry's partner should be equal to the task's partner/customer") self.task1.write({'partner_id': partner2}) self.assertEqual(timesheet_entry.partner_id, partner2, "The timesheet entry's partner should still be equal to the task's partner/customer, after the change") 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. ''' 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')])], }) project = self.env['project.project'].create({ 'name': 'Project With Timesheets', 'privacy_visibility': 'employees', 'allow_timesheets': True, 'user_id': project_manager.id, }) second_project = self.env['project.project'].create({ 'name': 'Project w/ timesheets', 'privacy_visibility': 'employees', 'allow_timesheets': True, 'user_id': project_manager.id, }) task_1 = self.env['project.task'].create({ 'name': 'First task', 'user_ids': self.user_employee2, 'project_id': project.id }) timesheet = self.env['account.analytic.line'].create({ 'name': 'FirstTimeSheet', 'project_id': project.id, 'task_id': task_1.id, 'unit_amount': 2, 'employee_id': self.empl_employee2.id }) task_1.with_user(project_manager).write({ 'project_id': second_project.id }) self.assertEqual(timesheet.project_id, project, 'The project_id of timesheet shouldn\'t have changed') 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. ''' company_2 = self.env['res.company'].create({'name': 'Company 2'}) company_3 = self.env['res.company'].create({'name': 'Company 3'}) analytic_plan = self.env['account.analytic.plan'].create({ 'name': 'Plan Test', }) analytic_account = self.env['account.analytic.account'].create({ 'name': 'Aa Aa', 'plan_id': analytic_plan.id, 'company_id': company_3.id, }) project = self.env['project.project'].create({ 'name': 'Aa Project', 'company_id': company_3.id, 'analytic_account_id': analytic_account.id, }) task = self.env['project.task'].create({ 'name': 'Aa Task', 'project_id': project.id, }) Timesheet = self.env['account.analytic.line'].with_context(allowed_company_ids=[company_3.id, company_2.id, self.env.company.id]) timesheet = Timesheet.create({ 'name': 'Timesheet', 'project_id': project.id, 'task_id': task.id, 'unit_amount': 2, 'user_id': self.user_manager.id, 'company_id': company_3.id, }) self.assertEqual(timesheet.employee_id, self.user_manager.employee_id, 'As there is a unique employee for this user, it must be found') self.env['hr.employee'].with_company(company_2).create({ 'name': 'Employee 2', 'user_id': self.user_manager.id, }) with self.assertRaises(ValidationError): # As there are several employees for this user, but none of them in this company, none must be found Timesheet.create({ 'name': 'Timesheet', 'project_id': project.id, 'task_id': task.id, 'unit_amount': 2, 'user_id': self.user_manager.id, 'company_id': company_3.id, }) def test_create_timesheet_with_multi_company(self): """ Always set the current company in the timesheet, not the employee company """ company_4 = self.env['res.company'].create({'name': 'Company 4'}) empl_employee, archived_employee = self.env['hr.employee'].with_company(company_4).create([ {'name': 'Employee 3'}, {'name': 'Employee 4', 'active': False}, ]) Timesheet = self.env['account.analytic.line'].with_context(allowed_company_ids=[company_4.id, self.env.company.id]) timesheet = Timesheet.create({ 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'name': 'my first timesheet', 'unit_amount': 4, 'employee_id': empl_employee.id, }) self.assertEqual(timesheet.company_id.id, self.env.company.id) with self.assertRaises(UserError, msg="The employee must be active to encode a timesheet"): Timesheet.create({ 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'name': 'my first timesheet', 'unit_amount': 4, 'employee_id': archived_employee.id, }) def test_subtask_log_timesheet(self): """ Test parent task takes into account the timesheets of its sub-tasks. Test Case: ---------- 1) Create parent task 2) Create child/subtask task 3) Enter the 8 hour timesheet in the child task 4) Check subtask Effective hours in parent task """ 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'})], }, ]) 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': 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') 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') self.assertFalse(self.project_customer.total_timesheet_time, 'The total time recorded should be equal to 0 since no timesheet is recorded.') timesheet1, timesheet2 = self.env['account.analytic.line'].with_user(self.user_employee).create([ {'unit_amount': 1.0, 'project_id': self.project_customer.id}, {'unit_amount': 3.0, 'project_id': self.project_customer.id, 'product_uom_id': False}, ]) self.assertEqual( timesheet1.product_uom_id, self.project_customer.analytic_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, '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.' ) self.assertEqual(self.project_customer.timesheet_ids, timesheet1 + timesheet2) self.assertEqual( self.project_customer.total_timesheet_time, timesheet1.unit_amount + timesheet2.unit_amount, 'The total timesheet time of this project should be equal to 4.' ) def test_create_timesheet_with_archived_employee(self): ''' the timesheet can be created or edited only with an active employee ''' self.empl_employee2.active = False batch_vals = { 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'name': 'archived employee timesheet', 'unit_amount': 3, 'employee_id': self.empl_employee2.id } self.assertRaises(UserError, self.env['account.analytic.line'].create, batch_vals) batch_vals["employee_id"] = self.empl_employee.id timesheet = self.env['account.analytic.line'].create(batch_vals) 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.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.analytic_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): """ We check that we don't over transform the timesheet unit amount when changing the company encoding timesheet uom, we keep it in the project as hours. So it will be transformed only once when encoding the timesheet. """ Timesheet = self.env['account.analytic.line'] project = self.env['project.project'].create({ 'name': 'Project', 'allow_timesheets': True, 'partner_id': self.partner.id, }) project.allocated_hours = 40.0 Timesheet.create({ 'name': 'FirstTimeSheet', 'project_id': project.id, 'unit_amount': 8, 'employee_id': self.empl_employee2.id }) 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, 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.") 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_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 = False self.task1.project_id = self.project_customer with self.assertRaises(UserError): self.task1.write({'project_id': False, 'parent_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([ { 'name': 'Timesheet', 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'unit_amount': 3/60, 'employee_id': self.empl_employee.id, }, { 'name': 'Timesheet', 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'unit_amount': 4/60, 'employee_id': self.empl_employee.id, }, { 'name': 'Timesheet', 'project_id': self.project_customer.id, 'task_id': self.task1.id, 'unit_amount': 4/60, 'employee_id': self.empl_employee.id, }, ]) self.assertEqual(self.task1.progress, 100, 'The percentage of allocated hours should be 100%.')