Merge 'bemade_fsm' into bemade-addons

This commit is contained in:
Marc Durepos 2023-06-27 11:25:31 -04:00
commit 377141c3b0
25 changed files with 1096 additions and 0 deletions

View file

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[ENH]"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen. Ex. As a [type of user], I could click [...] and [...] would happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

1
bemade_fsm/__init__.py Normal file
View file

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

View file

@ -0,0 +1,55 @@
######################################################################################
#
# 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': 'Durpro Field Service Management',
'version': '15.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',
'author': 'Bemade Inc.',
'website': 'http://www.bemade.org',
'license': 'OPL-1',
'depends': ['project',
'stock',
'sale',
'sale_project',
'sale_stock',
'industry_fsm',
'industry_fsm_sale',
'bemade_partner_root_ancestor',
],
'data': ['views/task_template_views.xml',
'views/equipment.xml',
'security/ir.model.access.csv',
'views/product_views.xml',
'views/res_partner.xml',
'views/menus.xml',
'views/task_views.xml',
],
'assets': {
'web.assets_tests': [
'bemade_fsm/static/tests/tours/task_template_tour.js',
'bemade_fsm/static/tests/tours/task_equipment_tour.js',
],
},
'installable': True,
'auto_install': False
}

View file

@ -0,0 +1,6 @@
from . import task_template
from . import product_template
from . import sale_order
from . import equipment
from . import task
from . import res_partner

View file

@ -0,0 +1,90 @@
from odoo import api, fields, models
class EquipmentTag(models.Model):
_name = "bemade_fsm.equipment.tag"
_description = 'Field service equipment category'
name = fields.Char('Name', required=True, translate=True)
color = fields.Integer('Color Index', default=10)
_sql_constraints = [
('name_uniq', 'unique (name)', "Tag name already exists !"),
]
class EquipmentType(models.Model):
_name = 'bemade_fsm.equipment.type'
_description = 'Field service equipment type'
_order = 'id'
name = fields.Char(string='Intervention Name', required=True, translate=True)
class Equipment(models.Model):
_name = 'bemade_fsm.equipment'
_rec_name = 'complete_name'
_description = 'Field service equipment'
_inherit = ['mail.thread', 'mail.activity.mixin']
pid_tag = fields.Char(string="P&ID Tag", tracking=True)
name = fields.Char(string="Name", tracking=True, required=True)
complete_name = fields.Char(string="Equipment Name", compute="_compute_complete_name", store=True)
tag_ids = fields.Many2many('bemade_fsm.equipment.tag',
string='Application',
tracking=True,
help="Classify and analyze your equipment categories like: Boiler, Laboratory, "
"Waste water, Pure water")
partner_id = fields.Many2one('res.partner',
string="Owner",
compute="_compute_partner")
description = fields.Text(string="Description",
tracking=True)
partner_location_id = fields.Many2one('res.partner',
string="Physical Address",
tracking=True,)
location_notes = fields.Text(string="Physical Location Notes",
tracking=True)
task_ids = fields.One2many(comodel_name='project.task',
inverse_name='equipment_id',
string='Interventions')
@api.depends('partner_location_id')
def _compute_partner(self):
for rec in self:
rec.partner_id = rec.partner_location_id and rec.partner_location_id.root_ancestor
@api.depends('pid_tag', 'name')
def _compute_complete_name(self):
for rec in self:
rec.complete_name = "[%s] %s" % (rec.pid_tag or ' ', rec.name)
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
args = args or []
if name:
equipments = self.search([
'|', '|', '|',
('pid_tag', operator, name),
('name', operator, name),
('partner_id.name', operator, name),
('partner_location_id.name', operator, name)],
limit=limit)
else:
equipments = self.search(args, limit=limit)
return equipments.name_get()
def action_view_equipment(self):
return {
'name': 'Equipment',
'view_type': 'form',
'view_mode': 'form',
'res_id': self.id,
'context': self.env.context,
'res_model': 'bemade_fsm.equipment',
'type': 'ir.actions.act_window',
}

View file

@ -0,0 +1,7 @@
from odoo import fields, models, api
class ProductTemplate(models.Model):
_inherit = 'product.template'
task_template_id = fields.Many2one("project.task.template", string="Task Template", ondelete='restrict')

View file

