mgmtsystem_kpi rename to kpi (#543)
* kpi migration to odoo 9 * UI error connected * Correction base on last maxime review * correction on @elicoidal review * Warning exception improve in kpi_threshold * Latest Ellicoidal comment implemented * last Ellicoial recommendation done. * Copyright correctedpull/637/head
-
77kpi/README.rst
-
5kpi/__init__.py
-
33kpi/__openerp__.py
-
18kpi/data/kpi.xml
-
0kpi/i18n/fr.po
-
0kpi/i18n/kpi.pot
-
0kpi/i18n/nb.po
-
0kpi/i18n/pt_BR.po
-
0kpi/i18n/vi.po
-
0kpi/images/kpi_computation.png
-
0kpi/images/kpi_definition.png
-
0kpi/images/kpi_range.png
-
0kpi/images/kpi_threshold.png
-
9kpi/models/__init__.py
-
189kpi/models/kpi.py
-
14kpi/models/kpi_category.py
-
29kpi/models/kpi_history.py
-
85kpi/models/kpi_threshold.py
-
161kpi/models/kpi_threshold_range.py
-
11kpi/security/ir.model.access.csv
-
39kpi/security/kpi_security.xml
-
0kpi/static/src/img/icon.png
-
97kpi/views/kpi.xml
-
45kpi/views/kpi_category.xml
-
38kpi/views/kpi_history.xml
-
54kpi/views/kpi_threshold.xml
-
69kpi/views/kpi_threshold_range.xml
-
48kpi/views/menu.xml
-
3mgmtsystem_kpi/__init__.py
-
70mgmtsystem_kpi/__openerp__.py
-
532mgmtsystem_kpi/mgmtsystem_kpi.py
-
300mgmtsystem_kpi/mgmtsystem_kpi_view.xml
-
11mgmtsystem_kpi/security/ir.model.access.csv
-
36mgmtsystem_kpi/security/mgmtsystem_kpi_security.xml
@ -0,0 +1,77 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
|
|||
=== |
|||
KPI |
|||
=== |
|||
|
|||
This module provides the basis for creating key performance indicators, |
|||
including static and dynamic thresholds (SQL query or Python code), |
|||
on local and remote data sources. |
|||
|
|||
The module also provides the mecanism to update KPIs automatically. |
|||
A scheduler is executed every hour and updates the KPI values, based |
|||
on the periodicity of each KPI. KPI computation can also be done |
|||
manually. |
|||
|
|||
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) |
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
Users must be added to the appropriate groups within Odoo as follows: |
|||
* Creators: Settings > Users > Groups > Management System / User |
|||
* Responsible Persons: Settings > Users > Groups > Management System / Approving User |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
https://www.youtube.com/watch?v=OC4-y2klzIk |
|||
|
|||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
|||
:alt: Try me on Runbot |
|||
:target: https://runbot.odoo-community.org/runbot/128/9.0 |
|||
|
|||
Known issues / Roadmap |
|||
====================== |
|||
|
|||
* Use web_widget_color to display color associated to threshold range |
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. |
|||
In case of trouble, please check there if your issue has already been reported. |
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Daniel Reis <dreis.pt@hotmail.com> |
|||
* Glen Dromgoole <gdromgoole@tier1engineering.com> |
|||
* Loic Lacroix <loic.lacroix@savoirfairelinux.com> |
|||
* Sandy Carter <sandy.carter@savoirfairelinux.com> |
|||
* Gervais Naoussi <gervaisnaoussi@gmail.com> |
|||
|
|||
Maintainer |
|||
---------- |
|||
|
|||
.. image:: https://odoo-community.org/logo.png |
|||
:alt: Odoo Community Association |
|||
:target: https://odoo-community.org |
|||
|
|||
This module is maintained by the OCA. |
|||
|
|||
OCA, or the Odoo Community Association, is a nonprofit organization whose |
|||
mission is to support the collaborative development of Odoo features and |
|||
promote its widespread use. |
|||
|
|||
To contribute to this module, please visit https://odoo-community.org. |
@ -0,0 +1,5 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from . import models |
@ -0,0 +1,33 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
{ |
|||
"name": "Key Performance Indicator", |
|||
"version": "9.0.1.0.0", |
|||
"author": "Savoir-faire Linux,Odoo Community Association (OCA)", |
|||
"website": "http://www.savoirfairelinux.com", |
|||
"license": "AGPL-3", |
|||
"category": "Report", |
|||
"depends": [ |
|||
'base_external_dbsource', |
|||
], |
|||
"data": [ |
|||
'security/ir.model.access.csv', |
|||
'security/kpi_security.xml', |
|||
'views/kpi_category.xml', |
|||
'views/kpi_history.xml', |
|||
'views/kpi_threshold_range.xml', |
|||
'views/kpi_threshold.xml', |
|||
'views/kpi.xml', |
|||
'views/menu.xml', |
|||
'data/kpi.xml', |
|||
], |
|||
"images": [ |
|||
"images/kpi_definition.png", |
|||
"images/kpi_computation.png", |
|||
"images/kpi_threshold.png", |
|||
"images/kpi_range.png", |
|||
], |
|||
'installable': True, |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?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="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="interval_number">1</field> |
|||
<field name="interval_type">hours</field> |
|||
<field name="numbercall">-1</field> |
|||
<field eval="False" name="doall"/> |
|||
<field eval="'kpi'" name="model"/> |
|||
<field eval="'update_kpi_value'" name="function"/> |
|||
<field eval="'()'" name="args"/> |
|||
</record> |
|||
</data> |
|||
</odoo> |
Before Width: 658 | Height: 352 | Size: 41 KiB After Width: 658 | Height: 352 | Size: 41 KiB |
Before Width: 655 | Height: 353 | Size: 46 KiB After Width: 655 | Height: 353 | Size: 46 KiB |
Before Width: 818 | Height: 449 | Size: 27 KiB After Width: 818 | Height: 449 | Size: 27 KiB |
Before Width: 655 | Height: 266 | Size: 25 KiB After Width: 655 | Height: 266 | Size: 25 KiB |
@ -0,0 +1,9 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from . import kpi_category |
|||
from . import kpi_threshold_range |
|||
from . import kpi_threshold |
|||
from . import kpi_history |
|||
from . import kpi |
@ -0,0 +1,189 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# 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, timedelta |
|||
from openerp import fields, models, api |
|||
from openerp.tools.safe_eval import safe_eval |
|||
from openerp.tools import ( |
|||
DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT, |
|||
) |
|||
import re |
|||
import logging |
|||
_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(): |
|||
return True |
|||
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', |
|||
)) + ')') |
|||
|
|||
|
|||
def is_sql_or_ddl_statement(query): |
|||
"""Check if sql query is a SELECT statement""" |
|||
return not RE_SELECT_QUERY.match(query.upper()) |
|||
|
|||
|
|||
class KPI(models.Model): |
|||
"""Key Performance Indicators.""" |
|||
|
|||
_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) |
|||
|
|||
periodicity_uom = fields.Selection(( |
|||
('hour', 'Hour'), |
|||
('day', 'Day'), |
|||
('week', 'Week'), |
|||
('month', 'Month') |
|||
), 'Periodicity UoM', required=True, default='day') |
|||
|
|||
next_execution_date = fields.Datetime( |
|||
'Next execution date', |
|||
readonly=True, |
|||
) |
|||
value = fields.Float(string='Value', |
|||
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', |
|||
) |
|||
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', |
|||
) |
|||
active = fields.Boolean( |
|||
'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) |
|||
|
|||
@api.multi |
|||
def _compute_display_last_kpi_value(self): |
|||
history_obj = self.env['kpi.history'] |
|||
for obj in self: |
|||
history_ids = history_obj.search([("kpi_id", "=", obj.id)]) |
|||
if history_ids: |
|||
obj.value = obj.history_ids[0].value |
|||
else: |
|||
obj.value = 0 |
|||
|
|||
@api.multi |
|||
def compute_kpi_value(self): |
|||
for obj in self: |
|||
kpi_value = 0 |
|||
if obj.kpi_code: |
|||
if obj.kpi_type == 'local' and is_sql_or_ddl_statement( |
|||
obj.kpi_code): |
|||
self.env.cr.execute(obj.kpi_code) |
|||
dic = self.env.cr.dictfetchall() |
|||
if is_one_value(dic): |
|||
kpi_value = dic[0]['value'] |
|||
elif (obj.kpi_type == 'external' and obj.dbsource_id.id and |
|||
is_sql_or_ddl_statement(obj.kpi_code)): |
|||
dbsrc_obj = obj.dbsource_id |
|||
res = dbsrc_obj.execute(obj.kpi_code) |
|||
if is_one_value(res): |
|||
kpi_value = res[0]['value'] |
|||
elif obj.kpi_type == 'python': |
|||
kpi_value = safe_eval(obj.kpi_code) |
|||
|
|||
threshold_obj = obj.threshold_id |
|||
values = { |
|||
'kpi_id': obj.id, |
|||
'value': kpi_value, |
|||
'color': threshold_obj.get_color(kpi_value), |
|||
} |
|||
history_obj = self.env['kpi.history'] |
|||
history_obj.create(values) |
|||
return True |
|||
|
|||
@api.multi |
|||
def update_next_execution_date(self): |
|||
for obj in self: |
|||
if obj.periodicity_uom == 'hour': |
|||
delta = timedelta(hours=obj.periodicity) |
|||
elif obj.periodicity_uom == 'day': |
|||
delta = timedelta(days=obj.periodicity) |
|||
elif obj.periodicity_uom == 'week': |
|||
delta = timedelta(weeks=obj.periodicity) |
|||
elif obj.periodicity_uom == 'month': |
|||
delta = timedelta(months=obj.periodicity) |
|||
else: |
|||
delta = timedelta() |
|||
new_date = datetime.now() + delta |
|||
|
|||
obj.next_execution_date = new_date.strftime(DATETIME_FORMAT) |
|||
|
|||
return True |
|||
|
|||
# Method called by the scheduler |
|||
@api.model |
|||
def update_kpi_value(self): |
|||
filters = [ |
|||
'&', |
|||
'|', |
|||
('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']) |
|||
obj_ids = self.search(filters) |
|||
res = None |
|||
|
|||
try: |
|||
for obj in obj_ids: |
|||
obj.compute_kpi_value() |
|||
obj.update_next_execution_date() |
|||
except Exception: |
|||
_logger.exception("Failed updating KPI values") |
|||
|
|||
return res |
@ -0,0 +1,14 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from openerp import fields, models |
|||
|
|||
|
|||
class KPICategory(models.Model): |
|||
"""KPI Category.""" |
|||
|
|||
_name = "kpi.category" |
|||
_description = "KPI Category" |
|||
name = fields.Char('Name', size=50, required=True) |
|||
description = fields.Text('Description') |
@ -0,0 +1,29 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from openerp import fields, models |
|||
|
|||
|
|||
class KPIHistory(models.Model): |
|||
"""History of the KPI.""" |
|||
|
|||
_name = "kpi.history" |
|||
_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) |
|||
date = fields.Datetime( |
|||
'Execution Date', |
|||
required=True, |
|||
readonly=True, |
|||
default=fields.Datetime.now() |
|||
) |
|||
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) |
@ -0,0 +1,85 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from openerp import fields, models, api, exceptions, _ |
|||
|
|||
|
|||
class KPIThreshold(models.Model): |
|||
"""KPI Threshold.""" |
|||
|
|||
_name = "kpi.threshold" |
|||
_description = "KPI Threshold" |
|||
|
|||
@api.multi |
|||
def _compute_is_valid_threshold(self): |
|||
result = {} |
|||
for obj in self: |
|||
# check if ranges overlap |
|||
# TODO: This code can be done better |
|||
for range1 in obj.range_ids: |
|||
for range2 in obj.range_ids: |
|||
if (range1.valid and range2.valid and |
|||
range1.min_value < range2.min_value): |
|||
result[obj.id] = range1.max_value <= range2.min_value |
|||
return result |
|||
|
|||
@api.multi |
|||
def _compute_generate_invalid_message(self): |
|||
result = {} |
|||
for obj in self: |
|||
if obj.valid: |
|||
result[obj.id] = "" |
|||
else: |
|||
result[obj.id] = ("Two of your ranges are overlapping. Please " |
|||
"make sure your ranges do not overlap.") |
|||
return result |
|||
|
|||
name = fields.Char('Name', size=50, required=True) |
|||
range_ids = fields.Many2many( |
|||
'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_generate_invalid_message") |
|||
kpi_ids = fields.One2many('kpi', 'threshold_id', 'KPIs') |
|||
company_id = fields.Many2one( |
|||
'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 = range_obj1.browse(range1) |
|||
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.max_value > range_obj2.min_value: |
|||
raise exceptions.Warning( |
|||
_("Two of your ranges are overlapping."), |
|||
_("Make sure your ranges do not overlap!") |
|||
) |
|||
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' |
|||
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): |
|||
color = range_obj.color |
|||
return color |
@ -0,0 +1,161 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2012 - Now Savoir-faire Linux <https://www.savoirfairelinux.com/> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from openerp import fields, models, api |
|||
from openerp.tools.safe_eval import safe_eval |
|||
import re |
|||
|
|||
|
|||
def is_one_value(result): |
|||
# check if sql query returns only one value |
|||
if type(result) is dict and 'value' in result.dictfetchone(): |
|||
return True |
|||
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', |
|||
)) + ')') |
|||
|
|||
|
|||
def is_sql_or_ddl_statement(query): |
|||
"""Check if sql query is a SELECT statement""" |
|||
return not RE_SELECT_QUERY.match(query.upper()) |
|||
|
|||
|
|||
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_generate_invalid_message") |
|||
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', compute="_compute_min_value") |
|||
min_fixed_value = fields.Float('Minimum') |
|||
min_code = fields.Text('Minimum Computation Code') |
|||
min_dbsource_id = fields.Many2one( |
|||
'base.external.dbsource', |
|||
'External DB Source', |
|||
) |
|||
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', compute="_compute_max_value") |
|||
max_fixed_value = fields.Float('Maximum') |
|||
max_code = fields.Text('Maximum Computation Code') |
|||
max_dbsource_id = fields.Many2one( |
|||
'base.external.dbsource', |
|||
'External DB Source', |
|||
) |
|||
|
|||
color = fields.Char( |
|||
string="Color", |
|||
help="Choose your color" |
|||
) |
|||
|
|||
threshold_ids = fields.Many2many( |
|||
'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) |
|||
|
|||
@api.multi |
|||
def _compute_min_value(self): |
|||
result = {} |
|||
for obj in self: |
|||
value = None |
|||
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)): |
|||
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 = safe_eval(obj.min_code) |
|||
else: |
|||
value = obj.min_fixed_value |
|||
obj.min_value = value |
|||
return result |
|||
|
|||
@api.multi |
|||
def _compute_max_value(self): |
|||
result = {} |
|||
for obj in self: |
|||
value = None |
|||
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 == 'python': |
|||
value = safe_eval(obj.max_code) |
|||
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'] |
|||
else: |
|||
value = obj.max_fixed_value |
|||
obj.max_value = value |
|||
return result |
|||
|
|||
@api.multi |
|||
def _compute_is_valid_range(self): |
|||
result = {} |
|||
for obj in self: |
|||
if obj.max_value < obj.min_value: |
|||
obj.valid = False |
|||
else: |
|||
obj.valid = True |
|||
return result |
|||
|
|||
@api.multi |
|||
def _compute_generate_invalid_message(self): |
|||
result = {} |
|||
for obj in self: |
|||
if obj.valid: |
|||
obj.invalid_message = "" |
|||
else: |
|||
obj.invalid_message = ( |
|||
"Minimum value is greater than the maximum " |
|||
"value! Please adjust them.") |
|||
return result |
@ -0,0 +1,11 @@ |
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
|||
"access_kpi_user","kpi.user","model_kpi","base.group_user",1,0,0,0 |
|||
"access_kpi_history_user","kpi.history.user","model_kpi_history","base.group_user",1,0,0,0 |
|||
"access_kpi_category_user","kpi.category.user","model_kpi_category","base.group_user",1,0,0,0 |
|||
"access_kpi_threshold_user","kpi.threshold.user","model_kpi_threshold","base.group_user",1,0,0,0 |
|||
"access_kpi_threshold_range_user","kpi.threshold.range.user","model_kpi_threshold_range","base.group_user",1,0,0,0 |
|||
"access_kpi_manager","kpi.manager","model_kpi","base.group_user",1,1,1,1 |
|||
"access_kpi_category_manager","kpi.category.manager","model_kpi_category","base.group_user",1,1,1,1 |
|||
"access_kpi_threshold_manager","kpi.threshold.manager","model_kpi_threshold","base.group_user",1,1,1,1 |
|||
"access_kpi_threshold_range_manager","kpi.threshold.range.manager","model_kpi_threshold_range","base.group_user",1,1,1,1 |
|||
"access_base_external_dbsource_manager","base.external.dbsource.manager","base_external_dbsource.model_base_external_dbsource","base.group_user",1,1,1,1 |
@ -0,0 +1,39 @@ |
|||
<?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="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> |
|||
</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> |
|||
</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> |
|||
</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> |
|||
</record> |
|||
|
|||
</data> |
|||
</odoo> |
Before Width: 100 | Height: 100 | Size: 2.3 KiB After Width: 100 | Height: 100 | Size: 2.3 KiB |
@ -0,0 +1,97 @@ |
|||
<?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> |
|||
<!-- KPI --> |
|||
<record id="view_kpi_tree" model="ir.ui.view"> |
|||
<field name="name">kpi.tree</field> |
|||
<field name="model">kpi</field> |
|||
<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"/> |
|||
</tree> |
|||
</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"/> |
|||
</group> |
|||
<newline/> |
|||
<group expand="0" string="Group By..."> |
|||
<filter string="Category" context="{'group_by':'category_id'}"/> |
|||
<filter string="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> |
|||
<field name="arch" type="xml"> |
|||
<form string="Key Performance Indicator"> |
|||
<group col="6" colspan="6"> |
|||
<field name="name" colspan="2"/> |
|||
<field name="threshold_id" colspan="2"/> |
|||
<field name="category_id" colspan="2"/> |
|||
<newline/> |
|||
<field name="value" colspan="2"/> |
|||
<button name="compute_kpi_value" string="Compute KPI Now" colspan="2" type="object"/> |
|||
<field name="active" colspan="2"/> |
|||
<field name="company_id" groups="base.group_multi_company"/> |
|||
</group> |
|||
<notebook colspan="6"> |
|||
<page string="History"> |
|||
<field name="history_ids" readonly="1" nolabel="1"/> |
|||
</page> |
|||
<page string="Computation"> |
|||
<group col="6"> |
|||
<field name="periodicity" colspan="2"/> |
|||
<field name="periodicity_uom" colspan="2"/> |
|||
<field name="next_execution_date" colspan="2"/> |
|||
<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"/> |
|||
</page> |
|||
</notebook> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="open_kpi_list"> |
|||
<field name="name">Key Performance Indicators</field> |
|||
<field name="res_model">kpi</field> |
|||
<field name="view_type">form</field> |
|||
<field name="view_mode">tree,form</field> |
|||
<field name="view_id" ref="view_kpi_tree"/> |
|||
<field name="search_view_id" ref="view_kpi_filter"/> |
|||
</record> |
|||
|
|||
|
|||
</data> |
|||
</odoo> |
@ -0,0 +1,45 @@ |
|||
<?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> |
|||
|
|||
|
|||
<!-- 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"/> |
|||
</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> |
|||
<field name="arch" type="xml"> |
|||
<form string="Category"> |
|||
<group col="2" colspan="2"> |
|||
<field name="name" colspan="2"/> |
|||
<newline/> |
|||
<field name="description" colspan="2"/> |
|||
</group> |
|||
</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"/> |
|||
</record> |
|||
|
|||
|
|||
</data> |
|||
</odoo> |
@ -0,0 +1,38 @@ |
|||
<?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> |
|||
<!-- KPI history --> |
|||
<record id="view_kpi_history_tree" model="ir.ui.view"> |
|||
<field name="name">kpi.history.tree</field> |
|||
<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"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
<record id="view_kpi_history_form" model="ir.ui.view"> |
|||
<field name="name">kpi.history.form</field> |
|||
<field name="model">kpi.history</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="KPI History"> |
|||
<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"/> |
|||
</group> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
</data> |
|||
</odoo> |
@ -0,0 +1,54 @@ |
|||
<?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> |
|||
<!-- Thresholds --> |
|||
<record id="view_kpi_threshold_tree" model="ir.ui.view"> |
|||
<field name="name">kpi.threshold.tree</field> |
|||
<field name="model">kpi.threshold</field> |
|||
<field name="arch" type="xml"> |
|||
<tree string="Thresholds"> |
|||
<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> |
|||
<field name="arch" type="xml"> |
|||
<form string="Threshold"> |
|||
<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/> |
|||
<field name="invalid_message" nolabel="1" attrs="{'invisible' : [('invalid_message', '=', '')]}" colspan="2"/> |
|||
<newline/> |
|||
</group> |
|||
</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"/> |
|||
</record> |
|||
|
|||
|
|||
</data> |
|||
</odoo> |
@ -0,0 +1,69 @@ |
|||
<?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> |
|||
<!-- Ranges --> |
|||
<record id="view_kpi_threshold_range_tree" model="ir.ui.view"> |
|||
<field name="name">kpi.threshold.range.tree</field> |
|||
<field name="model">kpi.threshold.range</field> |
|||
<field name="arch" type="xml"> |
|||
<tree string="Ranges"> |
|||
<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> |
|||
<field name="arch" type="xml"> |
|||
<form string="Range"> |
|||
<group col="6" colspan="6"> |
|||
<field name="name"/> |
|||
<field name="color"/> |
|||
<field name="company_id" groups="base.group_multi_company"/> |
|||
<newline/> |
|||
|
|||
<separator string="Minimum" colspan="4"/> |
|||
<newline/> |
|||
<field name="min_type" colspan="2"/> |
|||
<field name="min_fixed_value" colspan="2" attrs="{'invisible' : [('min_type', '!=', 'static')]}"/> |
|||
<field name="min_dbsource_id" colspan="2" attrs="{'invisible' : [('min_type', '!=', 'external')]}"/> |
|||
<newline/> |
|||
<field name="min_code" colspan="4" attrs="{'invisible' : [('min_type', 'NOT IN', ('local','external','python'))]}"/> |
|||
<newline/> |
|||
<separator string="Maximum" colspan="4"/> |
|||
<newline/> |
|||
<field name="max_type"/> |
|||
<field name="max_fixed_value" attrs="{'invisible' : [('max_type', '!=', 'static')]}"/> |
|||
<field name="max_dbsource_id" attrs="{'invisible' : [('max_type', '!=', 'external')]}"/> |
|||
<newline/> |
|||
<field name="max_code" colspan="4" attrs="{'invisible' : [('max_type', 'NOT IN', ('local','external','python'))]}"/> |
|||
<newline/> |
|||
<separator string="Thresholds" colspan="4"/> |
|||
<field name="threshold_ids" nolabel="1" colspan="4"/> |
|||
<field name="invalid_message" nolabel="1" attrs="{'invisible' : [('invalid_message', '=', '')]}" colspan="4"/> |
|||
</group> |
|||
</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"/> |
|||
</record> |
|||
|
|||
|
|||
|
|||
</data> |
|||
</odoo> |
@ -0,0 +1,48 @@ |
|||
<?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> |
|||
<menuitem id="menu_reporting_kpi" |
|||
name="KPI" |
|||
action="open_kpi_list" |
|||
sequence="15" |
|||
parent="base.menu_reporting_dashboard" |
|||
groups="base.group_user"/> |
|||
|
|||
<menuitem id="menu_configuration_kpi" |
|||
name="KPI" |
|||
parent="base.menu_reporting_config" |
|||
groups="base.group_user" |
|||
sequence="20"/> |
|||
|
|||
<menuitem id="menu_configuration_kpi_category" |
|||
name="Categories" |
|||
action="open_category_list" |
|||
parent="menu_configuration_kpi" |
|||
groups="base.group_user" |
|||
sequence="10"/> |
|||
|
|||
<menuitem id="menu_configuration_kpi_range" |
|||
name="Ranges" |
|||
action="open_threshold_range_list" |
|||
parent="menu_configuration_kpi" |
|||
groups="base.group_user" |
|||
sequence="20"/> |
|||
|
|||
<menuitem id="menu_configuration_kpi_dbsource" |
|||
name="Data Sources" |
|||
action="base_external_dbsource.action_dbsource" |
|||
parent="menu_configuration_kpi" |
|||
groups="base.group_user" |
|||
sequence="20"/> |
|||
|
|||
<menuitem id="menu_configuration_kpi_threshold" |
|||
name="Thresholds" |
|||
action="open_threshold_list" |
|||
parent="menu_configuration_kpi" |
|||
groups="base.group_user" |
|||
sequence="10"/> |
|||
</data> |
|||
</odoo> |
@ -1,3 +0,0 @@ |
|||
# -*- encoding: utf-8 -*- |
|||
|
|||
from . import mgmtsystem_kpi |
@ -1,70 +0,0 @@ |
|||
# -*- encoding: utf-8 -*- |
|||
############################################################################## |
|||
# |
|||
# OpenERP, Open Source Management Solution |
|||
# Copyright (C) 2012 Savoir-faire Linux (<http://www.savoirfairelinux.com>). |
|||
# |
|||
# This program is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Affero General Public License as |
|||
# published by the Free Software Foundation, either version 3 of the |
|||
# License, or (at your option) any later version. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU Affero General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU Affero General Public License |
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################## |
|||
{ |
|||
"name": "Key Performance Indicator", |
|||
"version": "7.0.1.1.1", |
|||
"author": "Savoir-faire Linux,Odoo Community Association (OCA)", |
|||
"website": "http://www.savoirfairelinux.com", |
|||
"license": "AGPL-3", |
|||
"category": "Management System", |
|||
"complexity": "normal", |
|||
"description": """\ |
|||
This module provides the basis for creating key performance indicators, |
|||
including static and dynamic thresholds (SQL query or Python code), |
|||
on local and remote data sources. |
|||
|
|||
The module also provides the mecanism to update KPIs automatically. |
|||
A scheduler is executed every hour and updates the KPI values, based |
|||
on the periodicity of each KPI. KPI computation can also be done |
|||
manually. |
|||
|
|||
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) |
|||
|
|||
This module depends on: |
|||
* base_external_dbsource (available in lp:openobject-extension) |
|||
* web_color (available in lp:web-addons) |
|||
""", |
|||
"depends": [ |
|||
'mgmtsystem', |
|||
'base_external_dbsource', |
|||
'web_color', |
|||
], |
|||
"data": [ |
|||
'security/ir.model.access.csv', |
|||
'security/mgmtsystem_kpi_security.xml', |
|||
'mgmtsystem_kpi_view.xml', |
|||
], |
|||
"images": [ |
|||
"images/kpi_definition.png", |
|||
"images/kpi_computation.png", |
|||
"images/kpi_threshold.png", |
|||
"images/kpi_range.png", |
|||
], |
|||
"demo": [], |
|||
"test": [], |
|||
'installable': False, |
|||
} |
|||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
@ -1,532 +0,0 @@ |
|||
# -*- encoding: utf-8 -*- |
|||
############################################################################## |
|||
# |
|||
# OpenERP, Open Source Management Solution |
|||
# Copyright (C) 2012 Savoir-faire Linux (<http://www.savoirfairelinux.com>). |
|||
# |
|||
# This program is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Affero General Public License as |
|||
# published by the Free Software Foundation, either version 3 of the |
|||
# License, or (at your option) any later version. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU Affero General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU Affero General Public License |
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################## |
|||
|
|||
from datetime import datetime, timedelta |
|||
from openerp.osv import fields, orm |
|||
from openerp.tools.translate import _ |
|||
from openerp.tools.safe_eval import safe_eval |
|||
from openerp.tools import ( |
|||
DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT, |
|||
) |
|||
import time |
|||
import re |
|||
import logging |
|||
_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(): |
|||
return True |
|||
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', |
|||
)) + ')') |
|||
|
|||
|
|||
def is_select_query(query): |
|||
"""Check if sql query is a SELECT statement""" |
|||
return not RE_SELECT_QUERY.match(query.upper()) |
|||
|
|||
|
|||
class mgmtsystem_kpi_category(orm.Model): |
|||
""" |
|||
KPI Category |
|||
""" |
|||
_name = "mgmtsystem.kpi.category" |
|||
_description = "KPI Category" |
|||
_columns = { |
|||
'name': fields.char('Name', size=50, required=True), |
|||
'description': fields.text('Description') |
|||
} |
|||
|
|||
|
|||
class mgmtsystem_kpi_threshold_range(orm.Model): |
|||
""" |
|||
KPI Threshold Range |
|||
""" |
|||
_name = "mgmtsystem.kpi.threshold.range" |
|||
_description = "KPI Threshold Range" |
|||
|
|||
def compute_min_value(self, cr, uid, ids, field_name, arg, context=None): |
|||
if context is None: |
|||
context = {} |
|||
result = {} |
|||
for obj in self.browse(cr, uid, ids): |
|||
value = None |
|||
if obj.min_type == 'local' and is_select_query(obj.min_code): |
|||
cr.execute(obj.min_code) |
|||
dic = cr.dictfetchall() |
|||
if is_one_value(dic): |
|||
value = dic[0]['value'] |
|||
elif (obj.min_type == 'external' |
|||
and obj.min_dbsource_id.id |
|||
and is_select_query(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 = safe_eval(obj.min_code) |
|||
else: |
|||
value = obj.min_fixed_value |
|||
result[obj.id] = value |
|||
return result |
|||
|
|||
def compute_max_value(self, cr, uid, ids, field_name, arg, context=None): |
|||
if context is None: |
|||
context = {} |
|||
result = {} |
|||
for obj in self.browse(cr, uid, ids, context): |
|||
value = None |
|||
if obj.max_type == 'local' and is_select_query(obj.max_code): |
|||
cr.execute(obj.max_code) |
|||
dic = cr.dictfetchall() |
|||
if is_one_value(dic): |
|||
value = dic[0]['value'] |
|||
elif obj.max_type == 'python': |
|||
value = safe_eval(obj.max_code) |
|||
elif (obj.max_type == 'external' |
|||
and obj.max_dbsource_id.id |
|||
and is_select_query(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'] |
|||
else: |
|||
value = obj.max_fixed_value |
|||
result[obj.id] = value |
|||
return result |
|||
|
|||
def _is_valid_range(self, cr, uid, ids, field_name, arg, context=None): |
|||
if context is None: |
|||
context = {} |
|||
result = {} |
|||
for obj in self.browse(cr, uid, ids, context): |
|||
if obj.max_value < obj.min_value: |
|||
result[obj.id] = False |
|||
else: |
|||
result[obj.id] = True |
|||
return result |
|||
|
|||
def _generate_invalid_message( |
|||
self, cr, uid, ids, field_name, arg, context=None): |
|||
if context is None: |
|||
context = {} |
|||
result = {} |
|||
for obj in self.browse(cr, uid, ids, context): |
|||
if obj.valid: |
|||
result[obj.id] = "" |
|||
else: |
|||
result[obj.id] = ("Minimum value is greater than the maximum " |
|||
"value! Please adjust them.") |
|||
return result |
|||
|
|||
_columns = { |
|||
'name': fields.char('Name', size=50, required=True), |
|||
'valid': fields.function( |
|||
_is_valid_range, |
|||
string='Valid', |
|||
type='boolean', |
|||
required=True, |
|||
), |
|||
'invalid_message': fields.function( |
|||
_generate_invalid_message, |
|||
string='Message', |
|||
type='char', |
|||
size=100, |
|||
), |
|||
'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.function( |
|||
compute_min_value, |
|||
string='Minimum', |
|||
type='float', |
|||
), |
|||
'min_fixed_value': fields.float('Minimum'), |
|||
'min_code': fields.text('Minimum Computation Code'), |
|||
'min_dbsource_id': fields.many2one( |
|||
'base.external.dbsource', |
|||
'External DB Source', |
|||
), |
|||
'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.function( |
|||
compute_max_value, |
|||
string='Maximum', |
|||
type='float', |
|||
), |
|||
'max_fixed_value': fields.float('Maximum'), |
|||
'max_code': fields.text('Maximum Computation Code'), |
|||
'max_dbsource_id': fields.many2one( |
|||
'base.external.dbsource', |
|||
'External DB Source', |
|||
), |
|||
'color': fields.char( |
|||
'Color', |
|||
help='RGB code with #', |
|||
size=7, |
|||
required=True, |
|||
), |
|||
'threshold_ids': fields.many2many( |
|||
'mgmtsystem.kpi.threshold', |
|||
'mgmtsystem_kpi_threshold_range_rel', |
|||
'range_id', |
|||
'threshold_id', |
|||
'Thresholds', |
|||
), |
|||
'company_id': fields.many2one('res.company', 'Company') |
|||
} |
|||
|
|||
_defaults = { |
|||
'company_id': ( |
|||
lambda self, cr, uid, c: |
|||
self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id), |
|||
'valid': True, |
|||
} |
|||
|
|||
|
|||
class mgmtsystem_kpi_threshold(orm.Model): |
|||
""" |
|||
KPI Threshold |
|||
""" |
|||
_name = "mgmtsystem.kpi.threshold" |
|||
_description = "KPI Threshold" |
|||
|
|||
def _is_valid_threshold(self, cr, uid, ids, field_name, arg, context=None): |
|||
if context is None: |
|||
context = {} |
|||
result = {} |
|||
for obj in self.browse(cr, uid, ids, context): |
|||
# check if ranges overlap |
|||
# TODO: This code can be done better |
|||
for range1 in obj.range_ids: |
|||
for range2 in obj.range_ids: |
|||
if (range1.valid and range2.valid |
|||
and range1.min_value < range2.min_value): |
|||
result[obj.id] = range1.max_value <= range2.min_value |
|||
return result |
|||
|
|||
def _generate_invalid_message( |
|||
self, cr, uid, ids, field_name, arg, context=None): |
|||
if context is None: |
|||
context = {} |
|||
result = {} |
|||
for obj in self.browse(cr, uid, ids, context): |
|||
if obj.valid: |
|||
result[obj.id] = "" |
|||
else: |
|||
result[obj.id] = ("2 of your ranges are overlapping! Please " |
|||
"make sure your ranges do not overlap.") |
|||
return result |
|||
|
|||
_columns = { |
|||
'name': fields.char('Name', size=50, required=True), |
|||
'range_ids': fields.many2many( |
|||
'mgmtsystem.kpi.threshold.range', |
|||
'mgmtsystem_kpi_threshold_range_rel', |
|||
'threshold_id', |
|||
'range_id', |
|||
'Ranges' |
|||
), |
|||
'valid': fields.function( |
|||
_is_valid_threshold, |
|||
string='Valid', |
|||
type='boolean', |
|||
required=True, |
|||
), |
|||
'invalid_message': fields.function( |
|||
_generate_invalid_message, |
|||
string='Message', |
|||
type='char', |
|||
size=100, |
|||
), |
|||
'kpi_ids': fields.one2many('mgmtsystem.kpi', 'threshold_id', 'KPIs'), |
|||
'company_id': fields.many2one('res.company', 'Company') |
|||
} |
|||
|
|||
_defaults = { |
|||
'company_id': ( |
|||
lambda self, cr, uid, c: |
|||
self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id), |
|||
'valid': True, |
|||
} |
|||
|
|||
def create(self, cr, uid, data, context=None): |
|||
if context is None: |
|||
context = {} |
|||
|
|||
# check if ranges overlap |
|||
# TODO: This code can be done better |
|||
range_obj1 = self.pool.get('mgmtsystem.kpi.threshold.range') |
|||
range_obj2 = self.pool.get('mgmtsystem.kpi.threshold.range') |
|||
for range1 in data['range_ids'][0][2]: |
|||
range_obj1 = range_obj1.browse(cr, uid, range1, context) |
|||
for range2 in data['range_ids'][0][2]: |
|||
range_obj2 = range_obj2.browse(cr, uid, range2, context) |
|||
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 orm.except_orm( |
|||
_("2 of your ranges are overlapping!"), |
|||
_("Please make sure your ranges do not overlap!") |
|||
) |
|||
range_obj2 = self.pool.get('mgmtsystem.kpi.threshold.range') |
|||
range_obj1 = self.pool.get('mgmtsystem.kpi.threshold.range') |
|||
return super(mgmtsystem_kpi_threshold, self).create( |
|||
cr, uid, data, context |
|||
) |
|||
|
|||
def get_color(self, cr, uid, ids, kpi_value, context=None): |
|||
if context is None: |
|||
context = {} |
|||
|
|||
color = '#FFFFFF' |
|||
for obj in self.browse(cr, uid, ids, context): |
|||
for range_obj in obj.range_ids: |
|||
if (range_obj.min_value <= kpi_value <= range_obj.max_value |
|||
and range_obj.valid): |
|||
color = range_obj.color |
|||
return color |
|||
|
|||
|
|||
class mgmtsystem_kpi_history(orm.Model): |
|||
""" |
|||
History of the KPI |
|||
""" |
|||
_name = "mgmtsystem.kpi.history" |
|||
_description = "History of the KPI" |
|||
|
|||
_columns = { |
|||
'name': fields.char('Name', size=150, required=True), |
|||
'kpi_id': fields.many2one('mgmtsystem.kpi', 'KPI', required=True), |
|||
'date': fields.datetime( |
|||
'Execution Date', |
|||
required=True, |
|||
readonly=True, |
|||
), |
|||
'value': fields.float('Value', required=True, readonly=True), |
|||
'color': fields.text('Color', required=True, readonly=True), |
|||
'company_id': fields.many2one('res.company', 'Company') |
|||
} |
|||
|
|||
_defaults = { |
|||
'company_id': ( |
|||
lambda self, cr, uid, c: |
|||
self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id), |
|||
'name': lambda *a: time.strftime('%d %B %Y'), |
|||
'date': lambda *a: time.strftime(DATETIME_FORMAT), |
|||
'color': '#FFFFFF', |
|||
} |
|||
|
|||
_order = "date desc" |
|||
|
|||
|
|||
class mgmtsystem_kpi(orm.Model): |
|||
""" |
|||
Key Performance Indicators |
|||
""" |
|||
_name = "mgmtsystem.kpi" |
|||
_description = "Key Performance Indicator" |
|||
|
|||
def _display_last_kpi_value( |
|||
self, cr, uid, ids, field_name, arg, context=None): |
|||
if context is None: |
|||
context = {} |
|||
|
|||
result = {} |
|||
for obj in self.browse(cr, uid, ids): |
|||
if obj.history_ids: |
|||
result[obj.id] = obj.history_ids[0].value |
|||
else: |
|||
result[obj.id] = 0 |
|||
|
|||
return result |
|||
|
|||
def compute_kpi_value(self, cr, uid, ids, context=None): |
|||
if context is None: |
|||
context = {} |
|||
for obj in self.browse(cr, uid, ids): |
|||
kpi_value = 0 |
|||
if obj.kpi_type == 'local' and is_select_query(obj.kpi_code): |
|||
cr.execute(obj.kpi_code) |
|||
dic = cr.dictfetchall() |
|||
if is_one_value(dic): |
|||
kpi_value = dic[0]['value'] |
|||
elif (obj.kpi_type == 'external' |
|||
and obj.dbsource_id.id |
|||
and is_select_query(obj.kpi_code)): |
|||
dbsrc_obj = obj.dbsource_id |
|||
res = dbsrc_obj.execute(obj.kpi_code) |
|||
if is_one_value(res): |
|||
kpi_value = res[0]['value'] |
|||
elif obj.kpi_type == 'python': |
|||
kpi_value = safe_eval(obj.kpi_code) |
|||
|
|||
threshold_obj = obj.threshold_id |
|||
values = { |
|||
'kpi_id': obj.id, |
|||
'value': kpi_value, |
|||
'color': threshold_obj.get_color(kpi_value), |
|||
} |
|||
|
|||
history_obj = self.pool.get('mgmtsystem.kpi.history') |
|||
history_id = history_obj.create(cr, uid, values, context=context) |
|||
obj.history_ids.append(history_id) |
|||
|
|||
return True |
|||
|
|||
def update_next_execution_date(self, cr, uid, ids, context=None): |
|||
if context is None: |
|||
context = {} |
|||
|
|||
for obj in self.browse(cr, uid, ids): |
|||
if obj.periodicity_uom == 'hour': |
|||
delta = timedelta(hours=obj.periodicity) |
|||
elif obj.periodicity_uom == 'day': |
|||
delta = timedelta(days=obj.periodicity) |
|||
elif obj.periodicity_uom == 'week': |
|||
delta = timedelta(weeks=obj.periodicity) |
|||
elif obj.periodicity_uom == 'month': |
|||
delta = timedelta(months=obj.periodicity) |
|||
else: |
|||
delta = timedelta() |
|||
new_date = datetime.now() + delta |
|||
|
|||
values = { |
|||
'next_execution_date': new_date.strftime(DATETIME_FORMAT), |
|||
} |
|||
|
|||
obj.write(values) |
|||
|
|||
return True |
|||
|
|||
# Method called by the scheduler |
|||
def update_kpi_value(self, cr, uid, ids=None, context=None): |
|||
if context is None: |
|||
context = {} |
|||
if not ids: |
|||
filters = [ |
|||
'&', |
|||
'|', |
|||
('active', '=', True), |
|||
('next_execution_date', '<=', |
|||
datetime.now().strftime(DATETIME_FORMAT)), |
|||
('next_execution_date', '=', False), |
|||
] |
|||
if 'filters' in context: |
|||
filters.extend(context['filters']) |
|||
ids = self.search(cr, uid, filters, context=context) |
|||
res = None |
|||
|
|||
try: |
|||
res = self.compute_kpi_value(cr, uid, ids, context=context) |
|||
self.update_next_execution_date(cr, uid, ids, context=context) |
|||
except Exception: |
|||
_logger.exception("Failed updating KPI values") |
|||
|
|||
return res |
|||
|
|||
_columns = { |
|||
'name': fields.char('Name', size=50, required=True), |
|||
'description': fields.text('Description'), |
|||
'category_id': fields.many2one( |
|||
'mgmtsystem.kpi.category', |
|||
'Category', |
|||
required=True, |
|||
), |
|||
'threshold_id': fields.many2one( |
|||
'mgmtsystem.kpi.threshold', |
|||
'Threshold', |
|||
required=True, |
|||
), |
|||
'periodicity': fields.integer('Periodicity'), |
|||
'periodicity_uom': fields.selection(( |
|||
('hour', 'Hour'), |
|||
('day', 'Day'), |
|||
('week', 'Week'), |
|||
('month', 'Month') |
|||
), 'Periodicity UoM', required=True), |
|||
'next_execution_date': fields.datetime( |
|||
'Next execution date', |
|||
readonly=True, |
|||
), |
|||
'value': fields.function( |
|||
_display_last_kpi_value, |
|||
string='Value', |
|||
type='float', |
|||
), |
|||
'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', |
|||
), |
|||
'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( |
|||
'mgmtsystem.kpi.history', |
|||
'kpi_id', |
|||
'History', |
|||
), |
|||
'active': fields.boolean( |
|||
'Active', |
|||
help=("Only active KPIs will be updated by the scheduler based on" |
|||
" the periodicity configuration."), |
|||
), |
|||
'company_id': fields.many2one('res.company', 'Company') |
|||
} |
|||
|
|||
_defaults = { |
|||
'company_id': ( |
|||
lambda self, cr, uid, c: |
|||
self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id), |
|||
'active': True, |
|||
'periodicity': 1, |
|||
'periodicity_uom': 'day', |
|||
} |
@ -1,300 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<openerp> |
|||
<data> |
|||
|
|||
<!-- KPI --> |
|||
|
|||
<record id="view_mgmtsystem_kpi_tree" model="ir.ui.view"> |
|||
<field name="name">mgmtsystem.kpi.tree</field> |
|||
<field name="model">mgmtsystem.kpi</field> |
|||
<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" group="base.group_multi_company"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_mgmtsystem_kpi_filter" model="ir.ui.view"> |
|||
<field name="name">mgmtsystem.kpi.filter</field> |
|||
<field name="model">mgmtsystem.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"/> |
|||
</group> |
|||
<newline /> |
|||
<group expand="0" string="Group By..."> |
|||
<filter string="Category" context="{'group_by':'category_id'}"/> |
|||
<filter string="Type" context="{'group_by':'kpi_type'}"/> |
|||
</group> |
|||
</search> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_mgmtsystem_kpi_form" model="ir.ui.view"> |
|||
<field name="name">mgmtsystem.kpi.form</field> |
|||
<field name="model">mgmtsystem.kpi</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="Key Performance Indicator"> |
|||
<group col="6" colspan="6"> |
|||
<field name="name" colspan="2"/> |
|||
<field name="threshold_id" colspan="2"/> |
|||
<field name="category_id" colspan="2"/> |
|||
<newline/> |
|||
<field name="value" colspan="2"/> |
|||
<button name="compute_kpi_value" string="Compute KPI Now" colspan="2" type="object"/> |
|||
<field name="active" colspan="2"/> |
|||
<field name="company_id" group="base.group_multi_company"/> |
|||
</group> |
|||
<notebook colspan="6"> |
|||
<page string="History"> |
|||
<field name="history_ids" readonly="1" nolabel="1"/> |
|||
</page> |
|||
<page string="Computation"> |
|||
<group col="6"> |
|||
<field name="periodicity" colspan="2"/> |
|||
<field name="periodicity_uom" colspan="2"/> |
|||
<field name="next_execution_date" colspan="2"/> |
|||
<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"/> |
|||
</page> |
|||
</notebook> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="open_mgmtsystem_kpi_list"> |
|||
<field name="name">Key Performance Indicators</field> |
|||
<field name="res_model">mgmtsystem.kpi</field> |
|||
<field name="view_type">form</field> |
|||
<field name="view_mode">tree,form</field> |
|||
<field name="view_id" ref="view_mgmtsystem_kpi_tree"/> |
|||
<field name="search_view_id" ref="view_mgmtsystem_kpi_filter"/> |
|||
</record> |
|||
|
|||
<menuitem id="menu_mgmtsystem_kpi" |
|||
name="KPI" |
|||
action="open_mgmtsystem_kpi_list" |
|||
sequence="15" |
|||
parent="mgmtsystem.menu_mgmtsystem_main" |
|||
groups="base.group_user"/> |
|||
|
|||
<!-- KPI history --> |
|||
|
|||
<record id="view_mgmtsystem_kpi_history_tree" model="ir.ui.view"> |
|||
<field name="name">mgmtsystem.kpi.history.tree</field> |
|||
<field name="model">mgmtsystem.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" group="base.group_multi_company"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<!-- Configuration menu --> |
|||
|
|||
<menuitem id="menu_mgmtsystem_configuration_kpi" |
|||
name="KPI" |
|||
parent="mgmtsystem.menu_mgmtsystem_configuration" |
|||
groups="mgmtsystem.group_mgmtsystem_manager" |
|||
sequence="20"/> |
|||
|
|||
<!-- Categories --> |
|||
|
|||
<record id="view_mgmtsystem_kpi_category_tree" model="ir.ui.view"> |
|||
<field name="name">mgmtsystem.kpi.category.tree</field> |
|||
<field name="model">mgmtsystem.kpi.category</field> |
|||
<field name="arch" type="xml"> |
|||
<tree string="Categories"> |
|||
<field name="name"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_mgmtsystem_kpi_category_form" model="ir.ui.view"> |
|||
<field name="name">mgmtsystem.kpi.category.form</field> |
|||
<field name="model">mgmtsystem.kpi.category</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="Category"> |
|||
<field name="name"/> |
|||
<newline/> |
|||
<field name="description" colspan="4"/> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="open_mgmtsystem_category_list"> |
|||
<field name="name">Categories</field> |
|||
<field name="res_model">mgmtsystem.kpi.category</field> |
|||
<field name="view_type">form</field> |
|||
<field name="view_mode">tree,form</field> |
|||
<field name="view_id" ref="view_mgmtsystem_kpi_category_tree"/> |
|||
</record> |
|||
|
|||
<menuitem id="menu_mgmtsystem_configuration_kpi_category" |
|||
name="Categories" |
|||
action="open_mgmtsystem_category_list" |
|||
parent="menu_mgmtsystem_configuration_kpi" |
|||
groups="mgmtsystem.group_mgmtsystem_manager" |
|||
sequence="10"/> |
|||
|
|||
<!-- Thresholds --> |
|||
|
|||
<record id="view_mgmtsystem_kpi_threshold_tree" model="ir.ui.view"> |
|||
<field name="name">mgmtsystem.kpi.threshold.tree</field> |
|||
<field name="model">mgmtsystem.kpi.threshold</field> |
|||
<field name="arch" type="xml"> |
|||
<tree string="Thresholds"> |
|||
<field name="name"/> |
|||
<field name="invalid_message"/> |
|||
<field name="company_id" group="base.group_multi_company"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_mgmtsystem_kpi_threshold_form" model="ir.ui.view"> |
|||
<field name="name">mgmtsystem.kpi.threshold.form</field> |
|||
<field name="model">mgmtsystem.kpi.threshold</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="Threshold"> |
|||
<field name="name"/> |
|||
<newline/> |
|||
<separator string="Ranges" colspan="2"/> |
|||
<newline/> |
|||
<field name="range_ids" nolabel="1" colspan="2"/> |
|||
<newline/> |
|||
<separator string="KPIs" colspan="2"/> |
|||
<newline/> |
|||
<field name="kpi_ids" nolabel="1" colspan="2"/> |
|||
<newline/> |
|||
<field name="invalid_message" nolabel="1" attrs="{'invisible' : [('invalid_message', '=', '')]}" colspan="2"/> |
|||
<newline/> |
|||
<field name="company_id" group="base.group_multi_company"/> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="open_mgmtsystem_threshold_list"> |
|||
<field name="name">Thresholds</field> |
|||
<field name="res_model">mgmtsystem.kpi.threshold</field> |
|||
<field name="view_type">form</field> |
|||
<field name="view_mode">tree,form</field> |
|||
<field name="view_id" ref="view_mgmtsystem_kpi_threshold_tree"/> |
|||
</record> |
|||
|
|||
<menuitem id="menu_mgmtsystem_configuration_kpi_threshold" |
|||
name="Thresholds" |
|||
action="open_mgmtsystem_threshold_list" |
|||
parent="menu_mgmtsystem_configuration_kpi" |
|||
groups="mgmtsystem.group_mgmtsystem_manager" |
|||
sequence="10"/> |
|||
|
|||
<!-- Ranges --> |
|||
|
|||
<record id="view_mgmtsystem_kpi_threshold_range_tree" model="ir.ui.view"> |
|||
<field name="name">mgmtsystem.kpi.threshold.range.tree</field> |
|||
<field name="model">mgmtsystem.kpi.threshold.range</field> |
|||
<field name="arch" type="xml"> |
|||
<tree string="Ranges"> |
|||
<field name="name"/> |
|||
<field name="min_value"/> |
|||
<field name="max_value"/> |
|||
<field name="color" widget="color"/> |
|||
<field name="invalid_message"/> |
|||
<field name="company_id" group="base.group_multi_company"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_mgmtsystem_kpi_threshold_range_form" model="ir.ui.view"> |
|||
<field name="name">mgmtsystem.kpi.threshold.range.form</field> |
|||
<field name="model">mgmtsystem.kpi.threshold.range</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="Range"> |
|||
<field name="name"/> |
|||
<field name="color"/> |
|||
<field name="company_id" group="base.group_multi_company"/> |
|||
<newline/> |
|||
<separator string="Minimum" colspan="4"/> |
|||
<newline/> |
|||
<field name="min_type"/> |
|||
<field name="min_fixed_value" attrs="{'invisible' : [('min_type', '!=', 'static')]}"/> |
|||
<field name="min_dbsource_id" attrs="{'invisible' : [('min_type', '!=', 'external')]}"/> |
|||
<newline/> |
|||
<field name="min_code" colspan="4" attrs="{'invisible' : [('min_type', 'NOT IN', ('local','external','python'))]}"/> |
|||
<newline/> |
|||
<separator string="Maximum" colspan="4"/> |
|||
<newline/> |
|||
<field name="max_type"/> |
|||
<field name="max_fixed_value" attrs="{'invisible' : [('max_type', '!=', 'static')]}"/> |
|||
<field name="max_dbsource_id" attrs="{'invisible' : [('max_type', '!=', 'external')]}"/> |
|||
<newline/> |
|||
<field name="max_code" colspan="4" attrs="{'invisible' : [('max_type', 'NOT IN', ('local','external','python'))]}"/> |
|||
<newline/> |
|||
<separator string="Thresholds" colspan="4"/> |
|||
<field name="threshold_ids" nolabel="1" colspan="4"/> |
|||
<field name="invalid_message" nolabel="1" attrs="{'invisible' : [('invalid_message', '=', '')]}" colspan="4"/> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="open_mgmtsystem_threshold_range_list"> |
|||
<field name="name">Ranges</field> |
|||
<field name="res_model">mgmtsystem.kpi.threshold.range</field> |
|||
<field name="view_type">form</field> |
|||
<field name="view_mode">tree,form</field> |
|||
<field name="view_id" ref="view_mgmtsystem_kpi_threshold_range_tree"/> |
|||
</record> |
|||
|
|||
<menuitem id="menu_mgmtsystem_configuration_kpi_range" |
|||
name="Ranges" |
|||
action="open_mgmtsystem_threshold_range_list" |
|||
parent="menu_mgmtsystem_configuration_kpi" |
|||
groups="mgmtsystem.group_mgmtsystem_manager" |
|||
sequence="20"/> |
|||
|
|||
<menuitem id="menu_mgmtsystem_configuration_kpi_dbsource" |
|||
name="Data Sources" |
|||
action="base_external_dbsource.action_dbsource" |
|||
parent="menu_mgmtsystem_configuration_kpi" |
|||
groups="mgmtsystem.group_mgmtsystem_manager" |
|||
sequence="20"/> |
|||
|
|||
</data> |
|||
|
|||
<data noupdate="1"> |
|||
|
|||
<record forcecreate="True" id="ir_cron_mgmtsystem_kpi_action" model="ir.cron"> |
|||
<field name="name">Update KPI values</field> |
|||
<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 eval="False" name="doall"/> |
|||
<field eval="'mgmtsystem.kpi'" name="model"/> |
|||
<field eval="'update_kpi_value'" name="function"/> |
|||
<field eval="'()'" name="args"/> |
|||
</record> |
|||
|
|||
</data> |
|||
</openerp> |
@ -1,11 +0,0 @@ |
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
|||
"access_mgmtsystem_kpi_user","mgmtsystem.kpi.user","model_mgmtsystem_kpi","base.group_user",1,0,0,0 |
|||
"access_mgmtsystem_kpi_history_user","mgmtsystem.kpi.history.user","model_mgmtsystem_kpi_history","base.group_user",1,0,0,0 |
|||
"access_mgmtsystem_kpi_category_user","mgmtsystem.kpi.category.user","model_mgmtsystem_kpi_category","base.group_user",1,0,0,0 |
|||
"access_mgmtsystem_kpi_threshold_user","mgmtsystem.kpi.threshold.user","model_mgmtsystem_kpi_threshold","base.group_user",1,0,0,0 |
|||
"access_mgmtsystem_kpi_threshold_range_user","mgmtsystem.kpi.threshold.range.user","model_mgmtsystem_kpi_threshold_range","base.group_user",1,0,0,0 |
|||
"access_mgmtsystem_kpi_manager","mgmtsystem.kpi.manager","model_mgmtsystem_kpi","mgmtsystem.group_mgmtsystem_manager",1,1,1,1 |
|||
"access_mgmtsystem_kpi_category_manager","mgmtsystem.kpi.category.manager","model_mgmtsystem_kpi_category","mgmtsystem.group_mgmtsystem_manager",1,1,1,1 |
|||
"access_mgmtsystem_kpi_threshold_manager","mgmtsystem.kpi.threshold.manager","model_mgmtsystem_kpi_threshold","mgmtsystem.group_mgmtsystem_manager",1,1,1,1 |
|||
"access_mgmtsystem_kpi_threshold_range_manager","mgmtsystem.kpi.threshold.range.manager","model_mgmtsystem_kpi_threshold_range","mgmtsystem.group_mgmtsystem_manager",1,1,1,1 |
|||
"access_base_external_dbsource_mgmtsystem_manager","base.external.dbsource.manager","base_external_dbsource.model_base_external_dbsource","mgmtsystem.group_mgmtsystem_manager",1,1,1,1 |
@ -1,36 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<openerp> |
|||
<data noupdate="1"> |
|||
|
|||
<!-- Rule --> |
|||
|
|||
<record model="ir.rule" id="mgmtsystem_kpi_rule"> |
|||
<field name="name">mgmtsystem_kpi multi-company</field> |
|||
<field name="model_id" ref="model_mgmtsystem_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="mgmtsystem_kpi_threshold_range_rule"> |
|||
<field name="name">mgmtsystem_kpi_threshold_range multi-company</field> |
|||
<field name="model_id" ref="model_mgmtsystem_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="mgmtsystem_kpi_threshold_rule"> |
|||
<field name="name">mgmtsystem_kpi_threshold multi-company</field> |
|||
<field name="model_id" ref="model_mgmtsystem_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="mgmtsystem_kpi_history_rule"> |
|||
<field name="name">mgmtsystem_kpi_history multi-company</field> |
|||
<field name="model_id" ref="model_mgmtsystem_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> |
|||
</openerp> |