separated equipment out of bemade_fsm into fsm_equipment module

This commit is contained in:
Marc Durepos 2024-08-20 15:58:17 -04:00
parent d94fabf8ba
commit 382b573a86
28 changed files with 302 additions and 265 deletions

View file

@ -36,13 +36,13 @@
"industry_fsm_stock", "industry_fsm_stock",
"industry_fsm_report", "industry_fsm_report",
"industry_fsm_sale_report", "industry_fsm_sale_report",
"fsm_equipment",
"bemade_partner_root_ancestor", "bemade_partner_root_ancestor",
"mail", "mail",
], ],
"data": [ "data": [
"data/fsm_data.xml", "data/fsm_data.xml",
"views/task_template_views.xml", "views/task_template_views.xml",
"views/equipment.xml",
"security/ir.model.access.csv", "security/ir.model.access.csv",
"views/product_views.xml", "views/product_views.xml",
"views/res_partner.xml", "views/res_partner.xml",

View file

@ -2,9 +2,6 @@ from . import task_template
from . import product_template from . import product_template
from . import sale_order_line from . import sale_order_line
from . import sale_order from . import sale_order
from . import equipment
from . import equipment_type
from . import equipment_tag
from . import task from . import task
from . import res_partner from . import res_partner
from . import fsm_visit from . import fsm_visit

View file

@ -1,9 +0,0 @@
from odoo import fields, models
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)

View file

@ -40,7 +40,7 @@ class FSMVisit(models.Model):
) )
summarized_equipment_ids = fields.Many2many( summarized_equipment_ids = fields.Many2many(
comodel_name="bemade_fsm.equipment", comodel_name="fsm.equipment",
string="Equipment to Service", string="Equipment to Service",
compute="_compute_summarized_equipment_ids", compute="_compute_summarized_equipment_ids",
) )

View file

