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 c976f1c..861a9e8 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.0', + 'version': '16.0.1.0.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', @@ -55,6 +55,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': [ @@ -64,6 +65,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/models/task.py b/bemade_fsm/models/task.py index b9b5a92..1a37f85 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=False, + ) + @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: @@ -234,6 +250,11 @@ class Task(models.Model): task.write(values) + def _get_full_hierarchy(self): + 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/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/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..7f99dcc --- /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, res_model: 'project.task'}, + }); + }, + 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..dc60c3a --- /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, res_model: 'project.task'}, + }); + }, + 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..e2c48c6 --- /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/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 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 2801ff3..1e39371 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/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/views/task_views.xml b/bemade_fsm/views/task_views.xml index ddc48b8..9d8d201 100644 --- a/bemade_fsm/views/task_views.xml +++ b/bemade_fsm/views/task_views.xml @@ -46,6 +46,9 @@ + + + @@ -168,4 +171,4 @@ - \ No newline at end of file + 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..6c8c5e1 --- /dev/null +++ b/bemade_fsm/wizard/new_task_from_template.py @@ -0,0 +1,51 @@ +from odoo import models, fields, api, _ +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', + 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) + 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 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: + res.update({'project_id': self.env.ref('industry_fsm.fsm_project').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..1290b8f --- /dev/null +++ b/bemade_fsm/wizard/new_task_from_template.xml @@ -0,0 +1,29 @@ + + + + project.task.from.template.wizard.view.form + project.task.from.template.wizard + +
+ + + + + + + +
+
+
+
+
+ + New Task from Template + project.task.from.template.wizard + + form + new + +
\ No newline at end of file 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..fbb2b83 --- /dev/null +++ b/bemade_full_formview_from_modal/static/src/js/form_view_dialog.js @@ -0,0 +1,40 @@ +/** @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); + }, + 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, + }) + } +});