[ADD]: rework of fsm_equipment, add client_applications and glue module

This commit is contained in:
Marc Durepos 2024-10-25 10:44:35 -04:00
parent 4baef176fb
commit 87d7cb5b28
38 changed files with 1056 additions and 113 deletions

View file

@ -80,7 +80,7 @@
<tree position="attributes">
<attribute name="js_class">project_list</attribute>
</tree>
<field name="partner_id" position="after">
<field name="name" position="before">
<field name="work_order_number" optional="show" />
</field>
<field name="company_id" position="attributes">
@ -92,6 +92,12 @@
<field name="project_id" position="attributes">
<attribute name="optional">hide</attribute>
</field>
<field name="progress" position="attributes">
<attribute name="optional">hide</attribute>
</field>
<field name="activity_ids" position="attributes">
<attribute name="optional">hide</attribute>
</field>
</field>
</record>
<record id="industry_fsm.project_task_action_fsm_map" model="ir.actions.act_window">

View file

@ -458,6 +458,7 @@ class CalendarEvent(models.Model):
partner = self.env["res.partner"].search(
[("email", "=", _extract_vcal_email(organizer))]
)
# TODO: prioritize partner with a user if there is one
return partner[0] if partner else partner # partner[0] in case many matches
else:
return self.env["res.partner"]

View file

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

View file

@ -0,0 +1,40 @@
#
# 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": "Customer Applications",
"version": "17.0.1.0.0",
"summary": "Adds the notion of applications to partners.",
"category": "Contacts",
"author": "Bemade Inc.",
"website": "http://www.bemade.org",
"license": "LGPL-3",
"depends": ["contacts", "incrementing_sequence_mixin"],
"data": [
"security/groups.xml",
"security/ir.model.access.csv",
"data/menus_actions.xml",
"views/application_type_views.xml",
"views/res_partner_views.xml",
"views/application_views.xml",
],
"assets": {},
"installable": True,
"auto_install": False,
}

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_application_types" model="ir.actions.act_window">
<field name="name">Application Types</field>
<field name="res_model">partner.application.type</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No application types found. Create one?
</p>
</field>
</record>
<record id="partner_related_applications_action" model="ir.actions.act_window">
<field name="name">Partner Applications</field>
<field name="res_model">partner.application</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('partner_id', '=', context.get('partner_id'))]</field>
<field name="context">{'default_partner_id': context.get('partner_id')}</field>
</record>
<record id="action_view_partner_applications_by_type" model="ir.actions.act_window">
<field name="name">Partner Applications by Type</field>
<field name="res_model">partner.application</field>
<field name="view_mode">tree,form</field>
<field name="domain">[
('application_type_id', '=', context.get('application_type_id'))
]
</field>
<field name="context">{
'default_application_type_id': context.get('application_type_id')
}
</field>
</record>
<menuitem id="application_types"
parent="contacts.res_partner_menu_config"
groups="group_applications_admin"
action="view_application_types"
/>
<record id="contacts.res_partner_menu_config" model="ir.ui.menu">
<field name="sequence" eval="99"/>
</record>
</odoo>

View file

@ -0,0 +1,5 @@
from . import application
from . import application_specification
from . import application_type
from . import res_partner
from . import application_specification_key

View file

@ -0,0 +1,36 @@
from odoo import models, fields, Command
class Application(models.Model):
_name = "partner.application"
_description = "Partner Application"
_inherit = ["mail.thread", "mail.activity.mixin"]
name = fields.Char(tracking=1)
description = fields.Text(tracking=2)
partner_id = fields.Many2one(
comodel_name="res.partner",
required=True,
tracking=3,
copy=False,
)
application_type_id = fields.Many2one(
comodel_name="partner.application.type",
required=True,
tracking=4,
ondelete="restrict",
)
specification_ids = fields.One2many(
comodel_name="partner.application.specification",
inverse_name="application_id",
tracking=5,
)
def copy(self, default=None):
self.ensure_one() # This logic won't work for batches, and it doesn't need to
default = default or {}
if "specification_ids" not in default:
default["specification_ids"] = [
Command.create(line.copy_data()[0]) for line in self.specification_ids
]
return super().copy(default)

