Compare commits
6 commits
18.0
...
17.0-clien
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4eb57e349 | ||
|
|
bb9ad5afbe | ||
|
|
0a6f048708 | ||
|
|
87a1cb8860 | ||
|
|
c304035edd | ||
|
|
cf7a59ad98 |
38 changed files with 1056 additions and 113 deletions
|
|
@ -80,7 +80,7 @@
|
||||||
<tree position="attributes">
|
<tree position="attributes">
|
||||||
<attribute name="js_class">project_list</attribute>
|
<attribute name="js_class">project_list</attribute>
|
||||||
</tree>
|
</tree>
|
||||||
<field name="partner_id" position="after">
|
<field name="name" position="before">
|
||||||
<field name="work_order_number" optional="show" />
|
<field name="work_order_number" optional="show" />
|
||||||
</field>
|
</field>
|
||||||
<field name="company_id" position="attributes">
|
<field name="company_id" position="attributes">
|
||||||
|
|
@ -92,6 +92,12 @@
|
||||||
<field name="project_id" position="attributes">
|
<field name="project_id" position="attributes">
|
||||||
<attribute name="optional">hide</attribute>
|
<attribute name="optional">hide</attribute>
|
||||||
</field>
|
</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>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="industry_fsm.project_task_action_fsm_map" model="ir.actions.act_window">
|
<record id="industry_fsm.project_task_action_fsm_map" model="ir.actions.act_window">
|
||||||
|
|
|
||||||
|
|
@ -458,6 +458,7 @@ class CalendarEvent(models.Model):
|
||||||
partner = self.env["res.partner"].search(
|
partner = self.env["res.partner"].search(
|
||||||
[("email", "=", _extract_vcal_email(organizer))]
|
[("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
|
return partner[0] if partner else partner # partner[0] in case many matches
|
||||||
else:
|
else:
|
||||||
return self.env["res.partner"]
|
return self.env["res.partner"]
|
||||||
|
|
|
||||||
1
customer_applications/__init__.py
Normal file
1
customer_applications/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from . import models
|
||||||
40
customer_applications/__manifest__.py
Normal file
40
customer_applications/__manifest__.py
Normal 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,
|
||||||
|
}
|
||||||
43
customer_applications/data/menus_actions.xml
Normal file
43
customer_applications/data/menus_actions.xml
Normal 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>
|
||||||
5
customer_applications/models/__init__.py
Normal file
5
customer_applications/models/__init__.py
Normal 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
|
||||||
36
customer_applications/models/application.py
Normal file
36
customer_applications/models/application.py
Normal 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)
|
||||||
42
customer_applications/models/application_specification.py
Normal file
42
customer_applications/models/application_specification.py
Normal 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."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -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."),
|
||||||
|
]
|
||||||
54
customer_applications/models/application_type.py
Normal file
54
customer_applications/models/application_type.py
Normal 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)
|
||||||
42
customer_applications/models/res_partner.py
Normal file
42
customer_applications/models/res_partner.py
Normal 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)
|
||||||
14
customer_applications/security/groups.xml
Normal file
14
customer_applications/security/groups.xml
Normal 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>
|
||||||
7
customer_applications/security/ir.model.access.csv
Normal file
7
customer_applications/security/ir.model.access.csv
Normal 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
customer_applications/tests/__init__.py
Normal file
1
customer_applications/tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from . import test_application
|
||||||
59
customer_applications/tests/test_application.py
Normal file
59
customer_applications/tests/test_application.py
Normal 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)
|
||||||
74
customer_applications/views/application_type_views.xml
Normal file
74
customer_applications/views/application_type_views.xml
Normal 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>
|
||||||
86
customer_applications/views/application_views.xml
Normal file
86
customer_applications/views/application_views.xml
Normal 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>
|
||||||
45
customer_applications/views/res_partner_views.xml
Normal file
45
customer_applications/views/res_partner_views.xml
Normal 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>
|
||||||
|
|
@ -19,17 +19,18 @@
|
||||||
#
|
#
|
||||||
{
|
{
|
||||||
"name": "FSM Equipment",
|
"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",
|
"summary": "Add the notion of client equipment for Field Service",
|
||||||
"category": "Services/Field Service",
|
"category": "Services/Field Service",
|
||||||
"author": "Bemade Inc.",
|
"author": "Bemade Inc.",
|
||||||
"website": "http://www.bemade.org",
|
"website": "http://www.bemade.org",
|
||||||
"license": "LGPL-3",
|
"license": "LGPL-3",
|
||||||
"depends": ["industry_fsm", "account"],
|
"depends": ["industry_fsm", "account", "contacts", "incrementing_sequence_mixin"],
|
||||||
"data": [
|
"data": [
|
||||||
"security/ir.model.access.csv",
|
"security/ir.model.access.csv",
|
||||||
"views/equipment_views.xml",
|
"views/equipment_views.xml",
|
||||||
"views/res_partner_views.xml",
|
"views/res_partner_views.xml",
|
||||||
|
"views/project_task_views.xml",
|
||||||
],
|
],
|
||||||
"assets": {},
|
"assets": {},
|
||||||
"installable": True,
|
"installable": True,
|
||||||
|
|
|
||||||
35
fsm_equipment/migrations/17.0.0.2.0/post-migrate.py
Normal file
35
fsm_equipment/migrations/17.0.0.2.0/post-migrate.py
Normal 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
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
from . import equipment_tag
|
from . import equipment_tag
|
||||||
from . import equipment
|
from . import equipment
|
||||||
from . import res_partner
|
from . import res_partner
|
||||||
from . import equipment_component
|
|
||||||
from . import task
|
from . import task
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _
|
||||||
from odoo.osv import expression
|
from odoo.osv import expression
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
class Equipment(models.Model):
|
class Equipment(models.Model):
|
||||||
_name = "fsm.equipment"
|
_name = "fsm.equipment"
|
||||||
_description = "Partner-Owned 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(
|
code = fields.Char(
|
||||||
tracking=True,
|
tracking=True,
|
||||||
|
|
@ -26,7 +28,8 @@ class Equipment(models.Model):
|
||||||
comodel_name="res.partner",
|
comodel_name="res.partner",
|
||||||
string="Physical Address",
|
string="Physical Address",
|
||||||
tracking=True,
|
tracking=True,
|
||||||
ondelete="cascade",
|
ondelete="restrict",
|
||||||
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
location_notes = fields.Text(
|
location_notes = fields.Text(
|
||||||
|
|
@ -49,11 +52,52 @@ class Equipment(models.Model):
|
||||||
tracking=True,
|
tracking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
equipment_component_ids = fields.One2many(
|
parent_id = fields.Many2one(
|
||||||
"fsm.equipment.component",
|
"fsm.equipment",
|
||||||
inverse_name="equipment_id",
|
|
||||||
tracking=True,
|
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
|
@api.model
|
||||||
def name_search(self, name="", args=None, operator="ilike", limit=100):
|
def name_search(self, name="", args=None, operator="ilike", limit=100):
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -41,7 +41,7 @@ class Partner(models.Model):
|
||||||
def _compute_equipment_count(self):
|
def _compute_equipment_count(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
all_equipment_ids = self.env["fsm.equipment"].search(
|
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)
|
rec.equipment_count = len(all_equipment_ids)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
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_fsm_equipment,fsm_equipment,model_fsm_equipment,base.group_user,1,1,1,1
|
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_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
|
|
||||||
|
|
|
||||||
|
|
|
@ -9,49 +9,69 @@
|
||||||
<sheet>
|
<sheet>
|
||||||
<group>
|
<group>
|
||||||
<group name="left">
|
<group name="left">
|
||||||
<field name="code" />
|
<field name="code"/>
|
||||||
<field name="name" />
|
<field name="name"/>
|
||||||
<field name="description" />
|
<field name="parent_id" invisible="partner_id"/>
|
||||||
<field name="location_notes" />
|
<field name="description"/>
|
||||||
|
<field name="location_notes"/>
|
||||||
</group>
|
</group>
|
||||||
<group name="right">
|
<group name="right">
|
||||||
<field
|
<field
|
||||||
name="partner_id"
|
name="partner_id"
|
||||||
groups="account.group_delivery_invoice_address"
|
context="{
|
||||||
context="{
|
|
||||||
'default_type': 'delivery',
|
'default_type': 'delivery',
|
||||||
'show_address': 1
|
'show_address': 1
|
||||||
}"
|
}"
|
||||||
options='{"always_reload": True}'
|
options='{"always_reload": True}'
|
||||||
/>
|
invisible="parent_id"
|
||||||
|
required="not parent_id"
|
||||||
|
/>
|
||||||
<field
|
<field
|
||||||
name="tag_ids"
|
name="inherited_partner_id"
|
||||||
widget="many2many_tags"
|
context="{
|
||||||
options="{'no_open': False}"
|
'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>
|
||||||
</group>
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Components">
|
<page string="Components">
|
||||||
<field name="equipment_component_ids">
|
<field name="child_ids">
|
||||||
<tree editable="bottom">
|
<tree editable="bottom" open_form_view="True">
|
||||||
<field name="sequence" widget="handle" />
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="name" />
|
<field name="name"/>
|
||||||
<field name="product_id" />
|
<field name="description"/>
|
||||||
<field name="purpose_id" />
|
<field name="product_id"/>
|
||||||
<field name="note" />
|
<field name="tag_ids" widget="many2many_tags"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
<page string="Interventions">
|
<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>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
<div class="oe_chatter">
|
<div class="oe_chatter">
|
||||||
<field name="message_follower_ids" />
|
<field name="message_follower_ids"/>
|
||||||
<field name="activity_ids" />
|
<field name="activity_ids"/>
|
||||||
<field name="message_ids" />
|
<field name="message_ids"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
|
|
@ -62,15 +82,15 @@
|
||||||
<field name="model">fsm.equipment</field>
|
<field name="model">fsm.equipment</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree>
|
<tree>
|
||||||
<field name="code" />
|
<field name="code"/>
|
||||||
<field name="name" />
|
<field name="name"/>
|
||||||
<field name="description" />
|
<field name="description"/>
|
||||||
<field
|
<field
|
||||||
name="tag_ids"
|
name="tag_ids"
|
||||||
widget="many2many_tags"
|
widget="many2many_tags"
|
||||||
options="{'no_open': False}"
|
options="{'no_open': False}"
|
||||||
/>
|
/>
|
||||||
<field name="partner_id" />
|
<field name="partner_id"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
@ -80,21 +100,21 @@
|
||||||
<field name="model">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="code" />
|
<field name="code"/>
|
||||||
<field name="name" />
|
<field name="name"/>
|
||||||
<field name="description" />
|
<field name="description"/>
|
||||||
<field
|
<field
|
||||||
name="tag_ids"
|
name="tag_ids"
|
||||||
widget="many2many_tags"
|
widget="many2many_tags"
|
||||||
options="{'no_open': False}"
|
options="{'no_open': False}"
|
||||||
/>
|
/>
|
||||||
<field name="partner_id" />
|
<field name="partner_id"/>
|
||||||
<button
|
<button
|
||||||
name="action_view_equipment"
|
name="action_view_equipment"
|
||||||
type="object"
|
type="object"
|
||||||
string="Details"
|
string="Details"
|
||||||
icon="fa-external-link"
|
icon="fa-external-link"
|
||||||
/>
|
/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
@ -103,34 +123,69 @@
|
||||||
<field name="name">Equipment</field>
|
<field name="name">Equipment</field>
|
||||||
<field name="res_model">fsm.equipment</field>
|
<field name="res_model">fsm.equipment</field>
|
||||||
<field name="view_mode">tree,form</field>
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="context">
|
||||||
|
{'search_default_parent_only': True}
|
||||||
|
</field>
|
||||||
</record>
|
</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">
|
<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">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
|
<menuitem
|
||||||
id="menu_service_client"
|
id="menu_contact_equipment"
|
||||||
name="Clients"
|
name="Customer Equipment"
|
||||||
sequence="10"
|
action="action_window_equipment"
|
||||||
parent="industry_fsm.fsm_menu_root"
|
sequence="3"
|
||||||
groups="industry_fsm.group_fsm_user"
|
parent="contacts.menu_contacts"/>
|
||||||
/>
|
|
||||||
<menuitem
|
<menuitem
|
||||||
id="menu_service_client_clients"
|
id="menu_service_client"
|
||||||
name="Clients"
|
name="Clients"
|
||||||
action="base.action_partner_customer_form"
|
sequence="10"
|
||||||
sequence="20"
|
parent="industry_fsm.fsm_menu_root"
|
||||||
parent="menu_service_client"
|
groups="industry_fsm.group_fsm_user"
|
||||||
groups="industry_fsm.group_fsm_user"
|
/>
|
||||||
/>
|
|
||||||
<menuitem
|
<menuitem
|
||||||
id="menu_service_client_equipment"
|
id="menu_service_client_clients"
|
||||||
name="Client Equipment"
|
name="Clients"
|
||||||
action="action_window_equipment"
|
action="base.action_partner_customer_form"
|
||||||
sequence="21"
|
sequence="20"
|
||||||
parent="menu_service_client"
|
parent="menu_service_client"
|
||||||
groups="industry_fsm.group_fsm_user"
|
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>
|
||||||
|
|
|
||||||
14
fsm_equipment/views/project_task_views.xml
Normal file
14
fsm_equipment/views/project_task_views.xml
Normal 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>
|
||||||
1
incrementing_sequence_mixin/__init__.py
Normal file
1
incrementing_sequence_mixin/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from . import models
|
||||||
45
incrementing_sequence_mixin/__manifest__.py
Normal file
45
incrementing_sequence_mixin/__manifest__.py
Normal 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,
|
||||||
|
}
|
||||||
1
incrementing_sequence_mixin/models/__init__.py
Normal file
1
incrementing_sequence_mixin/models/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from . import incrementing_sequence_mixin
|
||||||
|
|
@ -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
|
||||||
1
partner_equipment_applications/__init__.py
Normal file
1
partner_equipment_applications/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from . import models
|
||||||
36
partner_equipment_applications/__manifest__.py
Normal file
36
partner_equipment_applications/__manifest__.py
Normal 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,
|
||||||
|
}
|
||||||
2
partner_equipment_applications/models/__init__.py
Normal file
2
partner_equipment_applications/models/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
from . import application
|
||||||
|
from . import equipment
|
||||||
29
partner_equipment_applications/models/application.py
Normal file
29
partner_equipment_applications/models/application.py
Normal 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)
|
||||||
17
partner_equipment_applications/models/equipment.py
Normal file
17
partner_equipment_applications/models/equipment.py
Normal 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,
|
||||||
|
)
|
||||||
39
partner_equipment_applications/views/application_views.xml
Normal file
39
partner_equipment_applications/views/application_views.xml
Normal 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>
|
||||||
42
partner_equipment_applications/views/equipment_views.xml
Normal file
42
partner_equipment_applications/views/equipment_views.xml
Normal 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>
|
||||||
Loading…
Reference in a new issue