[IMP] kpi: black, isort

This commit is contained in:
flachica
2020-06-04 14:12:11 +02:00
parent eb749005ec
commit 227e26d39d
17 changed files with 641 additions and 534 deletions

View File

@@ -8,20 +8,17 @@
"website": "http://www.savoirfairelinux.com",
"license": "AGPL-3",
"category": "Report",
"depends": [
'base_external_dbsource',
'web_widget_color',
],
"depends": ["base_external_dbsource", "web_widget_color",],
"data": [
'security/kpi_security.xml',
'security/ir.model.access.csv',
'views/kpi_category_views.xml',
'views/kpi_history_views.xml',
'views/kpi_threshold_range_views.xml',
'views/kpi_threshold_views.xml',
'views/kpi_views.xml',
'views/menu.xml',
'data/kpi_data.xml',
"security/kpi_security.xml",
"security/ir.model.access.csv",
"views/kpi_category_views.xml",
"views/kpi_history_views.xml",
"views/kpi_threshold_range_views.xml",
"views/kpi_threshold_views.xml",
"views/kpi_views.xml",
"views/menu.xml",
"data/kpi_data.xml",
],
"images": [
"images/kpi_definition.png",
@@ -29,5 +26,5 @@
"images/kpi_threshold.png",
"images/kpi_range.png",
],
'installable': True,
"installable": True,
}

View File

@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo noupdate="1">
<record forcecreate="True" id="ir_cron_kpi_action" model="ir.cron">
<field name="name">Update KPI values</field>
<field name="user_id" ref="base.user_root"/>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="model_id" ref="model_kpi"/>
<field name="doall" eval="False" />
<field name="model_id" ref="model_kpi" />
<field name="state">code</field>
<field name="code">model.update_kpi_value()</field>
</record>

View File

@@ -1,39 +1,46 @@
# Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo import fields, models, api
from odoo.tools.safe_eval import safe_eval
from odoo.tools import (
DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT,
)
import re
import logging
import re
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
def is_one_value(result):
# check if sql query returns only one value
if type(result) is dict and 'value' in result.dictfetchone():
if type(result) is dict and "value" in result.dictfetchone():
return True
elif type(result) is list and 'value' in result[0]:
elif type(result) is list and "value" in result[0]:
return True
else:
return False
RE_SELECT_QUERY = re.compile('.*(' + '|'.join((
'INSERT',
'UPDATE',
'DELETE',
'CREATE',
'ALTER',
'DROP',
'GRANT',
'REVOKE',
'INDEX',
)) + ')')
RE_SELECT_QUERY = re.compile(
".*("
+ "|".join(
(
"INSERT",
"UPDATE",
"DELETE",
"CREATE",
"ALTER",
"DROP",
"GRANT",
"REVOKE",
"INDEX",
)
)
+ ")"
)
def is_sql_or_ddl_statement(query):
@@ -47,70 +54,63 @@ class KPI(models.Model):
_name = "kpi"
_description = "Key Performance Indicator"
name = fields.Char('Name', required=True)
description = fields.Text('Description')
category_id = fields.Many2one(
'kpi.category',
'Category',
required=True,
)
threshold_id = fields.Many2one(
'kpi.threshold',
'Threshold',
required=True,
)
periodicity = fields.Integer('Periodicity', default=1)
name = fields.Char("Name", required=True)
description = fields.Text("Description")
category_id = fields.Many2one("kpi.category", "Category", required=True,)
threshold_id = fields.Many2one("kpi.threshold", "Threshold", required=True,)
periodicity = fields.Integer("Periodicity", default=1)
periodicity_uom = fields.Selection((
('minute', 'Minute'),
('hour', 'Hour'),
('day', 'Day'),
('week', 'Week'),
('month', 'Month')
), 'Periodicity UoM', required=True, default='day')
next_execution_date = fields.Datetime(
'Next execution date',
readonly=True,
periodicity_uom = fields.Selection(
(
("minute", "Minute"),
("hour", "Hour"),
("day", "Day"),
("week", "Week"),
("month", "Month"),
),
"Periodicity UoM",
required=True,
default="day",
)
value = fields.Float(string='Value',
compute="_compute_display_last_kpi_value",
)
color = fields.Text('Color', compute="_compute_display_last_kpi_value",)
next_execution_date = fields.Datetime("Next execution date", readonly=True,)
value = fields.Float(string="Value", compute="_compute_display_last_kpi_value",)
color = fields.Text("Color", compute="_compute_display_last_kpi_value",)
last_execution = fields.Datetime(
'Last execution', compute="_compute_display_last_kpi_value",)
kpi_type = fields.Selection((
('python', 'Python'),
('local', 'SQL - Local DB'),
('external', 'SQL - External DB')
), 'KPI Computation Type')
"Last execution", compute="_compute_display_last_kpi_value",
)
kpi_type = fields.Selection(
(
("python", "Python"),
("local", "SQL - Local DB"),
("external", "SQL - External DB"),
),
"KPI Computation Type",
)
dbsource_id = fields.Many2one(
'base.external.dbsource',
'External DB Source',
)
dbsource_id = fields.Many2one("base.external.dbsource", "External DB Source",)
kpi_code = fields.Text(
'KPI Code',
help=("SQL code must return the result as 'value' "
"(i.e. 'SELECT 5 AS value')."),
)
history_ids = fields.One2many(
'kpi.history',
'kpi_id',
'History',
"KPI Code",
help=(
"SQL code must return the result as 'value' " "(i.e. 'SELECT 5 AS value')."
),
)
history_ids = fields.One2many("kpi.history", "kpi_id", "History",)
active = fields.Boolean(
'Active',
help=("Only active KPIs will be updated by the scheduler based on"
" the periodicity configuration."), default=True
"Active",
help=(
"Only active KPIs will be updated by the scheduler based on"
" the periodicity configuration."
),
default=True,
)
company_id = fields.Many2one(
'res.company', 'Company',
default=lambda self: self.env.user.company_id.id)
"res.company", "Company", default=lambda self: self.env.user.company_id.id
)
@api.multi
def _compute_display_last_kpi_value(self):
history_obj = self.env['kpi.history']
history_obj = self.env["kpi.history"]
for obj in self:
history_ids = history_obj.search([("kpi_id", "=", obj.id)])
if history_ids:
@@ -120,7 +120,7 @@ class KPI(models.Model):
obj.last_execution = his.date
else:
obj.value = 0
obj.color = '#FFFFFF'
obj.color = "#FFFFFF"
obj.last_execution = False
@api.multi
@@ -128,51 +128,53 @@ class KPI(models.Model):
self.ensure_one()
kpi_value = 0
if self.kpi_code:
if self.kpi_type == 'local' and is_sql_or_ddl_statement(
self.kpi_code):
if self.kpi_type == "local" and is_sql_or_ddl_statement(self.kpi_code):
self.env.cr.execute(self.kpi_code)
dic = self.env.cr.dictfetchall()
if is_one_value(dic):
kpi_value = dic[0]['value']
elif (self.kpi_type == 'external' and self.dbsource_id.id and
is_sql_or_ddl_statement(self.kpi_code)):
kpi_value = dic[0]["value"]
elif (
self.kpi_type == "external"
and self.dbsource_id.id
and is_sql_or_ddl_statement(self.kpi_code)
):
dbsrc_obj = self.dbsource_id
res = dbsrc_obj.execute(self.kpi_code)
if is_one_value(res):
kpi_value = res[0]['value']
elif self.kpi_type == 'python':
kpi_value = safe_eval(self.kpi_code, {'self': self})
kpi_value = res[0]["value"]
elif self.kpi_type == "python":
kpi_value = safe_eval(self.kpi_code, {"self": self})
if isinstance(kpi_value, dict):
res = kpi_value
else:
threshold_obj = self.threshold_id
res = {
'value': kpi_value,
'color': threshold_obj.get_color(kpi_value),
"value": kpi_value,
"color": threshold_obj.get_color(kpi_value),
}
res.update({'kpi_id': self.id})
res.update({"kpi_id": self.id})
return res
@api.multi
def compute_kpi_value(self):
for obj in self:
history_vals = obj._get_kpi_value()
history_obj = self.env['kpi.history']
history_obj = self.env["kpi.history"]
history_obj.sudo().create(history_vals)
return True
@api.multi
def update_next_execution_date(self):
for obj in self:
if obj.periodicity_uom == 'hour':
if obj.periodicity_uom == "hour":
delta = relativedelta(hours=obj.periodicity)
elif obj.periodicity_uom == 'minute':
elif obj.periodicity_uom == "minute":
delta = relativedelta(minutes=obj.periodicity)
elif obj.periodicity_uom == 'day':
elif obj.periodicity_uom == "day":
delta = relativedelta(days=obj.periodicity)
elif obj.periodicity_uom == 'week':
elif obj.periodicity_uom == "week":
delta = relativedelta(weeks=obj.periodicity)
elif obj.periodicity_uom == 'month':
elif obj.periodicity_uom == "month":
delta = relativedelta(months=obj.periodicity)
else:
delta = relativedelta()
@@ -186,15 +188,14 @@ class KPI(models.Model):
@api.model
def update_kpi_value(self):
filters = [
'&',
'|',
('active', '=', True),
('next_execution_date', '<=', datetime.now().strftime(
DATETIME_FORMAT)),
('next_execution_date', '=', False),
"&",
"|",
("active", "=", True),
("next_execution_date", "<=", datetime.now().strftime(DATETIME_FORMAT)),
("next_execution_date", "=", False),
]
if 'filters' in self.env.context:
filters.extend(self.env.context['filters'])
if "filters" in self.env.context:
filters.extend(self.env.context["filters"])
obj_ids = self.search(filters)
res = None