View file

@ -0,0 +1,42 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class PartnerApplicationSpecification(models.Model):
_name = "partner.application.specification"
_description = "Partner Application Specification"
_inherit = ["mail.thread", "mail.activity.mixin", "incrementing.sequence.mixin"]
_sequence_group = "application_id"
key_id = fields.Many2one(
comodel_name="partner.application.specification.key",
tracking=1,
ondelete="restrict",
domain="[('id', 'in', allowed_specification_keys)]",
string="Specification Name",
required=True,
)
name = fields.Char(
related="key_id.name",
)
value = fields.Text(
tracking=2,
)
application_id = fields.Many2one(
comodel_name="partner.application",
tracking=1,
ondelete="cascade",
)
allowed_specification_keys = fields.Many2many(
related="application_id.application_type_id.allowed_specification_keys",
)
@api.constrains("key_id")
def _constrain_key_id(self):
for rec in self:
if rec.key_id not in rec.allowed_specification_keys:
raise ValidationError(
_(
f"Key '{rec.key_id.name}' is not allowed for this application type."
)
)

View file

@ -0,0 +1,11 @@
from odoo import models, fields, api
class ApplicationSpecificationKey(models.Model):
_name = "partner.application.specification.key"
_description = "Application Specification Key"
name = fields.Char(required=True, index="trigram")
_sql_constraints = [
("name_uniq", "unique (name)", "Specification key name must be unique."),
]

View file

@ -0,0 +1,54 @@
from odoo import models, fields, api
class PartnerApplicationType(models.Model):
_name = "partner.application.type"
_description = "Partner Application Type"
_inherit = ["mail.thread", "mail.activity.mixin"]
color = fields.Integer()
name = fields.Char(
required=True,
tracking=1,
)
description = fields.Text(tracking=2)
application_ids = fields.One2many(
comodel_name="partner.application",
inverse_name="application_type_id",
tracking=3,
)
applications_count = fields.Integer(
string="Applications Count",
compute="_compute_applications_count",
)
partner_ids = fields.One2many(
comodel_name="res.partner",
compute="_compute_partner_ids",
search="_search_partner_ids",
string="Partners",
readonly=True,
)
allowed_specification_keys = fields.Many2many(
comodel_name="partner.application.specification.key",
relation="application_specification_key_application_type_rel",
column1="application_type_id",
column2="application_specification_key_id",
)
@api.depends("application_ids", "application_ids.partner_id")
def _compute_partner_ids(self):
for application_type in self:
application_type.partner_ids = application_type.application_ids.mapped(
"partner_id"
)
def _search_partner_ids(self, operator, value):
return [("application_ids.partner_id", operator, value)]
@api.depends("application_ids")
def _compute_applications_count(self):
for record in self:
record.applications_count = len(record.application_ids)

View file

@ -0,0 +1,42 @@
from odoo import models, fields, api
class ResPartner(models.Model):
_inherit = "res.partner"
application_ids = fields.One2many(
"partner.application",
"partner_id",
string="Applications",
)
applications_count = fields.Integer(
string="Applications Count",
compute="_compute_applications_count",
)
application_type_ids = fields.One2many(
comodel_name="partner.application.type",
compute="_compute_application_type_ids",
string="Application Types",
readonly=True,
search="_search_application_type_ids",
)
@api.depends("application_ids.application_type_id")
def _compute_application_type_ids(self):
for partner in self:
partner.application_type_ids = partner.application_ids.mapped(
"application_type_id"
)
def _search_application_type_ids(self, operator, value):
return [("application_ids.application_type_id", operator, value)]
@api.depends("application_ids")
def _compute_applications_count(self):
for partner in self:
partner.applications_count = len(partner.application_ids)
@api.depends("application_ids")
def _compute_applications_count(self):
for rec in self:
rec.applications_count = len(rec.application_ids)

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="group_applications_user" model="res.groups">
<field name="name">Applications User</field>
</record>
<record id="group_applications_admin" model="res.groups">
<field name="name">Applications Admin</field>
</record>
<record id="base.group_user" model="res.groups">
<field name="implied_ids" eval="[(4, ref('group_applications_user'))]"/>
</record>
</data>
</odoo>