@ -0,0 +1,45 @@
from odoo import api, fields, models, Command
class Partner(models.Model):
_inherit = 'res.partner'
equipment_count = fields.Integer(compute='_compute_equipment_count',
string='Equipment Count')
owned_equipment_ids = fields.One2many(comodel_name="bemade_fsm.equipment",
inverse_name="partner_id",
string="Owned Equipments")
equipment_ids = fields.One2many(comodel_name='bemade_fsm.equipment',
inverse_name='partner_location_id',
string='Site Equipment')
is_site_contact = fields.Boolean(string='Is a site contact',
compute="_compute_is_site_contact")
site_ids = fields.Many2many(string='Work Sites',
comodel_name='res.partner',
relation='res_partner_site_contact_rel',
column1='site_contact_id',
column2='site_id',
tracking=True)
site_contacts = fields.Many2many(string='Site Contacts',
comodel_name='res.partner',
relation='res_partner_site_contact_rel',
column1='site_id',
column2='site_contact_id',
domain=[('is_company', '=', False)],
tracking=True)
@api.depends('site_ids')
def _compute_is_site_contact(self):
for rec in self:
rec.is_site_contact = rec.site_ids is not False
@api.depends('equipment_ids')
def _compute_equipment_count(self):
for rec in self:
all_equipmemt_ids = self.env['bemade_fsm.equipment'].search([('partner_id', '=', rec.id)])
rec.equipment_count = len(all_equipmemt_ids)

View file

@ -0,0 +1,61 @@
from odoo import fields, models, api, _, Command
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
def _timesheet_create_task(self, project):
""" Generate task for the given so line, and link it.
:param project: record of project.project in which the task should be created
:return task: record of the created task
Override to add the logic needed to implement task templates."""
def _create_task_from_template(project, template, parent):
""" Recursively generates the task and any subtasks from a project.task.template.
:param project: project.project record to set on the task's project_id field.
:param template: project.task.template to use to create the task.
:param parent: project.task to set as the parent to this task.
"""
values = _timesheet_create_task_prepare_values_from_template(project, template, parent)
task = self.env['project.task'].sudo().create(values)
subtasks = []
for t in template.subtasks:
subtask = _create_task_from_template(project, t, task)
subtasks.append(subtask)
task.write({'child_ids': [Command.set([t.id for t in subtasks])]})
# We don't want to see the sub-tasks on the SO
task.child_ids.write({'sale_order_id': None, 'sale_line_id': None, })
return task
def _timesheet_create_task_prepare_values_from_template(project, template, parent):
""" Copies the values from a project.task.template over to the set of values used to create a project.task.
:param project: project.project record to set on the task's project_id field.
Pass the project.project model or an empty recordset to leave task project_id blank.
DO NOT pass False or None as this will cause an error in _timesheet_create_task_prepare_values(project).
:param template: project.task.template to use to create the task.
:param parent: project.task to set as the parent to this task.
"""
vals = self._timesheet_create_task_prepare_values(project)
vals['name'] = f"{vals['name']} ({template.name})" if not parent else template.name
vals['description'] = template.description or vals['description']
vals['parent_id'] = parent and parent.id
vals['user_ids'] = template.assignees.ids
vals['tag_ids'] = template.tags.ids
vals['planned_hours'] = template.planned_hours
return vals
tmpl = self.product_id.task_template_id
if not tmpl:
task = super()._timesheet_create_task(project)
else:
task = _create_task_from_template(project, tmpl, None)
self.write({'task_id': task.id})
# post message on task
task_msg = _(
"This task has been created from: <a href=# data-oe-model=sale.order data-oe-id=%d>%s</a> (%s)") % (
self.order_id.id, self.order_id.name, self.product_id.name)
task.message_post(body=task_msg)
return task

View file

@ -0,0 +1,7 @@
from odoo import fields, models, api
class Task(models.Model):
_inherit = "project.task"
equipment_id = fields.Many2one("bemade_fsm.equipment", string="Target Equipment")

View file

