forward port 15.0 into 16.0
This commit is contained in:
commit
6d167a4cc1
23 changed files with 379 additions and 269 deletions
|
|
@ -1 +1,2 @@
|
|||
from . import models
|
||||
from . import wizard
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
37
bemade_fsm/static/src/js/kanban_view.js
Normal file
37
bemade_fsm/static/src/js/kanban_view.js
Normal 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)
|
||||
36
bemade_fsm/static/src/js/list_view.js
Normal file
36
bemade_fsm/static/src/js/list_view.js
Normal 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)
|
||||
21
bemade_fsm/static/src/xml/project_view_buttons.xml
Normal file
21
bemade_fsm/static/src/xml/project_view_buttons.xml
Normal 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 && (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>
|
||||
|
|
@ -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})`,
|
||||
}
|
||||
]);
|
||||
|
|
@ -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)',
|
||||
}
|
||||
]);
|
||||
|
|
@ -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() {},
|
||||
}
|
||||
])
|
||||
|
|
@ -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")',
|
||||
},
|
||||
])
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
40
bemade_fsm/tests/test_task.py
Normal file
40
bemade_fsm/tests/test_task.py
Normal 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]))
|
||||
|
|
@ -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()]))
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
1
bemade_fsm/wizard/__init__.py
Normal file
1
bemade_fsm/wizard/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import new_task_from_template
|
||||
51
bemade_fsm/wizard/new_task_from_template.py
Normal file
51
bemade_fsm/wizard/new_task_from_template.py
Normal 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',
|
||||
}
|
||||
|
||||
|
||||
29
bemade_fsm/wizard/new_task_from_template.xml
Normal file
29
bemade_fsm/wizard/new_task_from_template.xml
Normal 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>
|
||||
0
bemade_full_formview_from_modal/__init__.py
Normal file
0
bemade_full_formview_from_modal/__init__.py
Normal file
37
bemade_full_formview_from_modal/__manifest__.py
Normal file
37
bemade_full_formview_from_modal/__manifest__.py
Normal 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
|
||||
}
|
||||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
});
|
||||
Loading…
Reference in a new issue