[MIG] fsm_equipment to 18.0

This commit is contained in:
Marc Durepos 2025-09-09 11:38:26 -04:00
parent bb8c1f2f35
commit c7d0a1be2b
11 changed files with 537 additions and 0 deletions

View file

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

View file

@ -0,0 +1,38 @@
#
# 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": "18.0.0.2.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", "contacts", "incrementing_sequence_mixin"],
"data": [
"security/ir.model.access.csv",
"views/equipment_views.xml",
"views/res_partner_views.xml",
"views/project_task_views.xml",
],
"assets": {},
"installable": True,
"auto_install": False,
}

View file

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

View file

@ -0,0 +1,144 @@
from odoo import models, fields, api, _
from odoo.osv import expression
from odoo.exceptions import ValidationError
class Equipment(models.Model):
_name = "fsm.equipment"
_description = "Partner-Owned Equipment"
_inherit = ["mail.thread", "mail.activity.mixin", "incrementing.sequence.mixin"]
_sequence_group = "parent_id"
code = fields.Char(
tracking=True,
)
name = fields.Char(
tracking=True,
required=True,
)
tag_ids = fields.Many2many(
comodel_name="fsm.equipment.tag",
string="Tags",
)
description = fields.Text(tracking=True)
partner_id = fields.Many2one(
comodel_name="res.partner",
string="Physical Address",
tracking=True,
ondelete="restrict",
required=False,
)
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(
comodel_name="project.task",
relation="fsm_task_equipment_rel",
column1="equipment_id",
column2="task_id",
string="Interventions",
)
active = fields.Boolean(
default=True,
tracking=True,
)
parent_id = fields.Many2one(
"fsm.equipment",
tracking=True,
)
inherited_partner_id = fields.Many2one(
comodel_name="res.partner",
string="Main Equipment Location",
readonly=True,
compute="_compute_inherited_partner_id",
recursive=True,
)
child_ids = fields.One2many(
"fsm.equipment",
inverse_name="parent_id",
string="Components",
tracking=True,
)
product_id = fields.Many2one(
"product.product",
ondelete="restrict",
help="The product that represents this equipment, if any.",
)
@api.depends("parent_id", "parent_id.partner_id")
def _compute_inherited_partner_id(self):
for rec in self:
if not rec.parent_id:
rec.inherited_partner_id = False
continue
rec.inherited_partner_id = (
rec.parent_id.partner_id or rec.parent_id.inherited_partner_id
)
@api.constrains("product_id", "partner_id")
def _constrain_only_root_has_partner(self):
for rec in self:
if rec.partner_id and rec.parent_id:
raise ValidationError(
_("Only top-level (root) equipments can be linked to a partner.")
)
if not rec.parent_id and not rec.partner_id:
raise ValidationError(
_("Top-level (root) equipments must be linked to a partner.")
)
@api.model
def name_search(self, name="", args=None, operator="ilike", limit=100):
args = args or []
domain = expression.AND(
[
args,
[
"|",
"|",
("code", operator, name),
("name", operator, name),
("partner_id.name", operator, name),
],
]
)
if name:
equipments = self.search(
domain,
limit=limit,
)
else:
equipments = self.search(args, limit=limit)
return [(equipment.id, equipment.display_name) for equipment in equipments]
def action_view_equipment(self):
return {
"name": "Equipment",
"view_type": "form",
"view_mode": "form",
"res_id": self.id,
"context": self.env.context,
"res_model": "fsm.equipment",
"type": "ir.actions.act_window",
}
@api.depends("code", "name")
def _compute_display_name(self):
for rec in self:
tag_part = "[%s] " % rec.code if rec.code else ""
name = rec.name or ""
rec.display_name = tag_part + name

View file

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

View file

@ -0,0 +1,51 @@
from odoo import models, fields, api
from odoo.osv import expression
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.",
store=True,
compute_sudo=True,
)
@api.depends(
"equipment_ids",
"child_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), ("parent_id", "=", False)]
)
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,14 @@
from odoo import fields, models
class Task(models.Model):
_inherit = "project.task"
equipment_ids = fields.Many2many(
comodel_name="fsm.equipment",
relation="fsm_task_equipment_rel",
column1="task_id",
column2="equipment_id",
string="Equipment to Service",
tracking=True,
)

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