View File

@@ -9,5 +9,5 @@ class KPICategory(models.Model):
_name = "kpi.category"
_description = "KPI Category"
name = fields.Char('Name', size=50, required=True)
description = fields.Text('Description')
name = fields.Char("Name", size=50, required=True)
description = fields.Text("Description")

View File

@@ -11,18 +11,16 @@ class KPIHistory(models.Model):
_description = "History of the KPI"
_order = "date desc"
name = fields.Char('Name', size=150, required=True,
default=fields.Datetime.now(),)
kpi_id = fields.Many2one('kpi', 'KPI', required=True)
name = fields.Char("Name", size=150, required=True, default=fields.Datetime.now(),)
kpi_id = fields.Many2one("kpi", "KPI", required=True)
date = fields.Datetime(
'Execution Date',
"Execution Date",
required=True,
readonly=True,
default=lambda r: fields.Datetime.now()
default=lambda r: fields.Datetime.now(),
)
value = fields.Float('Value', required=True, readonly=True)
color = fields.Text('Color', required=True,
readonly=True, default='#FFFFFF')
value = fields.Float("Value", required=True, readonly=True)
color = fields.Text("Color", required=True, readonly=True, default="#FFFFFF")
company_id = fields.Many2one(
'res.company', 'Company',
default=lambda self: self.env.user.company_id.id)
"res.company", "Company", default=lambda self: self.env.user.company_id.id
)

View File

