Browse Source

Merge pull request #1095 from ivantodorovich/9.0-fix

[9.0][kpi] FIX #1093
pull/1232/head
Maxime Chambreuil 7 years ago
committed by GitHub
parent
commit
a0ffec92ae
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      kpi/README.rst
  2. 2
      kpi/__openerp__.py
  3. 30
      kpi/models/kpi_threshold.py
  4. 107
      kpi/models/kpi_threshold_range.py
  5. 4
      kpi/tests/__init__.py
  6. 87
      kpi/tests/test_kpi.py
  7. 64
      kpi/views/kpi.xml
  8. 12
      kpi/views/kpi_category.xml
  9. 18
      kpi/views/kpi_history.xml
  10. 35
      kpi/views/kpi_threshold.xml
  11. 59
      kpi/views/kpi_threshold_range.xml

1
kpi/README.rst

@ -60,6 +60,7 @@ Contributors
* Loic Lacroix <loic.lacroix@savoirfairelinux.com> * Loic Lacroix <loic.lacroix@savoirfairelinux.com>
* Sandy Carter <sandy.carter@savoirfairelinux.com> * Sandy Carter <sandy.carter@savoirfairelinux.com>
* Gervais Naoussi <gervaisnaoussi@gmail.com> * Gervais Naoussi <gervaisnaoussi@gmail.com>
* Iván Todorovich <ivan.todorovich@gmail.com>
Maintainer Maintainer
---------- ----------

2
kpi/__openerp__.py