View file

@ -0,0 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_partner_application_user,access.partner.application.user,model_partner_application,group_applications_user,1,1,1,1
access_partner_application_type_user,access.partner.application.type.user,model_partner_application_type,group_applications_user,1,0,0,0
access_partner_application_type_manager,access.partner.application.type.manager,model_partner_application_type,group_applications_admin,1,1,1,1
access_partner_application_specification_user,access.partner.application.specification.user,model_partner_application_specification,group_applications_user,1,1,1,1
access_partner_application_specification_key_user,access.partner.application.specification.key.user,model_partner_application_specification_key,group_applications_user,1,0,0,0
access_partner_application_specification_key_admin,access.partner.application.specification.key.admin,model_partner_application_specification_key,group_applications_admin,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_partner_application_user access.partner.application.user model_partner_application group_applications_user 1 1 1 1
3 access_partner_application_type_user access.partner.application.type.user model_partner_application_type group_applications_user 1 0 0 0
4 access_partner_application_type_manager access.partner.application.type.manager model_partner_application_type group_applications_admin 1 1 1 1
5 access_partner_application_specification_user access.partner.application.specification.user model_partner_application_specification group_applications_user 1 1 1 1
6 access_partner_application_specification_key_user access.partner.application.specification.key.user model_partner_application_specification_key group_applications_user 1 0 0 0
7 access_partner_application_specification_key_admin access.partner.application.specification.key.admin model_partner_application_specification_key group_applications_admin 1 1 1 1

View file

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

View file