@@ -1,7 +1,7 @@
# Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models, api, exceptions, _
from odoo import _, api, exceptions, fields, models
class KPIThreshold(models.Model):
@@ -20,9 +20,11 @@ class KPIThreshold(models.Model):
if not range1.valid:
obj.valid = False
break
for range2 in (obj.range_ids-range1):
if (range1.max_value >= range2.min_value and
range1.min_value <= range2.max_value):
for range2 in obj.range_ids - range1:
if (
range1.max_value >= range2.min_value
and range1.min_value <= range2.max_value
):
obj.valid = False
break
if obj.valid:
@@ -30,53 +32,64 @@ class KPIThreshold(models.Model):
else:
obj.invalid_message = (
"Some ranges are invalid or overlapping. "
"Please make sure your ranges do not overlap.")
"Please make sure your ranges do not overlap."
)
name = fields.Char('Name', size=50, required=True)
name = fields.Char("Name", size=50, required=True)
range_ids = fields.Many2many(
'kpi.threshold.range',
'kpi_threshold_range_rel',
'threshold_id',
'range_id',
'Ranges'
"kpi.threshold.range",
"kpi_threshold_range_rel",
"threshold_id",
"range_id",
"Ranges",
)
valid = fields.Boolean(string='Valid', required=True,
compute="_compute_is_valid_threshold", default=True)
invalid_message = fields.Char(string='Message', size=100,
compute="_compute_is_valid_threshold")
kpi_ids = fields.One2many('kpi', 'threshold_id', 'KPIs')
valid = fields.Boolean(
string="Valid",
required=True,
compute="_compute_is_valid_threshold",
default=True,
)
invalid_message = fields.Char(
string="Message", size=100, compute="_compute_is_valid_threshold"
)
kpi_ids = fields.One2many("kpi", "threshold_id", "KPIs")
company_id = fields.Many2one(
'res.company', 'Company',
default=lambda self: self.env.user.company_id.id)
"res.company", "Company", default=lambda self: self.env.user.company_id.id
)
@api.model
def create(self, data):
# check if ranges overlap
# TODO: This code can be done better
range_obj1 = self.env['kpi.threshold.range']
range_obj2 = self.env['kpi.threshold.range']
if data.get('range_ids'):
for range1 in data['range_ids'][0][2]:
range_obj1 = self.env["kpi.threshold.range"]
range_obj2 = self.env["kpi.threshold.range"]
if data.get("range_ids"):
for range1 in data["range_ids"][0][2]:
range_obj1 = range_obj1.browse(range1)
for range2 in data['range_ids'][0][2]:
for range2 in data["range_ids"][0][2]:
range_obj2 = range_obj2.browse(range2)
if (range_obj1.valid and range_obj2.valid and
range_obj1.min_value < range_obj2.min_value):
if (
range_obj1.valid
and range_obj2.valid
and range_obj1.min_value < range_obj2.min_value
):
if range_obj1.max_value > range_obj2.min_value:
raise exceptions.Warning(
_("Two of your ranges are overlapping."),
_("Make sure your ranges do not overlap!")
_("Make sure your ranges do not overlap!"),
)
range_obj2 = self.env['kpi.threshold.range']
range_obj1 = self.env['kpi.threshold.range']
range_obj2 = self.env["kpi.threshold.range"]
range_obj1 = self.env["kpi.threshold.range"]
return super(KPIThreshold, self).create(data)
@api.multi
def get_color(self, kpi_value):
color = '#FFFFFF'
color = "#FFFFFF"
for obj in self:
for range_obj in obj.range_ids:
if (range_obj.min_value <= kpi_value <= range_obj.max_value and
range_obj.valid):
if (
range_obj.min_value <= kpi_value <= range_obj.max_value
and range_obj.valid
):
color = range_obj.color
return color

View File