@ -4,7 +4,7 @@
{ {
"name": "Key Performance Indicator", "name": "Key Performance Indicator",
"version": "9.0.1.0.0",
"version": "9.0.1.1.0",
"author": "Savoir-faire Linux,Odoo Community Association (OCA)", "author": "Savoir-faire Linux,Odoo Community Association (OCA)",
"website": "http://www.savoirfairelinux.com", "website": "http://www.savoirfairelinux.com",
"license": "AGPL-3", "license": "AGPL-3",

30
kpi/models/kpi_threshold.py

@ -13,27 +13,25 @@ class KPIThreshold(models.Model):
@api.multi @api.multi
def _compute_is_valid_threshold(self): def _compute_is_valid_threshold(self):
result = {}
for obj in self: for obj in self:
# check if ranges overlap # check if ranges overlap
# TODO: This code can be done better # TODO: This code can be done better
obj.valid = True
for range1 in obj.range_ids: 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 not range1.valid:
obj.valid = False
break
for range2 in (obj.range_ids-range1):
if (range1.max_value >= range2.min_value and
range1.min_value <= range2.max_value):
obj.valid = False
break
if obj.valid: if obj.valid:
result[obj.id] = ""
obj.invalid_message = None
else: else:
result[obj.id] = ("Two of your ranges are overlapping. Please "
"make sure your ranges do not overlap.")
return result
obj.invalid_message = (
"Some ranges are invalid or overlapping. "
"Please make sure your ranges do not overlap.")
name = fields.Char('Name', size=50, required=True) name = fields.Char('Name', size=50, required=True)
range_ids = fields.Many2many( range_ids = fields.Many2many(
@ -46,7 +44,7 @@ class KPIThreshold(models.Model):
valid = fields.Boolean(string='Valid', required=True, valid = fields.Boolean(string='Valid', required=True,
compute="_compute_is_valid_threshold", default=True) compute="_compute_is_valid_threshold", default=True)
invalid_message = fields.Char(string='Message', size=100, invalid_message = fields.Char(string='Message', size=100,
compute="_compute_generate_invalid_message")
compute="_compute_is_valid_threshold")
kpi_ids = fields.One2many('kpi', 'threshold_id', 'KPIs') kpi_ids = fields.One2many('kpi', 'threshold_id', 'KPIs')
company_id = fields.Many2one( company_id = fields.Many2one(
'res.company', 'Company', 'res.company', 'Company',

107
kpi/models/kpi_threshold_range.py

@ -46,7 +46,7 @@ class KPIThresholdRange(models.Model):
valid = fields.Boolean(string='Valid', required=True, valid = fields.Boolean(string='Valid', required=True,
compute="_compute_is_valid_range", default=True) compute="_compute_is_valid_range", default=True)
invalid_message = fields.Char(string='Message', size=100, invalid_message = fields.Char(string='Message', size=100,
compute="_compute_generate_invalid_message")
compute="_compute_is_valid_range")
min_type = fields.Selection(( min_type = fields.Selection((
('static', 'Fixed value'), ('static', 'Fixed value'),
('python', 'Python Code'), ('python', 'Python Code'),
@ -56,6 +56,7 @@ class KPIThresholdRange(models.Model):
min_value = fields.Float(string='Minimum', compute="_compute_min_value") min_value = fields.Float(string='Minimum', compute="_compute_min_value")
min_fixed_value = fields.Float('Minimum') min_fixed_value = fields.Float('Minimum')
min_code = fields.Text('Minimum Computation Code') min_code = fields.Text('Minimum Computation Code')
min_error = fields.Char('Error', compute="_compute_min_value")
min_dbsource_id = fields.Many2one( min_dbsource_id = fields.Many2one(
'base.external.dbsource', 'base.external.dbsource',
'External DB Source', 'External DB Source',
@ -69,6 +70,7 @@ class KPIThresholdRange(models.Model):
max_value = fields.Float(string='Maximum', compute="_compute_max_value") max_value = fields.Float(string='Maximum', compute="_compute_max_value")
max_fixed_value = fields.Float('Maximum') max_fixed_value = fields.Float('Maximum')
max_code = fields.Text('Maximum Computation Code') max_code = fields.Text('Maximum Computation Code')
max_error = fields.Char('Error', compute="_compute_max_value")
max_dbsource_id = fields.Many2one( max_dbsource_id = fields.Many2one(
'base.external.dbsource', 'base.external.dbsource',
'External DB Source', 'External DB Source',
@ -92,70 +94,73 @@ class KPIThresholdRange(models.Model):
@api.multi @api.multi
def _compute_min_value(self): def _compute_min_value(self):
result = {}
for obj in self: for obj in self:
value = None 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
error = None
try:
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
except Exception as e:
value = None
error = str(e)
obj.min_value = value obj.min_value = value
return result
obj.min_error = error
@api.multi @api.multi
def _compute_max_value(self): def _compute_max_value(self):
result = {}
for obj in self: for obj in self:
value = None 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
error = None
try:
if obj.max_type == 'local' and is_sql_or_ddl_statement(
obj.max_code):
self.env.cr.execute(obj.max_code)
dic = self.env.cr.dictfetchall()
if is_one_value(dic):
value = dic[0]['value']
elif (obj.max_type == 'external' and obj.max_dbsource_id.id and
is_sql_or_ddl_statement(obj.max_code)):
dbsrc_obj = obj.max_dbsource_id
res = dbsrc_obj.execute(obj.max_code)
if is_one_value(res):
value = res[0]['value']
elif obj.max_type == 'python':
value = safe_eval(obj.max_code)
else:
value = obj.max_fixed_value
except Exception as e:
value = None
error = str(e)
obj.max_value = value obj.max_value = value
return result
obj.max_error = error
@api.multi @api.multi
def _compute_is_valid_range(self): def _compute_is_valid_range(self):
result = {}
for obj in self: for obj in self:
if obj.max_value < obj.min_value:
if obj.min_error or obj.max_error:
obj.valid = False
obj.invalid_message = (
"Either minimum or maximum value has "
"computation errors. Please fix them.")
elif obj.max_value < obj.min_value:
obj.valid = False 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 = ( obj.invalid_message = (
"Minimum value is greater than the maximum " "Minimum value is greater than the maximum "
"value! Please adjust them.") "value! Please adjust them.")
return result
else:
obj.valid = True
obj.invalid_message = ""

4
kpi/tests/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import test_kpi

87
kpi/tests/test_kpi.py

@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp.tests.common import TransactionCase
class TestKPI(TransactionCase):
def setUp(self):
super(TestKPI, self).setUp()
def test_invalid_threshold_range(self):
range1 = self.env['kpi.threshold.range'].create({
'name': 'Range1',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 3,
'max_fixed_value': 1,
})
range2 = self.env['kpi.threshold.range'].create({
'name': 'Range2',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 4,
'max_fixed_value': 10,
})
self.assertFalse(range1.valid)
self.assertTrue(range2.valid)
def test_invalid_threshold(self):
range1 = self.env['kpi.threshold.range'].create({
'name': 'Range1',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 1,
'max_fixed_value': 4,
})
range2 = self.env['kpi.threshold.range'].create({
'name': 'Range2',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 4,
'max_fixed_value': 10,
})
range3 = self.env['kpi.threshold.range'].create({
'name': 'Range3',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 1,
'max_fixed_value': 3,
})
range_invalid = self.env['kpi.threshold.range'].create({
'name': 'RangeInvalid',
'min_type': 'static',
'max_type': 'static',
'min_fixed_value': 3,
'max_fixed_value': 1,
})
threshold1 = self.env['kpi.threshold'].create({
'name': 'Threshold1',
'range_ids': [(6, 0, [range1.id, range2.id])],
})
threshold2 = self.env['kpi.threshold'].create({
'name': 'Threshold1',
'range_ids': [(6, 0, [range3.id, range2.id])],
})
threshold3 = self.env['kpi.threshold'].create({
'name': 'Threshold1',
'range_ids': [(6, 0, [range_invalid.id, range2.id])],
})
self.assertFalse(threshold1.valid)
self.assertTrue(threshold2.valid)
self.assertFalse(threshold3.valid)
def test_invalid_threshold_range_exception(self):
range_error = self.env['kpi.threshold.range'].create({
'name': 'RangeError',
'min_type': 'python',
'min_code': '<Not a valid python expression>',
'max_type': 'static',
'max_fixed_value': 1,
})
self.assertFalse(range_error.valid)

64
kpi/views/kpi.xml

@ -48,37 +48,39 @@
<field name="model">kpi</field> <field name="model">kpi</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Key Performance Indicator"> <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>
<sheet>
<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>
</sheet>
</form> </form>
</field> </field>
</record> </record>

12
kpi/views/kpi_category.xml

@ -23,11 +23,13 @@
<field name="model">kpi.category</field> <field name="model">kpi.category</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Category"> <form string="Category">
<group col="2" colspan="2">
<field name="name" colspan="2"/>
<newline/>
<field name="description" colspan="2"/>
</group>
<sheet>
<group col="2" colspan="2">
<field name="name" colspan="2"/>
<newline/>
<field name="description" colspan="2"/>
</group>
</sheet>
</form> </form>
</field> </field>
</record> </record>

18
kpi/views/kpi_history.xml

@ -23,14 +23,16 @@
<field name="model">kpi.history</field> <field name="model">kpi.history</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="KPI History"> <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>
<sheet>
<group col="4" colspan="4">
<field name="kpi_id"/>
<field name="name"/>
<field name="date"/>
<field name="value"/>
<field name="color" widget="color"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</sheet>
</form> </form>
</field> </field>
</record> </record>

35
kpi/views/kpi_threshold.xml

@ -9,7 +9,7 @@
<field name="name">kpi.threshold.tree</field> <field name="name">kpi.threshold.tree</field>
<field name="model">kpi.threshold</field> <field name="model">kpi.threshold</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Thresholds">
<tree string="Thresholds" decoration-danger="invalid_message">
<field name="name"/> <field name="name"/>
<field name="invalid_message"/> <field name="invalid_message"/>
<field name="company_id" groups="base.group_multi_company"/> <field name="company_id" groups="base.group_multi_company"/>
@ -22,21 +22,24 @@
<field name="model">kpi.threshold</field> <field name="model">kpi.threshold</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Threshold"> <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>
<sheet>
<group col="6" colspan="6">
<field name="name" colspan="2"/>
<field name="company_id" groups="base.group_multi_company" colspan="2"/>
<newline/>
<separator string="Ranges" colspan="6"/>
<newline/>
<field name="range_ids" nolabel="1" colspan="6"/>
<newline/>
<separator string="KPIs" colspan="6"/>
<newline/>
<field name="kpi_ids" nolabel="1" colspan="6"/>
<newline/>
<separator string="Errors" attrs="{'invisible' : [('invalid_message', '=', False)]}" colspan="4"/>
<field name="invalid_message" nolabel="1" attrs="{'invisible' : [('invalid_message', '=', False)]}" colspan="4"/>
<newline/>
</group>
</sheet>
</form> </form>
</field> </field>
</record> </record>

59
kpi/views/kpi_threshold_range.xml

@ -9,7 +9,7 @@
<field name="name">kpi.threshold.range.tree</field> <field name="name">kpi.threshold.range.tree</field>
<field name="model">kpi.threshold.range</field> <field name="model">kpi.threshold.range</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Ranges">
<tree string="Ranges" decoration-danger="invalid_message">
<field name="name"/> <field name="name"/>
<field name="min_value"/> <field name="min_value"/>
<field name="max_value"/> <field name="max_value"/>
@ -25,32 +25,39 @@
<field name="model">kpi.threshold.range</field> <field name="model">kpi.threshold.range</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Range"> <form string="Range">
<group col="6" colspan="6">
<field name="name"/>
<field name="color"/>
<field name="company_id" groups="base.group_multi_company"/>
<newline/>
<sheet>
<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>
<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="6" attrs="{'invisible' : [('min_type', 'NOT IN', ('local','external','python'))]}"/>
<newline/>
<field name="min_error" colspan="6" attrs="{'invisible': [('min_error', '=', False)]}" />
<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="6" attrs="{'invisible' : [('max_type', 'NOT IN', ('local','external','python'))]}"/>
<newline/>
<field name="max_error" colspan="6" attrs="{'invisible': [('max_error', '=', False)]}" />
<newline/>
<separator string="Thresholds" colspan="4"/>
<field name="threshold_ids" nolabel="1" colspan="4"/>
<separator string="Errors" attrs="{'invisible' : [('invalid_message', '=', False)]}" colspan="4"/>
<field name="invalid_message" nolabel="1" attrs="{'invisible' : [('invalid_message', '=', False)]}" colspan="4"/>
</group>
</sheet>
</form> </form>
</field> </field>
</record> </record>

Loading…
Cancel
Save