From 40694c42d4724c96eb707528b224f06f0474a1fa Mon Sep 17 00:00:00 2001 From: Gervais Naoussi Date: Fri, 2 Dec 2016 08:43:56 -0500 Subject: [PATCH] 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 corrected --- kpi/README.rst | 77 +++ kpi/__init__.py | 5 + kpi/__openerp__.py | 33 ++ kpi/data/kpi.xml | 18 + {mgmtsystem_kpi => kpi}/i18n/fr.po | 0 .../mgmtsystem_kpi.pot => kpi/i18n/kpi.pot | 0 {mgmtsystem_kpi => kpi}/i18n/nb.po | 0 {mgmtsystem_kpi => kpi}/i18n/pt_BR.po | 0 {mgmtsystem_kpi => kpi}/i18n/vi.po | 0 .../images/kpi_computation.png | Bin .../images/kpi_definition.png | Bin {mgmtsystem_kpi => kpi}/images/kpi_range.png | Bin .../images/kpi_threshold.png | Bin kpi/models/__init__.py | 9 + kpi/models/kpi.py | 189 +++++++ kpi/models/kpi_category.py | 14 + kpi/models/kpi_history.py | 29 + kpi/models/kpi_threshold.py | 85 +++ kpi/models/kpi_threshold_range.py | 161 ++++++ kpi/security/ir.model.access.csv | 11 + kpi/security/kpi_security.xml | 39 ++ .../static/src/img/icon.png | Bin kpi/views/kpi.xml | 97 ++++ kpi/views/kpi_category.xml | 45 ++ kpi/views/kpi_history.xml | 38 ++ kpi/views/kpi_threshold.xml | 54 ++ kpi/views/kpi_threshold_range.xml | 69 +++ kpi/views/menu.xml | 48 ++ mgmtsystem_kpi/__init__.py | 3 - mgmtsystem_kpi/__openerp__.py | 70 --- mgmtsystem_kpi/mgmtsystem_kpi.py | 532 ------------------ mgmtsystem_kpi/mgmtsystem_kpi_view.xml | 300 ---------- mgmtsystem_kpi/security/ir.model.access.csv | 11 - .../security/mgmtsystem_kpi_security.xml | 36 -- 34 files changed, 1021 insertions(+), 952 deletions(-) create mode 100644 kpi/README.rst create mode 100644 kpi/__init__.py create mode 100644 kpi/__openerp__.py create mode 100644 kpi/data/kpi.xml rename {mgmtsystem_kpi => kpi}/i18n/fr.po (100%) rename mgmtsystem_kpi/i18n/mgmtsystem_kpi.pot => kpi/i18n/kpi.pot (100%) rename {mgmtsystem_kpi => kpi}/i18n/nb.po (100%) rename {mgmtsystem_kpi => kpi}/i18n/pt_BR.po (100%) rename {mgmtsystem_kpi => kpi}/i18n/vi.po (100%) rename {mgmtsystem_kpi => kpi}/images/kpi_computation.png (100%) rename {mgmtsystem_kpi => kpi}/images/kpi_definition.png (100%) rename {mgmtsystem_kpi => kpi}/images/kpi_range.png (100%) rename {mgmtsystem_kpi => kpi}/images/kpi_threshold.png (100%) create mode 100644 kpi/models/__init__.py create mode 100644 kpi/models/kpi.py create mode 100644 kpi/models/kpi_category.py create mode 100644 kpi/models/kpi_history.py create mode 100644 kpi/models/kpi_threshold.py create mode 100644 kpi/models/kpi_threshold_range.py create mode 100644 kpi/security/ir.model.access.csv create mode 100755 kpi/security/kpi_security.xml rename {mgmtsystem_kpi => kpi}/static/src/img/icon.png (100%) create mode 100644 kpi/views/kpi.xml create mode 100644 kpi/views/kpi_category.xml create mode 100644 kpi/views/kpi_history.xml create mode 100644 kpi/views/kpi_threshold.xml create mode 100644 kpi/views/kpi_threshold_range.xml create mode 100644 kpi/views/menu.xml delete mode 100755 mgmtsystem_kpi/__init__.py delete mode 100644 mgmtsystem_kpi/__openerp__.py delete mode 100755 mgmtsystem_kpi/mgmtsystem_kpi.py delete mode 100644 mgmtsystem_kpi/mgmtsystem_kpi_view.xml delete mode 100644 mgmtsystem_kpi/security/ir.model.access.csv delete mode 100755 mgmtsystem_kpi/security/mgmtsystem_kpi_security.xml diff --git a/kpi/README.rst b/kpi/README.rst new file mode 100644 index 000000000..ebc356eb9 --- /dev/null +++ b/kpi/README.rst @@ -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 `_. +In case of trouble, please check there if your issue has already been reported. + +Credits +======= + +Contributors +------------ + +* Daniel Reis +* Glen Dromgoole +* Loic Lacroix +* Sandy Carter +* Gervais Naoussi + +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. diff --git a/kpi/__init__.py b/kpi/__init__.py new file mode 100644 index 000000000..bdb4c9bb3 --- /dev/null +++ b/kpi/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2012 - Now Savoir-faire Linux +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/kpi/__openerp__.py b/kpi/__openerp__.py new file mode 100644 index 000000000..d966a733a --- /dev/null +++ b/kpi/__openerp__.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Copyright 2012 - Now Savoir-faire Linux +# 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, +} diff --git a/kpi/data/kpi.xml b/kpi/data/kpi.xml new file mode 100644 index 000000000..f32a79864 --- /dev/null +++ b/kpi/data/kpi.xml @@ -0,0 +1,18 @@ + + + + + + Update KPI values + + 1 + hours + -1 + + + + + + + diff --git a/mgmtsystem_kpi/i18n/fr.po b/kpi/i18n/fr.po similarity index 100% rename from mgmtsystem_kpi/i18n/fr.po rename to kpi/i18n/fr.po diff --git a/mgmtsystem_kpi/i18n/mgmtsystem_kpi.pot b/kpi/i18n/kpi.pot similarity index 100% rename from mgmtsystem_kpi/i18n/mgmtsystem_kpi.pot rename to kpi/i18n/kpi.pot diff --git a/mgmtsystem_kpi/i18n/nb.po b/kpi/i18n/nb.po similarity index 100% rename from mgmtsystem_kpi/i18n/nb.po rename to kpi/i18n/nb.po diff --git a/mgmtsystem_kpi/i18n/pt_BR.po b/kpi/i18n/pt_BR.po similarity index 100% rename from mgmtsystem_kpi/i18n/pt_BR.po rename to kpi/i18n/pt_BR.po diff --git a/mgmtsystem_kpi/i18n/vi.po b/kpi/i18n/vi.po similarity index 100% rename from mgmtsystem_kpi/i18n/vi.po rename to kpi/i18n/vi.po diff --git a/mgmtsystem_kpi/images/kpi_computation.png b/kpi/images/kpi_computation.png similarity index 100% rename from mgmtsystem_kpi/images/kpi_computation.png rename to kpi/images/kpi_computation.png diff --git a/mgmtsystem_kpi/images/kpi_definition.png b/kpi/images/kpi_definition.png similarity index 100% rename from mgmtsystem_kpi/images/kpi_definition.png rename to kpi/images/kpi_definition.png diff --git a/mgmtsystem_kpi/images/kpi_range.png b/kpi/images/kpi_range.png similarity index 100% rename from mgmtsystem_kpi/images/kpi_range.png rename to kpi/images/kpi_range.png diff --git a/mgmtsystem_kpi/images/kpi_threshold.png b/kpi/images/kpi_threshold.png similarity index 100% rename from mgmtsystem_kpi/images/kpi_threshold.png rename to kpi/images/kpi_threshold.png diff --git a/kpi/models/__init__.py b/kpi/models/__init__.py new file mode 100644 index 000000000..f46e81d90 --- /dev/null +++ b/kpi/models/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright 2012 - Now Savoir-faire Linux +# 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 diff --git a/kpi/models/kpi.py b/kpi/models/kpi.py new file mode 100644 index 000000000..f4c1a9af7 --- /dev/null +++ b/kpi/models/kpi.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +# Copyright 2012 - Now Savoir-faire Linux +# 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 diff --git a/kpi/models/kpi_category.py b/kpi/models/kpi_category.py new file mode 100644 index 000000000..2c34c9c6b --- /dev/null +++ b/kpi/models/kpi_category.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Copyright 2012 - Now Savoir-faire Linux +# 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') diff --git a/kpi/models/kpi_history.py b/kpi/models/kpi_history.py new file mode 100644 index 000000000..598431560 --- /dev/null +++ b/kpi/models/kpi_history.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright 2012 - Now Savoir-faire Linux +# 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) diff --git a/kpi/models/kpi_threshold.py b/kpi/models/kpi_threshold.py new file mode 100644 index 000000000..09625f42d --- /dev/null +++ b/kpi/models/kpi_threshold.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# Copyright 2012 - Now Savoir-faire Linux +# 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 diff --git a/kpi/models/kpi_threshold_range.py b/kpi/models/kpi_threshold_range.py new file mode 100644 index 000000000..8d5653f1d --- /dev/null +++ b/kpi/models/kpi_threshold_range.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +# Copyright 2012 - Now Savoir-faire Linux +# 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 diff --git a/kpi/security/ir.model.access.csv b/kpi/security/ir.model.access.csv new file mode 100644 index 000000000..59ff6e6a2 --- /dev/null +++ b/kpi/security/ir.model.access.csv @@ -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 diff --git a/kpi/security/kpi_security.xml b/kpi/security/kpi_security.xml new file mode 100755 index 000000000..64153cbb5 --- /dev/null +++ b/kpi/security/kpi_security.xml @@ -0,0 +1,39 @@ + + + + + + + + + + kpi multi-company + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + + kpi_threshold_range multi-company + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + + kpi_threshold multi-company + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + + kpi_history multi-company + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + + diff --git a/mgmtsystem_kpi/static/src/img/icon.png b/kpi/static/src/img/icon.png similarity index 100% rename from mgmtsystem_kpi/static/src/img/icon.png rename to kpi/static/src/img/icon.png diff --git a/kpi/views/kpi.xml b/kpi/views/kpi.xml new file mode 100644 index 000000000..ae7a9054e --- /dev/null +++ b/kpi/views/kpi.xml @@ -0,0 +1,97 @@ + + + + + + + kpi.tree + kpi + + + + + + + + + + + + + kpi.filter + kpi + + + + + + + + + + + + + + + + + + + + kpi.form + kpi + +
+ + + + + + +