From dc04ab952b47ae0786fc8fd1c93e7d36766a4054 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 2 May 2024 09:56:52 -0400 Subject: [PATCH] bemade_fsm: Changes to site and work order contact behaviour 1. Site contacts and work order contacts now get set on create and write of all FSM project tasks. If the task has no parent_id, it will try to set them from the sale order if it has one. Otherwise, they are simply inherited from the parent. 2. Changing the site or work order contact on a task no longer feeds back to the sale order. 3. Changing the site or work order contacts on a parent task propagates the change to its subtasks. --- bemade_fsm/__manifest__.py | 2 +- bemade_fsm/models/equipment.py | 4 +- bemade_fsm/models/task.py | 48 +++++---------- bemade_fsm/tests/test_sale_order.py | 27 --------- bemade_fsm/tests/test_task.py | 93 +++++++++++++++++++++++++---- bemade_fsm/views/res_partner.xml | 4 +- 6 files changed, 102 insertions(+), 76 deletions(-) diff --git a/bemade_fsm/__manifest__.py b/bemade_fsm/__manifest__.py index 456a968..02e8490 100644 --- a/bemade_fsm/__manifest__.py +++ b/bemade_fsm/__manifest__.py @@ -20,7 +20,7 @@ ######################################################################################## { 'name': 'Improved Field Service Management', - 'version': '17.0.0.1.1', + 'version': '17.0.0.2.0', 'summary': 'Adds functionality necessary for managing field service operations at Durpro.', 'description': 'Adds functionality necessary for managing field service operations at Durpro.', 'category': 'Services/Field Service', diff --git a/bemade_fsm/models/equipment.py b/bemade_fsm/models/equipment.py index 24587ab..ed15c9a 100644 --- a/bemade_fsm/models/equipment.py +++ b/bemade_fsm/models/equipment.py @@ -48,14 +48,14 @@ class Equipment(models.Model): args = args or [] if name: equipments = self.search([ - '|', '|', '|', + '|', '|', ('pid_tag', operator, name), ('name', operator, name), ('partner_location_id.name', operator, name)], limit=limit) else: equipments = self.search(args, limit=limit) - return equipments.name_get() + return [(equipment.id, equipment.display_name) for equipment in equipments] def action_view_equipment(self): return { diff --git a/bemade_fsm/models/task.py b/bemade_fsm/models/task.py index e144222..80843b2 100644 --- a/bemade_fsm/models/task.py +++ b/bemade_fsm/models/task.py @@ -1,7 +1,4 @@ from odoo import fields, models, api, Command, _ -from odoo.exceptions import ValidationError, UserError -from odoo.osv import expression -from collections import defaultdict, namedtuple from odoo.addons.project.models.project_task import CLOSED_STATES import re @@ -23,9 +20,6 @@ class Task(models.Model): relation="task_work_order_contact_rel", column1="task_id", column2="res_partner_id", - compute="_compute_contacts", - inverse="_inverse_contacts", - store=True ) site_contacts = fields.Many2many( @@ -33,9 +27,6 @@ class Task(models.Model): relation="task_site_contact_rel", column1="task_id", column2="res_partner_id", - compute="_compute_contacts", - inverse="_inverse_contacts", - store=True ) # Override related field to make it return false if this is an FSM subtask @@ -77,6 +68,12 @@ class Task(models.Model): def create(self, vals): res = super().create(vals) for rec in res: + if rec.parent_id and rec.is_fsm: + rec.partner_id = rec.parent_id.partner_id + if not rec.work_order_contacts and rec.parent_id: + rec.work_order_contacts = rec.parent_id.work_order_contacts + if not rec.site_contacts and rec.parent_id: + rec.site_contacts = rec.parent_id.site_contacts if rec.sale_order_id: seq = 1 prev_seqs = self.sale_order_id.tasks_ids and \ @@ -87,6 +84,11 @@ class Task(models.Model): seq += max(map(lambda n: int(n.group(1)) if n else 0, matches)) rec.work_order_number = rec.sale_order_id.name.replace('SO', 'SVR', 1) \ + f"-{seq}" + # If the task is linked to a sales order and has no parent, it should inherit SO work order contacts + if not rec.parent_id and not rec.work_order_contacts and rec.sale_order_id.work_order_contacts: + rec.work_order_contacts = rec.sale_order_id.work_order_contacts + if not rec.parent_id and not rec.site_contacts and rec.sale_order_id.site_contacts: + rec.site_contacts = rec.sale_order_id.site_contacts return res def write(self, vals): @@ -101,6 +103,10 @@ class Task(models.Model): # Here we use child_ids instead of _get_all_subtasks() so as to allow for setting propagate_assignment # to false on a child task. to_propagate.child_ids.write({'user_ids': vals['user_ids']}) + if 'site_contacts' in vals and self.child_ids: + self._get_all_subtasks().write({'site_contacts': [Command.set(self.site_contacts.ids)]}) + if 'work_order_contacts' in vals and self.child_ids: + self._get_all_subtasks().write({'work_order_contacts': [Command.set(self.work_order_contacts.ids)]}) return res @api.depends('sale_order_id') @@ -123,30 +129,6 @@ class Task(models.Model): for project in self.project_id } - @api.depends('sale_line_id.order_id.site_contacts', - 'sale_line_id.order_id.work_order_contacts') - def _compute_contacts(self): - """ The work order contacts and site contacts for a given task are taken from the sale order if the task - is related to one, and from the task's customer if there is no sale order related to the task.""" - for rec in self: - site_contacts = self.sale_line_id and self.sale_line_id.order_id.site_contacts or \ - self.partner_id.site_contacts - work_order_contacts = self.sale_line_id and self.sale_line_id.order_id.work_order_contacts or \ - self.partner_id.work_order_contacts - rec.write({ - 'site_contacts': [Command.set(site_contacts.ids)], - 'work_order_contacts': [Command.set(work_order_contacts.ids)] - }) - - def _inverse_contacts(self): - """ If the task is linked to a sales order, the sales order should have its contacts updated to match.""" - for rec in self: - if rec.sale_line_id: - rec.sale_line_id.order_id.write({ - 'work_order_contacts': [Command.set(rec.work_order_contacts.ids)], - 'site_contacts': [Command.set(rec.site_contacts.ids)], - }) - @api.depends('parent_id.visit_id', 'project_id.is_fsm', 'project_id.allow_billable') def _compute_allow_billable(self): for rec in self: diff --git a/bemade_fsm/tests/test_sale_order.py b/bemade_fsm/tests/test_sale_order.py index 1e0561a..fd5d620 100644 --- a/bemade_fsm/tests/test_sale_order.py +++ b/bemade_fsm/tests/test_sale_order.py @@ -211,33 +211,6 @@ class TestSalesOrder(BemadeFSMBaseTest): self.assertEqual(sol.task_id.work_order_contacts, partner.work_order_contacts) self.assertEqual(sol.task_id.site_contacts, partner.site_contacts) - def test_changing_task_contacts_mirrors_with_sale_order(self): - partner = self._generate_partner() - contact = self._generate_partner("Contact", "person", partner) - so = self._generate_sale_order(partner) - product = self._generate_product() - sol = self._generate_sale_order_line(so, product) - so.action_confirm() - task = sol.task_id - task_form = Form(task) - - # Now change the site/work order contact on the task and make sure it feeds back to the sales order - task_form.site_contacts.add(contact) - task_form.work_order_contacts.add(contact) - task_form.save() - - self.assertEqual(task.site_contacts, so.site_contacts) - self.assertEqual(task.work_order_contacts, so.work_order_contacts) - - # Test changing it on the SO feeds back to the task as well - f = Form(so) - f.work_order_contacts.remove(contact.id) - f.site_contacts.remove(contact.id) - f.save() - - self.assertEqual(task.site_contacts, so.site_contacts) - self.assertEqual(task.work_order_contacts, so.work_order_contacts) - def test_tasks_created_at_order_confirmation_have_no_assignees(self): so, visit, sol1, sol2 = self._generate_so_with_one_visit_two_lines() user = self._generate_project_user(name="User", login='login') diff --git a/bemade_fsm/tests/test_task.py b/bemade_fsm/tests/test_task.py index 14b838a..8d5a3ef 100644 --- a/bemade_fsm/tests/test_task.py +++ b/bemade_fsm/tests/test_task.py @@ -1,5 +1,5 @@ from .test_bemade_fsm_common import BemadeFSMBaseTest -from odoo.tests.common import tagged +from odoo.tests.common import tagged, Form from odoo import Command @@ -10,18 +10,21 @@ class TaskTest(BemadeFSMBaseTest): def setUpClass(cls): # Chose to set up all tests the same way since this code was becoming very redundant super().setUpClass() - cls.so = cls._generate_sale_order() - cls.template = cls._generate_task_template(names=['Parent', 'Child', 'Grandchild'], structure=[2, 1]) - cls.product = cls._generate_product(task_template=cls.template) - cls.sol = cls._generate_sale_order_line(sale_order=cls.so, product=cls.product) cls.user = cls._generate_project_manager_user('Bob', 'Bob') - cls.so.action_confirm() - cls.task = cls.sol.task_id + + def _generate_so_with_multilevel_task_template(self): + so = self._generate_sale_order() + template = self._generate_task_template(names=['Parent', 'Child', 'Grandchild'], structure=[2, 1]) + product = self._generate_product(task_template=template) + sol = self._generate_sale_order_line(sale_order=so, product=product) + return so, sol def test_reassigning_assignment_propagating_task_changes_subtasks(self): - task = self.task - task.propagate_assignment = True + so, sol = self._generate_so_with_multilevel_task_template() + so.action_confirm() + task = sol.task_id + task.propagate_assignment = True task.write({ 'user_ids': [Command.set([self.user.id])], 'propagate_assignment': True, @@ -30,7 +33,10 @@ class TaskTest(BemadeFSMBaseTest): self.assertTrue(all([t.user_ids == self.user for t in task | task._get_all_subtasks()])) def test_reassigning_task_doesnt_propagate_by_default(self): - task = self.task + so, sol = self._generate_so_with_multilevel_task_template() + so.action_confirm() + task = sol.task_id + task.write({ 'user_ids': [Command.set([self.user.id])], }) @@ -38,7 +44,9 @@ class TaskTest(BemadeFSMBaseTest): self.assertFalse(any([t.user_ids for t in task.child_ids.child_ids])) def test_unset_propagate_assignment_unsets_for_all_children(self): - task = self.task + so, sol = self._generate_so_with_multilevel_task_template() + so.action_confirm() + task = sol.task_id # First, set propagation and assign task.propagate_assignment = True task.write({ @@ -53,3 +61,66 @@ class TaskTest(BemadeFSMBaseTest): }) self.assertTrue(all([not t.user_ids for t in task | task.child_ids])) self.assertTrue(all([t.user_ids == self.user for t in task.child_ids.child_ids])) + + def test_task_gets_work_order_contacts_from_sale_order(self): + so, sol = self._generate_so_with_multilevel_task_template() + work_order_contacts = self._generate_partner(parent=so.partner_id) | self._generate_partner( + parent=so.partner_id) + so.write({'work_order_contacts': [(6, 0, work_order_contacts.ids)]}) + + so.action_confirm() + task = sol.task_id + + self.assertEqual(task.work_order_contacts, so.work_order_contacts) + # Just a safeguard to make sure we set it properly on the SO + self.assertEqual(len(task.work_order_contacts), 2) + # Make sure all subtasks got the same + for subtask in task._get_all_subtasks(): + self.assertEqual(subtask.work_order_contacts, so.work_order_contacts) + + def test_task_gets_site_contacts_from_sale_order(self): + so, sol = self._generate_so_with_multilevel_task_template() + site_contacts = self._generate_partner(parent=so.partner_id) | self._generate_partner(parent=so.partner_id) + so.write({'site_contacts': [(6, 0, site_contacts.ids)]}) + + so.action_confirm() + task = sol.task_id + + self.assertEqual(task.site_contacts, so.site_contacts) + # Just a safeguard to make sure we set it properly on the SO + self.assertEqual(len(task.site_contacts), 2) + # Make sure all subtasks got the same + for subtask in task._get_all_subtasks(): + self.assertEqual(subtask.site_contacts, so.site_contacts) + + def test_task_gets_work_order_contacts_from_parent(self): + so, sol = self._generate_so_with_multilevel_task_template() + work_order_contacts = self._generate_partner(parent=so.partner_id) | self._generate_partner(parent=so.partner_id) + so.write({'work_order_contacts': [(6, 0, work_order_contacts.ids)]}) + + so.action_confirm() + task = sol.task_id + task.write({'work_order_contacts': [Command.link(self._generate_partner(parent=so.partner_id).id)]}) + for subtask in task._get_all_subtasks(): + self.assertEqual(subtask.work_order_contacts, task.work_order_contacts) + with Form(task) as task_form: + with task_form.child_ids.new() as subtask: + subtask.name = "Subtask 1" + subtask = task.child_ids[-1] + self.assertEqual(subtask.work_order_contacts, task.work_order_contacts) + + def test_task_gets_site_contacts_from_parent(self): + so, sol = self._generate_so_with_multilevel_task_template() + site_contacts = self._generate_partner(parent=so.partner_id) | self._generate_partner(parent=so.partner_id) + so.write({'site_contacts': [(6, 0, site_contacts.ids)]}) + + so.action_confirm() + task = sol.task_id + task.write({'site_contacts': [Command.link(self._generate_partner(parent=so.partner_id).id)]}) + for subtask in task._get_all_subtasks(): + self.assertEqual(subtask.site_contacts, task.site_contacts) + with Form(task) as task_form: + with task_form.child_ids.new() as subtask: + subtask.name = "Subtask 1" + subtask = task.child_ids[-1] + self.assertEqual(subtask.site_contacts, task.site_contacts) diff --git a/bemade_fsm/views/res_partner.xml b/bemade_fsm/views/res_partner.xml index 3061fb2..68d7efb 100644 --- a/bemade_fsm/views/res_partner.xml +++ b/bemade_fsm/views/res_partner.xml @@ -4,8 +4,8 @@ Equipments bemade_fsm.equipment tree,form - {'default_partner_id': active_id} - [("partner_id", "=", active_id)] + {'default_partner_location_id': active_id} + [("partner_location_id", "=", active_id)]