From 1e050584a54e494370478124c0cc84604b82d76b Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 13 Jul 2023 21:42:16 -0400 Subject: [PATCH] bemade_fsm: major refactoring of tests in progress (broken). --- bemade_fsm/models/equipment.py | 10 +- bemade_fsm/models/sale_order.py | 6 +- bemade_fsm/models/task.py | 7 +- bemade_fsm/tests/test_bemade_fsm_common.py | 139 ++++++++++++++++--- bemade_fsm/tests/test_equipment.py | 49 ++----- bemade_fsm/tests/test_fsm_contact_setting.py | 6 +- bemade_fsm/tests/test_sale_order.py | 23 ++- bemade_fsm/tests/test_task_template.py | 89 +++--------- bemade_fsm/views/equipment.xml | 2 +- bemade_fsm/views/task_views.xml | 4 +- 10 files changed, 182 insertions(+), 153 deletions(-) diff --git a/bemade_fsm/models/equipment.py b/bemade_fsm/models/equipment.py index 2cbb096..d26b966 100644 --- a/bemade_fsm/models/equipment.py +++ b/bemade_fsm/models/equipment.py @@ -41,7 +41,7 @@ class Equipment(models.Model): partner_id = fields.Many2one('res.partner', string="Owner", compute="_compute_partner", - search="_search_partner",) + search="_search_partner", ) description = fields.Text(string="Description", tracking=True) @@ -53,9 +53,11 @@ class Equipment(models.Model): location_notes = fields.Text(string="Physical Location Notes", tracking=True) - task_ids = fields.One2many(comodel_name='project.task', - inverse_name='equipment_id', - string='Interventions') + task_ids = fields.Many2many(comodel_name='project.task', + relation="bemade_fsm_task_equipment_rel", + column1="equipment_id", + column2="task_id", + string='Interventions') @api.depends('partner_location_id') def _compute_partner(self): diff --git a/bemade_fsm/models/sale_order.py b/bemade_fsm/models/sale_order.py index df88a4d..7b8b988 100644 --- a/bemade_fsm/models/sale_order.py +++ b/bemade_fsm/models/sale_order.py @@ -114,7 +114,7 @@ class SaleOrderLine(models.Model): vals['user_ids'] = template.assignees.ids vals['tag_ids'] = template.tags.ids vals['planned_hours'] = template.planned_hours - vals['equipment_id'] = template.equipment_id.id + vals['equipment_ids'] = [Command.set([template.equipment_id.id])] return vals tmpl = self.product_id.task_template_id @@ -128,8 +128,8 @@ class SaleOrderLine(models.Model): "This task has been created from: %s (%s)") % ( self.order_id.id, self.order_id.name, self.product_id.name) task.message_post(body=task_msg) - if not task.equipment_id: - task.equipment_id = self.order_id.equipment_id + if not task.equipment_ids: + task.write({'equipment_ids': [Command.set(self.order_id.equipment_id)]}) task.name = _generate_task_name(tmpl) return task diff --git a/bemade_fsm/models/task.py b/bemade_fsm/models/task.py index ce5c99d..f4da20f 100644 --- a/bemade_fsm/models/task.py +++ b/bemade_fsm/models/task.py @@ -5,7 +5,12 @@ from odoo.exceptions import ValidationError class Task(models.Model): _inherit = "project.task" - equipment_id = fields.Many2one("bemade_fsm.equipment", string="Equipment to Service", tracking=True) + equipment_ids = fields.Many2many(comodel_name="bemade_fsm.equipment", + relation="bemade_fsm_task_equipment_rel", + column1="task_id", + column2="equipment_id", + string="Equipment to Service", + tracking=True,) work_order_contacts = fields.Many2many(comodel_name="res.partner", relation="task_work_order_contact_rel", diff --git a/bemade_fsm/tests/test_bemade_fsm_common.py b/bemade_fsm/tests/test_bemade_fsm_common.py index 313dcf9..94de29c 100644 --- a/bemade_fsm/tests/test_bemade_fsm_common.py +++ b/bemade_fsm/tests/test_bemade_fsm_common.py @@ -3,11 +3,17 @@ from odoo import Command @tagged("-at_install", "post_install") -class FSMManagerUserTransactionCase(TransactionCase): +class BemadeFSMBaseTest(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() + cls.user = cls._generate_project_manager_user('Project Manager', 'misterpm') + cls.user_limited = cls._generate_project_user('Project User', 'mruser') + cls.env.ref("industry_fsm.fsm_project").write({'allow_subtasks': True}) + + @classmethod + def _generate_project_manager_user(cls, name, login): user_group_employee = cls.env.ref('base.group_user') user_group_project_user = cls.env.ref('project.group_project_user') user_group_project_manager = cls.env.ref('project.group_project_manager') @@ -26,24 +32,117 @@ class FSMManagerUserTransactionCase(TransactionCase): user_group_sales_manager.id, ] if user_product_customer: group_ids.append(user_product_customer.id) - - # Test user with project access rights for the various tests - Users = cls.env['res.users'].with_context({'no_reset_password': True}) - cls.user = Users.create({ - 'name': 'Project Manager', - 'login': 'misterpm', - 'password': 'misterpm', - 'email': 'mrpm@testco.com', - 'signature': 'Mr. PM', - 'groups_id': [Command.set(group_ids)], - }) - group_ids.remove(user_group_fsm_manager.id) - group_ids.remove(user_group_project_manager.id) - cls.user_limited = Users.create({ - 'name': 'Project User', - 'login': 'mruser', - 'password': 'mruser', - 'email': 'mruser@testco.com', - 'signature': 'Mr. User', + return cls.env['res.users'].with_context({'no_reset_password': True}).create({ + 'name': name, + 'login': login, + 'password': login, + 'email': f"{login}@test.co", 'groups_id': [Command.set(group_ids)] }) + + @classmethod + def _generate_project_user(cls, name, login): + user = cls._generate_project_manager_user(name, login) + user.write({'groups_id': [Command.unlink(cls.env.ref('project.group_project_manager'))]}) + user.write({'groups_id': [Command.unlink(cls.env.ref('industry_fsm.group_fsm_manager'))]}) + + @classmethod + def _generate_partner(cls, name: str = 'Test Company', company_type: str = 'company', parent=None): + """ Generates a partner with basic address filled in. + + :param name: The partner's name. + :param company_type: The type of partner, either 'company' or 'person' are accepted.""" + return cls.env['res.partner'].create({ + 'name': name, + 'company_type': company_type, + 'street': '123 Street St.', + 'city': 'Montreal', + 'state_id': cls.env.ref('base.state_ca_qc').id, + 'country_id': cls.env.ref('base.ca').id, + 'parent_id': parent and parent.id, + }) + + @classmethod + def _generate_sale_order(cls, partner, client_order_ref='Test Order', equipment=None): + return cls.env['sale.order'].create({ + 'partner_id': partner.id, + 'client_order_ref': client_order_ref, + 'equipment_id': equipment.id, + }) + + @classmethod + def _generate_sale_order_line(cls, sale_order, product, project, qty=1.0, uom=None, price=100.0, tax_id=False): + return cls.env['sale.order.line'].create({ + 'order_id': sale_order.id, + 'product_id': product.id, + 'product_uom_qty': qty, + 'product_uom': uom or cls.env.ref("uom.product_uom_hour"), + 'price_unit': price, + 'tax_id': tax_id, + }) + + @classmethod + def _generate_equipment(cls, name='test equipment', partner=None): + return cls.env['bemade_fsm.equipment'].create({ + 'name': name, + 'partner_location_id': partner and partner.id, + }) + + @classmethod + def _generate_product(cls, name='Test Product', product_type='service', service_tracking='task_global_project', + project=None, task_template=None, service_policy='delivered_manual', uom=None): + if 'project' in service_tracking and not project: + project = cls.env.ref("industry_fsm.fsm_project") + uom_id = uom and uom.id or cls.env.ref("uom.product_uom_hour").id + return cls.env['product.product'].create({ + 'name': name, + 'type': product_type, + 'service_tracking': service_tracking, + 'project_id': service_tracking in ('task_global_project', 'project_only') and project.id, + 'project_template_id': service_tracking == 'task_in_project' and project.id, + 'task_template_id': task_template and task_template.id, + 'service_policy': service_policy, + 'uom_id': uom_id, + 'uom_po_id': uom_id, + }) + + @classmethod + def _generate_fsm_project(cls, name='Test Project'): + return cls.env['project.project'].create({ + 'name': name, + 'allow_material': True, + 'allow_timesheets': True, + 'allow_subtasks': True, + 'allow_quotations': True, + 'allow_worksheets': True, + 'is_fsm': True, + }) + + @classmethod + def _generate_task_template(cls, parent=None, structure=[], names=['Test Task Template']): + """ Generates a task template with the specified structure and naming. + + :param structure: A list of integers describing the number of tasks for each level of descendants. An empty + list represents only one top-level task template. + :param names: The name prefixes to be given to the task templates at each level. Each prefix will be followed + by a sequential integer for its level. Child 1, Child 2, Grandchild 1, etc.""" + if len(structure) != len(names) - 1: + raise ValueError("The length of the structure argument must contain exactly one element less than the " + "names argument.") + name = names.pop(0) + template = cls.env['project.task.template'].create({ + 'name': name, + 'parent': parent and parent.id, + }) + parent = template + while structure: + subtasks = [] + for i in range(0, structure[0]): + subtasks.append(cls.env['project.task.template'].create({ + 'parent': parent.id, + 'name': names[0] + f" {i}", + })) + structure.pop(0) + names.pop(0) + parent = subtasks[0] + return template diff --git a/bemade_fsm/tests/test_equipment.py b/bemade_fsm/tests/test_equipment.py index 8a38c49..65fba3c 100644 --- a/bemade_fsm/tests/test_equipment.py +++ b/bemade_fsm/tests/test_equipment.py @@ -1,53 +1,34 @@ from odoo.tests.common import HttpCase, tagged -from .test_bemade_fsm_common import FSMManagerUserTransactionCase +from .test_bemade_fsm_common import BemadeFSMBaseTest from odoo import Command from odoo.exceptions import MissingError @tagged("-at_install", "post_install") -class TestEquipmentCommon(FSMManagerUserTransactionCase): +class TestEquipment(BemadeFSMBaseTest): + def test_crud(self): + self.partner_company = self._generate_partner() + self.partner_contact = self._generate_partner('Site Contact', 'person', self.partner_company) + self.equipment = self._generate_equipment('Test Equipment 1', self.partner_company) - @classmethod - def setUpClass(cls): - super().setUpClass() - - # Set up the test partner - cls.partner_company = cls.env['res.partner'].create({ - 'name': 'Test Partner Company', - 'company_type': 'company', - 'street': '123 Street St.', - 'city': 'Montreal', - 'state_id': cls.env['res.country.state'].search([('name', 'ilike', 'Quebec%')]).id, - 'country_id': cls.env['res.country'].search([('name', '=', 'Canada')]).id - }) - - cls.partner_contact = cls.env['res.partner'].create({ - 'name': 'Site Contact', - 'company_type': 'person', - 'parent_id': cls.partner_company.id, - }) - - cls.equipment = cls.env['bemade_fsm.equipment'].create({ - 'name': 'Test Equipment 1', - 'partner_location_id': cls.partner_company.id, - }) - - -@tagged('-at_install', 'post_install') -class TestEquipmentBase(TestEquipmentCommon): - - def test_crd(self): # Just make sure the basic ORM stuff is OK self.assertTrue(self.equipment in self.partner_company.equipment_ids) self.assertTrue(len(self.partner_company.equipment_ids) == 1) - self.partner_company.write({'equipment_ids': [Command.set([])]}) + # Delete should cascade + self.partner_company.write({'equipment_ids': [Command.set([])]}) with self.assertRaises(MissingError): self.equipment.name @tagged('-at_install', 'post_install', 'slow') -class TestEquipmentTours(HttpCase, TestEquipmentCommon): +class TestEquipmentTours(HttpCase, BemadeFSMBaseTest): + + @classmethod + def setUpClass(cls): + cls._generate_partner() + cls._generate_partner('Site Contact', 'person', cls.partner_company) + cls._generate_equipment('Test Equipment 1', cls.partner_company) def test_equipment_base_tour(self): self.start_tour('/web', 'equipment_base_tour', diff --git a/bemade_fsm/tests/test_fsm_contact_setting.py b/bemade_fsm/tests/test_fsm_contact_setting.py index ce8a60e..0c097ab 100644 --- a/bemade_fsm/tests/test_fsm_contact_setting.py +++ b/bemade_fsm/tests/test_fsm_contact_setting.py @@ -1,6 +1,6 @@ from odoo.tests import TransactionCase, HttpCase, tagged, Form from odoo import Command -from .test_bemade_fsm_common import FSMManagerUserTransactionCase +from .test_bemade_fsm_common import BemadeFSMBaseTest def create_base_contacts(cls): @@ -22,7 +22,7 @@ def create_base_contacts(cls): @tagged("-at_install", "post_install") -class SaleOrderFSMContactsCase(FSMManagerUserTransactionCase): +class SaleOrderFSMContactsCase(BemadeFSMBaseTest): @classmethod def setUpClass(cls): @@ -60,7 +60,7 @@ class SaleOrderFSMContactsCase(FSMManagerUserTransactionCase): so.work_order_contacts != so.partner_id.work_order_contacts and len(so.partner_id.work_order_contacts) == 2) -class SaleOrderMultiLocationContactsTest(FSMManagerUserTransactionCase): +class SaleOrderMultiLocationContactsTest(BemadeFSMBaseTest): @classmethod def setUpClass(cls): super().setUpClass() diff --git a/bemade_fsm/tests/test_sale_order.py b/bemade_fsm/tests/test_sale_order.py index 7cd7398..ae558f7 100644 --- a/bemade_fsm/tests/test_sale_order.py +++ b/bemade_fsm/tests/test_sale_order.py @@ -10,20 +10,12 @@ class TestSalesOrder(TestTaskTemplateCommon): cls.partner = cls.env['res.partner'].create({ 'name': 'Test Partner', }) - cls.equipment =cls.env['bemade_fsm.equipment'].create({ - 'name': 'test equipment', - 'partner_location_id': cls.partner.id, - }) - cls.so_equipment = cls.env['bemade_fsm.equipment'].create({ - 'name': 'test equipment 2', - 'partner_location_id': cls.partner.id, - }) - cls.sale_order1 = cls.env['sale.order'].create({ - 'partner_id': cls.partner.id, - 'client_order_ref': 'TEST ORDER 1', - 'state': 'draft', - 'equipment_id': cls.so_equipment.id, - }) + cls.equipment = TestSalesOrder._generate_equipment() + cls.so_equipment = TestSalesOrder._generate_equipment('test equipment 2') + + cls.sale_order1 = TestSalesOrder._generate_equipment(partner=cls.partner, client_order_ref='TEST ORDER 1', + equipment=cls.so_equipment) + cls.sol_serv_order = TestSalesOrder._generate_sale_order_line(product, project, ) cls.sol_serv_order = cls.env['sale.order.line'].create({ 'name': cls.product_task_global_project.name, 'product_id': cls.product_task_global_project.id, @@ -66,6 +58,7 @@ class TestSalesOrder(TestTaskTemplateCommon): 'tax_id': False, }) + @tagged('-at_install', 'post_install') def test_order_confirmation_simple_template(self): """ Confirming the order should create a task in the global project. """ @@ -144,4 +137,4 @@ class TestSaleOrderTour(HttpCase, TestSalesOrder): so.action_confirm() sol = so.order_line.filtered(lambda l: 'Test Product 3' in l.name) self.start_tour('/web', 'sale_order_tour', login='misterpm') - self.assertTrue(sol.qty_delivered != 0) \ No newline at end of file + self.assertTrue(sol.qty_delivered != 0) diff --git a/bemade_fsm/tests/test_task_template.py b/bemade_fsm/tests/test_task_template.py index 97fd66a..3d20375 100644 --- a/bemade_fsm/tests/test_task_template.py +++ b/bemade_fsm/tests/test_task_template.py @@ -1,4 +1,4 @@ -from .test_bemade_fsm_common import FSMManagerUserTransactionCase +from .test_bemade_fsm_common import BemadeFSMBaseTest from odoo.tests.common import HttpCase, tagged from odoo.exceptions import MissingError from odoo import Command @@ -6,7 +6,7 @@ from psycopg2.errors import ForeignKeyViolation @tagged("-at_install", "post_install") -class TestTaskTemplateCommon(FSMManagerUserTransactionCase): +class TestTaskTemplateCommon(BemadeFSMBaseTest): @classmethod def setUpClass(cls): @@ -19,79 +19,26 @@ class TestTaskTemplateCommon(FSMManagerUserTransactionCase): }) cls.project = cls.env.ref('industry_fsm.fsm_project') - cls.project.write({'allow_subtasks': True,}) - cls.product_task_global_project = cls.env['product.product'].create({ - 'name': 'Test Product 1', - 'type': 'service', - 'service_tracking': 'task_global_project', - 'project_id': cls.project.id, - 'task_template_id': cls.task1.id, - 'uom_id': hours_uom.id, - 'uom_po_id': hours_uom.id, - 'service_policy': 'delivered_manual', - }) - cls.project_template = cls.env['project.project'].create({ - 'name': 'Test Project Template', - 'allow_material': True, - 'allow_timesheets': True, - 'allow_subtasks': True, - 'allow_quotations': True, - 'allow_worksheets': True, - 'is_fsm': True, - }) - cls.product_task_in_project = cls.env['product.product'].create({ - 'name': 'Test Product 2', - 'type': 'service', - 'service_tracking': 'task_in_project', - 'task_template_id': cls.task1.id, - 'project_template_id': cls.project_template.id, - 'uom_po_id': hours_uom.id, - 'uom_id': hours_uom.id, - 'service_policy': 'delivered_manual', - }) + cls.product_task_global_project = cls._generate_product(name='Test Product 1', task_template=cls.task1) + cls.project_template = cls._generate_fsm_project('Test Project Template') + cls.product_task_in_project = cls._generate_product(name='Test Product 2', project=cls.project_template, + service_tracking='task_in_project', task_template=cls.task1) # Set up a task template tree with 2 children and 1 grandchild - cls.parent_task = cls.env['project.task.template'].create({ - 'name': 'Parent Template', - 'planned_hours': cls.PLANNED_HOURS, - }) - cls.child_task_1 = cls.env['project.task.template'].create({ - 'name': 'Child Template 1', - 'parent': cls.parent_task.id, - }) - cls.child_task_2 = cls.env['project.task.template'].create({ - 'name': 'Child Template 2', - 'parent': cls.parent_task.id, - }) - cls.parent_task.write({'subtasks': [Command.set([cls.child_task_1.id, cls.child_task_2.id])]}) - cls.grandchild_task = cls.env['project.task.template'].create({ - 'name': 'Grandchild Template', - 'parent': cls.child_task_2.id - }) - cls.child_task_2.write({'subtasks': [Command.set([cls.grandchild_task.id])]}) + cls.parent_task = cls._generate_task_template(structure=[2, 1], + names=['Parent Template', 'Child Template', + 'Grandchild Template']) + cls.child_task_1 = cls.parent_task.subtasks[0] + cls.child_task_2 = cls.parent_task.subtasks[1] + cls.grandchild_task = cls.child_task_1.subtasks[0] # Create products using the task tree we just created - cls.product_task_tree_global_project = cls.env['product.product'].create({ - 'name': 'Test Product 3', - 'type': 'service', - 'service_tracking': 'task_global_project', - 'project_id': cls.project.id, - 'task_template_id': cls.parent_task.id, - 'service_policy': 'delivered_manual', - 'uom_id': hours_uom.id, - 'uom_po_id': hours_uom.id, - }) - cls.product_task_tree_in_project = cls.env['product.product'].create({ - 'name': 'Test Product 2', - 'type': 'service', - 'service_tracking': 'task_in_project', - 'task_template_id': cls.parent_task.id, - 'project_template_id': cls.project_template.id, - 'uom_po_id': hours_uom.id, - 'uom_id': hours_uom.id, - 'service_policy': 'delivered_manual', - }) + cls.product_task_tree_global_project = cls._generate_product(name='Test Product 3', + task_template=cls.parent_task) + cls.product_task_tree_in_project = cls._generate_product(name="Test Product 2", project=cls.project_template, + service_tracking='task_in_project', + task_template=cls.parent_task) @tagged('-at_install', 'post_install') @@ -105,7 +52,7 @@ class TestTaskTemplate(TestTaskTemplateCommon): def test_delete_subtask_template(self): """ Deletion of a child task should be OK even if the parent is on a product. Children of the deleted subtask should be deleted.""" - self.child_task_2.unlink() + self.child_task_1.unlink() # Reading deleted child's name field should be impossible with self.assertRaises(MissingError): test = self.grandchild_task.name diff --git a/bemade_fsm/views/equipment.xml b/bemade_fsm/views/equipment.xml index 4047df6..869181d 100644 --- a/bemade_fsm/views/equipment.xml +++ b/bemade_fsm/views/equipment.xml @@ -50,7 +50,7 @@ bemade_fsm.equipment.tree bemade_fsm.equipment - + diff --git a/bemade_fsm/views/task_views.xml b/bemade_fsm/views/task_views.xml index 75af60a..e63fedb 100644 --- a/bemade_fsm/views/task_views.xml +++ b/bemade_fsm/views/task_views.xml @@ -8,7 +8,9 @@ - +