@ -0,0 +1,32 @@
from odoo import models, fields, api, _
class TaskTemplate(models.Model):
_name = 'project.task.template'
@api.model
def _current_company(self):
return self.env.company
name = fields.Char(string="Task Title", required=True)
description = fields.Html(string="Description")
assignees = fields.Many2many("res.users", string="Default Assignees", help="Employees assigned to tasks created from this template.")
customer = fields.Many2one("res.partner", string="Default Customer", help="Default customer for tasks created from this template.")
project = fields.Many2one("project.project", string="Default Project", help="Default project for tasks created from this template.")
tags = fields.Many2many("project.tags", string="Default Tags", help="Default tags for tasks created from this template.")
parent = fields.Many2one("project.task.template", string="Parent Task Template", ondelete='cascade')
subtasks = fields.One2many("project.task.template", inverse_name="parent", string="Subtask Templates")
sequence = fields.Integer(string="Sequence")
company_id = fields.Many2one("res.company", string="Company", index=1, default=_current_company)
planned_hours = fields.Float("Initially Planned Hours")
def action_open_task(self):
return {
'view_mode': 'form',
'res_model': 'project.task.template',
'res_id': self.id,
'type': 'ir.actions.act_window',
'context': self._context
}

View file

@ -0,0 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_bemade_fsm_task_template,bemade_fsm_task_template,model_project_task_template,project.group_project_manager,1,1,1,1
access_bemade_fsm_task_template,bemade_fsm_task_template,model_project_task_template,project.group_project_user,1,1,1,1
access_bemade_fsm_equipment,bemade_fsm_equipment,model_bemade_fsm_equipment,base.group_user,1,1,1,1
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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_bemade_fsm_task_template bemade_fsm_task_template model_project_task_template project.group_project_manager 1 1 1 1
3 access_bemade_fsm_task_template bemade_fsm_task_template model_project_task_template project.group_project_user 1 1 1 1
4 access_bemade_fsm_equipment bemade_fsm_equipment model_bemade_fsm_equipment base.group_user 1 1 1 1
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

View file