@ -0,0 +1,59 @@
from odoo.tests import TransactionCase
class TestApplication(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner_1, cls.partner_2 = cls.env["res.partner"].create(
[
{
"name": "Test Partner",
},
{
"name": "Test Partner 2",
},
]
)
cls.application_type = cls.env["partner.application.type"].create(
{
"name": "application type",
}
)
def test_copy_correctly_creates_specification_lines(self):
application = self.env["partner.application"].create(
{
"partner_id": self.partner_1.id,
"application_type_id": self.application_type.id,
}
)
specifications = self.env["partner.application.specification"].create(
[
{
"name": "Spec 1",
"value": "Spec 1 value",
"application_id": application.id,
},
{
"name": "Spec 2",
"value": "Spec 2 value",
"application_id": application.id,
},
]
)
application_copy = application.copy(
default={
"partner_id": self.partner_2.id,
}
)
self.assertEqual(len(application_copy.specification_ids), 2)
self.assertEqual(len(application.specification_ids), 2)
self.assertNotEqual(
application.specification_ids, application_copy.specification_ids
)
# TODO: move this to a test in the mixin module once we figure out how
# to dynamically create and load models
self.assertEqual(specifications[0].sequence, 1)
self.assertEqual(specifications[1].sequence, 2)

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_partner_application_type_list" model="ir.ui.view">
<field name="name">partner.application.type.list</field>
<field name="model">partner.application.type</field>
<field name="arch" type="xml">
<tree multi_edit="True">
<field name="name"/>
<field name="description"/>
<field name="color" widget="color_picker"/>
</tree>
</field>
</record>
<record id="view_partner_application_type_form" model="ir.ui.view">
<field name="name">partner.application.type.form</field>
<field name="model">partner.application.type</field>
<field name="arch" type="xml">
<form>
<header/>
<sheet>
<div name="button_box" class="oe_button_box">
<button name="%(action_view_partner_applications_by_type)d"
type="action"
class="oe_stat_button"
context="{'application_type_id': id}"
icon="fa-gears"
>
<field name="applications_count" string="Applications"
widget="statinfo"/>
</button>
</div>
<div name="title" class="oe_title">
<div class="oe_title">
<div class="oe_edit_only">
Application Type
</div>
<h1>
<field name="name"/>
</h1>
</div>
</div>
<group>
<field name="description"/>
<field name="color" widget="color_picker"/>
</group>
<notebook>
<page string="Allowed Specifications"
groups="customer_applications.group_applications_admin">
<field name="allowed_specification_keys" widget="many2many_tags"/>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
<field name="activity_ids"/>
</div>
</form>
</field>
</record>
<record id="view_partner_application_type_search" model="ir.ui.view">
<field name="name">partner.application.type.search</field>
<field name="model">partner.application.type</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="description"/>
<field name="partner_ids"/>
</search>
</field>
</record>
</odoo>

View file

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_partner_application_form" model="ir.ui.view">
<field name="name">partner.application.form</field>
<field name="model">partner.application</field>
<field name="arch" type="xml">
<form string="Partner Application">
<header/>
<sheet>
<div name="button_box" class="oe_button_box"/>
<div name="title" class="oe_title">
<div class="oe_title">
<div class="oe_edit_only">
Application
</div>
<h1>
<field name="name"/>
</h1>
</div>
</div>
<group>
<group>
<field name="partner_id"/>
</group>
<group>
<field name="application_type_id"/>
<field name="description"/>
</group>
</group>
<notebook>
<page string="Specifications">
<field name="specification_ids">
<tree editable="bottom">
<field name="allowed_specification_keys" column_invisible="1"/>
<field name="sequence" widget="handle"/>
<field name="key_id" />
<field name="value"/>
</tree>
</field>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids"/>
<field name="message_follower_ids"/>
<field name="activity_ids"/>
</div>
</form>
</field>
</record>
<record id="view_partner_application_tree" model="ir.ui.view">
<field name="name">partner.application.tree</field>
<field name="model">partner.application</field>
<field name="arch" type="xml">
<tree string="Partner Applications">
<field name="name"/>
<field name="partner_id"/>
<field name="application_type_id"/>
</tree>
</field>
</record>
<record id="view_partner_application_search" model="ir.ui.view">
<field name="name">partner.application.search</field>
<field name="model">partner.application</field>
<field name="arch" type="xml">
<search string="Partner Applications">
<field name="name"/>
<field name="partner_id"/>
<field name="application_type_id"/>
</search>
</field>
</record>
<record id="partner_application_action" model="ir.actions.act_window">
<field name="name">Partner Applications</field>
<field name="res_model">partner.application</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_partner_application_tree"/>
</record>
<menuitem id="menu_partner_application" name="Applications"
parent="contacts.menu_contacts"
action="partner_application_action"/>
</odoo>

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_partner_form" model="ir.ui.view">
<field name="name">view.partner.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="%(partner_related_applications_action)d"
type="action"
class="oe_stat_button"
icon="fa-cogs"
context="{'partner_id': id}"
>
<field string="Applications" name="applications_count"
widget="statinfo"/>
</button>
</div>
</field>
</record>
<record id="view_partner_tree" model="ir.ui.view">
<field name="name">view.partner.tree</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='display_name']" position="after">
<field name="application_type_ids" widget="many2many_tags"
optional="show"/>
</xpath>
</field>
</record>
<record id="view_partner_search" model="ir.ui.view">
<field name="name">view.partner.search</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
<field name="user_id" position="after">
<field name="application_type_ids" string="Application Type"/>
</field>
</field>
</record>
</odoo>

View file

@ -19,17 +19,18 @@
#
{
"name": "FSM Equipment",
"version": "17.0.0.1.2",
"version": "17.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"],
"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,

View file

@ -0,0 +1,35 @@
from odoo import SUPERUSER_ID, api, Command
from odoo.tools.sql import SQL
def migrate(cr, version):
sql = "select * from fsm_equipment_component"
cr.execute(SQL(sql))
components = cr.dictfetchall()
sql = "select * from fsm_equipment_component_purpose"
cr.execute(SQL(sql))
purposes = cr.dictfetchall()
env = api.Environment(cr, SUPERUSER_ID, {})
tags = env["fsm.equipment.tag"].create(
[{"name": purpose["name"]} for purpose in purposes]
)
purpose_dict = {
purpose["id"]: tags.filtered(lambda tag: tag.name == purpose["name"]).id
for purpose in purposes
}
env["fsm.equipment"].create(
[
{
"name": component["name"],
"sequence": component["sequence"],
"tag_ids": [Command.link(purpose_dict[component["purpose_id"]])],
"parent_id": component["equipment_id"],
"description": component["note"],
"product_id": component["product_id"],
}
for component in components
]
)