@@ -1,32 +1,39 @@
# Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models, api
from odoo.tools.safe_eval import safe_eval
import re
from odoo import api, fields, models
from odoo.tools.safe_eval import safe_eval
def is_one_value(result):
# check if sql query returns only one value
if type(result) is dict and 'value' in result.dictfetchone():
if type(result) is dict and "value" in result.dictfetchone():
return True
elif type(result) is list and 'value' in result[0]:
elif type(result) is list and "value" in result[0]:
return True
else:
return False
RE_SELECT_QUERY = re.compile('.*(' + '|'.join((
'INSERT',
'UPDATE',
'DELETE',
'CREATE',
'ALTER',
'DROP',
'GRANT',
'REVOKE',
'INDEX',
)) + ')')
RE_SELECT_QUERY = re.compile(
".*("
+ "|".join(
(
"INSERT",
"UPDATE",
"DELETE",
"CREATE",
"ALTER",
"DROP",
"GRANT",
"REVOKE",
"INDEX",
)
)
+ ")"
)
def is_sql_or_ddl_statement(query):
@@ -38,60 +45,64 @@ class KPIThresholdRange(models.Model):
"""
KPI Threshold Range
"""
_name = "kpi.threshold.range"
_description = "KPI Threshold Range"
name = fields.Char('Name', size=50, required=True)
valid = fields.Boolean(string='Valid', required=True,
compute="_compute_is_valid_range", default=True)
invalid_message = fields.Char(string='Message', size=100,
compute="_compute_is_valid_range")
min_type = fields.Selection((
('static', 'Fixed value'),
('python', 'Python Code'),
('local', 'SQL - Local DB'),
('external', 'SQL - Externa DB'),
), 'Min Type', required=True)
min_value = fields.Float(string='Minimum Value',
compute="_compute_min_value")
min_fixed_value = fields.Float('Minimum Fixed Value')
min_code = fields.Text('Minimum Computation Code')
min_error = fields.Char('Minimum Error', compute="_compute_min_value")
min_dbsource_id = fields.Many2one(
'base.external.dbsource',
'External DB Source Minimum',
name = fields.Char("Name", size=50, required=True)
valid = fields.Boolean(
string="Valid", required=True, compute="_compute_is_valid_range", default=True
)
max_type = fields.Selection((
('static', 'Fixed value'),
('python', 'Python Code'),
('local', 'SQL - Local DB'),
('external', 'SQL - External DB'),
), 'Max Type', required=True)
max_value = fields.Float(string='Maximum Value',
compute="_compute_max_value")
max_fixed_value = fields.Float('Maximum Fixed Value')
max_code = fields.Text('Maximum Computation Code')
max_error = fields.Char('Maximum Error', compute="_compute_max_value")
invalid_message = fields.Char(
string="Message", size=100, compute="_compute_is_valid_range"
)
min_type = fields.Selection(
(
("static", "Fixed value"),
("python", "Python Code"),
("local", "SQL - Local DB"),
("external", "SQL - Externa DB"),
),
"Min Type",
required=True,
)
min_value = fields.Float(string="Minimum Value", compute="_compute_min_value")
min_fixed_value = fields.Float("Minimum Fixed Value")
min_code = fields.Text("Minimum Computation Code")
min_error = fields.Char("Minimum Error", compute="_compute_min_value")
min_dbsource_id = fields.Many2one(
"base.external.dbsource", "External DB Source Minimum",
)
max_type = fields.Selection(
(
("static", "Fixed value"),
("python", "Python Code"),
("local", "SQL - Local DB"),
("external", "SQL - External DB"),
),
"Max Type",
required=True,
)
max_value = fields.Float(string="Maximum Value", compute="_compute_max_value")
max_fixed_value = fields.Float("Maximum Fixed Value")
max_code = fields.Text("Maximum Computation Code")
max_error = fields.Char("Maximum Error", compute="_compute_max_value")
max_dbsource_id = fields.Many2one(
'base.external.dbsource',
'External DB Source Maximum',
"base.external.dbsource", "External DB Source Maximum",
)
color = fields.Char(
string="Color",
help="Choose your color"
)
color = fields.Char(string="Color", help="Choose your color")
threshold_ids = fields.Many2many(
'kpi.threshold',
'kpi_threshold_range_rel',
'range_id',
'threshold_id',
'Thresholds',
"kpi.threshold",
"kpi_threshold_range_rel",
"range_id",
"threshold_id",
"Thresholds",
)
company_id = fields.Many2one(
'res.company', 'Company',
default=lambda self: self.env.user.company_id.id)
"res.company", "Company", default=lambda self: self.env.user.company_id.id
)
@api.multi
def _compute_min_value(self):
@@ -99,19 +110,21 @@ class KPIThresholdRange(models.Model):
value = None
error = None
try:
if obj.min_type == 'local' and is_sql_or_ddl_statement(
obj.min_code):
if obj.min_type == "local" and is_sql_or_ddl_statement(obj.min_code):
self.env.cr.execute(obj.min_code)
dic = self.env.cr.dictfetchall()
if is_one_value(dic):
value = dic[0]['value']
elif (obj.min_type == 'external' and obj.min_dbsource_id.id and
is_sql_or_ddl_statement(obj.min_code)):
value = dic[0]["value"]
elif (
obj.min_type == "external"
and obj.min_dbsource_id.id
and is_sql_or_ddl_statement(obj.min_code)
):
dbsrc_obj = obj.min_dbsource_id
res = dbsrc_obj.execute(obj.min_code)
if is_one_value(res):
value = res[0]['value']
elif obj.min_type == 'python':
value = res[0]["value"]
elif obj.min_type == "python":
value = safe_eval(obj.min_code)
else:
value = obj.min_fixed_value
@@ -127,19 +140,21 @@ class KPIThresholdRange(models.Model):
value = None
error = None
try:
if obj.max_type == 'local' and is_sql_or_ddl_statement(
obj.max_code):
if obj.max_type == "local" and is_sql_or_ddl_statement(obj.max_code):
self.env.cr.execute(obj.max_code)
dic = self.env.cr.dictfetchall()
if is_one_value(dic):
value = dic[0]['value']
elif (obj.max_type == 'external' and obj.max_dbsource_id.id and
is_sql_or_ddl_statement(obj.max_code)):
value = dic[0]["value"]
elif (
obj.max_type == "external"
and obj.max_dbsource_id.id
and is_sql_or_ddl_statement(obj.max_code)
):
dbsrc_obj = obj.max_dbsource_id
res = dbsrc_obj.execute(obj.max_code)
if is_one_value(res):
value = res[0]['value']
elif obj.max_type == 'python':
value = res[0]["value"]
elif obj.max_type == "python":
value = safe_eval(obj.max_code)
else:
value = obj.max_fixed_value
@@ -156,12 +171,14 @@ class KPIThresholdRange(models.Model):
obj.valid = False
obj.invalid_message = (
"Either minimum or maximum value has "
"computation errors. Please fix them.")
"computation errors. Please fix them."
)
elif obj.max_value < obj.min_value:
obj.valid = False
obj.invalid_message = (
"Minimum value is greater than the maximum "
"value! Please adjust them.")
"value! Please adjust them."
)
else:
obj.valid = True
obj.invalid_message = ""

View File

