forward port 15.0 into 16.0

This commit is contained in:
Marc Durepos 2023-12-19 16:13:45 -05:00
commit 6d167a4cc1
23 changed files with 379 additions and 269 deletions

View file

@ -1 +1,2 @@
from . import models
from . import wizard

View file

@ -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,

View file

@ -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:

View file

@ -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

View file

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_bemade_fsm_equipment_tag bemade_fsm_equipment_tag model_bemade_fsm_equipment_tag base.group_user 1 1 1 1
6 access_bemade_fsm_equipment_type access_bemade_fsm_equipment_type model_bemade_fsm_equipment_type base.group_user 1 0 0 0
7 access_bemade_fsm_visit access_bemade_fsm_visit model_bemade_fsm_visit base.group_user 1 1 1 1
8 access_bemade_fsm_task_wizard access_bemade_fsm_task_wizard model_project_task_from_template_wizard project.group_project_user 1 1 1 1

View file

@ -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)

View file

@ -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)

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<!-- Add a button to create a task from a template to task (project) list and kanban views -->
<t t-name='project.KanbanView.buttons' t-inherit="web.KanbanView.buttons" t-inherit-mode="primary">
<xpath expr="//button[contains(@t-attf-class, 'o-kanban-button-new')]" position="after">
<button title="Create Task from Template" t-if="!noCreate" type="button"
t-attf-class="btn {{ btnClass }} o-kanban-button-new-from-template">
<t t-esc="create_text &amp;&amp; (create_text + ' from Template') || _t('Create from Template')"/>
</button>
</xpath>
</t>
<t t-name='project.ListView.buttons' t-inherit="web.ListView.buttons" t-inherit-mode="primary">
<xpath expr="//button[hasclass('o_list_button_add')]" position="after">
<!-- Create is enabled in the parent template at this point; check is done prior -->
<button type="button" class="btn ml-1 btn-primary o_list_button_add_from_template"
title="Create Task from Template">
Create from Template
</button>
</xpath>
</t>
</templates>

View file

@ -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})`,
}
]);

View file

@ -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)',
}
]);

View file

@ -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() {},
}
])

View file

@ -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")',
},
])

View file

@ -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

View file

@ -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()

View file

@ -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]))

View file

@ -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()]))

View file

@ -46,6 +46,9 @@
<xpath expr="//field[@name='child_ids']/tree//field[@name='name']" position="after">
<field name="description" string="Description/Comments"/>
</xpath>
<field name="user_ids" position="after">
<field name="propagate_assignment"/>
</field>
</field>
</record>
<!-- Add parent_id = false to domain for My Tasks, All Tasks: To Schedule, All Tasks and To Invoice-->
@ -168,4 +171,4 @@
</field>
</record>
</data>
</odoo>
</odoo>

View file

@ -0,0 +1 @@
from . import new_task_from_template

View file

@ -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',
}

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="project_task_from_template_wizard_view_form" model="ir.ui.view">
<field name="name">project.task.from.template.wizard.view.form</field>
<field name="model">project.task.from.template.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="task_template_id"/>
<field name="new_task_title"/>
<field name="project_id"/>
</group>
</sheet>
<footer>
<button class='btn btn-primary' name="action_create_task_from_template" type="object" string="Create Task"/>
<button class='btn btn-secondary' special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_new_task_from_template">
<field name="name">New Task from Template</field>
<field name="res_model">project.task.from.template.wizard</field>
<field name="binding_model_id" ref="bemade_fsm.model_project_task_template"/>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>

View file

@ -0,0 +1,37 @@
#
# Bemade Inc.
#
# Copyright (C) 2023-June Bemade Inc. (<https://www.bemade.org>).
# 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
}

View file

@ -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,
})
}
});