View file

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

View file

@ -1,11 +1,13 @@
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"]
_inherit = ["mail.thread", "mail.activity.mixin", "incrementing.sequence.mixin"]
_sequence_group = "parent_id"
code = fields.Char(
tracking=True,
@ -26,7 +28,8 @@ class Equipment(models.Model):
comodel_name="res.partner",
string="Physical Address",
tracking=True,
ondelete="cascade",
ondelete="restrict",
required=False,
)
location_notes = fields.Text(
@ -49,11 +52,52 @@ class Equipment(models.Model):
tracking=True,
)
equipment_component_ids = fields.One2many(
"fsm.equipment.component",
inverse_name="equipment_id",
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):

View file

@ -1,34 +0,0 @@
from odoo import models, fields, api
class EquipmentComponent(models.Model):
_name = "fsm.equipment.component"
_description = "Equipment Component"
sequence = fields.Integer()
name = fields.Char()
product_id = fields.Many2one(
"product.product",
ondelete="cascade",
)
purpose_id = fields.Many2one(
"fsm.equipment.component.purpose",
ondelete="restrict",
)
equipment_id = fields.Many2one(
"fsm.equipment",
ondelete="cascade",
)
note = fields.Text()
@api.onchange("product_id")
def onchange_product_id(self):
for rec in self:
rec.name = rec.product_id.display_name
class EquipmentComponentPurpose(models.Model):
_name = "fsm.equipment.component.purpose"
_description = "Component Purpose"
name = fields.Char(translate=True)

View file

@ -41,7 +41,7 @@ class Partner(models.Model):
def _compute_equipment_count(self):
for rec in self:
all_equipment_ids = self.env["fsm.equipment"].search(
[("partner_id", "=", rec.id)]
[("partner_id", "=", rec.id), ("parent_id", "=", False)]
)
rec.equipment_count = len(all_equipment_ids)

View file

@ -1,6 +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
access_fsm_equipment_component,fsm_equipment_component,model_fsm_equipment_component,industry_fsm.group_fsm_user,1,1,1,1
access_fsm_equipment_component_purpose_user,access_fsm_equipment_component_purpose_user,model_fsm_equipment_component_purpose,industry_fsm.group_fsm_user,1,0,0,0
access_fsm_equipment_component_purpose_manager,access_fsm_equipment_component_purpose_manager,model_fsm_equipment_component_purpose,industry_fsm.group_fsm_manager,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
access_fsm_equipment_component fsm_equipment_component model_fsm_equipment_component industry_fsm.group_fsm_user 1 1 1 1
access_fsm_equipment_component_purpose_user access_fsm_equipment_component_purpose_user model_fsm_equipment_component_purpose industry_fsm.group_fsm_user 1 0 0 0
access_fsm_equipment_component_purpose_manager access_fsm_equipment_component_purpose_manager model_fsm_equipment_component_purpose industry_fsm.group_fsm_manager 1 1 1 1

View file

