From 0d5935a1e4bd4a0524fb4b26a9a2081f82de2244 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Mon, 4 Dec 2023 13:58:58 -0500 Subject: [PATCH 01/10] new module to open full form view from a dialog. --- bemade_full_formview_from_modal/__init__.py | 0 .../__manifest__.py | 37 ++++++++++++++++ .../static/src/js/form_view_dialog.js | 43 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 bemade_full_formview_from_modal/__init__.py create mode 100644 bemade_full_formview_from_modal/__manifest__.py create mode 100644 bemade_full_formview_from_modal/static/src/js/form_view_dialog.js diff --git a/bemade_full_formview_from_modal/__init__.py b/bemade_full_formview_from_modal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bemade_full_formview_from_modal/__manifest__.py b/bemade_full_formview_from_modal/__manifest__.py new file mode 100644 index 0000000..66d3327 --- /dev/null +++ b/bemade_full_formview_from_modal/__manifest__.py @@ -0,0 +1,37 @@ +# +# Bemade Inc. +# +# Copyright (C) 2023-June Bemade Inc. (). +# Author: Marc Durepos (Contact : mdurepos@durpro.com) +# +# This program is under the terms of the Odoo Proprietary License v1.0 (OPL-1) +# It is forbidden to publish, distribute, sublicense, or sell copies of the Software +# or modified copies of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +{ + 'name': 'Full Form from Dialog', + 'version': '15.0.1.0.0', + 'summary': 'Allows opening the full form view from the dialog (modal) view.', + 'description': 'Adds a button to open the full form view when viewing the form view for a record in a dialog.', + 'category': 'Technical', + 'author': 'Bemade Inc.', + 'website': 'http://www.bemade.org', + 'license': 'OPL-1', + 'depends': [], + 'data': [], + 'assets': { + 'web.assets_backend': [ + 'bemade_full_formview_from_modal/static/src/js/form_view_dialog.js', + ], + }, + 'installable': True, + 'auto_install': False +} diff --git a/bemade_full_formview_from_modal/static/src/js/form_view_dialog.js b/bemade_full_formview_from_modal/static/src/js/form_view_dialog.js new file mode 100644 index 0000000..e94ad00 --- /dev/null +++ b/bemade_full_formview_from_modal/static/src/js/form_view_dialog.js @@ -0,0 +1,43 @@ +/** @odoo-module **/ + +import FormViewDialog from 'web.FormView'; +import Dialog from 'web.Dialog'; +import {patch} from '@web/core/utils/patch'; +import {_t} from 'web.core'; + +const dom = require('web.dom') + +patch(Dialog.prototype, "bemade_full_formview_from_modal.Dialog", { + init: function (parent, options) { + this._super(...arguments); + }, + custom_events: _.extend({}, Dialog.prototype.custom_events, { + open_full_form_view: '_onOpen', + }), + willStart: function () { + const self = this; + return this._super(...arguments).then(function () { + if (self.$footer) { + const $button = dom.renderButton({ + attrs: { + class: 'btn btn-secondary o_form_button_open', + }, + text: _t('Open'), + }); + $button.on('click', function() {self._onOpen.call(self)}); + self.$footer.append($button); + } + } + ); + }, + _onOpen: function () { + this.do_action({ + type: 'ir.actions.act_window', + res_model: this.options.res_model, + res_id: this.options.res_id, + views: [[false, 'form']], + target: 'current', + context: this.context, + }) + } +}); From 8af9704b67ade939dcdd1cd955ca0a2e1f7bb987 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 5 Dec 2023 06:59:51 -0500 Subject: [PATCH 02/10] bemade_fsm: work for creating tasks directly from templates. --- bemade_fsm/models/task_template.py | 35 ++++++++++++++++ bemade_fsm/tests/test_task_template.py | 14 +++++++ bemade_fsm/wizard/__init__.py | 1 + bemade_fsm/wizard/new_task_from_template.py | 44 ++++++++++++++++++++ bemade_fsm/wizard/new_task_from_template.xml | 22 ++++++++++ 5 files changed, 116 insertions(+) create mode 100644 bemade_fsm/wizard/__init__.py create mode 100644 bemade_fsm/wizard/new_task_from_template.py create mode 100644 bemade_fsm/wizard/new_task_from_template.xml diff --git a/bemade_fsm/models/task_template.py b/bemade_fsm/models/task_template.py index 7253fbf..fdc1a46 100644 --- a/bemade_fsm/models/task_template.py +++ b/bemade_fsm/models/task_template.py @@ -81,3 +81,38 @@ class TaskTemplate(models.Model): for rec in self: new_equipment_ids = [eq.id for eq in rec.equipment_ids if eq.partner_location_id == rec.customer] rec.write({'equipment_ids': [Command.set(new_equipment_ids)]}) + + def _prepare_new_task_values_from_self(self, project, name=False, parent_id=False): + vals = { + 'project_id': project.id, + 'name': name or self.name, + 'description': self.description, + 'parent_id': parent_id, + 'user_ids': self.assignees.ids, + 'tag_ids': self.tags.ids, + 'planned_hours': self.planned_hours, + 'sequence': self.sequence, + 'equipment_ids': [Command.set(self.equipment_ids.ids)] if self.equipment_ids else False, + 'partner_id': project.partner_id and project.partner_id.id, + 'company_id': self.company_id.id, + } + return vals + + def create_task_from_self(self, project, name=False, parent_id=False): + """ Create a project.task from this template and return it. Can be called on a RecordSet of multiple templates. + + :param project: project.project record the task should be added to + :param name: name for the new task (defaults to template name) + :param parent_id: parent task for the new task (none by default) + :return: project.task record created from this template + """ + tasks = self.env['project.task'] + for rec in self: + vals = rec._prepare_new_task_values_from_self(project, name, parent_id) + task = rec.env['project.task'].create(vals) + rec.subtasks.create_task_from_self(project, name=False, parent_id=task.id) + tasks |= task + return tasks + + + diff --git a/bemade_fsm/tests/test_task_template.py b/bemade_fsm/tests/test_task_template.py index d0cf173..178911a 100644 --- a/bemade_fsm/tests/test_task_template.py +++ b/bemade_fsm/tests/test_task_template.py @@ -78,3 +78,17 @@ class TestTaskTemplate(BemadeFSMBaseTest): self.assertEqual(sol1.task_id.name, "Short Name 1") self.assertEqual(sol2.task_id.name, "Short Name 2") self.assertEqual(sol3.task_id.name, "Task") + + def test_task_creation_directly_from_template(self): + project = self.env.ref("industry_fsm.fsm_project") + template = self._generate_task_template(names=['Task', 'Child', 'Grandchild'], structure=[2, 1]) + + task = template.create_task_from_self(project, "My new task") + + self.assertEqual(len(task.child_ids), len(template.subtasks)) + self.assertEqual(len(task.child_ids[0].child_ids), len(template.subtasks[0].subtasks)) + self.assertEqual(len(task.child_ids[1].child_ids), len(template.subtasks[1].subtasks)) + self.assertEqual(task.name, "My new task") + self.assertEqual(task.child_ids[0].name, template.subtasks[0].name) + self.assertTrue(all([t.project_id == project for t in task | task._get_all_subtasks()])) + diff --git a/bemade_fsm/wizard/__init__.py b/bemade_fsm/wizard/__init__.py new file mode 100644 index 0000000..5cc0245 --- /dev/null +++ b/bemade_fsm/wizard/__init__.py @@ -0,0 +1 @@ +from . import new_task_from_template diff --git a/bemade_fsm/wizard/new_task_from_template.py b/bemade_fsm/wizard/new_task_from_template.py new file mode 100644 index 0000000..575629c --- /dev/null +++ b/bemade_fsm/wizard/new_task_from_template.py @@ -0,0 +1,44 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError + + +class NewTaskFromTemplateWizard(models.TransientModel): + _name = "project.task.from.template.wizard" + + project_id = fields.Many2one( + comodel_name='project.project', + string='Project', + help='The project the new task should be created in.', + required=True, + ) + + task_template_id = fields.Many2one( + comodel_name='project.task.template', + string='Task Template', + help='The template to use when creating the new task.', + required=True, + ) + + new_task_title = fields.Char( + help='The title (name) for the newly created task. If left blank, the name of the template will be used.', + ) + + def default_get(self, fields_list): + res = super().default_get(fields_list) + if 'project_id' in fields_list: + active_id = self.env.context.get('active_id', False) + res.update({'project_id': active_id}) + return res + + def action_create_task_from_template(self): + self.ensure_one() + task = self.task_template_id.create_task_from_self(self.project_id, self.new_task_title) + return { + 'type': 'ir.actions.act_window', + 'res_model': 'project.task', + 'res_id': task.id, + 'view_mode': 'form', + 'target': 'current', + } + + diff --git a/bemade_fsm/wizard/new_task_from_template.xml b/bemade_fsm/wizard/new_task_from_template.xml new file mode 100644 index 0000000..49a22ce --- /dev/null +++ b/bemade_fsm/wizard/new_task_from_template.xml @@ -0,0 +1,22 @@ + + + + project.task.from.template.wizard.view.form + project.task.from.template.wizard + +
+
+
+ + + + + + + +
+
+
+
\ No newline at end of file From 22936eee1c0fb7b403ca91611ad94f7d2d258ae8 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 5 Dec 2023 09:58:39 -0500 Subject: [PATCH 03/10] bemade_fsm: Create task from template (from FSM views) Adds a button to create a task from a task template to the list view and kanban view for the project.task when viewed from the FSM application. --- bemade_fsm/__init__.py | 1 + bemade_fsm/__manifest__.py | 8 ++++ bemade_fsm/security/ir.model.access.csv | 1 + bemade_fsm/static/src/js/kanban_view.js | 37 +++++++++++++++++++ bemade_fsm/static/src/js/list_view.js | 36 ++++++++++++++++++ .../static/src/xml/project_view_buttons.xml | 21 +++++++++++ bemade_fsm/views/task_views.xml | 13 +++++++ bemade_fsm/wizard/new_task_from_template.py | 14 +++++-- bemade_fsm/wizard/new_task_from_template.xml | 15 ++++++-- 9 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 bemade_fsm/static/src/js/kanban_view.js create mode 100644 bemade_fsm/static/src/js/list_view.js create mode 100644 bemade_fsm/static/src/xml/project_view_buttons.xml diff --git a/bemade_fsm/__init__.py b/bemade_fsm/__init__.py index 0650744..9b42961 100644 --- a/bemade_fsm/__init__.py +++ b/bemade_fsm/__init__.py @@ -1 +1,2 @@ from . import models +from . import wizard diff --git a/bemade_fsm/__manifest__.py b/bemade_fsm/__manifest__.py index c71d220..c378151 100644 --- a/bemade_fsm/__manifest__.py +++ b/bemade_fsm/__manifest__.py @@ -53,6 +53,7 @@ 'views/sale_order_views.xml', 'reports/worksheet_custom_report_templates.xml', 'reports/worksheet_custom_reports.xml', + 'wizard/new_task_from_template.xml', ], 'assets': { 'web.assets_tests': [ @@ -62,6 +63,13 @@ ], 'web.report_assets_common': [ 'bemade_fsm/static/src/scss/bemade_fsm.scss' + ], + 'web.assets_backend': [ + 'bemade_fsm/static/src/js/kanban_view.js', + 'bemade_fsm/static/src/js/list_view.js', + ], + 'web.assets_qweb': [ + 'bemade_fsm/static/src/xml/project_view_buttons.xml', ] }, 'installable': True, diff --git a/bemade_fsm/security/ir.model.access.csv b/bemade_fsm/security/ir.model.access.csv index df83ebc..2c30345 100644 --- a/bemade_fsm/security/ir.model.access.csv +++ b/bemade_fsm/security/ir.model.access.csv @@ -5,3 +5,4 @@ access_bemade_fsm_equipment,bemade_fsm_equipment,model_bemade_fsm_equipment,base access_bemade_fsm_equipment_tag,bemade_fsm_equipment_tag,model_bemade_fsm_equipment_tag,base.group_user,1,1,1,1 access_bemade_fsm_equipment_type,access_bemade_fsm_equipment_type,model_bemade_fsm_equipment_type,base.group_user,1,0,0,0 access_bemade_fsm_visit,access_bemade_fsm_visit,model_bemade_fsm_visit,base.group_user,1,1,1,1 +access_bemade_fsm_task_wizard,access_bemade_fsm_task_wizard,model_project_task_from_template_wizard,project.group_project_user,1,1,1,1 diff --git a/bemade_fsm/static/src/js/kanban_view.js b/bemade_fsm/static/src/js/kanban_view.js new file mode 100644 index 0000000..2185d01 --- /dev/null +++ b/bemade_fsm/static/src/js/kanban_view.js @@ -0,0 +1,37 @@ +/** @odoo-module **/ + +import KanbanController from 'web.KanbanController'; +import KanbanView from 'web.KanbanView'; +const viewRegistry = require('web.view_registry') + +const ProjectKanbanController = KanbanController.extend({ + buttons_template: 'project.KanbanView.buttons', + custom_events: _.extend({}, KanbanController.prototype.custom_events, { + create_from_template: '_onCreateFromTemplate', + }), + _onCreateFromTemplate(ev) { + ev.stopPropagation(); + const record = this.model.get(this.handle, {raw: true}); + let context = record.context; + context.active_id = record.project_id; + this.do_action({ + type: 'ir.actions.act_window', + res_model: 'project.task.from.template.wizard', + views: [[false, 'form']], + target: 'new', + context: {...record.context, active_id: record.project_id}, + }); + }, + renderButtons ($node) { + this._super.apply(this, arguments); + this.$buttons.on('click', 'button.o-kanban-button-new-from-template', this._onCreateFromTemplate.bind(this)) + }, +}) + +const ProjectKanbanView = KanbanView.extend({ + config: _.extend({}, KanbanView.prototype.config, { + Controller: ProjectKanbanController, + }) +}); + +viewRegistry.add('project_kanban', ProjectKanbanView) \ No newline at end of file diff --git a/bemade_fsm/static/src/js/list_view.js b/bemade_fsm/static/src/js/list_view.js new file mode 100644 index 0000000..3486965 --- /dev/null +++ b/bemade_fsm/static/src/js/list_view.js @@ -0,0 +1,36 @@ +/** @odoo-module **/ + +import ListController from 'web.ListController'; +import ListView from 'web.ListView'; +const viewRegistry = require('web.view_registry') + +const ProjectListController = ListController.extend({ + buttons_template: 'project.ListView.buttons', + custom_events: _.extend({}, ListController.prototype.custom_events, { + create_from_template: '_onCreateFromTemplate', + }), + _onCreateFromTemplate(ev) { + ev.stopPropagation(); + const record = this.model.get(this.handle, {raw: true}); + this.do_action({ + type: 'ir.actions.act_window', + res_model: 'project.task.from.template.wizard', + views: [[false, 'form']], + target: 'new', + context: {...record.context, active_id: record.project_id}, + }); + }, + renderButtons ($node) { + self = this; + this._super.apply(this, arguments); + this.$buttons.on('click', 'button.o_list_button_add_from_template', this._onCreateFromTemplate.bind(this)) + }, +}) + +const ProjectListView = ListView.extend({ + config: _.extend({}, ListView.prototype.config, { + Controller: ProjectListController, + }) +}); + +viewRegistry.add('project_list', ProjectListView) \ No newline at end of file diff --git a/bemade_fsm/static/src/xml/project_view_buttons.xml b/bemade_fsm/static/src/xml/project_view_buttons.xml new file mode 100644 index 0000000..df3484a --- /dev/null +++ b/bemade_fsm/static/src/xml/project_view_buttons.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bemade_fsm/views/task_views.xml b/bemade_fsm/views/task_views.xml index 1fbfecf..25f71be 100644 --- a/bemade_fsm/views/task_views.xml +++ b/bemade_fsm/views/task_views.xml @@ -98,6 +98,9 @@ project.task + + project_list + @@ -112,6 +115,16 @@ + + project.task.view.kanban.fsm.inherit + project.task + + + + project_kanban + + + project.task.view.search.fsm.inherit project.task diff --git a/bemade_fsm/wizard/new_task_from_template.py b/bemade_fsm/wizard/new_task_from_template.py index 575629c..26d67ba 100644 --- a/bemade_fsm/wizard/new_task_from_template.py +++ b/bemade_fsm/wizard/new_task_from_template.py @@ -4,6 +4,7 @@ from odoo.exceptions import UserError class NewTaskFromTemplateWizard(models.TransientModel): _name = "project.task.from.template.wizard" + _description = "Create Task from Template Wizard" project_id = fields.Many2one( comodel_name='project.project', @@ -25,9 +26,16 @@ class NewTaskFromTemplateWizard(models.TransientModel): def default_get(self, fields_list): res = super().default_get(fields_list) - if 'project_id' in fields_list: - active_id = self.env.context.get('active_id', False) - res.update({'project_id': active_id}) + active_id = self.env.context.get('active_id', False) + active_model = self.env.context.get('active_model', False) + if not active_model: + params = self.env.context.get('params', False) + active_model = params.get('model', False) + + if active_model == 'project.task.template' and active_id and 'task_template_id' in fields_list: + res.update({'task_template_id': active_id}) + if active_model == 'project.task' and 'project_id' in fields_list: + res.update({'project_id': self.env.ref('industry_fsm.fsm_project').id}) return res def action_create_task_from_template(self): diff --git a/bemade_fsm/wizard/new_task_from_template.xml b/bemade_fsm/wizard/new_task_from_template.xml index 49a22ce..1290b8f 100644 --- a/bemade_fsm/wizard/new_task_from_template.xml +++ b/bemade_fsm/wizard/new_task_from_template.xml @@ -5,10 +5,6 @@ project.task.from.template.wizard
-
-
@@ -16,7 +12,18 @@ +
+
+ + New Task from Template + project.task.from.template.wizard + + form + new + \ No newline at end of file From 79d1cbe330870b6f7b30023bdfbfbad8062b0f80 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 5 Dec 2023 11:16:10 -0500 Subject: [PATCH 04/10] bemade_full_formview_from_modal: remove useless lines of JS. --- .../static/src/js/form_view_dialog.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/bemade_full_formview_from_modal/static/src/js/form_view_dialog.js b/bemade_full_formview_from_modal/static/src/js/form_view_dialog.js index e94ad00..fbb2b83 100644 --- a/bemade_full_formview_from_modal/static/src/js/form_view_dialog.js +++ b/bemade_full_formview_from_modal/static/src/js/form_view_dialog.js @@ -11,9 +11,6 @@ patch(Dialog.prototype, "bemade_full_formview_from_modal.Dialog", { init: function (parent, options) { this._super(...arguments); }, - custom_events: _.extend({}, Dialog.prototype.custom_events, { - open_full_form_view: '_onOpen', - }), willStart: function () { const self = this; return this._super(...arguments).then(function () { From 0806bc96f0292e54349cf9a1010b8da3e69549b8 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 5 Dec 2023 11:19:16 -0500 Subject: [PATCH 05/10] version bump for bemade_fsm --- bemade_fsm/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bemade_fsm/__manifest__.py b/bemade_fsm/__manifest__.py index c378151..ecc58bc 100644 --- a/bemade_fsm/__manifest__.py +++ b/bemade_fsm/__manifest__.py @@ -20,7 +20,7 @@ ######################################################################################## { 'name': 'Improved Field Service Management', - 'version': '15.0.1.0.3', + 'version': '15.0.1.0.4', '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', From 6ab1bda2b7bb0a3fd277bad84b9c9f50a03953a7 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 5 Dec 2023 11:43:24 -0500 Subject: [PATCH 06/10] bemade_fsm: bug fix in new_task_from_template.py default_get --- bemade_fsm/wizard/new_task_from_template.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bemade_fsm/wizard/new_task_from_template.py b/bemade_fsm/wizard/new_task_from_template.py index 26d67ba..6c8c5e1 100644 --- a/bemade_fsm/wizard/new_task_from_template.py +++ b/bemade_fsm/wizard/new_task_from_template.py @@ -30,8 +30,7 @@ class NewTaskFromTemplateWizard(models.TransientModel): active_model = self.env.context.get('active_model', False) if not active_model: params = self.env.context.get('params', False) - active_model = params.get('model', False) - + active_model = params and params.get('model', False) if active_model == 'project.task.template' and active_id and 'task_template_id' in fields_list: res.update({'task_template_id': active_id}) if active_model == 'project.task' and 'project_id' in fields_list: From 9ff8934556c0a9a0c9004ec0bc4a6b70aca17604 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 5 Dec 2023 12:11:48 -0500 Subject: [PATCH 07/10] Fixed new task from template wizard to show right default project. --- bemade_fsm/static/src/js/kanban_view.js | 2 +- bemade_fsm/static/src/js/list_view.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bemade_fsm/static/src/js/kanban_view.js b/bemade_fsm/static/src/js/kanban_view.js index 2185d01..7f99dcc 100644 --- a/bemade_fsm/static/src/js/kanban_view.js +++ b/bemade_fsm/static/src/js/kanban_view.js @@ -19,7 +19,7 @@ const ProjectKanbanController = KanbanController.extend({ res_model: 'project.task.from.template.wizard', views: [[false, 'form']], target: 'new', - context: {...record.context, active_id: record.project_id}, + context: {...record.context, res_model: 'project.task'}, }); }, renderButtons ($node) { diff --git a/bemade_fsm/static/src/js/list_view.js b/bemade_fsm/static/src/js/list_view.js index 3486965..dc60c3a 100644 --- a/bemade_fsm/static/src/js/list_view.js +++ b/bemade_fsm/static/src/js/list_view.js @@ -17,7 +17,7 @@ const ProjectListController = ListController.extend({ res_model: 'project.task.from.template.wizard', views: [[false, 'form']], target: 'new', - context: {...record.context, active_id: record.project_id}, + context: {...record.context, res_model: 'project.task'}, }); }, renderButtons ($node) { From 41ef2c1484e28f1b72b62b5f9dfe26b8b5341a4d Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 5 Dec 2023 12:23:38 -0500 Subject: [PATCH 08/10] bemade_fsm: spacing for task from template button, delete js tests. --- .../static/src/xml/project_view_buttons.xml | 2 +- .../static/tests/tours/equipment_tour.js | 104 ------------------ .../static/tests/tours/sale_order_tour.js | 40 ------- .../static/tests/tours/task_equipment_tour.js | 75 ------------- .../static/tests/tours/task_template_tour.js | 47 -------- 5 files changed, 1 insertion(+), 267 deletions(-) delete mode 100644 bemade_fsm/static/tests/tours/equipment_tour.js delete mode 100644 bemade_fsm/static/tests/tours/sale_order_tour.js delete mode 100644 bemade_fsm/static/tests/tours/task_equipment_tour.js delete mode 100644 bemade_fsm/static/tests/tours/task_template_tour.js diff --git a/bemade_fsm/static/src/xml/project_view_buttons.xml b/bemade_fsm/static/src/xml/project_view_buttons.xml index df3484a..e2c48c6 100644 --- a/bemade_fsm/static/src/xml/project_view_buttons.xml +++ b/bemade_fsm/static/src/xml/project_view_buttons.xml @@ -12,7 +12,7 @@ - diff --git a/bemade_fsm/static/tests/tours/equipment_tour.js b/bemade_fsm/static/tests/tours/equipment_tour.js deleted file mode 100644 index ec69e1e..0000000 --- a/bemade_fsm/static/tests/tours/equipment_tour.js +++ /dev/null @@ -1,104 +0,0 @@ -/** @odoo-module **/ - -import tour from 'web_tour.tour'; - -const TEST_COMPANY = "Test Partner"; -const TEST_EQPT1 = "Test Equipment 1"; -const TEST_EQPT2 = "Test Equipment 2"; -tour.register('equipment_base_tour', { - test: true, - url: '/web', - }, /* Create a new "Test Equipment" and link it to "Test Partner Company" */ - [tour.stepUtils.showAppsMenuItem(), { - content: 'Navigate to the Service menu', - trigger: '.o_app[data-menu-xmlid="industry_fsm.fsm_menu_root"]', - }, { - content: 'Navigate to the Clients submenu', - trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', - }, { - content: 'Navigate to the Equipment menu', - trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_equipment"]', - }, { - content: 'Click the create button', - trigger: '.o_list_button_add', - }, { - content: 'Add a tag', - trigger: 'input[name="pid_tag"]', - run: 'text TestPIDTag', - }, { - content: 'Set the name', - trigger: 'input[name="name"]', - run: `text ${TEST_EQPT2}`, - }, { - content: 'Set the partner', - trigger: 'div[name="partner_location_id"] div div input', - run: `text ${TEST_COMPANY}`, - }, { - content: 'Click the partner in the dropdown', - trigger: `li a.dropdown-item:contains(${TEST_COMPANY})`, - }, { - content: 'Save equipment', - trigger: 'button.o_list_button_save', - }, { - /* Navigate to the client and make sure that there are two equipments saved (one from the Python test case) */ - content: 'Navigate to the Clients submenu', - trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', - }, { - content: 'Click on the clients submenu', - trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_clients"]', - }, { - content: 'Search for the Test Partner Company', - trigger: 'input.o_searchview_input', - extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Customers))', - run: `text ${TEST_COMPANY}` - }, { - content: 'Validate Search', - trigger: '.o_menu_item.o_selection_focus', - run: 'click', - }, { - content: 'Open the test client.', - trigger: `div.o_kanban_record:has(span:contains(${TEST_COMPANY}))`, - }, { - content: 'Click the Field Service tab.', - trigger: 'a.nav-link:contains(Field Service)', - extra_trigger: `h1 span.o_field_partner_autocomplete[name="name"]:contains(${TEST_COMPANY})`, - }, { - content: 'Make sure we have a first test equipment', - trigger: `td:contains(${TEST_EQPT1})`, - run: function () { - }, - }, { - content: 'Make sure we have a second test equipment', - trigger: `div[name="equipment_ids"]:has(td:contains(${TEST_EQPT2}))`, - run: function () { - }, - } - ]); - -tour.register('equipment_sale_order_tour', { - test: true, - url: '/web', -}, [tour.stepUtils.showAppsMenuItem(), { - content: 'Navigate to the Sales menu', - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', -}, { - content: 'Create an order', - trigger: 'button.o_list_button_add', -}, { - content: 'Select the test partner', - trigger: 'div[name="partner_id"] input', - run: `text ${TEST_COMPANY}`, -}, { - content: 'Click the partner in the dropdown', - trigger: `li a.dropdown-item:contains(${TEST_COMPANY})`, -}, { - content: 'Save', - trigger: 'button.o_form_button_save', -}, { - content: 'Navigate to the Field Service tab.', - trigger: 'a.nav-link[role="tab"]:contains(Field Service)' -}, { - content: 'Check that the equipment is listed in the tab.', - trigger: `li.ui-menu-item > a:contains(${TEST_EQPT1})`, -} -]); \ No newline at end of file diff --git a/bemade_fsm/static/tests/tours/sale_order_tour.js b/bemade_fsm/static/tests/tours/sale_order_tour.js deleted file mode 100644 index 6ea8eee..0000000 --- a/bemade_fsm/static/tests/tours/sale_order_tour.js +++ /dev/null @@ -1,40 +0,0 @@ -/** @odoo-module **/ - -import tour from 'web_tour.tour'; - -const SO_NAME = "TEST ORDER 2" -const PRODUCT_NAME = "Test Product 3" -tour.register('sale_order_tour', { - test: true, - url: '/web', - }, - [tour.stepUtils.showAppsMenuItem(), { - content: 'Navigate to the Service menu', - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - }, { - content: 'Search for the sales order', - trigger: 'input.o_searchview_input', - run: `text ${SO_NAME}`, - }, { - content: 'Validate Search', - trigger: '.o_menu_item.o_selection_focus', - run: 'click', - }, { - content: 'Open the test order', - trigger: `.o_data_cell[name="client_order_ref"]:contains(${SO_NAME})`, - }, { - content: 'Click the view tasks button', - trigger: 'button[name="action_view_task"]', - }, /*{ - content: 'Click the first task', - trigger: `div.o_kanban_record:has(span:contains(${PRODUCT_NAME}))`, - },*/ { - content: 'Click on the ready to invoice button', - trigger: 'button[name="action_fsm_validate"]', - extra_trigger: `li.breadcrumb-item.active:has(span:contains(${PRODUCT_NAME}))` - }, { - content: 'View the SO', - trigger: 'button[name="action_view_so"]', - // extra_trigger: 'button[title="Current state"]:contains(Done)', - } - ]); diff --git a/bemade_fsm/static/tests/tours/task_equipment_tour.js b/bemade_fsm/static/tests/tours/task_equipment_tour.js deleted file mode 100644 index f8a244d..0000000 --- a/bemade_fsm/static/tests/tours/task_equipment_tour.js +++ /dev/null @@ -1,75 +0,0 @@ -/** @odoo-module **/ - -import tour from 'web_tour.tour'; -const TEST_COMPANY = "Test Partner Company"; -const TEST_EQPT1 = "Test Equipment 1"; -const TEST_EQPT2 = "Test Equipment 2"; -tour.register('task_equipment_tour', { - test: true, - url: '/web', - }, /* Create a new "Test Equipment" and link it to "Test Partner Company" */ - [tour.stepUtils.showAppsMenuItem(), { - content: 'Navigate to the Service menu', - trigger: '.o_app[data-menu-xmlid="industry_fsm.fsm_menu_root"]', - }, { - content: 'Navigate to the Clients submenu', - trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', - }, { - content: 'Navigate to the Equipment menu', - trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_equipment"]', - }, { - content: 'Click the create button', - trigger: '.o_list_button_add', - extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Equipment))', - }, { - content: 'Add a tag', - trigger: 'input[name="pid_tag"]', - run: 'text TestPIDTag', - }, { - content: 'Set the name', - trigger: 'input[name="name"]', - run: `text ${TEST_EQPT2}`, - }, { - content: 'Set the partner', - trigger: 'div[name="partner_location_id"] div div input', - run: 'text Test Partner Company', - }, { - content: 'Click the partner in the dropdown', - trigger: `li a.dropdown-item:contains(${TEST_COMPANY})`, - }, { - content: 'Save equipment', - trigger: 'button.o_list_button_save', - }, { - /* Navigate to the client and make sure that there are two equipments saved (one from the Python test case) */ - content: 'Navigate to the Clients submenu', - trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', - }, { - content: 'Click on the clients submenu', - trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_clients"]', - }, { - content: 'Search for the Test Partner Company', - trigger: 'input.o_searchview_input', - extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Customers))', - run: `text ${TEST_COMPANY}` - }, { - content: 'Validate Search', - trigger: '.o_menu_item.o_selection_focus', - run: 'click', - }, { - content: 'Open the test client.', - trigger: `div.o_kanban_record:has(span:contains(${TEST_COMPANY}))`, - }, { - content: 'Click the Field Service tab.', - trigger: 'a.nav-link:contains(Field Service)', - extra_trigger: `h1 span.o_field_partner_autocomplete[name="name"]:contains(${TEST_COMPANY})`, - }, { - content: 'Make sure we have a first test equipment', - /*trigger: `div[name="equipment_ids"]:has(td:contains(${TEST_EQPT1}))`,*/ - trigger: `td:contains(${TEST_EQPT1})`, - run: function() {}, - }, { - content: 'Make sure we have a second test equipment', - trigger: `div[name="equipment_ids"]:has(td:contains(${TEST_EQPT2}))`, - run: function() {}, - } - ]) \ No newline at end of file diff --git a/bemade_fsm/static/tests/tours/task_template_tour.js b/bemade_fsm/static/tests/tours/task_template_tour.js deleted file mode 100644 index 3573a45..0000000 --- a/bemade_fsm/static/tests/tours/task_template_tour.js +++ /dev/null @@ -1,47 +0,0 @@ -/** @odoo-module */ - -import tour from 'web_tour.tour'; - -tour.register('task_template_tour', { - test: true, - url: '/web', - }, /* Make sure the test task is visible via the Project > Task Templates path */ - [tour.stepUtils.showAppsMenuItem(), { - trigger: '.o_app[data-menu-xmlid="project.menu_main_pm"]', - }, { - content: 'Open the task templates list view.', - trigger: '.o_nav_entry[data-menu-xmlid="bemade_fsm.project_task_template_menu"]', - }, { - content: 'Open the existing task template.', - trigger: '.o_data_cell:contains("Template 1"):parent()', - }, { - content: 'Confirm that the header contains the task title.', - trigger: 'span[name="name"]:contains("Template 1")', - }, /* Make sure we can see the task template from the product */ - tour.stepUtils.toggleHomeMenu(), - { - content: 'Open the sales menu.', - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - }, { - content: 'Open the product dropdown menu.', - trigger: 'button[data-menu-xmlid="sale.product_menu_catalog"]', - }, { - content: 'Click the products menu item.', - trigger: 'a.dropdown-item[data-menu-xmlid="sale.menu_product_template_action"]', - }, { - content: 'Search for the test product.', - trigger: 'input.o_searchview_input', - extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Products))', - run: 'text Test Product 1', - }, { - trigger: '.o_menu_item.o_selection_focus', - content: 'Validate search', - run: 'click', - }, { - content: 'Open the test product.', - trigger: 'div.o_kanban_record:has(span:contains(Test Product 1))', - }, { - content: 'Ensure the product_template_id field is displayed with the "Template 1" mention.', - trigger: 'a.o_quick_editable[name="task_template_id"]:first-child:contains("Template 1")', - }, - ]) \ No newline at end of file From 74ec46382a2f435263f2d1caf5496e679b3ea451 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Tue, 5 Dec 2023 14:56:06 -0500 Subject: [PATCH 09/10] bemade_fsm: Propagation of task assignee to subtasks (optional). --- bemade_fsm/__manifest__.py | 2 +- bemade_fsm/models/task.py | 17 ++++++++++++ bemade_fsm/tests/__init__.py | 1 + bemade_fsm/tests/test_sale_order.py | 2 +- bemade_fsm/tests/test_task.py | 40 +++++++++++++++++++++++++++++ bemade_fsm/views/task_views.xml | 3 +++ 6 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 bemade_fsm/tests/test_task.py diff --git a/bemade_fsm/__manifest__.py b/bemade_fsm/__manifest__.py index ecc58bc..9b7669d 100644 --- a/bemade_fsm/__manifest__.py +++ b/bemade_fsm/__manifest__.py @@ -20,7 +20,7 @@ ######################################################################################## { 'name': 'Improved Field Service Management', - 'version': '15.0.1.0.4', + 'version': '15.0.1.0.5', '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/task.py b/bemade_fsm/models/task.py index 296a7c6..c0b20e0 100644 --- a/bemade_fsm/models/task.py +++ b/bemade_fsm/models/task.py @@ -76,6 +76,12 @@ class Task(models.Model): work_order_number = fields.Char(readonly=True) + propagate_assignment = fields.Boolean( + string='Propagate Assignment', + help='Propagate assignment of this task to all subtasks.', + default=True, + ) + @api.model_create_multi def create(self, vals): res = super().create(vals) @@ -92,6 +98,16 @@ class Task(models.Model): + f"-{seq}" return res + def write(self, vals): + super().write(vals) + if not self: # End recursion on empty RecordSet + return + if 'user_ids' in vals: + to_propagate = self.filtered(lambda task: task.propagate_assignment) + # 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']}) + @api.depends('sale_order_id') def _compute_relevant_order_lines(self): for rec in self: @@ -199,6 +215,7 @@ class Task(models.Model): if self.child_ids: return self | self.child_ids._get_full_hierarchy() return self + def synchronize_name_fsm(self): """ Applies naming to the entire task tree for tasks that are part of this recordset. Root tasks are named: diff --git a/bemade_fsm/tests/__init__.py b/bemade_fsm/tests/__init__.py index 381536f..4b317ee 100644 --- a/bemade_fsm/tests/__init__.py +++ b/bemade_fsm/tests/__init__.py @@ -4,3 +4,4 @@ from . import test_sale_order from . import test_equipment from . import test_fsm_contact_setting from . import test_fsm_visit +from . import test_task diff --git a/bemade_fsm/tests/test_sale_order.py b/bemade_fsm/tests/test_sale_order.py index c51246c..484392e 100644 --- a/bemade_fsm/tests/test_sale_order.py +++ b/bemade_fsm/tests/test_sale_order.py @@ -251,7 +251,7 @@ class TestSalesOrder(BemadeFSMBaseTest): self.assertFalse(visit_task.user_ids) self.assertFalse(subtask1.user_ids) self.assertFalse(subtask2.user_ids) - + def test_long_line_name_overflows_to_task_description(self): so = self._generate_sale_order() product = self._generate_product() diff --git a/bemade_fsm/tests/test_task.py b/bemade_fsm/tests/test_task.py new file mode 100644 index 0000000..63185de --- /dev/null +++ b/bemade_fsm/tests/test_task.py @@ -0,0 +1,40 @@ +from .test_bemade_fsm_common import BemadeFSMBaseTest +from odoo.tests.common import tagged +from odoo import Command + + +@tagged('post_install', '-at_install') +class TaskTest(BemadeFSMBaseTest): + + def test_reassigning_assignment_propagating_task_changes_subtasks(self): + # This creation step is a bit lazy - we use defaults to make tasks with a hierachy and settings we want + so = self._generate_sale_order() + template = self._generate_task_template(names=['Parent', 'Child'], structure=[2]) + product = self._generate_product(task_template=template) + sol = self._generate_sale_order_line(sale_order=so, product=product) + user = self._generate_project_manager_user('Bob', 'Bob') + so.action_confirm() + task = sol.task_id + + task.write({ + 'user_ids': [Command.set([user.id])] + }) + + self.assertTrue(all([t.user_ids == user for t in task | task._get_all_subtasks()])) + + def test_reassigning_assignment_non_propagating_task_doesnt_change_subtasks(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) + user = self._generate_project_manager_user('Bob', 'Bob') + so.action_confirm() + task = sol.task_id + task.child_ids.write({'propagate_assignment': False}) # Stop propagation after the first level + + task.write({ + 'user_ids': [Command.set([user.id])] + }) + + self.assertTrue(all([t.user_ids == user for t in task | task.child_ids])) + self.assertFalse(any([t.user_ids for t in task.child_ids.child_ids])) diff --git a/bemade_fsm/views/task_views.xml b/bemade_fsm/views/task_views.xml index 25f71be..8db34b3 100644 --- a/bemade_fsm/views/task_views.xml +++ b/bemade_fsm/views/task_views.xml @@ -46,6 +46,9 @@ + + + From 165ad8a226c55bf433105b9cc045f47791322bad Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Fri, 8 Dec 2023 14:01:41 -0500 Subject: [PATCH 10/10] bemade_fsm: propagate assignees to false by default. --- bemade_fsm/__manifest__.py | 2 +- bemade_fsm/models/task.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bemade_fsm/__manifest__.py b/bemade_fsm/__manifest__.py index 9b7669d..fa6c83b 100644 --- a/bemade_fsm/__manifest__.py +++ b/bemade_fsm/__manifest__.py @@ -20,7 +20,7 @@ ######################################################################################## { 'name': 'Improved Field Service Management', - 'version': '15.0.1.0.5', + 'version': '15.0.1.0.6', '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/task.py b/bemade_fsm/models/task.py index c0b20e0..43a147d 100644 --- a/bemade_fsm/models/task.py +++ b/bemade_fsm/models/task.py @@ -79,7 +79,7 @@ class Task(models.Model): propagate_assignment = fields.Boolean( string='Propagate Assignment', help='Propagate assignment of this task to all subtasks.', - default=True, + default=False, ) @api.model_create_multi