@ -4,20 +4,6 @@ from odoo import api, fields, models
class Partner(models.Model): class Partner(models.Model):
_inherit = "res.partner" _inherit = "res.partner"
equipment_count = fields.Integer(compute="_compute_equipment_count")
owned_equipment_ids = fields.One2many(
comodel_name="bemade_fsm.equipment",
compute="_compute_owned_equipment_ids",
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( is_site_contact = fields.Boolean(
string="Is a site contact", string="Is a site contact",
compute="_compute_is_site_contact", compute="_compute_is_site_contact",
@ -52,33 +38,11 @@ class Partner(models.Model):
tracking=True, tracking=True,
) )
is_service_site = fields.Boolean(
compute="_compute_is_service_site",
)
@api.depends("equipment_ids", "child_ids.company_type", "child_ids.equipment_ids")
def _compute_owned_equipment_ids(self):
for rec in self:
ids = rec.equipment_ids | rec.child_ids.mapped("equipment_ids")
rec.owned_equipment_ids = ids or False
@api.depends("site_ids") @api.depends("site_ids")
def _compute_is_site_contact(self): def _compute_is_site_contact(self):
for rec in self: for rec in self:
rec.is_site_contact = rec.site_ids rec.is_site_contact = rec.site_ids
@api.depends("equipment_ids")
def _compute_equipment_count(self):
for rec in self:
all_equipment_ids = self.env["bemade_fsm.equipment"].search(
[("partner_location_id", "=", rec.id)]
)
rec.equipment_count = len(all_equipment_ids)
@api.model @api.model
def _search_is_site_contact(self, operator, value): def _search_is_site_contact(self, operator, value):
return [("site_ids", "!=", False)] return [("site_ids", "!=", False)]
def _compute_is_service_site(self):
for rec in self:
rec.is_service_site = bool(rec.equipment_ids)

View file

@ -5,11 +5,11 @@ class SaleOrder(models.Model):
_inherit = "sale.order" _inherit = "sale.order"
valid_equipment_ids = fields.One2many( valid_equipment_ids = fields.One2many(
comodel_name="bemade_fsm.equipment", related="partner_id.owned_equipment_ids" comodel_name="fsm.equipment", related="partner_id.owned_equipment_ids"
) )
default_equipment_ids = fields.Many2many( default_equipment_ids = fields.Many2many(
comodel_name="bemade_fsm.equipment", comodel_name="fsm.equipment",
string="Default Equipment to Service", string="Default Equipment to Service",
help="The default equipment to service for new sale order lines.", help="The default equipment to service for new sale order lines.",
compute="_compute_default_equipment", compute="_compute_default_equipment",
@ -18,7 +18,7 @@ class SaleOrder(models.Model):
) )
summary_equipment_ids = fields.Many2many( summary_equipment_ids = fields.Many2many(
comodel_name="bemade_fsm.equipment", comodel_name="fsm.equipment",
string="Equipment Being Serviced", string="Equipment Being Serviced",
compute="_compute_summary_equipment_ids", compute="_compute_summary_equipment_ids",
) )
@ -106,8 +106,9 @@ class SaleOrder(models.Model):
return rec return rec
def _create_default_visit(self): def _create_default_visit(self):
"""Called when an order is confirmed with lines that will create an FSM task, in order to make sure there is """Called when an order is confirmed with lines that will create an FSM task,
a visit line grouping all the service being done.""" in order to make sure there is a visit line grouping all the service being done.
"""
self.ensure_one() self.ensure_one()
visit = self.env["bemade_fsm.visit"].create( visit = self.env["bemade_fsm.visit"].create(
{ {
@ -119,8 +120,8 @@ class SaleOrder(models.Model):
visit.so_section_id.sequence = 0 visit.so_section_id.sequence = 0
def _create_or_organize_visits_if_needed(self): def _create_or_organize_visits_if_needed(self):
"""Adds a visit line to the top of the order if there are not already visit lines for an order with lines that """Adds a visit line to the top of the order if there are not already visit
will create an FSM task.""" lines for an order with lines that will create an FSM task."""
for order in self.filtered("company_id.create_default_fsm_visit"): for order in self.filtered("company_id.create_default_fsm_visit"):
if not order.visit_ids and order.is_fsm: if not order.visit_ids and order.is_fsm:
order._create_default_visit() order._create_default_visit()

View file

@ -4,7 +4,7 @@ from odoo import fields, models, api, _, Command
class SaleOrderLine(models.Model): class SaleOrderLine(models.Model):
_inherit = "sale.order.line" _inherit = "sale.order.line"
valid_equipment_ids = fields.One2many( valid_equipment_ids = fields.One2many(
comodel_name="bemade_fsm.equipment", related="order_id.valid_equipment_ids" comodel_name="fsm.equipment", related="order_id.valid_equipment_ids"
) )
visit_ids = fields.One2many( visit_ids = fields.One2many(
@ -42,7 +42,7 @@ class SaleOrderLine(models.Model):
equipment_ids = fields.Many2many( equipment_ids = fields.Many2many(
string="Equipment to Service", string="Equipment to Service",
comodel_name="bemade_fsm.equipment", comodel_name="fsm.equipment",
relation="bemade_fsm_equipment_sale_order_line_rel", relation="bemade_fsm_equipment_sale_order_line_rel",
column1="sale_order_line_id", column1="sale_order_line_id",
column2="equipment_id", column2="equipment_id",

View file

@ -7,7 +7,7 @@ class Task(models.Model):
_inherit = "project.task" _inherit = "project.task"
equipment_ids = fields.Many2many( equipment_ids = fields.Many2many(
comodel_name="bemade_fsm.equipment", comodel_name="fsm.equipment",
relation="bemade_fsm_task_equipment_rel", relation="bemade_fsm_task_equipment_rel",
column1="task_id", column1="task_id",
column2="equipment_id", column2="equipment_id",
@ -59,6 +59,11 @@ class Task(models.Model):
compute="_compute_is_closed", compute="_compute_is_closed",
) )
root_ancestor = fields.Many2one(
comodel_name="project.task",
compute="_compute_root_ancestor",
)
def _compute_is_closed(self): def _compute_is_closed(self):
for rec in self: for rec in self:
rec.is_closed = rec.state in CLOSED_STATES rec.is_closed = rec.state in CLOSED_STATES
@ -213,6 +218,7 @@ class Task(models.Model):
else: else:
rec.name = title rec.name = title
@property @api.depends("parent_id", "parent_id.root_ancestor")
def root_ancestor(self): def _compute_root_ancestor(self):
return self.parent_id and self.parent_id.root_ancestor or self for rec in self:
rec.root_ancestor = rec.parent_id and rec.parent_id.root_ancestor or self

View file

@ -58,7 +58,7 @@ class TaskTemplate(models.Model):
planned_hours = fields.Float("Initially Planned Hours") planned_hours = fields.Float("Initially Planned Hours")
equipment_ids = fields.Many2many( equipment_ids = fields.Many2many(
comodel_name="bemade_fsm.equipment", comodel_name="fsm.equipment",
relation="bemade_fsm_task_template_equipment_rel", relation="bemade_fsm_task_template_equipment_rel",
column1="task_template_id", column1="task_template_id",
column2="equipment_id", column2="equipment_id",
@ -78,9 +78,7 @@ class TaskTemplate(models.Model):
def _onchange_customer(self): def _onchange_customer(self):
for rec in self: for rec in self:
new_equipment_ids = [ new_equipment_ids = [
eq.id eq.id for eq in rec.equipment_ids if eq.partner_id == rec.customer
for eq in rec.equipment_ids
if eq.partner_location_id == rec.customer
] ]
rec.write({"equipment_ids": [Command.set(new_equipment_ids)]}) rec.write({"equipment_ids": [Command.set(new_equipment_ids)]})
@ -107,7 +105,8 @@ class TaskTemplate(models.Model):
return vals return vals
def create_task_from_self(self, project, name=False, parent_id=False): 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. """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 project: project.project record the task should be added to
:param name: name for the new task (defaults to template name) :param name: name for the new task (defaults to template name)

View file

@ -1,8 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_bemade_fsm_task_template_manager,bemade_fsm_task_template,model_project_task_template,project.group_project_manager,1,1,1,1 access_bemade_fsm_task_template_manager,bemade_fsm_task_template,model_project_task_template,project.group_project_manager,1,1,1,1
access_bemade_fsm_task_template_user,bemade_fsm_task_template,model_project_task_template,project.group_project_user,1,1,1,1 access_bemade_fsm_task_template_user,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
access_bemade_fsm_visit,access_bemade_fsm_visit,model_bemade_fsm_visit,base.group_user,1,1,1,1 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 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
2 access_bemade_fsm_task_template_manager bemade_fsm_task_template model_project_task_template project.group_project_manager 1 1 1 1
3 access_bemade_fsm_task_template_user 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
4 access_bemade_fsm_visit access_bemade_fsm_visit model_bemade_fsm_visit base.group_user 1 1 1 1
5 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

@ -1,7 +1,6 @@
from . import test_bemade_fsm_common from . import test_bemade_fsm_common
from . import test_task_template from . import test_task_template
from . import test_sale_order from . import test_sale_order
from . import test_equipment
from . import test_fsm_contact_setting from . import test_fsm_contact_setting
from . import test_fsm_visit from . import test_fsm_visit
from . import test_task from . import test_task

View file

@ -69,7 +69,8 @@ class BemadeFSMBaseTest(TransactionCase):
"""Generates a partner with basic address filled in. """Generates a partner with basic address filled in.
:param name: The partner's name. :param name: The partner's name.
:param company_type: The type of partner, either 'company' or 'person' are accepted. :param company_type: The type of partner, either 'company' or 'person' are
accepted.
""" """
return cls.env["res.partner"].create( return cls.env["res.partner"].create(
{ {
@ -119,10 +120,10 @@ class BemadeFSMBaseTest(TransactionCase):
@classmethod @classmethod
def _generate_equipment(cls, name="test equipment", partner=None): def _generate_equipment(cls, name="test equipment", partner=None):
return cls.env["bemade_fsm.equipment"].create( return cls.env["fsm.equipment"].create(
{ {
"name": name, "name": name,
"partner_location_id": partner and partner.id or False, "partner_id": partner and partner.id or False,
} }
) )
@ -186,14 +187,18 @@ class BemadeFSMBaseTest(TransactionCase):
): ):
"""Generates a task template with the specified structure and naming. """Generates a task template with the specified structure and naming.
:param parent: The parent task template for the top-level task template being generated :param parent: The parent task template for the top-level task template being
:param structure: A list of integers describing the number of tasks for each level of descendants. An empty generated
list represents only one top-level task template. If no structure is given, an empty list :param structure: A list of integers describing the number of tasks for each
will be used in its place. level of descendants. An empty list represents only one
:param names: The name prefixes to be given to the task templates at each level. Each prefix will be followed top-level task template. If no structure is given, an empty
by a sequential integer for its level. Child 1, Child 2, Grandchild 1, etc. If no names argument list will be used in its place.
:param names: The name prefixes to be given to the task templates at each level.
Each prefix will be followed by a sequential integer for its
level. Child 1, Child 2, Grandchild 1, etc. If no names argument
is passed, a default ['Task Template'] argument will be used. is passed, a default ['Task Template'] argument will be used.
:param planned_hours: The number of planned hours for the top-level task template being generated. :param planned_hours: The number of planned hours for the top-level task
template being generated.
:param equipment: The equipment to add as linked equipment to the task template. :param equipment: The equipment to add as linked equipment to the task template.
""" """
if not names: if not names:

View file

@ -1,21 +0,0 @@
from odoo.tests.common import tagged
from .test_bemade_fsm_common import BemadeFSMBaseTest
from odoo import Command
from odoo.exceptions import MissingError
@tagged("-at_install", "post_install")
class TestEquipment(BemadeFSMBaseTest):
def test_crud(self):
partner_company = self._generate_partner()
self._generate_partner("Site Contact", "person", partner_company)
equipment = self._generate_equipment("Test Equipment 1", partner_company)
# Just make sure the basic ORM stuff is OK
self.assertTrue(equipment in partner_company.equipment_ids)
self.assertTrue(len(partner_company.equipment_ids) == 1)
# Delete should cascade
partner_company.write({"equipment_ids": [Command.set([])]})
with self.assertRaises(MissingError):
_ = equipment.name

View file

@ -15,7 +15,8 @@ class SaleOrderFSMContactsCase(BemadeFSMBaseTest):
so = self._generate_sale_order(parent_co) so = self._generate_sale_order(parent_co)
self.assertTrue(so.site_contacts == parent_co.site_contacts) self.assertTrue(so.site_contacts == parent_co.site_contacts)
# Make sure updating the site contacts on the SO doesn't feed back to the partner # Make sure updating the site contacts on the SO doesn't feed back to the
# partner
so.write({"site_contacts": [Command.set([contact_1.id])]}) so.write({"site_contacts": [Command.set([contact_1.id])]})
self.assertTrue(contact_1 in so.site_contacts) self.assertTrue(contact_1 in so.site_contacts)
self.assertTrue(contact_2 not in so.site_contacts) self.assertTrue(contact_2 not in so.site_contacts)
@ -39,7 +40,8 @@ class SaleOrderFSMContactsCase(BemadeFSMBaseTest):
self.assertTrue(contact_1 in so.work_order_contacts) self.assertTrue(contact_1 in so.work_order_contacts)
self.assertTrue(contact_2 in so.work_order_contacts) self.assertTrue(contact_2 in so.work_order_contacts)
# Make sure setting the work order contacts on the SO doesn't feed back to the partner # Make sure setting the work order contacts on the SO doesn't feed back to the
# partner
so.write({"work_order_contacts": [Command.set([contact_1.id])]}) so.write({"work_order_contacts": [Command.set([contact_1.id])]})
self.assertTrue(contact_1 in so.work_order_contacts) self.assertTrue(contact_1 in so.work_order_contacts)
self.assertTrue(contact_2 not in so.work_order_contacts) self.assertTrue(contact_2 not in so.work_order_contacts)
@ -119,7 +121,8 @@ class SaleOrderFSMContactsCase(BemadeFSMBaseTest):
self.assertFalse(so.work_order_contacts) self.assertFalse(so.work_order_contacts)
self.assertFalse(so.site_contacts) self.assertFalse(so.site_contacts)
# Now set back to the location with the FSM contacts and make sure they get set on the SO # Now set back to the location with the FSM contacts and make sure they get set
# on the SO
form.partner_shipping_id = shipping_location form.partner_shipping_id = shipping_location
form.save() form.save()
self.assertEqual(so.work_order_contacts, shipping_location.work_order_contacts) self.assertEqual(so.work_order_contacts, shipping_location.work_order_contacts)

View file

@ -7,7 +7,8 @@ from odoo import Command
class TaskTest(BemadeFSMBaseTest): class TaskTest(BemadeFSMBaseTest):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
# Chose to set up all tests the same way since this code was becoming very redundant # Chose to set up all tests the same way since this code was becoming very
# redundant
super().setUpClass() super().setUpClass()
cls.user = cls._generate_project_manager_user("Bob", "Bob") cls.user = cls._generate_project_manager_user("Bob", "Bob")
@ -62,7 +63,8 @@ class TaskTest(BemadeFSMBaseTest):
self.assertFalse( self.assertFalse(
any([t.propagate_assignment for t in task._get_all_subtasks()]) any([t.propagate_assignment for t in task._get_all_subtasks()])
) )
# Then, test that assigning the parent only assigns its children, not its grandchildren # Then, test that assigning the parent only assigns its children, not its
# grandchildren
task.write({"user_ids": [Command.set([])]}) task.write({"user_ids": [Command.set([])]})
self.assertTrue(all([not t.user_ids for t in task | task.child_ids])) self.assertTrue(all([not t.user_ids for t in task | task.child_ids]))
self.assertTrue( self.assertTrue(

View file

@ -14,27 +14,4 @@
parent="industry_fsm.fsm_menu_root" parent="industry_fsm.fsm_menu_root"
groups="project.group_project_manager,industry_fsm.group_fsm_manager" 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"
/>
</odoo> </odoo>

View file

@ -1,45 +1,15 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<odoo> <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_location_id': active_id}</field>
<field name="domain">[("partner_location_id", "=", active_id)]</field>
</record>
<record id="partner_equipment_location_view_form" model="ir.ui.view"> <record id="partner_equipment_location_view_form" model="ir.ui.view">
<field name="name">bemade_fsm.partner.equipment.location.form</field> <field name="name">partner.equipment.location.view.form</field>
<field name="model">res.partner</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">
<field name="is_site_contact" invisible="True" />
<field name="is_service_site" invisible="True" />
<page
name="field_service"
string="Service Site Responsibilities"
invisible="is_service_site or is_company"
>
<field name="owned_equipment_ids" invisible="True" />
<group>
<field name="site_ids">
<tree editable="bottom">
<field name="name" widget="res_partner_many2one" />
</tree>
</field>
</group>
</page>
<page
name="service_equipment"
string="Service Site Info"
invisible="is_site_contact"
>
<field <field
name="equipment_ids" name="inherit_id"
context="{'tree_view_ref': 'bemade_fsm.fsm_equipment_view_tree'}" ref="fsm_equipment.partner_equipment_location_view_form"
readonly="False"
/> />
<field name="arch" type="xml">
<page name="equipment" position="after">
<page name="Service Contacts" invisible="not is_company">
<field <field
name="site_contacts" name="site_contacts"
context="{'tree_view_ref': 'bemade_fsm.fsm_contacts_view_tree'}" context="{'tree_view_ref': 'bemade_fsm.fsm_contacts_view_tree'}"
@ -47,36 +17,18 @@
<field <field
name="work_order_contacts" name="work_order_contacts"
context="{'tree_view_ref': 'bemade_fsm.fsm_contacts_view_tree'}" context="{'tree_view_ref': 'bemade_fsm.fsm_contacts_view_tree'}"
/>
<field
name="owned_equipment_ids"
context="{'tree_view_ref': 'bemade_fsm.fsm_equipment_view_tree'}"
readonly="True"
/> />
</page> </page>
</xpath> <page name="Service Sites" invisible="is_company">
<div name="button_box" position="inside"> <group>
<button <field name="site_ids">
class="oe_stat_button" <tree editable="bottom">
type="action" <field name="name" widget="res_partner_many2one" />
name="%(bemade_fsm.act_res_partner_2_equipment)d" </tree>
icon="fa-tachometer"
>
<field
string="Equipments"
name="equipment_count"
widget="statinfo"
/>
</button>
</div>
<field name="lang" position="after">
</field> </field>
<xpath </group>
expr="/form//field[@name='child_ids']/form//field[@name='comment']" </page>
position="after" </page>
>
<field name="is_site_contact" />
</xpath>
</field> </field>
</record> </record>
<record id="fsm_contacts_view_tree" model="ir.ui.view"> <record id="fsm_contacts_view_tree" model="ir.ui.view">
@ -91,21 +43,4 @@
</tree> </tree>
</field> </field>
</record> </record>
<record id="fsm_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>
<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>
</record>
</odoo> </odoo>

View file

@ -23,8 +23,8 @@
<field name="customer" /> <field name="customer" />
<field <field
name="equipment_ids" name="equipment_ids"
domain="[('partner_location_id', '=', customer)]" domain="[('partner_id', '=', customer)]"
context="{'tree_view_ref': 'bemade_fsm.equipment_view_tree'}" context="{'tree_view_ref': 'fsm_equipment.equipment_view_tree'}"
/> />
<field name="tags" widget="many2many_tags" /> <field name="tags" widget="many2many_tags" />
<field name="company_id" /> <field name="company_id" />

View file

@ -19,8 +19,8 @@
<group name="equipment_and_contacts"> <group name="equipment_and_contacts">
<field <field
name="equipment_ids" name="equipment_ids"
domain="[('partner_location_id', '=', partner_id)]" domain="[('partner_id', '=', partner_id)]"
context="{'tree_view_ref': 'bemade_fsm.equipment_view_tree'}" context="{'tree_view_ref': 'fsm_equipment.equipment_view_tree'}"
/> />
<field <field
name="site_contacts" name="site_contacts"

View file

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

View file

@ -0,0 +1,37 @@
#
# Bemade Inc.
#
# Copyright (C) 2023-June Bemade Inc. (<https://www.bemade.org>).
# Author: Marc Durepos (Contact : marc@bemade.org)
#
# This program is under the terms of the GNU Lesser General Public License,
# version 3.
#
# For full license details, see https://www.gnu.org/licenses/lgpl-3.0.en.html.
#
# 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": "FSM Equipment",
"version": "17.0.0.1.0",
"summary": "Add the notion of client equipment for Field Service",
"category": "Services/Field Service",
"author": "Bemade Inc.",
"website": "http://www.bemade.org",
"license": "LGPL-3",
"depends": ["industry_fsm", "account"],
"data": [
"security/ir.model.access.csv",
"views/equipment_views.xml",
"views/res_partner_views.xml",
],
"assets": {},
"installable": True,
"auto_install": False,
}

View file

@ -0,0 +1,3 @@
from . import equipment_tag
from . import equipment
from . import res_partner

View file

@ -1,39 +1,39 @@
from odoo import api, fields, models from odoo import models, fields, api, _
class Equipment(models.Model): class Equipment(models.Model):
_name = "bemade_fsm.equipment" _name = "fsm.equipment"
_rec_name = "complete_name" _description = "Partner-Owned Equipment"
_description = "Field service equipment"
_inherit = ["mail.thread", "mail.activity.mixin"] _inherit = ["mail.thread", "mail.activity.mixin"]
pid_tag = fields.Char(string="P&ID Tag", tracking=True) code = fields.Char(
tracking=True,
name = fields.Char(tracking=True, required=True) )
name = fields.Char(
complete_name = fields.Char( tracking=True,
string="Equipment Name", compute="_compute_complete_name", store=True required=True,
) )
tag_ids = fields.Many2many( tag_ids = fields.Many2many(
comodel_name="bemade_fsm.equipment.tag", comodel_name="fsm.equipment.tag",
string="Application", string="Tags",
help=(
"Classify and analyze your equipment categories like: Boiler, Laboratory,"
" Waste water, Pure water"
),
) )
description = fields.Text(tracking=True) description = fields.Text(tracking=True)
partner_location_id = fields.Many2one( partner_id = fields.Many2one(
comodel_name="res.partner", comodel_name="res.partner",
string="Physical Address", string="Physical Address",
tracking=True, tracking=True,
ondelete="cascade", ondelete="cascade",
) )
location_notes = fields.Text(string="Physical Location Notes", tracking=True) location_notes = fields.Text(
string="Physical Location Notes",
tracking=True,
help="Any useful information about physical location "
"(door codes, directions, etc.).",
)
task_ids = fields.Many2many( task_ids = fields.Many2many(
comodel_name="project.task", comodel_name="project.task",
@ -45,13 +45,6 @@ class Equipment(models.Model):
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
@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.model @api.model
def name_search(self, name="", args=None, operator="ilike", limit=100): def name_search(self, name="", args=None, operator="ilike", limit=100):
args = args or [] args = args or []
@ -60,9 +53,9 @@ class Equipment(models.Model):
[ [
"|", "|",
"|", "|",
("pid_tag", operator, name), ("code", operator, name),
("name", operator, name), ("name", operator, name),
("partner_location_id.name", operator, name), ("partner_id.name", operator, name),
], ],
limit=limit, limit=limit,
) )
@ -77,13 +70,13 @@ class Equipment(models.Model):
"view_mode": "form", "view_mode": "form",
"res_id": self.id, "res_id": self.id,
"context": self.env.context, "context": self.env.context,
"res_model": "bemade_fsm.equipment", "res_model": "fsm.equipment",
"type": "ir.actions.act_window", "type": "ir.actions.act_window",
} }
@api.depends("pid_tag", "name") @api.depends("code", "name")
def _compute_complete_name(self): def _compute_complete_name(self):
for rec in self: for rec in self:
tag_part = "[%s] " % rec.pid_tag if rec.pid_tag else "" tag_part = "[%s] " % rec.code if rec.code else ""
name = rec.name or "" name = rec.name or ""
rec.complete_name = tag_part + name rec.complete_name = tag_part + name

View file

@ -2,11 +2,17 @@ from odoo import fields, models
class EquipmentTag(models.Model): class EquipmentTag(models.Model):
_name = "bemade_fsm.equipment.tag" _name = "fsm.equipment.tag"
_description = "Field service equipment category" _description = "Field service equipment category"
name = fields.Char(required=True, translate=True) name = fields.Char(
color = fields.Integer("Color Index", default=10) required=True,
translate=True,
)
color = fields.Integer(
"Color Index",
default=10,
)
_sql_constraints = [ _sql_constraints = [
("name_uniq", "unique (name)", "Tag name already exists !"), ("name_uniq", "unique (name)", "Tag name already exists !"),

View file

@ -0,0 +1,47 @@
from odoo import models, fields, api
class Partner(models.Model):
_inherit = "res.partner"
equipment_count = fields.Integer(compute="_compute_equipment_count")
owned_equipment_ids = fields.One2many(
comodel_name="fsm.equipment",
compute="_compute_owned_equipment_ids",
string="Owned Equipments",
)
equipment_ids = fields.One2many(
comodel_name="fsm.equipment",
inverse_name="partner_id",
string="Site Equipment",
)
is_service_site = fields.Boolean(
compute="_compute_is_service_site",
help="A partner is a service site if it has one or more equipments.",
)
@api.depends(
"equipment_ids",
"child_ids.company_type",
"child_ids.equipment_ids",
)
def _compute_owned_equipment_ids(self):
for rec in self:
ids = rec.equipment_ids | rec.child_ids.mapped("equipment_ids")
rec.owned_equipment_ids = ids or False
@api.depends("equipment_ids")
def _compute_equipment_count(self):
for rec in self:
all_equipment_ids = self.env["fsm.equipment"].search(
[("partner_id", "=", rec.id)]
)
rec.equipment_count = len(all_equipment_ids)
@api.depends("equipment_ids")
def _compute_is_service_site(self):
for rec in self:
rec.is_service_site = bool(rec.equipment_ids)

View file

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fsm_equipment,fsm_equipment,model_fsm_equipment,base.group_user,1,1,1,1
access_fsm_equipment_tag,fsm_equipment_tag,model_fsm_equipment_tag,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fsm_equipment fsm_equipment model_fsm_equipment base.group_user 1 1 1 1
3 access_fsm_equipment_tag fsm_equipment_tag model_fsm_equipment_tag base.group_user 1 1 1 1

View file

@ -2,23 +2,26 @@
<odoo> <odoo>
<!-- Equipment Form View --> <!-- Equipment Form View -->
<record id="equipment_view_form" model="ir.ui.view"> <record id="equipment_view_form" model="ir.ui.view">
<field name="name">bemade_fsm.equipment.form</field> <field name="name">fsm.equipment.form</field>
<field name="model">bemade_fsm.equipment</field> <field name="model">fsm.equipment</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<sheet> <sheet>
<group> <group>
<group name="left"> <group name="left">
<field name="pid_tag" /> <field name="code" />
<field name="name" /> <field name="name" />
<field name="description" /> <field name="description" />
<field name="location_notes" /> <field name="location_notes" />
</group> </group>
<group name="right"> <group name="right">
<field <field
name="partner_location_id" name="partner_id"
groups="account.group_delivery_invoice_address" groups="account.group_delivery_invoice_address"
context="{'default_type': 'delivery', 'show_address': 1}" context="{
'default_type': 'delivery',
'show_address': 1
}"
options='{"always_reload": True}' options='{"always_reload": True}'
/> />
<field <field
@ -28,22 +31,27 @@
/> />
</group> </group>
</group> </group>
<notebook>
<page string="Interventions">
<field name="task_ids" groupby="root_ancestor" />
</page>
</notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" /> <field name="message_follower_ids" />
<field name="activity_ids" widget="mail_activity" /> <field name="activity_ids" />
<field name="message_ids" widget="mail_thread" /> <field name="message_ids" />
</div> </div>
</form> </form>
</field> </field>
</record> </record>
<record id="equipment_view_tree" model="ir.ui.view"> <record id="equipment_view_tree" model="ir.ui.view">
<field name="name">bemade_fsm.equipment.tree</field> <field name="name">fsm.equipment.tree</field>
<field name="model">bemade_fsm.equipment</field> <field name="model">fsm.equipment</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree editable="bottom"> <tree editable="bottom">
<field name="pid_tag" /> <field name="code" />
<field name="name" /> <field name="name" />
<field name="description" /> <field name="description" />
<field <field
@ -51,21 +59,43 @@
widget="many2many_tags" widget="many2many_tags"
options="{'no_open': False}" options="{'no_open': False}"
/> />
<field name="partner_location_id" /> <field name="partner_id" />
</tree> </tree>
</field> </field>
</record> </record>
<record model="ir.actions.act_window" id="action_window_equipment"> <record model="ir.actions.act_window" id="action_window_equipment">
<field name="name">Equipment</field> <field name="name">Equipment</field>
<field name="res_model">bemade_fsm.equipment</field> <field name="res_model">fsm.equipment</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
</record> </record>
<record model="ir.actions.act_window" id="action_window_equipment_tags"> <record model="ir.actions.act_window" id="action_window_equipment_tags">
<field name="name">Equipment Tag</field> <field name="name">Equipment Tag</field>
<field name="res_model">bemade_fsm.equipment.tag</field> <field name="res_model">fsm.equipment.tag</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
</record> </record>
<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"
/>
</odoo> </odoo>

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="act_res_partner_2_equipment" model="ir.actions.act_window">
<field name="name">Partner Equipment</field>
<field name="res_model">fsm.equipment</field>
<field name="view_mode">tree,form</field>
<field name="context">{'default_partner_location_id': active_id}</field>
<field name="domain">[("partner_id", "=", active_id)]</field>
</record>
<record id="fsm_equipment_view_tree" model="ir.ui.view">
<field name="name">fsm.equipment.tree</field>
<field name="model">fsm.equipment</field>
<field name="arch" type="xml">
<tree>
<field name="code" />
<field name="name" />
<field name="location_notes" />
<button
name="action_view_equipment"
type="object"
string="Details"
icon="fa-external-link"
/>
</tree>
</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="equipment" string="Equipment">
<field
name="equipment_ids"
context="{'tree_view_ref': 'fsm_equipment.fsm_equipment_view_tree'}"
readonly="False"
/>
<field
name="owned_equipment_ids"
context="{'tree_view_ref': 'fsm_equipment.fsm_equipment_view_tree'}"
readonly="True"
/>
</page>
</xpath>
<div name="button_box" position="inside">
<button
class="oe_stat_button"
type="action"
name="%(fsm_equipment.act_res_partner_2_equipment)d"
icon="fa-tachometer"
>
<field
string="Equipments"
name="equipment_count"
widget="statinfo"
/>
</button>
</div>
</field>
</record>
</odoo>