@ -9,68 +9,88 @@
<sheet>
<group>
<group name="left">
<field name="code" />
<field name="name" />
<field name="description" />
<field name="location_notes" />
<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"
groups="account.group_delivery_invoice_address"
context="{
name="partner_id"
context="{
'default_type': 'delivery',
'show_address': 1
}"
options='{"always_reload": True}'
/>
options='{"always_reload": True}'
invisible="parent_id"
required="not parent_id"
/>
<field
name="tag_ids"
widget="many2many_tags"
options="{'no_open': False}"
/>
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}"
/>
</group>
</group>
<notebook>
<page string="Components">
<field name="equipment_component_ids">
<tree editable="bottom">
<field name="sequence" widget="handle" />
<field name="name" />
<field name="product_id" />
<field name="purpose_id" />
<field name="note" />
<field name="child_ids">
<tree 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"/>
</tree>
</field>
</page>
<page string="Interventions">
<field name="task_ids" groupby="root_ancestor" />
<field name="task_ids" readonly="True">
<tree 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"/>
</tree>
</field>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" />
<field name="activity_ids" />
<field name="message_ids" />
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<record id="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="description" />
<field name="code"/>
<field name="name"/>
<field name="description"/>
<field
name="tag_ids"
widget="many2many_tags"
options="{'no_open': False}"
/>
<field name="partner_id" />
name="tag_ids"
widget="many2many_tags"
options="{'no_open': False}"
/>
<field name="partner_id"/>
</tree>
</field>
</record>
@ -80,21 +100,21 @@
<field name="model">fsm.equipment</field>
<field name="arch" type="xml">
<tree editable="bottom">
<field name="code" />
<field name="name" />
<field name="description" />
<field name="code"/>
<field name="name"/>
<field name="description"/>
<field
name="tag_ids"
widget="many2many_tags"
options="{'no_open': False}"
/>
<field name="partner_id" />
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"
/>
name="action_view_equipment"
type="object"
string="Details"
icon="fa-external-link"
/>
</tree>
</field>
</record>
@ -103,34 +123,69 @@
<field name="name">Equipment</field>
<field name="res_model">fsm.equipment</field>
<field name="view_mode">tree,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">tree,form</field>
</record>
<menuitem
id="menu_service_client"
name="Clients"
sequence="10"
parent="industry_fsm.fsm_menu_root"
groups="industry_fsm.group_fsm_user"
/>
id="menu_contact_equipment"
name="Customer Equipment"
action="action_window_equipment"
sequence="3"
parent="contacts.menu_contacts"/>
<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"
/>
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_equipment"
name="Client Equipment"
action="action_window_equipment"
sequence="21"
parent="menu_service_client"
groups="industry_fsm.group_fsm_user"
/>
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_tree_equipment" model="ir.ui.view">
<field name="name">project.task.view.tree.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 @@
from . import models

View file

@ -0,0 +1,45 @@
#
# 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": "Incrementing Sequence Mixin",
"version": "17.0.1.0.0",
"summary": "Adds an incrementing.sequence.mixin model to inherit from.",
"description": """
Adds an incrementing.sequence.mixin model to inherit from. This model adds a
sequence field to the models that inherit from it, including the logic to set it to
the highest current sequence number + 1 by default and to leave it otherwise
changeable by the user (i.e. with a drag and drop via the handle).
Specify the _sequence_group attribute on the model to indicate which field to use
to determine if records belong to the same group. For example, if one were to use
this on sale.order.line, they could specify "order_id" as the _sequence_group such
that newly created lines take on the highest sequence number of all lines on the
same sales order.
""",
"category": "",
"author": "Bemade Inc.",
"website": "http://www.bemade.org",
"license": "LGPL-3",
"depends": [],
"data": [],
"assets": {},
"installable": True,
"auto_install": False,
}

View file

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

View file

@ -0,0 +1,52 @@
from odoo import models, fields, api
class IncrementingSequenceMixin(models.AbstractModel):
_name = "incrementing.sequence.mixin"
_description = "Incrementing Sequence Mixin"
_order = "sequence, id asc"
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if "incrementing.sequence.mixin" in cls._inherit:
if not hasattr(cls, "_sequence_group"):
raise ValueError(
f"Model {cls._name} inheriting from incrementing.sequence.mixin must define a '_sequence_group' attribute."
)
sequence = fields.Integer()
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
for rec in res:
if rec.sequence == 0:
group_field = rec._sequence_group
group_field_data = getattr(rec, group_field)
if hasattr(group_field_data, "id"):
group_field_data = group_field_data.id
group = self.env[rec._name].search(
[(group_field, "=", group_field_data)]
)
else:
group = None
max_seq = max(group.mapped("sequence")) if group else 0
rec.sequence = max_seq + 1
return res
def _default_sequence(self):
group_field = self._sequence_group
group_field_data = getattr(self, group_field)
if hasattr(group_field_data, "id"):
group_field_data = group_field_data.id
group = self.env[self._name].search([(group_field, "=", group_field_data)])
else:
group = None
max_seq = max(group.mapped("sequence")) if group else 0
# Don't recalculate if already set
for rec in self.filtered(lambda r: r.sequence == 0):
max_seq += 1
rec.sequence = max_seq
def _inverse_sequence(self):
pass