@@ -12,4 +12,4 @@ A threshold is a list of ranges and a range is:
* a name (like Good, Warning, Bad)
* a minimum value (fixed, sql query or python code)
* a maximum value (fixed, sql query or python code)
* color (RGB code like #00FF00 for green, #FFA500 for orange, #FF0000 for red)
* color (RGB code like #00FF00 for green, #FFA500 for orange, #FF0000 for red)

View File

@@ -1,2 +1,2 @@
Example of usage:
https://www.youtube.com/watch?v=OC4-y2klzIk
https://www.youtube.com/watch?v=OC4-y2klzIk

View File

@@ -1,46 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2012 -Now Savoir-faire Linux <https://www.savoirfairelinux.com/>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<data noupdate="0">
<record id="group_kpi_manager" model="res.groups">
<field name="name">Manage KPI's</field>
<field name="category_id" ref="base.module_category_hidden"/>
<field name="users" eval="[(4, ref('base.user_admin'))]"/>
<field name="category_id" ref="base.module_category_hidden" />
<field name="users" eval="[(4, ref('base.user_admin'))]" />
</record>
</data>
<data noupdate="1">
<!-- Rule -->
<record model="ir.rule" id="kpi_rule">
<field name="name">kpi multi-company</field>
<field name="model_id" ref="model_kpi"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
<field name="model_id" ref="model_kpi" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record model="ir.rule" id="kpi_threshold_range_rule">
<field name="name">kpi_threshold_range multi-company</field>
<field name="model_id" ref="model_kpi_threshold_range"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
<field name="model_id" ref="model_kpi_threshold_range" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record model="ir.rule" id="kpi_threshold_rule">
<field name="name">kpi_threshold multi-company</field>
<field name="model_id" ref="model_kpi_threshold"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
<field name="model_id" ref="model_kpi_threshold" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record model="ir.rule" id="kpi_history_rule">
<field name="name">kpi_history multi-company</field>
<field name="model_id" ref="model_kpi_history"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
<field name="model_id" ref="model_kpi_history" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</odoo>

View File

@@ -4,113 +4,125 @@ from odoo.tests.common import TransactionCase
class TestKPI(TransactionCase):
def setUp(self):
super(TestKPI, self).setUp()
def test_invalid_threshold_range(self):
range1 = self.env['kpi.threshold.range'].create({
'name': 'Range1',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 3,
'max_fixed_value': 1,
})
range2 = self.env['kpi.threshold.range'].create({
'name': 'Range2',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 4,
'max_fixed_value': 10,
})
range1 = self.env["kpi.threshold.range"].create(
{
"name": "Range1",
"min_type": "static",
"max_type": "static",
"min_fixed_value": 3,
"max_fixed_value": 1,
}
)
range2 = self.env["kpi.threshold.range"].create(
{
"name": "Range2",
"min_type": "static",
"max_type": "static",
"min_fixed_value": 4,
"max_fixed_value": 10,
}
)
self.assertFalse(range1.valid)
self.assertTrue(range2.valid)
def test_invalid_threshold(self):
range1 = self.env['kpi.threshold.range'].create({
'name': 'Range1',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 1,
'max_fixed_value': 4,
})
range2 = self.env['kpi.threshold.range'].create({
'name': 'Range2',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 4,
'max_fixed_value': 10,
})
range3 = self.env['kpi.threshold.range'].create({
'name': 'Range3',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 1,
'max_fixed_value': 3,
})
range_invalid = self.env['kpi.threshold.range'].create({
'name': 'RangeInvalid',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 3,
'max_fixed_value': 1,
})
range1 = self.env["kpi.threshold.range"].create(
{
"name": "Range1",
"min_type": "static",
"max_type": "static",
"min_fixed_value": 1,
"max_fixed_value": 4,
}
)
range2 = self.env["kpi.threshold.range"].create(
{
"name": "Range2",
"min_type": "static",
"max_type": "static",
"min_fixed_value": 4,
"max_fixed_value": 10,
}
)
range3 = self.env["kpi.threshold.range"].create(
{
"name": "Range3",
"min_type": "static",
"max_type": "static",
"min_fixed_value": 1,
"max_fixed_value": 3,
}
)
range_invalid = self.env["kpi.threshold.range"].create(
{
"name": "RangeInvalid",
"min_type": "static",
"max_type": "static",
"min_fixed_value": 3,
"max_fixed_value": 1,
}
)
threshold1 = self.env['kpi.threshold'].create({
'name': 'Threshold1',
'range_ids': [(6, 0, [range1.id, range2.id])],
})
threshold1 = self.env["kpi.threshold"].create(
{"name": "Threshold1", "range_ids": [(6, 0, [range1.id, range2.id])],}
)
threshold2 = self.env['kpi.threshold'].create({
'name': 'Threshold1',
'range_ids': [(6, 0, [range3.id, range2.id])],
})
threshold2 = self.env["kpi.threshold"].create(
{"name": "Threshold1", "range_ids": [(6, 0, [range3.id, range2.id])],}
)
threshold3 = self.env['kpi.threshold'].create({
'name': 'Threshold1',
'range_ids': [(6, 0, [range_invalid.id, range2.id])],
})
threshold3 = self.env["kpi.threshold"].create(
{
"name": "Threshold1",
"range_ids": [(6, 0, [range_invalid.id, range2.id])],
}
)
self.assertFalse(threshold1.valid)
self.assertTrue(threshold2.valid)
self.assertFalse(threshold3.valid)
def test_invalid_threshold_range_exception(self):
range_error = self.env['kpi.threshold.range'].create({
'name': 'RangeError',
'min_type': 'python',
'min_code': '<Not a valid python expression>',
'max_type': 'static',
'max_fixed_value': 1,
})
range_error = self.env["kpi.threshold.range"].create(
{
"name": "RangeError",
"min_type": "python",
"min_code": "<Not a valid python expression>",
"max_type": "static",
"max_fixed_value": 1,
}
)
self.assertFalse(range_error.valid)
def test_kpi_python(self):
kpi_category = self.env['kpi.category'].create({
'name': 'Dynamic KPIs'
})
kpi_threshold = self.env['kpi.threshold'].create({
'name': 'KPI Threshold for dynamic KPIs'
})
kpi_category = self.env["kpi.category"].create({"name": "Dynamic KPIs"})
kpi_threshold = self.env["kpi.threshold"].create(
{"name": "KPI Threshold for dynamic KPIs"}
)
kpi_code = """
{
'value': 1.0,
'color': '#00FF00'
}
"""
kpi = self.env['kpi'].create({
'name': 'Dynamic python kpi',
'description': 'Dynamic python kpi',
'category_id': kpi_category.id,
'threshold_id': kpi_threshold.id,
'periodicity': 1,
'periodicity_uom': 'day',
'kpi_type': 'python',
'kpi_code': kpi_code,
})
kpi = self.env["kpi"].create(
{
"name": "Dynamic python kpi",
"description": "Dynamic python kpi",
"category_id": kpi_category.id,
"threshold_id": kpi_threshold.id,
"periodicity": 1,
"periodicity_uom": "day",
"kpi_type": "python",
"kpi_code": kpi_code,
}
)
kpi.update_kpi_value()
kpi_history = self.env['kpi.history'].search(
[('kpi_id', '=', kpi.id)])
kpi_history = self.env["kpi.history"].search([("kpi_id", "=", kpi.id)])
self.assertEqual(len(kpi_history), 1)
self.assertEqual(kpi_history.color, '#00FF00')
self.assertEqual(kpi_history.color, "#00FF00")
self.assertEqual(kpi_history.value, 1.0)

View File

@@ -1,20 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<!-- Categories -->
<!-- Categories -->
<record id="view_kpi_category_tree" model="ir.ui.view">
<field name="name">kpi.category.tree</field>
<field name="model">kpi.category</field>
<field name="arch" type="xml">
<tree string="Categories">
<field name="name"/>
<field name="name" />
</tree>
</field>
</record>
<record id="view_kpi_category_form" model="ir.ui.view">
<field name="name">kpi.category.form</field>
<field name="model">kpi.category</field>
@@ -22,20 +19,19 @@
<form string="Category">
<sheet>
<group col="2" colspan="2">
<field name="name" colspan="2"/>
<newline/>
<field name="description" colspan="2"/>
<field name="name" colspan="2" />
<newline />
<field name="description" colspan="2" />
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="open_category_list">
<field name="name">Categories</field>
<field name="res_model">kpi.category</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_kpi_category_tree"/>
<field name="view_id" ref="view_kpi_category_tree" />
</record>
</odoo>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<!-- KPI history -->
<record id="view_kpi_history_tree" model="ir.ui.view">
@@ -9,11 +8,11 @@
<field name="model">kpi.history</field>
<field name="arch" type="xml">
<tree string="KPI History">
<field name="name"/>
<field name="date"/>
<field name="value"/>
<field name="color" widget="color"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="name" />
<field name="date" />
<field name="value" />
<field name="color" widget="color" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
@@ -24,12 +23,12 @@
<form string="KPI History">
<sheet>
<group col="4" colspan="4">
<field name="kpi_id"/>
<field name="name"/>
<field name="date"/>
<field name="value"/>
<field name="color" widget="color"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="kpi_id" />
<field name="name" />
<field name="date" />
<field name="value" />
<field name="color" widget="color" />
<field name="company_id" groups="base.group_multi_company" />
</group>
</sheet>
</form>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<!-- Ranges -->
<record id="view_kpi_threshold_range_tree" model="ir.ui.view">
@@ -9,16 +8,15 @@
<field name="model">kpi.threshold.range</field>
<field name="arch" type="xml">
<tree string="Ranges" decoration-danger="invalid_message">
<field name="name"/>
<field name="min_value"/>
<field name="max_value"/>
<field name="color" widget="color"/>
<field name="invalid_message"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="name" />
<field name="min_value" />
<field name="max_value" />
<field name="color" widget="color" />
<field name="invalid_message" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
<record id="view_kpi_threshold_range_form" model="ir.ui.view">
<field name="name">kpi.threshold.range.form</field>
<field name="model">kpi.threshold.range</field>
@@ -26,46 +24,90 @@
<form string="Range">
<sheet>
<group col="6" colspan="6">
<field name="name" colspan="2"/>
<field name="color" colspan="2" widget="color"/>
<field name="company_id" groups="base.group_multi_company" colspan="2" />
<field name="name" colspan="2" />
<field name="color" colspan="2" widget="color" />
<field
name="company_id"
groups="base.group_multi_company"
colspan="2"
/>
</group>
<group col="6">
<separator string="Minimum"/>
<newline/>
<field name="min_type" colspan="3"/>
<field name="min_fixed_value" colspan="3" attrs="{'invisible' : [('min_type', '!=', 'static')]}"/>
<field name="min_dbsource_id" colspan="3" attrs="{'invisible' : [('min_type', '!=', 'external')]}"/>
<field name="min_code" colspan="6" attrs="{'invisible' : [('min_type', 'not in', ('local','external','python'))]}"/>
<field name="min_error" colspan="6" attrs="{'invisible': [('min_error', '=', False)]}" />
<newline/>
<separator string="Maximum"/>
<newline/>
<field name="max_type" colspan="3"/>
<field name="max_fixed_value" colspan="3" attrs="{'invisible' : [('max_type', '!=', 'static')]}"/>
<field name="max_dbsource_id" colspan="3" attrs="{'invisible' : [('max_type', '!=', 'external')]}"/>
<newline/>
<field name="max_code" colspan="6" attrs="{'invisible' : [('max_type', 'not in', ('local','external','python'))]}"/>
<newline/>
<field name="max_error" colspan="6" attrs="{'invisible': [('max_error', '=', False)]}" />
<newline/>
<separator string="Minimum" />
<newline />
<field name="min_type" colspan="3" />
<field
name="min_fixed_value"
colspan="3"
attrs="{'invisible' : [('min_type', '!=', 'static')]}"
/>
<field
name="min_dbsource_id"
colspan="3"
attrs="{'invisible' : [('min_type', '!=', 'external')]}"
/>
<field
name="min_code"
colspan="6"
attrs="{'invisible' : [('min_type', 'not in', ('local','external','python'))]}"
/>
<field
name="min_error"
colspan="6"
attrs="{'invisible': [('min_error', '=', False)]}"
/>
<newline />
<separator string="Maximum" />
<newline />
<field name="max_type" colspan="3" />
<field
name="max_fixed_value"
colspan="3"
attrs="{'invisible' : [('max_type', '!=', 'static')]}"
/>
<field
name="max_dbsource_id"
colspan="3"
attrs="{'invisible' : [('max_type', '!=', 'external')]}"
/>
<newline />
<field
name="max_code"
colspan="6"
attrs="{'invisible' : [('max_type', 'not in', ('local','external','python'))]}"
/>
<newline />
<field
name="max_error"
colspan="6"
attrs="{'invisible': [('max_error', '=', False)]}"
/>
<newline />
</group>
<group col="6" colspan="6">
<separator string="Thresholds" colspan="4"/>
<field name="threshold_ids" nolabel="1" colspan="4"/>
<separator string="Errors" attrs="{'invisible' : [('invalid_message', '=', False)]}" colspan="4"/>
<field name="invalid_message" nolabel="1" attrs="{'invisible' : [('invalid_message', '=', False)]}" colspan="4"/>
<separator string="Thresholds" colspan="4" />
<field name="threshold_ids" nolabel="1" colspan="4" />
<separator
string="Errors"
attrs="{'invisible' : [('invalid_message', '=', False)]}"
colspan="4"
/>
<field
name="invalid_message"
nolabel="1"
attrs="{'invisible' : [('invalid_message', '=', False)]}"
colspan="4"
/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="open_threshold_range_list">
<field name="name">Ranges</field>
<field name="res_model">kpi.threshold.range</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_kpi_threshold_range_tree"/>
<field name="view_id" ref="view_kpi_threshold_range_tree" />
</record>
</odoo>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<!-- Thresholds -->
<record id="view_kpi_threshold_tree" model="ir.ui.view">
@@ -9,13 +8,12 @@
<field name="model">kpi.threshold</field>
<field name="arch" type="xml">
<tree string="Thresholds" decoration-danger="invalid_message">
<field name="name"/>
<field name="invalid_message"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="name" />
<field name="invalid_message" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
<record id="view_kpi_threshold_form" model="ir.ui.view">
<field name="name">kpi.threshold.form</field>
<field name="model">kpi.threshold</field>
@@ -23,31 +21,43 @@
<form string="Threshold">
<sheet>
<group col="6" colspan="6">
<field name="name" colspan="2"/>
<field name="company_id" groups="base.group_multi_company" colspan="2"/>
<newline/>
<separator string="Ranges" colspan="6"/>
<newline/>
<field name="range_ids" nolabel="1" colspan="6"/>
<newline/>
<separator string="KPIs" colspan="6"/>
<newline/>
<field name="kpi_ids" nolabel="1" colspan="6"/>
<newline/>
<separator string="Errors" attrs="{'invisible' : [('invalid_message', '=', False)]}" colspan="4"/>
<field name="invalid_message" nolabel="1" attrs="{'invisible' : [('invalid_message', '=', False)]}" colspan="4"/>
<newline/>
<field name="name" colspan="2" />
<field
name="company_id"
groups="base.group_multi_company"
colspan="2"
/>
<newline />
<separator string="Ranges" colspan="6" />
<newline />
<field name="range_ids" nolabel="1" colspan="6" />
<newline />
<separator string="KPIs" colspan="6" />
<newline />
<field name="kpi_ids" nolabel="1" colspan="6" />
<newline />
<separator
string="Errors"
attrs="{'invisible' : [('invalid_message', '=', False)]}"
colspan="4"
/>
<field
name="invalid_message"
nolabel="1"
attrs="{'invisible' : [('invalid_message', '=', False)]}"
colspan="4"
/>
<newline />
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="open_threshold_list">
<field name="name">Thresholds</field>
<field name="res_model">kpi.threshold</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_kpi_threshold_tree"/>
<field name="view_id" ref="view_kpi_threshold_tree" />
</record>
</odoo>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
@@ -6,14 +6,14 @@
<record id="view_kpi_tree" model="ir.ui.view">
<field name="name">kpi.tree</field>
<field name="model">kpi</field>
<field eval="8" name="priority"/>
<field eval="8" name="priority" />
<field name="arch" type="xml">
<tree string="Key Performance Indicators">
<field name="name"/>
<field name="value" widget="progressbar"/>
<field name="category_id"/>
<field name="kpi_type"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="name" />
<field name="value" widget="progressbar" />
<field name="category_id" />
<field name="kpi_type" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
@@ -22,20 +22,32 @@
<field name="model">kpi</field>
<field name="arch" type="xml">
<kanban class="o_kpi_kanban" create="false" edit="false" delete="false">
<field name="id"/>
<field name="display_name"/>
<field name="color"/>
<field name="value"/>
<field name="last_execution"/>
<field name="id" />
<field name="display_name" />
<field name="color" />
<field name="value" />
<field name="last_execution" />
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click o_kpi_kanban">
<div class="oe_kanban_details">
<div class="o_kanban_record_title oe_partner_heading"><strong><field name="display_name"/></strong></div>
<div class="o_kanban_tags_section oe_kanban_partner_categories" t-attf-style="color:#{record.color.raw_value}">
<strong><field name="value"/></strong>
</div><div class="o_kanban_tags_section oe_kanban_partner_categories">
<field name="last_execution"/>
<div class="o_kanban_record_title oe_partner_heading">
<strong>
<field name="display_name" />
</strong>
</div>
<div
class="o_kanban_tags_section oe_kanban_partner_categories"
t-attf-style="color:#{record.color.raw_value}"
>
<strong>
<field name="value" />
</strong>
</div>
<div
class="o_kanban_tags_section oe_kanban_partner_categories"
>
<field name="last_execution" />
</div>
</div>
</div>
@@ -44,32 +56,32 @@
</kanban>
</field>
</record>
<record id="view_kpi_filter" model="ir.ui.view">
<field name="name">kpi.filter</field>
<field name="model">kpi</field>
<field name="arch" type="xml">
<search string="KPI">
<group>
<filter name="active"
icon="terp-document-new"
domain="[('active','=',True)]"
string="Active"
help="Only active KPIs are computed by the scheduler based on the periodicity configuration."/>
<separator orientation="vertical"/>
<field name="name"/>
<field name="category_id"/>
<field name="company_id" group="base.group_multi_company"/>
<filter
name="active"
icon="terp-document-new"
domain="[('active','=',True)]"
string="Active"
help="Only active KPIs are computed by the scheduler based on the periodicity configuration."
/>
<separator orientation="vertical" />
<field name="name" />
<field name="category_id" />
<field name="company_id" group="base.group_multi_company" />
</group>
<newline/>
<newline />
<group expand="0" name="Group By...">
<filter name="Category" context="{'group_by':'category_id'}"/>
<filter name="Type" context="{'group_by':'kpi_type'}"/>
<filter name="Category" context="{'group_by':'category_id'}" />
<filter name="Type" context="{'group_by':'kpi_type'}" />
</group>
</search>
</field>
</record>
<record id="view_kpi_form" model="ir.ui.view">
<field name="name">kpi.form</field>
<field name="model">kpi</field>
@@ -79,76 +91,78 @@
<group>
<group>
<field name="name" />
<field name="threshold_id"/>
<field name="category_id"/>
<field name="threshold_id" />
<field name="category_id" />
</group>
<group>
<field name="value" colspan="2"/>
<field name="active" colspan="2"/>
<field name="company_id"
groups="base.group_multi_company"/>
<button name="compute_kpi_value"
string="Compute KPI Now"
colspan="2"
type="object"
groups="kpi.group_kpi_manager"/>
<field name="value" colspan="2" />
<field name="active" colspan="2" />
<field
name="company_id"
groups="base.group_multi_company"
/>
<button
name="compute_kpi_value"
string="Compute KPI Now"
colspan="2"
type="object"
groups="kpi.group_kpi_manager"
/>
</group>
</group>
<notebook colspan="6">
<page string="History">
<field name="history_ids" readonly="1" nolabel="1"/>
<field name="history_ids" readonly="1" nolabel="1" />
</page>
<page string="Computation" groups="kpi.group_kpi_manager">
<group col="6">
<field name="periodicity" colspan="3"/>
<field name="periodicity_uom" colspan="3"/>
<field name="next_execution_date" colspan="3"/>
<separator string="KPI Computation" colspan="6"/>
<newline/>
<field name="kpi_type" colspan="2"/>
<field name="dbsource_id" colspan="2"
attrs="{'invisible' : [('kpi_type', '!=', 'external')]}"/>
<newline/>
<field name="kpi_code" colspan="6"/>
<field name="periodicity" colspan="3" />
<field name="periodicity_uom" colspan="3" />
<field name="next_execution_date" colspan="3" />
<separator string="KPI Computation" colspan="6" />
<newline />
<field name="kpi_type" colspan="2" />
<field
name="dbsource_id"
colspan="2"
attrs="{'invisible' : [('kpi_type', '!=', 'external')]}"
/>
<newline />
<field name="kpi_code" colspan="6" />
</group>
</page>
<page string="Description">
<field name="description" nolabel="1"/>
<field name="description" nolabel="1" />
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="open_kpi_dashboard">
<field name="name">KPI Dashboard</field>
<field name="res_model">kpi</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,form</field>
<field name="search_view_id" ref="view_kpi_filter"/>
<field name="search_view_id" ref="view_kpi_filter" />
</record>
<record id="action_view_kpi_kanban" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/>
<field eval="1" name="sequence" />
<field name="view_mode">kanban</field>
<field name="view_id" ref="view_kpi_kanban"/>
<field name="act_window_id" ref="open_kpi_dashboard"/>
<field name="view_id" ref="view_kpi_kanban" />
<field name="act_window_id" ref="open_kpi_dashboard" />
</record>
<record model="ir.actions.act_window" id="open_kpi_list">
<field name="name">KPI Maintenance</field>
<field name="res_model">kpi</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_kpi_filter"/>
<field name="search_view_id" ref="view_kpi_filter" />
</record>
<record id="action_view_kpi_tree" model="ir.actions.act_window.view">
<field eval="2" name="sequence"/>
<field eval="2" name="sequence" />
<field name="view_mode">tree</field>
<field name="view_id" ref="view_kpi_tree"/>
<field name="act_window_id" ref="open_kpi_list"/>
<field name="view_id" ref="view_kpi_tree" />
<field name="act_window_id" ref="open_kpi_list" />
</record>
</odoo>

View File

@@ -1,49 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<menuitem id="menu_kpi_dasboard"
name="Key Performance Indicators"
action="open_kpi_dashboard"
sequence="15"
parent="base.menu_reporting_dashboard"
groups="base.group_user"/>
<!-- CONFIGURATION -->
<menuitem id="menu_configuration_kpi"
name="Key Performance Indicators"
parent="base.menu_reporting_config"
groups="kpi.group_kpi_manager"
sequence="10"/>
<menuitem id="menu_configuration_kpi_kpi"
name="Key Performance Indicators"
action="open_kpi_list"
sequence="10"
parent="menu_configuration_kpi"/>
<menuitem id="menu_configuration_kpi_category"
name="Categories"
action="open_category_list"
parent="menu_configuration_kpi"
sequence="20"/>
<menuitem id="menu_configuration_kpi_range"
name="Ranges"
action="open_threshold_range_list"
parent="menu_configuration_kpi"
sequence="30"/>
<menuitem id="menu_configuration_kpi_dbsource"
name="Data Sources"
action="base_external_dbsource.action_dbsource"
parent="menu_configuration_kpi"
sequence="40"/>
<menuitem id="menu_configuration_kpi_threshold"
name="Thresholds"
action="open_threshold_list"
parent="menu_configuration_kpi"
sequence="50"/>
</odoo>
<menuitem
id="menu_kpi_dasboard"
name="Key Performance Indicators"
action="open_kpi_dashboard"
sequence="15"
parent="base.menu_reporting_dashboard"
groups="base.group_user"
/>
<!-- CONFIGURATION -->
<menuitem
id="menu_configuration_kpi"
name="Key Performance Indicators"
parent="base.menu_reporting_config"
groups="kpi.group_kpi_manager"
sequence="10"
/>
<menuitem
id="menu_configuration_kpi_kpi"
name="Key Performance Indicators"
action="open_kpi_list"
sequence="10"
parent="menu_configuration_kpi"
/>
<menuitem
id="menu_configuration_kpi_category"
name="Categories"
action="open_category_list"
parent="menu_configuration_kpi"
sequence="20"
/>
<menuitem
id="menu_configuration_kpi_range"
name="Ranges"
action="open_threshold_range_list"
parent="menu_configuration_kpi"
sequence="30"
/>
<menuitem
id="menu_configuration_kpi_dbsource"
name="Data Sources"
action="base_external_dbsource.action_dbsource"
parent="menu_configuration_kpi"
sequence="40"
/>
<menuitem
id="menu_configuration_kpi_threshold"
name="Thresholds"
action="open_threshold_list"
parent="menu_configuration_kpi"
sequence="50"
/>
</odoo>