@ -0,0 +1,192 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- Equipment Form View -->
<record id="equipment_view_form" model="ir.ui.view">
<field name="name">fsm.equipment.form</field>
<field name="model">fsm.equipment</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group name="left">
<field name="code"/>
<field name="name"/>
<field name="parent_id" invisible="partner_id"/>
<field name="description"/>
<field name="location_notes"/>
</group>
<group name="right">
<field
name="partner_id"
context="{
'default_type': 'delivery',
'show_address': 1
}"
options='{"always_reload": True}'
invisible="parent_id"
required="not parent_id"
/>
<field
name="inherited_partner_id"
context="{
'default_type': 'delivery',
'show_address': 1
}"
options='{"always_reload": True}'
invisible="partner_id or parent_id"
/>
<field
name="tag_ids"
widget="many2many_tags"
options="{'no_open': False}"
/>
<field name="product_id"/>
</group>
</group>
<notebook>
<page string="Components">
<field name="child_ids">
<list editable="bottom" open_form_view="True">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="description"/>
<field name="product_id"/>
<field name="tag_ids" widget="many2many_tags"/>
</list>
</field>
</page>
<page string="Interventions">
<field name="task_ids" readonly="True">
<list open_form_view="True" default_order="date_deadline desc">
<field name="date_deadline" widget="date" string="Date"/>
<field name="name"/>
<field name="user_ids" widget="many2many_avatar_user"/>
<field name="description" class="text-truncate"/>
<field name="effective_hours" optional="show"/>
<field name="stage_id" optional="show"/>
</list>
</field>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<record id="equipment_view_list" model="ir.ui.view">
<field name="name">fsm.equipment.list</field>
<field name="model">fsm.equipment</field>
<field name="arch" type="xml">
<list>
<field name="code"/>
<field name="name"/>
<field name="description"/>
<field
name="tag_ids"
widget="many2many_tags"
options="{'no_open': False}"
/>
<field name="partner_id"/>
</list>
</field>
</record>
<record id="equipment_view_list_editable" model="ir.ui.view">
<field name="name">fsm.equipment.list.editable</field>
<field name="model">fsm.equipment</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="code"/>
<field name="name"/>
<field name="description"/>
<field
name="tag_ids"
widget="many2many_tags"
options="{'no_open': False}"
/>
<field name="partner_id"/>
<button
name="action_view_equipment"
type="object"
string="Details"
icon="fa-external-link"
/>
</list>
</field>
</record>
<record model="ir.actions.act_window" id="action_window_equipment">
<field name="name">Equipment</field>
<field name="res_model">fsm.equipment</field>
<field name="view_mode">list,form</field>
<field name="context">
{'search_default_parent_only': True}
</field>
</record>
<record id="equipment_view_search" model="ir.ui.view">
<field name="name">fsm.equipment.search</field>
<field name="model">fsm.equipment</field>
<field name="arch" type="xml">
<search>
<field name="parent_id"/>
<field name="code"/>
<field name="name"/>
<field name="description"/>
<field name="tag_ids"/>
<field name="partner_id"/>
<filter string="Parent Only" name="parent_only"
domain="[('parent_id', '=', False)]"/>
<filter string="Has Description" name="has_description"
domain="[('description', '!=', False)]"/>
<filter string="No Description" name="no_description"
domain="[('description', '=', False)]"/>
<filter string="Partner" name="groupby_partner"
context="{'group_by':'partner_id'}"/>
<filter string="Tags" name="groupby_tags"
context="{'group_by':'tag_ids'}"/>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="action_window_equipment_tags">
<field name="name">Equipment Tag</field>
<field name="res_model">fsm.equipment.tag</field>
<field name="view_mode">list,form</field>
</record>
<menuitem
id="menu_contact_equipment"
name="Customer Equipment"
action="action_window_equipment"
sequence="3"
parent="contacts.menu_contacts"/>
<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>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="project_task_view_list_equipment" model="ir.ui.view">
<field name="name">project.task.view.list.equipment</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="industry_fsm.project_task_view_list_fsm"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="equipment_ids" widget="many2many_tags"
string="Equipment" optional="show"/>
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,57 @@
<?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">list,form</field>
<field name="context">{'default_partner_location_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="equipment" string="Equipment">
<label for="equipment_ids" />
<field
name="equipment_ids"
context="{'list_view_ref': 'fsm_equipment.fsm_equipment_view_list_editable_no_partner'}"
readonly="False"
/>
<label for="owned_equipment_ids" />
<field
name="owned_equipment_ids"
context="{'list_view_ref': 'fsm_equipment.equipment_view_list'}"
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>
<record id="fsm_equipment_view_list_editable_no_partner" model="ir.ui.view">
<field name="name">fsm.equipment.view.list.editable.no.partner</field>
<field name="inherit_id" ref="fsm_equipment.equipment_view_list_editable" />
<field name="model">fsm.equipment</field>
<field name="arch" type="xml">
<field name="partner_id" position="attributes">
<attribute name="column_invisible">True</attribute>
</field>
</field>
</record>
</odoo>