View file

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

View file

@ -0,0 +1,36 @@
#
# 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": "Partner Application Equipment",
"version": "17.0.1.0.0",
"summary": "Make the link between customer_applications and fsm_equipment.",
"category": "Services/Field Service",
"author": "Bemade Inc.",
"website": "http://www.bemade.org",
"license": "LGPL-3",
"depends": ["fsm_equipment", "customer_applications"],
"data": [
"views/equipment_views.xml",
"views/application_views.xml",
],
"assets": {},
"installable": True,
"auto_install": True,
}

View file

@ -0,0 +1,2 @@
from . import application
from . import equipment

View file

@ -0,0 +1,29 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class PartnerApplication(models.Model):
_inherit = "partner.application"
equipment_ids = fields.One2many(
comodel_name="fsm.equipment",
inverse_name="application_id",
string="Equipment",
)
equipment_count = fields.Integer(
compute="_compute_equipment_count",
string="Equipment Count",
)
@api.constrains("equipment_ids")
def _check_equipment_ids(self):
for application in self:
if len(application.equipment_ids.partner_id) > 1:
raise ValidationError(
_("An application can only be linked to one partner.")
)
@api.depends("equipment_ids")
def _compute_equipment_count(self):
for application in self:
application.equipment_count = len(application.equipment_ids)

View file

@ -0,0 +1,17 @@
from odoo import models, fields
class Equipment(models.Model):
_inherit = "fsm.equipment"
application_id = fields.Many2one(
comodel_name="partner.application",
tracking=1,
domain="[('partner_id', '=', partner_id)]",
ondelete="restrict",
)
application_type_id = fields.Many2one(
comodel_name="partner.application.type",
related="application_id.application_type_id",
readonly=True,
)

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="action_view_application_equipment" model="ir.actions.act_window">
<field name="name">Partner Equipments</field>
<field name="res_model">fsm.equipment</field>
<field name="view_mode">tree,form</field>
<field name="context">{'default_application_id': context.get("application_id")}</field>
<field name="domain">[('application_id', '=', context.get("application_id"))]</field>
</record>
<record id="view_partner_application_search" model="ir.ui.view">
<field name="name">partner.application.search</field>
<field name="model">partner.application</field>
<field name="inherit_id" ref="customer_applications.view_partner_application_search"/>
<field name="arch" type="xml">
<field name="application_type_id" position="after">
<field name="equipment_ids"/>
</field>
</field>
</record>
<record id="view_partner_application_form" model="ir.ui.view">
<field name="name">partner.application.form</field>
<field name="model">partner.application</field>
<field name="inherit_id" ref="customer_applications.view_partner_application_form"/>
<field name="arch" type="xml">
<div name="button_box">
<button name="%(action_view_application_equipment)d"
type="action"
class="oe_stat_button"
icon="fa-tachometer"
context="{'application_id': id}"
>
<field string="Equipments" name="equipment_count" widget="statinfo"/>
</button>
</div>
</field>
</record>
</odoo>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_search_fsm_equipment" model="ir.ui.view">
<field name="name">fsm.equipment.view.search</field>
<field name="model">fsm.equipment</field>
<field name="inherit_id" ref="fsm_equipment.equipment_view_search"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="application_id"/>
<field name="application_type_id"/>
<filter name="groupby_application_id" string="Application"
context="{'group_by':'application_id'}"/>
<filter name="groupby_application_type_id"
string="Application Type"
context="{'group_by':'application_type_id'}"/>
</field>
</field>
</record>
<record id="view_tree_fsm_equipment" model="ir.ui.view">
<field name="name">fsm.equipment.view.tree</field>
<field name="model">fsm.equipment</field>
<field name="inherit_id" ref="fsm_equipment.equipment_view_tree"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="application_id" optional="show"/>
<field name="application_type_id" optional="show"/>
</field>
</field>
</record>
<record id="view_form_fsm_equipment" model="ir.ui.view">
<field name="name">fsm.equipment.view.form</field>
<field name="model">fsm.equipment</field>
<field name="inherit_id" ref="fsm_equipment.equipment_view_form"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="application_id"/>
</field>
</field>
</record>
</odoo>