@ -0,0 +1,75 @@
/** @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_form_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

@ -0,0 +1,47 @@
/** @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

@ -0,0 +1,4 @@
from . import test_bemade_fsm_common
from . import test_task_template
from . import test_task_template_sale_order
from . import test_equipment

View file

@ -0,0 +1,37 @@
from odoo.tests.common import TransactionCase
from odoo import Command
class FSMManagerUserTransactionCase(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
user_group_employee = cls.env.ref('base.group_user')
user_group_project_user = cls.env.ref('project.group_project_user')
user_group_project_manager = cls.env.ref('project.group_project_manager')
user_group_fsm_user = cls.env.ref('industry_fsm.group_fsm_user')
user_group_fsm_manager = cls.env.ref('industry_fsm.group_fsm_manager')
user_group_sales_manager = cls.env.ref('sales_team.group_sale_manager')
user_group_sales_user = cls.env.ref('sales_team.group_sale_salesman')
user_product_customer = cls.env.ref('customer_product_code.group_product_customer_code_user')
group_ids = [user_group_employee.id,
user_group_project_user.id,
user_group_project_manager.id,
user_group_fsm_user.id,
user_group_fsm_manager.id,
user_group_sales_user.id,
user_group_sales_manager.id, ]
if user_product_customer:
group_ids.append(user_product_customer.id)
# Test user with project access rights for the various tests
Users = cls.env['res.users'].with_context({'no_reset_password': True})
cls.user = Users.create({
'name': 'Project Manager',
'login': 'misterpm',
'password': 'misterpm',
'email': 'mrpm@testco.com',
'signature': 'Mr. PM',
'groups_id': [Command.set(group_ids)],
})

View file

@ -0,0 +1,37 @@
from odoo.tests.common import HttpCase, tagged
from .test_bemade_fsm_common import FSMManagerUserTransactionCase
class TestEquipmentCommon(FSMManagerUserTransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Set up the test partner
cls.partner_company = cls.env['res.partner'].create({
'name': 'Test Partner Company',
'company_type': 'company',
'street': '123 Street St.',
'city': 'Montreal',
'state_id': cls.env['res.country.state'].search([('name','ilike','Quebec%')]).id,
'country_id': cls.env['res.country'].search([('name','=','Canada')]).id
})
cls.partner_contact = cls.env['res.partner'].create({
'name': 'Site Contact',
'company_type': 'person',
'parent_id': cls.partner_company.id,
})
cls.equipment = cls.env['bemade_fsm.equipment'].create({
'name': 'Test Equipment 1',
'partner_location_id': cls.partner_company.id,
})
@tagged('-at_install', 'post_install')
class TestEquipmentTour(HttpCase, TestEquipmentCommon):
def test_equipment_tour(self):
self.start_tour('/web', 'task_equipment_tour',
login=self.user.login, )

View file

@ -0,0 +1,30 @@
from odoo.tests import TransactionCase, HttpCase, tagged
from odoo import Command
from test_bemade_fsm_common import FSMManagerUserTransactionCase
class SaleOrderFSMContactsCase(FSMManagerUserTransactionCase):
def test_default_site_contacts(self):
# Make sure the SO pulls the defaults from the partner correctly
pass
def test_default_billing_contacts(self):
# Make sure the SO pulls the defaults from the partner correctly
pass
def test_default_workorder_contacts(self):
# Make sure the SO pulls the defaults from the partner correctly
pass
def test_change_site_contacts(self):
# Make sure the SO contacts can be updated without feeding back to the partner
pass
def test_change_workorder_contacts(self):
# Make sure the SO contacts can be updated without feeding back to the partner
pass
def test_change_billing_contacts(self):
# Make sure the SO contacts can be updated without feedin back to the partner
pass

View file

@ -0,0 +1,108 @@
from .test_bemade_fsm_common import FSMManagerUserTransactionCase
from odoo.tests.common import HttpCase, tagged
from odoo.exceptions import MissingError
from odoo import Command
from psycopg2.errors import ForeignKeyViolation
class TestTaskTemplateCommon(FSMManagerUserTransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.PLANNED_HOURS = 6
hours_uom = cls.env['uom.uom'].search([('name', '=', 'Hour')]) or False
# Test product to use with the various tests
cls.task1 = cls.env['project.task.template'].create({
'name': 'Template 1',
})
cls.project = cls.env['project.project'].create({
'name': 'Test Project',
})
cls.product_task_global_project = cls.env['product.product'].create({
'name': 'Test Product 1',
'type': 'service',
'service_tracking': 'task_global_project',
'project_id': cls.project.id,
'task_template_id': cls.task1.id,
'uom_id': hours_uom.id,
'uom_po_id': hours_uom.id,
})
cls.project_template = cls.env['project.project'].create({
'name': 'Test Project Template',
})
cls.product_task_in_project = cls.env['product.product'].create({
'name': 'Test Product 2',
'type': 'service',
'service_tracking': 'task_in_project',
'task_template_id': cls.task1.id,
'project_template_id': cls.project_template.id,
'uom_po_id': hours_uom.id,
'uom_id': hours_uom.id,
})
# Set up a task template tree with 2 children and 1 grandchild
cls.parent_task = cls.env['project.task.template'].create({
'name': 'Parent Template',
'planned_hours': cls.PLANNED_HOURS,
})
cls.child_task_1 = cls.env['project.task.template'].create({
'name': 'Child Template 1',
'parent': cls.parent_task.id,
})
cls.child_task_2 = cls.env['project.task.template'].create({
'name': 'Child Template 2',
'parent': cls.parent_task.id,
})
cls.parent_task.write({'subtasks': [Command.set([cls.child_task_1.id, cls.child_task_2.id])]})
cls.grandchild_task = cls.env['project.task.template'].create({
'name': 'Grandchild Template',
'parent': cls.child_task_2.id
})
cls.child_task_2.write({'subtasks': [Command.set([cls.grandchild_task.id])]})
# Create products using the task tree we just created
cls.product_task_tree_global_project = cls.env['product.product'].create({
'name': 'Test Product 3',
'type': 'service',
'service_tracking': 'task_global_project',
'project_id': cls.project.id,
'task_template_id': cls.parent_task.id,
'uom_id': hours_uom.id,
'uom_po_id': hours_uom.id,
})
cls.product_task_tree_in_project = cls.env['product.product'].create({
'name': 'Test Product 2',
'type': 'service',
'service_tracking': 'task_in_project',
'task_template_id': cls.parent_task.id,
'project_template_id': cls.project_template.id,
'uom_po_id': hours_uom.id,
'uom_id': hours_uom.id,
})
@tagged('-at_install', 'post_install')
class TestTaskTemplate(TestTaskTemplateCommon):
def test_delete_task_template(self):
"""User should never be able to delete a task template used on a product"""
with self.assertRaises(ForeignKeyViolation):
self.task1.unlink()
def test_delete_subtask_template(self):
""" Deletion of a child task should be OK even if the parent is on a product. Children of the deleted
subtask should be deleted."""
self.child_task_2.unlink()
# Reading deleted child's name field should be impossible
with self.assertRaises(MissingError):
test = self.grandchild_task.name
@tagged('-at_install', 'post_install')
class TestTaskTemplateTour(HttpCase, TestTaskTemplateCommon):
def test_task_template_tour(self):
self.start_tour('/web', 'task_template_tour',
login='misterpm', )

View file

@ -0,0 +1,86 @@
from .test_task_template import TestTaskTemplateCommon
from odoo.tests.common import tagged
class TestTaskTemplateSalesOrder(TestTaskTemplateCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner = cls.env['res.partner'].create({
'name': 'Test Partner',
})
cls.sale_order1 = cls.env['sale.order'].create({
'partner_id': cls.partner.id,
'client_order_ref': 'TEST ORDER',
})
cls.sol_serv_order = cls.env['sale.order.line'].create({
'name': cls.product_task_global_project.name,
'product_id': cls.product_task_global_project.id,
'product_uom_qty': 1,
'product_uom': cls.product_task_global_project.uom_id.id,
'price_unit': 120.0,
'order_id': cls.sale_order1.id,
'tax_id': False,
})
cls.sol_serv_order_task_in_project = cls.env['sale.order.line'].create({
'name': cls.product_task_in_project.name,
'product_id': cls.product_task_in_project.id,
'product_uom_qty': 1,
'product_uom': cls.product_task_in_project.uom_id.id,
'price_unit': 150.0,
'order_id': cls.sale_order1.id,
'tax_id': False,
})
cls.sale_order2 = cls.env['sale.order'].create({
'partner_id': cls.partner.id,
'client_order_ref': 'TEST ORDER',
})
cls.sol_tree_order = cls.env['sale.order.line'].create({
'name': cls.product_task_tree_global_project.name,
'product_id': cls.product_task_tree_global_project.id,
'product_uom_qty': 1,
'product_uom': cls.product_task_tree_global_project.uom_id.id,
'price_unit': 120.0,
'order_id': cls.sale_order2.id,
'tax_id': False,
})
cls.sol_serv_order_task_in_project = cls.env['sale.order.line'].create({
'name': cls.product_task_tree_in_project.name,
'product_id': cls.product_task_tree_in_project.id,
'product_uom_qty': 1,
'product_uom': cls.product_task_tree_in_project.uom_id.id,
'price_unit': 150.0,
'order_id': cls.sale_order2.id,
'tax_id': False,
})
@tagged('-at_install', 'post_install')
def test_order_confirmation_simple_template(self):
""" Confirming the order should create a task in the global project. """
so = self.sale_order1
so.action_confirm()
sol1 = so.order_line[0]
sol2 = so.order_line[1]
task1 = sol1.task_id
task2 = sol2.task_id
self.assertTrue(task1)
self.assertTrue(task2)
self.assertTrue(self.task1.name in task1.name)
self.assertTrue(self.task1.name in task2.name)
self.assertTrue(self.task1.planned_hours == task1.planned_hours)
def test_order_confirmation_tree_template(self):
def assert_structure(sol):
self.assertTrue(sol.task_id.child_ids and len(sol.task_id.child_ids) == 2)
self.assertTrue(self.parent_task.name in sol.task_id.name)
self.assertTrue(self.child_task_1.name in sol.task_id.child_ids[0].name)
self.assertTrue(self.child_task_2.name in sol.task_id.child_ids[1].name)
self.assertTrue(sol.task_id.child_ids[1].child_ids and len(sol.task_id.child_ids.child_ids) == 1)
self.assertTrue(self.grandchild_task.name in sol.task_id.child_ids.child_ids[0].name)
so = self.sale_order2
so.action_confirm()
sol1 = so.order_line[0]
sol2 = so.order_line[1]
assert_structure(sol1)
assert_structure(sol2)

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Equipment Form View -->
<record id="equipment_view_form" model="ir.ui.view">
<field name="name">bemade_fsm.equipment.form</field>
<field name="model">bemade_fsm.equipment</field>
<field name="arch" type="xml">
<form string="Equipment">
<sheet>
<group>
<group name="left">
<field name="pid_tag"/>
<field name="name"/>
<field name="description"/>
<field name="location_notes"/>
</group>
<group name="right">
<field name="partner_id"/>
<field name="partner_location_id"
groups="sale.group_delivery_invoice_address"
context="{'default_type': 'delivery', 'show_address': 1}"
options='{"always_reload": True}'/>
<field name="tag_ids" widget="many2many_tags" options="{'no_open': False}"/>
</group>
</group>
<!-- <notebook>-->
<!-- <page string="Interventions">-->
<!-- <field name="intervention_ids" mode="tree">-->
<!-- <tree string="Interventions" create="0" edit="0">-->
<!-- <field name="name"/>-->
<!-- <field name="description"/>-->
<!-- <field name="date_planned"/>-->
<!-- <field name="state"/>-->
<!-- </tree>-->
<!-- </field>-->
<!-- </page>-->
<!-- </notebook>-->
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="equipment_view_tree" model="ir.ui.view">
<field name="name">bemade_fsm.equipment.tree</field>
<field name="model">bemade_fsm.equipment</field>
<field name="arch" type="xml">
<tree string="Equipment">
<field name="pid_tag"/>
<field name="name"/>
<field name="description"/>
<field name="tag_ids" widget="many2many_tags" options="{'no_open': False}"/>
<field name="partner_id"/>
</tree>
</field>
</record>
<!-- BV: AN OTHER SEARCH VIEW TO BRING BACK
<record id="equipment_view_search" model="ir.ui.view">
<field name="name">bemade_fsm.equipment.search</field>
<field name="model">bemade_fsm.equipment</field>
<field name="arch" type="xml">
<search string="Equipments">
<field name="name"/>
<field name="partner_id"/>
<field name="pid_tag"/>
<field name="partner_location_id"/>
<group expand="0" string="Group By">
<filter string="Partner" context="{'group_by':'partner_id'}"/>
<filter string="Location" context="{'group_by':'partner_location_id'}"/>
</group>
</search>
</field>
</record>
-->
<record model="ir.actions.act_window" id="action_window_equipment">
<field name="name">Equipment</field>
<field name="res_model">bemade_fsm.equipment</field>
<field name="view_mode">tree,form</field>
</record>
<record model="ir.actions.act_window" id="action_window_equipment_tags">
<field name="name">Equipment Tag</field>
<field name="res_model">bemade_fsm.equipment.tag</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<menuitem id="project_task_template_menu"
name="Task Templates"
parent="project.menu_main_pm"
action="task_template_act_window"
groups="project.group_project_manager,project.group_project_user"/>
<menuitem id="service_task_template_meny"
name="Task Templates"
action="task_template_act_window"
parent="industry_fsm.fsm_menu_root"
groups="project.group_project_manager,industry_fsm.group_fsm_manager"/>
<menuitem id="menu_service_client"
name="Clients"
sequence="10"
parent="industry_fsm.fsm_menu_root"
groups="industry_fsm.group_fsm_user"/>
<menuitem id="menu_service_client_clients"
name="Clients"
action="base.action_partner_customer_form"
sequence="20"
parent="menu_service_client"
groups="industry_fsm.group_fsm_user"/>
<menuitem id="menu_service_client_equipment"
name="Client Equipment"
action="action_window_equipment"
sequence="21"
parent="menu_service_client"
groups="industry_fsm.group_fsm_user"/>
</data>
</odoo>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="product_template_form_inherit" model="ir.ui.view">
<field name="name">bemade_fsm.product.template.form</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="sale_project.product_template_form_view_invoice_policy_inherit_sale_project"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='project_id']" position="after">
<field name="task_template_id"
attrs="{'invisible': [('service_tracking', 'not in', ('task_global_project', 'task_in_project'))]}"/>
</xpath>
</field>
</record>
<record id="product_search_form_view_inherit_bemade_fsm" model="ir.ui.view">
<field name="name">bemade_fsm.product_search_form_view_inherit_bemade_fsm</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="industry_fsm_sale.product_search_form_view_inherit_fsm_sale"/>
<field name="arch" type="xml">
<xpath expr="//searchpanel//field[@name='categ_id']" position="attributes">
<attribute name="limit">0</attribute>
</xpath>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,70 @@
<?xml version="1.0"?>
<odoo>
<record id="act_res_partner_2_equipment" model="ir.actions.act_window">
<field name="name">Equipments</field>
<field name="res_model">bemade_fsm.equipment</field>
<field name="view_mode">tree,form</field>
<field name="context">{'default_partner_id': active_id}</field>
<field name="domain">[("partner_id", "=", active_id)]</field>
</record>
<record id="partner_equipment_location_view_form" model="ir.ui.view">
<field name="name">bemade_fsm.partner.equipment.location.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='internal_notes']" position="before">
<page name="field_service" string="Field Service">
<field name="owned_equipment_ids" invisible="True"/>
<group>
<field name="site_contacts" attrs="{'invisible': [('company_type','=','person')]}">
<tree editable="bottom">
<field name="name" widget="res_partner_many2one"/>
<field name="email" widget="email"/>
<field name="phone" widget="phone"/>
<field name="mobile" widget="phone"/>
</tree>
</field>
<field name="equipment_ids" attrs="{'invisible': [('company_type','=','person')]}">
<tree editable="bottom">
<field name="pid_tag"/>
<field name="name" />
<field name="location_notes"/>
<button name="action_view_equipment"
type="object"
string="Details"
icon="fa-external-link"/>
</tree>
</field>
<field attrs="{'invisible': ['|', ('company_type','=','person'), ('owned_equipment_ids','=',False)]}"
name="owned_equipment_ids">
<tree editable="False">
<field name="pid_tag"/>
<field name="name" />
<field name="partner_location_id" widget="res_partner_many2one"/>
<field name="location_notes"/>
</tree>
</field>
<field name="site_ids"
attrs="{'invisible': [('company_type','=','company')]}">
<tree editable="bottom">
<field name="name" widget="res_partner_many2one" />
</tree>
</field>
</group>
</page>
</xpath>
<div name="button_box" position="inside">
<button class="oe_stat_button" type="action" name="%(bemade_fsm.act_res_partner_2_equipment)d"
icon="fa-tachometer">
<field string="Equipments" name="equipment_count" widget="statinfo"/>
</button>
</div>
<field name="lang" position="after">
</field>
<xpath expr="/form//field[@name='child_ids']/form//field[@name='comment']" position="after">
<field name="is_site_contact"/>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="task_template_form_view" model="ir.ui.view">
<field name="name">bemade_fsm.task_template.form</field>
<field name="model">project.task.template</field>
<field name="arch" type="xml">
<form string="Task Template">
<sheet>
<div class="oe_title">
<label for="name"/>
<h1>
<field name="name" placeholder="Title"/>
</h1>
</div>
<group>
<group>
<field name="project"/>
<field name="assignees" widget="many2many_avatar_user"/>
<field name="parent"/>
<field name="planned_hours"/>
</group>
<group>
<field name="customer"/>
<field name="tags" widget="many2many_tags"/>
<field name="company_id" />
</group>
</group>
<notebook>
<page name="description_page" string="Description">
<field name="description" type="html" options="{'collaborative': true}"/>
</page>
<page name="subtasks_page" string="Subtasks">
<field name="subtasks">
<tree editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="customer"/>
<field name="assignees" widget="many2many_avatar_user"/>
<button name="action_open_task" type="object" title="View Task"
string="View Task" class="btn btn-link pull-right"/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="task_template_tree_view" model="ir.ui.view">
<field name="name">project.task_template.tree</field>
<field name="model">project.task.template</field>
<field name="arch" type="xml">
<tree string="Task Template">
<field name="name"/>
<field name="assignees" widget="many2many_avatar_user"/>
<field name="project"/>
<field name="parent"/>
<field name="planned_hours"/>
</tree>
</field>
</record>
<record id="task_template_search_view" model="ir.ui.view">
<field name="name">project.task_template.search</field>
<field name="model">project.task.template</field>
<field name="arch" type="xml">
<search string="Task Template">
<field name="name"/>
<field name="project"/>
<field name="assignees"/>
<field name="parent"/>
<field name="subtasks"/>
<field name="planned_hours"/>
<group expand="1" string="Group By">
<filter string="Project" name="groupby_project" domain="[]"
context="{'group_by':'project'}"/>
<filter string="Parent Task" name="groupby_parent" domain="[]"
context="{'group_by':'parent'}"/>
<filter string="Customer" name="groupby_customer" domain="[]"
context="{'group_by':'customer'}"/>
</group>
</search>
</field>
</record>
<record id="task_template_act_window" model="ir.actions.act_window">
<field name="name">Task Template</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">project.task.template</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
There are no task templates, click above to create one.
</p>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="bemade_fsm_project_task_form_inherit" model="ir.ui.view">
<field name="name">bemade_fsm.project_task.form</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="priority" eval="8"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='partner_id']" position="after">
<field name="equipment_id" domain="[('partner_location_id','=',partner_id)]"/>
</xpath>
</field>
</record>
</data>
</odoo>