[IMP] kpi_dashboard: black, isort, prettier

pull/446/head
Enric Tobella 2020-10-22 20:47:04 +02:00
parent 1d165764c5
commit de5a435343
26 changed files with 955 additions and 845 deletions

View File

@ -8,7 +8,6 @@
<field name="background_color">#020202</field> <field name="background_color">#020202</field>
<field name="compute_on_fly_refresh">30</field> <field name="compute_on_fly_refresh">30</field>
</record> </record>
<record id="widget_number_01" model="kpi.kpi"> <record id="widget_number_01" model="kpi.kpi">
<field name="name">Number 01</field> <field name="name">Number 01</field>
<field name="prefix">$</field> <field name="prefix">$</field>
@ -18,7 +17,6 @@
result = {"value": 10000,"previous": 12000} result = {"value": 10000,"previous": 12000}
</field> </field>
</record> </record>
<record id="widget_number_02" model="kpi.kpi"> <record id="widget_number_02" model="kpi.kpi">
<field name="name">Number 02</field> <field name="name">Number 02</field>
<field name="suffix"></field> <field name="suffix"></field>
@ -28,10 +26,11 @@ result = {"value": 10000,"previous": 12000}
result = {"value": 12000,"previous": 10000} result = {"value": 12000,"previous": 10000}
</field> </field>
</record> </record>
<function
<function model="kpi.kpi" name="compute" model="kpi.kpi"
eval="[[ref('widget_number_01'), ref('widget_number_02')]]"/> name="compute"
eval="[[ref('widget_number_01'), ref('widget_number_02')]]"
/>
<record id="widget_meter_01" model="kpi.kpi"> <record id="widget_meter_01" model="kpi.kpi">
<field name="name">Meter 01</field> <field name="name">Meter 01</field>
<field name="suffix"></field> <field name="suffix"></field>
@ -41,7 +40,6 @@ result = {"value": 12000,"previous": 10000}
result = {"min": 0, "max": 100, "value": 90} result = {"min": 0, "max": 100, "value": 90}
</field> </field>
</record> </record>
<record id="widget_meter_02" model="kpi.kpi"> <record id="widget_meter_02" model="kpi.kpi">
<field name="name">Meter 02</field> <field name="name">Meter 02</field>
<field name="prefix">$</field> <field name="prefix">$</field>
@ -51,10 +49,11 @@ result = {"min": 0, "max": 100, "value": 90}
result = {"min": 0, "max": 100, "value": 40} result = {"min": 0, "max": 100, "value": 40}
</field> </field>
</record> </record>
<function
<function model="kpi.kpi" name="compute" model="kpi.kpi"
eval="[[ref('widget_meter_01'), ref('widget_meter_02')]]"/> name="compute"
eval="[[ref('widget_meter_01'), ref('widget_meter_02')]]"
/>
<record id="widget_graph" model="kpi.kpi"> <record id="widget_graph" model="kpi.kpi">
<field name="name">Graph</field> <field name="name">Graph</field>
<field name="computation_method">code</field> <field name="computation_method">code</field>
@ -84,125 +83,116 @@ result = {"graphs": [
]} ]}
</field> </field>
</record> </record>
<function model="kpi.kpi" name="compute" eval="[[ref('widget_graph')]]" />
<function model="kpi.kpi" name="compute"
eval="[[ref('widget_graph')]]"/>
<record id="widget_integer" model="kpi.kpi"> <record id="widget_integer" model="kpi.kpi">
<field name="name">Integer counter</field> <field name="name">Integer counter</field>
<field name="computation_method">code</field> <field name="computation_method">code</field>
<field name="widget">integer</field> <field name="widget">integer</field>
<field name="compute_on_fly" eval="True"/> <field name="compute_on_fly" eval="True" />
<field name="code"> <field name="code">
result = {"value": self.env.context.get('counter', 990)} result = {"value": self.env.context.get('counter', 990)}
</field> </field>
</record> </record>
<record id="widget_counter" model="kpi.kpi"> <record id="widget_counter" model="kpi.kpi">
<field name="name">Counter</field> <field name="name">Counter</field>
<field name="computation_method">code</field> <field name="computation_method">code</field>
<field name="widget">counter</field> <field name="widget">counter</field>
<field name="compute_on_fly" eval="True"/> <field name="compute_on_fly" eval="True" />
<field name="code"> <field name="code">
result = {"value": self.env.context.get('counter', 990)} result = {"value": self.env.context.get('counter', 990)}
</field> </field>
</record> </record>
<record id="dashboard_widget_text" model="kpi.dashboard.item"> <record id="dashboard_widget_text" model="kpi.dashboard.item">
<field name="name">Dashboard title</field> <field name="name">Dashboard title</field>
<field name="dashboard_id" ref="demo_dashboard"/> <field name="dashboard_id" ref="demo_dashboard" />
<field name="column">1</field> <field name="column">1</field>
<field name="row">1</field> <field name="row">1</field>
<field name="size_x">4</field> <field name="size_x">4</field>
<field name="color">#707070</field> <field name="color">#707070</field>
<field name="font_color">#000000</field> <field name="font_color">#000000</field>
</record> </record>
<record id="dashboard_widget_number_01" model="kpi.dashboard.item"> <record id="dashboard_widget_number_01" model="kpi.dashboard.item">
<field name="name">Number 01</field> <field name="name">Number 01</field>
<field name="dashboard_id" ref="demo_dashboard"/> <field name="dashboard_id" ref="demo_dashboard" />
<field name="kpi_id" ref="widget_number_01"/> <field name="kpi_id" ref="widget_number_01" />
<field name="column">1</field> <field name="column">1</field>
<field name="row">2</field> <field name="row">2</field>
<field name="size_y">4</field> <field name="size_y">4</field>
<field name="color">#47bbb3</field> <field name="color">#47bbb3</field>
<field name="font_color">#ffffff</field> <field name="font_color">#ffffff</field>
</record> </record>
<record id="dashboard_widget_number_02" model="kpi.dashboard.item"> <record id="dashboard_widget_number_02" model="kpi.dashboard.item">
<field name="name">Number 02</field> <field name="name">Number 02</field>
<field name="dashboard_id" ref="demo_dashboard"/> <field name="dashboard_id" ref="demo_dashboard" />
<field name="kpi_id" ref="widget_number_02"/> <field name="kpi_id" ref="widget_number_02" />
<field name="column">1</field> <field name="column">1</field>
<field name="row">6</field> <field name="row">6</field>
<field name="size_y">4</field> <field name="size_y">4</field>
<field name="color">#ec663c</field> <field name="color">#ec663c</field>
<field name="font_color">#ffffff</field> <field name="font_color">#ffffff</field>
</record> </record>
<record id="dashboard_widget_meter_01" model="kpi.dashboard.item"> <record id="dashboard_widget_meter_01" model="kpi.dashboard.item">
<field name="name">Meter 01</field> <field name="name">Meter 01</field>
<field name="dashboard_id" ref="demo_dashboard"/> <field name="dashboard_id" ref="demo_dashboard" />
<field name="kpi_id" ref="widget_meter_01"/> <field name="kpi_id" ref="widget_meter_01" />
<field name="column">2</field> <field name="column">2</field>
<field name="row">2</field> <field name="row">2</field>
<field name="size_y">4</field> <field name="size_y">4</field>
<field name="color">#9c4274</field> <field name="color">#9c4274</field>
<field name="font_color">#ffffff</field> <field name="font_color">#ffffff</field>
</record> </record>
<record id="dashboard_widget_meter_02" model="kpi.dashboard.item"> <record id="dashboard_widget_meter_02" model="kpi.dashboard.item">
<field name="name">Meter 02</field> <field name="name">Meter 02</field>
<field name="dashboard_id" ref="demo_dashboard"/> <field name="dashboard_id" ref="demo_dashboard" />
<field name="kpi_id" ref="widget_meter_02"/> <field name="kpi_id" ref="widget_meter_02" />
<field name="column">2</field> <field name="column">2</field>
<field name="row">6</field> <field name="row">6</field>
<field name="size_y">4</field> <field name="size_y">4</field>
<field name="color">#12b0c5</field> <field name="color">#12b0c5</field>
<field name="font_color">#ffffff</field> <field name="font_color">#ffffff</field>
</record> </record>
<record id="dashboard_widget_add_counter" model="kpi.dashboard.item"> <record id="dashboard_widget_add_counter" model="kpi.dashboard.item">
<field name="name">+1 to Counter</field> <field name="name">+1 to Counter</field>
<field name="dashboard_id" ref="demo_dashboard"/> <field name="dashboard_id" ref="demo_dashboard" />
<field name="column">3</field> <field name="column">3</field>
<field name="row">10</field> <field name="row">10</field>
<field name="size_y">1</field> <field name="size_y">1</field>
<field name="size_x">2</field> <field name="size_x">2</field>
<field name="color">#B41F1F</field> <field name="color">#B41F1F</field>
<field name="font_color">#EEBF77</field> <field name="font_color">#EEBF77</field>
<field name="modify_context" eval="True"/> <field name="modify_context" eval="True" />
<field name="modify_context_expression">{'counter': (context.counter or 990) + 1}</field> <field
<field name="modify_color" eval="True"/> name="modify_context_expression"
<field name="modify_color_expression">check_if(((context.counter or 990) + 1) % 2, '#ff0000', '#00ff00')</field> >{'counter': (context.counter or 990) + 1}</field>
<field name="modify_color" eval="True" />
<field
name="modify_color_expression"
>check_if(((context.counter or 990) + 1) % 2, '#ff0000', '#00ff00')</field>
</record> </record>
<record id="dashboard_widget_counter" model="kpi.dashboard.item"> <record id="dashboard_widget_counter" model="kpi.dashboard.item">
<field name="name">Counter</field> <field name="name">Counter</field>
<field name="dashboard_id" ref="demo_dashboard"/> <field name="dashboard_id" ref="demo_dashboard" />
<field name="kpi_id" ref="widget_counter"/> <field name="kpi_id" ref="widget_counter" />
<field name="column">3</field> <field name="column">3</field>
<field name="row">11</field> <field name="row">11</field>
<field name="size_y">3</field> <field name="size_y">3</field>
<field name="color">#4B0082</field> <field name="color">#4B0082</field>
<field name="font_color">#ffffff</field> <field name="font_color">#ffffff</field>
</record> </record>
<record id="dashboard_widget_integer" model="kpi.dashboard.item"> <record id="dashboard_widget_integer" model="kpi.dashboard.item">
<field name="name">Integer</field> <field name="name">Integer</field>
<field name="dashboard_id" ref="demo_dashboard"/> <field name="dashboard_id" ref="demo_dashboard" />
<field name="kpi_id" ref="widget_integer"/> <field name="kpi_id" ref="widget_integer" />
<field name="column">4</field> <field name="column">4</field>
<field name="row">11</field> <field name="row">11</field>
<field name="size_y">3</field> <field name="size_y">3</field>
<field name="color">#ffffff</field> <field name="color">#ffffff</field>
<field name="font_color">#4B0082</field> <field name="font_color">#4B0082</field>
</record> </record>
<record id="dashboard_widget_graph" model="kpi.dashboard.item"> <record id="dashboard_widget_graph" model="kpi.dashboard.item">
<field name="name">Graph</field> <field name="name">Graph</field>
<field name="dashboard_id" ref="demo_dashboard"/> <field name="dashboard_id" ref="demo_dashboard" />
<field name="kpi_id" ref="widget_graph"/> <field name="kpi_id" ref="widget_graph" />
<field name="column">3</field> <field name="column">3</field>
<field name="row">2</field> <field name="row">2</field>
<field name="size_x">2</field> <field name="size_x">2</field>
@ -210,5 +200,4 @@ result = {"value": self.env.context.get('counter', 990)}
<field name="color">#ff9618</field> <field name="color">#ff9618</field>
<field name="font_color">#ffffff</field> <field name="font_color">#ffffff</field>
</record> </record>
</odoo> </odoo>

View File

@ -1,7 +1,7 @@
# Copyright 2020 Creu Blanca # Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
@ -17,8 +17,7 @@ class KpiDashboard(models.Model):
) )
number_of_columns = fields.Integer(default=5, required=True) number_of_columns = fields.Integer(default=5, required=True)
compute_on_fly_refresh = fields.Integer( compute_on_fly_refresh = fields.Integer(
default=0, default=0, help="Seconds to refresh on fly elements"
help="Seconds to refresh on fly elements"
) )
width = fields.Integer(compute="_compute_width") width = fields.Integer(compute="_compute_width")
margin_y = fields.Integer(default=10, required=True) margin_y = fields.Integer(default=10, required=True)
@ -34,9 +33,7 @@ class KpiDashboard(models.Model):
if "group_ids" in vals: if "group_ids" in vals:
for rec in self: for rec in self:
if rec.menu_id: if rec.menu_id:
rec.menu_id.write( rec.menu_id.write({"groups_id": [(6, 0, rec.group_ids.ids)]})
{"groups_id": [(6, 0, rec.group_ids.ids)]}
)
return res return res
@api.depends("widget_dimension_x", "margin_x", "number_of_columns") @api.depends("widget_dimension_x", "margin_x", "number_of_columns")
@ -78,7 +75,7 @@ class KpiDashboard(models.Model):
return { return {
"parent_id": menu.id or False, "parent_id": menu.id or False,
"name": self.name, "name": self.name,
"action": "%s,%s" % (action._name, action.id), "action": "{},{}".format(action._name, action.id),
"groups_id": [(6, 0, self.group_ids.ids)], "groups_id": [(6, 0, self.group_ids.ids)],
} }
@ -106,13 +103,11 @@ class KpiDashboardItem(models.Model):
name = fields.Char(required=True) name = fields.Char(required=True)
kpi_id = fields.Many2one("kpi.kpi") kpi_id = fields.Many2one("kpi.kpi")
dashboard_id = fields.Many2one( dashboard_id = fields.Many2one("kpi.dashboard", required=True, ondelete="cascade")
"kpi.dashboard", required=True, ondelete="cascade"
)
column = fields.Integer(required=True, default=1) column = fields.Integer(required=True, default=1)
row = fields.Integer(required=True, default=1) row = fields.Integer(required=True, default=1)
end_row = fields.Integer(store=True, compute='_compute_end_row') end_row = fields.Integer(store=True, compute="_compute_end_row")
end_column = fields.Integer(store=True, compute='_compute_end_column') end_column = fields.Integer(store=True, compute="_compute_end_column")
size_x = fields.Integer(required=True, default=1) size_x = fields.Integer(required=True, default=1)
size_y = fields.Integer(required=True, default=1) size_y = fields.Integer(required=True, default=1)
color = fields.Char() color = fields.Char()
@ -122,44 +117,43 @@ class KpiDashboardItem(models.Model):
modify_color = fields.Boolean() modify_color = fields.Boolean()
modify_color_expression = fields.Char() modify_color_expression = fields.Char()
@api.depends('row', 'size_y') @api.depends("row", "size_y")
def _compute_end_row(self): def _compute_end_row(self):
for r in self: for r in self:
r.end_row = r.row + r.size_y - 1 r.end_row = r.row + r.size_y - 1
@api.depends('column', 'size_x') @api.depends("column", "size_x")
def _compute_end_column(self): def _compute_end_column(self):
for r in self: for r in self:
r.end_column = r.column + r.size_x - 1 r.end_column = r.column + r.size_x - 1
@api.constrains('size_y') @api.constrains("size_y")
def _check_size_y(self): def _check_size_y(self):
for rec in self: for rec in self:
if rec.size_y > 10: if rec.size_y > 10:
raise ValidationError(_( raise ValidationError(
'Size Y of the widget cannot be bigger than 10')) _("Size Y of the widget cannot be bigger than 10")
)
def _check_size_domain(self): def _check_size_domain(self):
return [ return [
('dashboard_id', '=', self.dashboard_id.id), ("dashboard_id", "=", self.dashboard_id.id),
('id', '!=', self.id), ("id", "!=", self.id),
('row', '<=', self.end_row), ("row", "<=", self.end_row),
('end_row', '>=', self.row), ("end_row", ">=", self.row),
('column', '<=', self.end_column), ("column", "<=", self.end_column),
('end_column', '>=', self.column), ("end_column", ">=", self.column),
] ]
@api.constrains('end_row', 'end_column', 'row', 'column') @api.constrains("end_row", "end_column", "row", "column")
def _check_size(self): def _check_size(self):
for r in self: for r in self:
if self.search(r._check_size_domain(), limit=1): if self.search(r._check_size_domain(), limit=1):
raise ValidationError(_( raise ValidationError(_("Widgets cannot be crossed by other widgets"))
'Widgets cannot be crossed by other widgets'
))
if r.end_column > r.dashboard_id.number_of_columns: if r.end_column > r.dashboard_id.number_of_columns:
raise ValidationError(_( raise ValidationError(
'Widget %s is bigger than expected' _("Widget %s is bigger than expected") % r.display_name
) % r.display_name) )
@api.onchange("kpi_id") @api.onchange("kpi_id")
def _onchange_kpi(self): def _onchange_kpi(self):
@ -181,9 +175,9 @@ class KpiDashboardItem(models.Model):
"modify_color": self.modify_color, "modify_color": self.modify_color,
} }
if self.modify_context: if self.modify_context:
vals['modify_context_expression'] = self.modify_context_expression vals["modify_context_expression"] = self.modify_context_expression
if self.modify_color: if self.modify_color:
vals['modify_color_expression'] = self.modify_color_expression vals["modify_color_expression"] = self.modify_color_expression
if self.kpi_id: if self.kpi_id:
vals.update( vals.update(
{ {
@ -195,15 +189,19 @@ class KpiDashboardItem(models.Model):
} }
) )
if self.kpi_id.compute_on_fly: if self.kpi_id.compute_on_fly:
vals.update({ vals.update(
"value": self.kpi_id._compute_value(), {
"value_last_update": fields.Datetime.now(), "value": self.kpi_id._compute_value(),
}) "value_last_update": fields.Datetime.now(),
}
)
else: else:
vals.update({ vals.update(
"value": self.kpi_id.value, {
"value_last_update": self.kpi_id.value_last_update, "value": self.kpi_id.value,
}) "value_last_update": self.kpi_id.value_last_update,
}
)
if self.kpi_id.action_ids: if self.kpi_id.action_ids:
vals["actions"] = self.kpi_id.action_ids.read_dashboard() vals["actions"] = self.kpi_id.action_ids.read_dashboard()
else: else:
@ -219,12 +217,13 @@ class KpiDashboardItem(models.Model):
def technical_config(self): def technical_config(self):
self.ensure_one() self.ensure_one()
return { return {
'name': self.display_name, "name": self.display_name,
'res_model': self._name, "res_model": self._name,
'res_id': self.id, "res_id": self.id,
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'view_mode': 'form', "view_mode": "form",
'target': 'new', "target": "new",
'view_id': self.env.ref( "view_id": self.env.ref(
'kpi_dashboard.kpi_dashboard_item_config_form_view').id, "kpi_dashboard.kpi_dashboard_item_config_form_view"
).id,
} }

View File

@ -1,17 +1,20 @@
# Copyright 2020 Creu Blanca # Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
import ast import ast
from odoo.tools.safe_eval import safe_eval
from odoo.addons.base.models.ir_cron import _intervalTypes
from odoo.tools.float_utils import float_compare
import re
import json
import datetime import datetime
import json
import re
from dateutil import relativedelta from dateutil import relativedelta
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_compare
from odoo.tools.safe_eval import safe_eval
from odoo.addons.base.models.ir_cron import _intervalTypes
class KpiKpi(models.Model): class KpiKpi(models.Model):
_name = "kpi.kpi" _name = "kpi.kpi"
@ -30,8 +33,13 @@ class KpiKpi(models.Model):
args = fields.Char() args = fields.Char()
kwargs = fields.Char() kwargs = fields.Char()
widget = fields.Selection( widget = fields.Selection(
[('integer', 'Integer'), ("number", "Number"), ("meter", "Meter"), [
('counter', 'Counter'), ("graph", "Graph")], ("integer", "Integer"),
("number", "Number"),
("meter", "Meter"),
("counter", "Counter"),
("graph", "Graph"),
],
required=True, required=True,
default="number", default="number",
) )
@ -40,22 +48,21 @@ class KpiKpi(models.Model):
suffix = fields.Char() suffix = fields.Char()
action_ids = fields.One2many( action_ids = fields.One2many(
"kpi.kpi.action", "kpi.kpi.action",
inverse_name='kpi_id', inverse_name="kpi_id",
help="Actions that can be opened from the KPI" help="Actions that can be opened from the KPI",
) )
code = fields.Text("Code") code = fields.Text("Code")
store_history = fields.Boolean() store_history = fields.Boolean()
store_history_interval = fields.Selection( store_history_interval = fields.Selection(
selection=lambda self: selection=lambda self: self.env["ir.cron"]._fields["interval_type"].selection,
self.env['ir.cron']._fields['interval_type'].selection,
) )
store_history_interval_number = fields.Integer() store_history_interval_number = fields.Integer()
compute_on_fly = fields.Boolean() compute_on_fly = fields.Boolean()
history_ids = fields.One2many("kpi.kpi.history", inverse_name="kpi_id") history_ids = fields.One2many("kpi.kpi.history", inverse_name="kpi_id")
computed_value = fields.Serialized(compute='_compute_computed_value') computed_value = fields.Serialized(compute="_compute_computed_value")
computed_date = fields.Datetime(compute='_compute_computed_value') computed_date = fields.Datetime(compute="_compute_computed_value")
@api.depends('value', 'value_last_update', 'compute_on_fly') @api.depends("value", "value_last_update", "compute_on_fly")
def _compute_computed_value(self): def _compute_computed_value(self):
for record in self: for record in self:
if record.compute_on_fly: if record.compute_on_fly:
@ -95,18 +102,19 @@ class KpiKpi(models.Model):
value = self._compute_value() value = self._compute_value()
self.write({"value": value}) self.write({"value": value})
if self.store_history: if self.store_history:
last = self.env['kpi.kpi.history'].search([ last = self.env["kpi.kpi.history"].search(
('kpi_id', '=', self.id) [("kpi_id", "=", self.id)], limit=1
], limit=1) )
if ( if (
not last or not last
not self.store_history_interval or or not self.store_history_interval
last.create_date + _intervalTypes[self.store_history_interval]( or last.create_date
self.store_history_interval_number) < fields.Datetime.now() + _intervalTypes[self.store_history_interval](
): self.store_history_interval_number
self.env["kpi.kpi.history"].create(
self._generate_history_vals(value)
) )
< fields.Datetime.now()
):
self.env["kpi.kpi.history"].create(self._generate_history_vals(value))
notifications = [] notifications = []
for dashboard_item in self.dashboard_item_ids: for dashboard_item in self.dashboard_item_ids:
channel = "kpi_dashboard_%s" % dashboard_item.dashboard_id.id channel = "kpi_dashboard_%s" % dashboard_item.dashboard_id.id
@ -152,9 +160,9 @@ class KpiKpi(models.Model):
if len(message) > 0: if len(message) > 0:
message += _(" or ") message += _(" or ")
message += forbidden[-1] message += forbidden[-1]
raise ValidationError(_( raise ValidationError(
"The code cannot contain the following terms: %s." _("The code cannot contain the following terms: %s.") % message
) % message) )
results = self._get_code_input_dict() results = self._get_code_input_dict()
savepoint = "kpi_formula_%s" % self.id savepoint = "kpi_formula_%s" % self.id
self.env.cr.execute("savepoint %s" % savepoint) self.env.cr.execute("savepoint %s" % savepoint)
@ -164,30 +172,34 @@ class KpiKpi(models.Model):
def show_value(self): def show_value(self):
self.ensure_one() self.ensure_one()
action = self.env.ref('kpi_dashboard.kpi_kpi_act_window') action = self.env.ref("kpi_dashboard.kpi_kpi_act_window")
result = action.read()[0] result = action.read()[0]
result.update({ result.update(
'res_id': self.id, {
'target': 'new', "res_id": self.id,
'view_mode': 'form', "target": "new",
'views': [(self.env.ref( "view_mode": "form",
'kpi_dashboard.kpi_kpi_widget_form_view' "views": [
).id, 'form')], (self.env.ref("kpi_dashboard.kpi_kpi_widget_form_view").id, "form")
}) ],
}
)
return result return result
class KpiKpiAction(models.Model): class KpiKpiAction(models.Model):
_name = 'kpi.kpi.action' _name = "kpi.kpi.action"
_description = 'KPI action' _description = "KPI action"
kpi_id = fields.Many2one('kpi.kpi', required=True, ondelete='cascade') kpi_id = fields.Many2one("kpi.kpi", required=True, ondelete="cascade")
action = fields.Reference( action = fields.Reference(
selection=[('ir.actions.report', 'ir.actions.report'), selection=[
('ir.actions.act_window', 'ir.actions.act_window'), ("ir.actions.report", "ir.actions.report"),
('ir.actions.act_url', 'ir.actions.act_url'), ("ir.actions.act_window", "ir.actions.act_window"),
('ir.actions.server', 'ir.actions.server'), ("ir.actions.act_url", "ir.actions.act_url"),
('ir.actions.client', 'ir.actions.client')], ("ir.actions.server", "ir.actions.server"),
("ir.actions.client", "ir.actions.client"),
],
required=True, required=True,
) )
context = fields.Char() context = fields.Char()
@ -196,43 +208,45 @@ class KpiKpiAction(models.Model):
result = {} result = {}
for r in self: for r in self:
result[r.id] = { result[r.id] = {
'id': r.action.id, "id": r.action.id,
'type': r.action._name, "type": r.action._name,
'name': r.action.name, "name": r.action.name,
'context': safe_eval(r.context or '{}') "context": safe_eval(r.context or "{}"),
} }
return result return result
class KpiKpiHistory(models.Model): class KpiKpiHistory(models.Model):
_name = 'kpi.kpi.history' _name = "kpi.kpi.history"
_description = 'KPI history' _description = "KPI history"
_order = 'create_date DESC' _order = "create_date DESC"
kpi_id = fields.Many2one( kpi_id = fields.Many2one(
'kpi.kpi', required=True, ondelete='cascade', readonly=True "kpi.kpi", required=True, ondelete="cascade", readonly=True
) )
value = fields.Serialized(readonly=True) value = fields.Serialized(readonly=True)
raw_value = fields.Char(compute='_compute_raw_value') raw_value = fields.Char(compute="_compute_raw_value")
name = fields.Char(related='kpi_id.name') name = fields.Char(related="kpi_id.name")
widget = fields.Selection( widget = fields.Selection(
selection=lambda self: selection=lambda self: self.env["kpi.kpi"]._fields["widget"].selection,
self.env['kpi.kpi']._fields['widget'].selection, required=True,
required=True) )
@api.depends('value') @api.depends("value")
def _compute_raw_value(self): def _compute_raw_value(self):
for record in self: for record in self:
record.raw_value = json.dumps(record.value) record.raw_value = json.dumps(record.value)
def show_form(self): def show_form(self):
self.ensure_one() self.ensure_one()
action = self.env.ref('kpi_dashboard.kpi_kpi_history_act_window') action = self.env.ref("kpi_dashboard.kpi_kpi_history_act_window")
result = action.read()[0] result = action.read()[0]
result.update({ result.update(
'res_id': self.id, {
'target': 'new', "res_id": self.id,
'view_mode': 'form', "target": "new",
'views': [(self.env.context.get('form_id'), 'form')], "view_mode": "form",
}) "views": [(self.env.context.get("form_id"), "form")],
}
)
return result return result

View File

@ -1,22 +1,24 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="group_kpi_dashboard_manager" model="res.groups"> <record id="group_kpi_dashboard_manager" model="res.groups">
<field name="name">Manage KPI Dashboards</field> <field name="name">Manage KPI Dashboards</field>
<field name="category_id" ref="base.module_category_hidden"/> <field name="category_id" ref="base.module_category_hidden" />
<field name="users" eval="[(4, ref('base.user_admin'))]"/> <field name="users" eval="[(4, ref('base.user_admin'))]" />
</record> </record>
<data noupdate="1"> <data noupdate="1">
<record id="rule_kpi_dashboard" model="ir.rule"> <record id="rule_kpi_dashboard" model="ir.rule">
<field name="name">KPI Dashboard: User</field> <field name="name">KPI Dashboard: User</field>
<field name="model_id" ref="model_kpi_dashboard"/> <field name="model_id" ref="model_kpi_dashboard" />
<field name="domain_force">['|', ('group_ids', '=', False), ('group_ids', 'in', user.groups_id.ids)]</field> <field
<field name="groups" eval="[(4, ref('base.group_user'))]"/> name="domain_force"
>['|', ('group_ids', '=', False), ('group_ids', 'in', user.groups_id.ids)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]" />
</record> </record>
<record id="rule_kpi_dashboard_all" model="ir.rule"> <record id="rule_kpi_dashboard_all" model="ir.rule">
<field name="name">KPI Dashboard: All</field> <field name="name">KPI Dashboard: All</field>
<field name="model_id" ref="model_kpi_dashboard"/> <field name="model_id" ref="model_kpi_dashboard" />
<field name="domain_force">[(1, '=', 1)]</field> <field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('group_kpi_dashboard_manager'))]"/> <field name="groups" eval="[(4, ref('group_kpi_dashboard_manager'))]" />
</record> </record>
</data> </data>
</odoo> </odoo>

View File

@ -1,150 +1,159 @@
odoo.define('kpi_dashboard.DashboardController', function (require) { odoo.define("kpi_dashboard.DashboardController", function(require) {
"use strict"; "use strict";
var BasicController = require('web.BasicController'); var BasicController = require("web.BasicController");
var core = require('web.core'); var core = require("web.core");
var qweb = core.qweb; var qweb = core.qweb;
var _t = core._t; var _t = core._t;
var DashboardController = BasicController.extend({ var DashboardController = BasicController.extend({
init: function () { init: function() {
this._super.apply(this, arguments); this._super.apply(this, arguments);
this.dashboard_context = {}; this.dashboard_context = {};
this.dashboard_color_data = [] this.dashboard_color_data = [];
}, },
custom_events: _.extend({}, BasicController.prototype.custom_events, { custom_events: _.extend({}, BasicController.prototype.custom_events, {
addDashboard: '_addDashboard', addDashboard: "_addDashboard",
refresh_on_fly: '_refreshOnFly', refresh_on_fly: "_refreshOnFly",
modify_context: '_modifyContext', modify_context: "_modifyContext",
add_modify_color: '_addModifyColor', add_modify_color: "_addModifyColor",
refresh_colors: '_refreshColors', refresh_colors: "_refreshColors",
}), }),
_refreshOnFly: function (event) { _refreshOnFly: function(event) {
var self = this; var self = this;
this._rpc({ this._rpc({
model: this.modelName, model: this.modelName,
method: 'read_dashboard_on_fly', method: "read_dashboard_on_fly",
args: [[this.renderer.state.res_id]], args: [[this.renderer.state.res_id]],
context: this._getContext(), context: this._getContext(),
}).then(function (data) { }).then(function(data) {
_.each(data, function (item) { _.each(data, function(item) {
// We will follow the same logic used on Bus Notifications // We will follow the same logic used on Bus Notifications
self.renderer._onNotification([[ self.renderer._onNotification([
"kpi_dashboard_" + self.renderer.state.res_id, ["kpi_dashboard_" + self.renderer.state.res_id, item],
item ]);
]])
}); });
}); });
}, },
renderPager: function ($node, options) { renderPager: function($node, options) {
options = _.extend({}, options, { options = _.extend({}, options, {
validate: this.canBeDiscarded.bind(this), validate: this.canBeDiscarded.bind(this),
}); });
this._super($node, options); this._super($node, options);
}, },
_pushState: function (state) { _pushState: function(state) {
state = state || {}; state = state || {};
var env = this.model.get(this.handle, {env: true}); var env = this.model.get(this.handle, {env: true});
state.id = env.currentId; state.id = env.currentId;
this._super(state); this._super(state);
}, },
_addDashboard: function () { _addDashboard: function() {
var self = this; var self = this;
var action = self.initialState.specialData.action_id; var action = self.initialState.specialData.action_id;
var name = self.initialState.specialData.name; var name = self.initialState.specialData.name;
if (! action) { if (!action) {
self.do_warn(_t("First you must create the Menu")); self.do_warn(_t("First you must create the Menu"));
} }
return self._rpc({ return self
route: '/board/add_to_dashboard', ._rpc({
params: { route: "/board/add_to_dashboard",
action_id: action, params: {
context_to_save: {'res_id': self.initialState.res_id}, action_id: action,
domain: [('id', '=', self.initialState.res_id)], context_to_save: {res_id: self.initialState.res_id},
view_mode: 'dashboard', domain: [("id", "=", self.initialState.res_id)],
name: name, view_mode: "dashboard",
}, name: name,
}) },
.then(function (r) { })
if (r) { .then(function(r) {
self.do_notify( if (r) {
_.str.sprintf(_t("'%s' added to dashboard"), name), self.do_notify(
_t('Please refresh your browser for the changes to take effect.') _.str.sprintf(_t("'%s' added to dashboard"), name),
); _t(
} else { "Please refresh your browser for the changes to take effect."
self.do_warn(_t("Could not add KPI dashboard to dashboard")); )
} );
}); } else {
self.do_warn(_t("Could not add KPI dashboard to dashboard"));
}
});
}, },
_updateButtons: function () { _updateButtons: function() {
// HOOK Function // HOOK Function
this.$buttons.on( this.$buttons.on(
'click', '.o_dashboard_button_add', "click",
this._addDashboard.bind(this)); ".o_dashboard_button_add",
this._addDashboard.bind(this)
);
}, },
renderButtons: function ($node) { renderButtons: function($node) {
if (! $node) { if (!$node) {
return; return;
} }
this.$buttons = $('<div/>'); this.$buttons = $("<div/>");
this.$buttons.append(qweb.render( this.$buttons.append(qweb.render("kpi_dashboard.buttons", {widget: this}));
"kpi_dashboard.buttons", {widget: this}));
this._updateButtons(); this._updateButtons();
this.$buttons.appendTo($node); this.$buttons.appendTo($node);
}, },
_getContext: function () { _getContext: function() {
return _.extend( return _.extend(
{}, {},
this.model.get(this.handle, {raw: true}).getContext(), this.model.get(this.handle, {raw: true}).getContext(),
{bin_size: true}, {bin_size: true},
this.dashboard_context, this.dashboard_context
) );
}, },
_modifyContext: function (event) { _modifyContext: function(event) {
var ctx = this._getContext(); var ctx = this._getContext();
this.dashboard_context = _.extend( this.dashboard_context = _.extend(
this.dashboard_context, this.dashboard_context,
py.eval(event.data.context, {context: _.extend( py.eval(event.data.context, {
ctx, context: _.extend(
{__getattr__: function() {return false}} ctx,
// We need to add this in order to allow to use undefined {
// context items __getattr__: function() {
)}), return false;
},
}
// We need to add this in order to allow to use undefined
// context items
),
})
); );
this._refreshOnFly(event); this._refreshOnFly(event);
this._refreshColors(); this._refreshColors();
}, },
_addModifyColor: function (event) { _addModifyColor: function(event) {
this.dashboard_color_data.push([ this.dashboard_color_data.push([
event.data.element_id, event.data.element_id,
event.data.expression, event.data.expression,
]); ]);
}, },
_refreshColors: function () { _refreshColors: function() {
var self = this; var self = this;
var ctx = this._getContext(); var ctx = this._getContext();
_.each(this.dashboard_color_data, function (data) { _.each(this.dashboard_color_data, function(data) {
var color = py.eval(data[1], { var color = py.eval(data[1], {
context: _.extend(ctx, { context: _.extend(ctx, {
__getattr__: function() {return false}, __getattr__: function() {
return false;
},
}), }),
check_if: function(args) { check_if: function(args) {
if (args[0].toJSON()) { if (args[0].toJSON()) {
return args[1]; return args[1];
} }
return args[2]; return args[2];
} },
}); });
var $element = self.renderer.$el.find('#' + data[0]); var $element = self.renderer.$el.find("#" + data[0]);
$element.css('background-color', color); $element.css("background-color", color);
}); });
}, },
}); });
return DashboardController; return DashboardController;
}); });

View File

@ -1,23 +1,21 @@
odoo.define('kpi_dashboard.DashboardModel', function (require) { odoo.define("kpi_dashboard.DashboardModel", function(require) {
"use strict"; "use strict";
var BasicModel = require('web.BasicModel'); var BasicModel = require("web.BasicModel");
var DashboardModel = BasicModel.extend({ var DashboardModel = BasicModel.extend({
_fetchRecord: function (record, options) { _fetchRecord: function(record, options) {
return this._rpc({ return this._rpc({
model: record.model, model: record.model,
method: 'read_dashboard', method: "read_dashboard",
args: [[record.res_id]], args: [[record.res_id]],
context: _.extend({}, record.getContext(), {bin_size: true}), context: _.extend({}, record.getContext(), {bin_size: true}),
}) }).then(function(result) {
.then(function (result) {
record.specialData = result; record.specialData = result;
return result return result;
}) });
} },
}); });
return DashboardModel; return DashboardModel;
}); });

View File

@ -1,99 +1,98 @@
odoo.define('kpi_dashboard.DashboardRenderer', function (require) { odoo.define("kpi_dashboard.DashboardRenderer", function(require) {
"use strict"; "use strict";
var BasicRenderer = require('web.BasicRenderer'); var BasicRenderer = require("web.BasicRenderer");
var core = require('web.core'); var core = require("web.core");
var registry = require('kpi_dashboard.widget_registry'); var registry = require("kpi_dashboard.widget_registry");
var BusService = require('bus.BusService'); var BusService = require("bus.BusService");
var qweb = core.qweb; var qweb = core.qweb;
var DashboardRenderer= BasicRenderer.extend({ var DashboardRenderer = BasicRenderer.extend({
className: "o_dashboard_view", className: "o_dashboard_view",
_getDashboardWidget: function (kpi) { _getDashboardWidget: function(kpi) {
var Widget = registry.getAny([ var Widget = registry.getAny([kpi.widget, "abstract"]);
kpi.widget, 'abstract',
]);
var widget = new Widget(this, kpi); var widget = new Widget(this, kpi);
return widget; return widget;
}, },
_onClickModifyContext: function (modify_context_expression, event) { _onClickModifyContext: function(modify_context_expression, event) {
this.trigger_up('modify_context', { this.trigger_up("modify_context", {
context: modify_context_expression, context: modify_context_expression,
event: event, event: event,
}) });
}, },
_renderView: function () { _renderView: function() {
this.$el.html($(qweb.render('dashboard_kpi.dashboard'))); this.$el.html($(qweb.render("dashboard_kpi.dashboard")));
this.$el.css( this.$el.css("background-color", this.state.specialData.background_color);
'background-color', this.state.specialData.background_color); this.$el.find(".gridster").css("width", this.state.specialData.width);
this.$el.find('.gridster') this.$grid = this.$el.find(".gridster ul");
.css('width', this.state.specialData.width);
this.$grid = this.$el.find('.gridster ul');
var self = this; var self = this;
this.kpi_widget = {}; this.kpi_widget = {};
_.each(this.state.specialData.item_ids, function (kpi) { _.each(this.state.specialData.item_ids, function(kpi) {
var element = $(qweb.render( var element = $(qweb.render("kpi_dashboard.kpi", {widget: kpi}));
'kpi_dashboard.kpi', {widget: kpi})); element.css("background-color", kpi.color);
element.css('background-color', kpi.color); element.css("color", kpi.font_color);
element.css('color', kpi.font_color); element.attr("id", _.uniqueId("kpi_"));
element.attr('id', _.uniqueId('kpi_'));
self.$grid.append(element); self.$grid.append(element);
if (kpi.modify_color) { if (kpi.modify_color) {
self.trigger_up("add_modify_color", { self.trigger_up("add_modify_color", {
element_id: element.attr("id"), element_id: element.attr("id"),
expression: kpi.modify_color_expression, expression: kpi.modify_color_expression,
}) });
} }
if (kpi.modify_context) { if (kpi.modify_context) {
element.on("click", self._onClickModifyContext.bind( element.on(
self, kpi.modify_context_expression)); "click",
element.css('cursor', 'pointer'); self._onClickModifyContext.bind(
self,
kpi.modify_context_expression
)
);
element.css("cursor", "pointer");
// We want to set it show as clickable // We want to set it show as clickable
} }
self.kpi_widget[kpi.id] = self._getDashboardWidget(kpi); self.kpi_widget[kpi.id] = self._getDashboardWidget(kpi);
self.kpi_widget[kpi.id].appendTo(element); self.kpi_widget[kpi.id].appendTo(element);
}); });
this.$grid.gridster({ this.$grid
widget_margins: [ .gridster({
this.state.specialData.margin_x, widget_margins: [
this.state.specialData.margin_y, this.state.specialData.margin_x,
], this.state.specialData.margin_y,
widget_base_dimensions: [ ],
this.state.specialData.widget_dimension_x, widget_base_dimensions: [
this.state.specialData.widget_dimension_y, this.state.specialData.widget_dimension_x,
], this.state.specialData.widget_dimension_y,
cols: this.state.specialData.max_cols, ],
}).data('gridster').disable(); cols: this.state.specialData.max_cols,
this.channel = 'kpi_dashboard_' + this.state.res_id; })
this.call( .data("gridster")
'bus_service', 'addChannel', this.channel); .disable();
this.call('bus_service', 'startPolling'); this.channel = "kpi_dashboard_" + this.state.res_id;
this.call( this.call("bus_service", "addChannel", this.channel);
'bus_service', 'onNotification', this.call("bus_service", "startPolling");
this, this._onNotification this.call("bus_service", "onNotification", this, this._onNotification);
);
if (this.state.specialData.compute_on_fly_refresh > 0) { if (this.state.specialData.compute_on_fly_refresh > 0) {
// Setting the refresh interval // Setting the refresh interval
this.on_fly_interval = setInterval(function () { this.on_fly_interval = setInterval(function() {
self.trigger_up('refresh_on_fly'); self.trigger_up("refresh_on_fly");
}, this.state.specialData.compute_on_fly_refresh *1000); }, this.state.specialData.compute_on_fly_refresh * 1000);
}; }
this.trigger_up('refresh_colors'); this.trigger_up("refresh_colors");
this.trigger_up('refresh_on_fly'); this.trigger_up("refresh_on_fly");
// We need to refreshs data in order compute with the current // We need to refreshs data in order compute with the current
// context // context
return $.when(); return $.when();
}, },
on_detach_callback: function () { on_detach_callback: function() {
// We want to clear the refresh interval once we exit the view // We want to clear the refresh interval once we exit the view
if (this.on_fly_interval) { if (this.on_fly_interval) {
clearInterval(this.on_fly_interval) clearInterval(this.on_fly_interval);
} }
this._super.apply(this, arguments); this._super.apply(this, arguments);
}, },
_onNotification: function (notifications) { _onNotification: function(notifications) {
var self = this; var self = this;
_.each(notifications, function (notification) { _.each(notifications, function(notification) {
var channel = notification[0]; var channel = notification[0];
var message = notification[1]; var message = notification[1];
if (channel === self.channel && message) { if (channel === self.channel && message) {

View File

@ -1,26 +1,22 @@
odoo.define('kpi_dashboard.DashboardView', function (require) { odoo.define("kpi_dashboard.DashboardView", function(require) {
"use strict"; "use strict";
var BasicView = require('web.BasicView'); var BasicView = require("web.BasicView");
var DashboardController = require('kpi_dashboard.DashboardController'); var DashboardController = require("kpi_dashboard.DashboardController");
var DashboardModel = require('kpi_dashboard.DashboardModel'); var DashboardModel = require("kpi_dashboard.DashboardModel");
var DashboardRenderer = require('kpi_dashboard.DashboardRenderer'); var DashboardRenderer = require("kpi_dashboard.DashboardRenderer");
var view_registry = require('web.view_registry'); var view_registry = require("web.view_registry");
var core = require('web.core'); var core = require("web.core");
var _lt = core._lt; var _lt = core._lt;
var DashboardView = BasicView.extend({ var DashboardView = BasicView.extend({
jsLibs: [ jsLibs: ["/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.js"],
'/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.js', cssLibs: ["/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.css"],
],
cssLibs: [
'/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.css',
],
accesskey: "d", accesskey: "d",
display_name: _lt("Dashboard"), display_name: _lt("Dashboard"),
icon: 'fa-tachometer', icon: "fa-tachometer",
viewType: 'dashboard', viewType: "dashboard",
config: _.extend({}, BasicView.prototype.config, { config: _.extend({}, BasicView.prototype.config, {
Controller: DashboardController, Controller: DashboardController,
Renderer: DashboardRenderer, Renderer: DashboardRenderer,
@ -28,17 +24,17 @@ odoo.define('kpi_dashboard.DashboardView', function (require) {
}), }),
multi_record: false, multi_record: false,
searchable: false, searchable: false,
init: function () { init: function() {
this._super.apply(this, arguments); this._super.apply(this, arguments);
this.controllerParams.mode = 'readonly'; this.controllerParams.mode = "readonly";
this.loadParams.type = 'record'; this.loadParams.type = "record";
if (! this.loadParams.res_id && this.loadParams.context.res_id) { if (!this.loadParams.res_id && this.loadParams.context.res_id) {
this.loadParams.res_id = this.loadParams.context.res_id; this.loadParams.res_id = this.loadParams.context.res_id;
} }
}, },
}); });
view_registry.add('dashboard', DashboardView); view_registry.add("dashboard", DashboardView);
return DashboardView; return DashboardView;
}); });

View File

@ -1,28 +1,24 @@
odoo.define('kpi_dashboard.KpiFieldWidget', function(require) { odoo.define("kpi_dashboard.KpiFieldWidget", function(require) {
"use strict"; "use strict";
var basic_fields = require('web.basic_fields'); var basic_fields = require("web.basic_fields");
var field_registry = require('web.field_registry'); var field_registry = require("web.field_registry");
var core = require('web.core'); var core = require("web.core");
var qweb = core.qweb; var qweb = core.qweb;
var registry = require('kpi_dashboard.widget_registry'); var registry = require("kpi_dashboard.widget_registry");
var KpiFieldWidget = basic_fields.FieldChar.extend({ var KpiFieldWidget = basic_fields.FieldChar.extend({
jsLibs: [ jsLibs: ["/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.js"],
'/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.js', cssLibs: ["/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.css"],
], className: "o_dashboard_view",
cssLibs: [ _renderReadonly: function() {
'/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.css', this.$el.html($(qweb.render("dashboard_kpi.dashboard")));
],
className: 'o_dashboard_view',
_renderReadonly: function () {
this.$el.html($(qweb.render('dashboard_kpi.dashboard')));
var marginx = 0; var marginx = 0;
var marginy = 0; var marginy = 0;
var widgetx = 400; var widgetx = 400;
var widgety = 400; var widgety = 400;
this.$el.find('.gridster').css('width', widgety); this.$el.find(".gridster").css("width", widgety);
this.$grid = this.$el.find('.gridster ul'); this.$grid = this.$el.find(".gridster ul");
var widgetVals = { var widgetVals = {
value: this.value, value: this.value,
col: 1, col: 1,
@ -30,10 +26,11 @@ odoo.define('kpi_dashboard.KpiFieldWidget', function(require) {
sizex: 1, sizex: 1,
sizey: 1, sizey: 1,
name: this.recordData[this.nodeOptions.name], name: this.recordData[this.nodeOptions.name],
value_last_update: this.recordData[this.nodeOptions.date] value_last_update: this.recordData[this.nodeOptions.date],
} };
var Widget = registry.getAny([ var Widget = registry.getAny([
this.recordData[this.nodeOptions.widget], 'abstract', this.recordData[this.nodeOptions.widget],
"abstract",
]); ]);
this.state = { this.state = {
specialData: { specialData: {
@ -41,28 +38,24 @@ odoo.define('kpi_dashboard.KpiFieldWidget', function(require) {
margin_y: marginy, margin_y: marginy,
widget_dimension_x: widgetx, widget_dimension_x: widgetx,
widget_dimension_y: widgety, widget_dimension_y: widgety,
} },
} };
var widget = new Widget(this, widgetVals); var widget = new Widget(this, widgetVals);
var element = $(qweb.render( var element = $(qweb.render("kpi_dashboard.kpi", {widget: widgetVals}));
'kpi_dashboard.kpi', {widget: widgetVals})); element.css("background-color", "white");
element.css('background-color', 'white'); element.css("color", "black");
element.css('color', 'black');
this.$grid.append(element); this.$grid.append(element);
widget.appendTo(element) widget.appendTo(element);
this.$grid.gridster({ this.$grid
widget_margins: [ .gridster({
marginx, widget_margins: [marginx, marginy],
marginy, widget_base_dimensions: [widgetx, widgety],
], cols: 1,
widget_base_dimensions: [ })
widgetx, .data("gridster")
widgety, .disable();
],
cols: 1,
}).data('gridster').disable();
}, },
}); });
field_registry.add('kpi', KpiFieldWidget); field_registry.add("kpi", KpiFieldWidget);
return KpiFieldWidget; return KpiFieldWidget;
}); });

View File

@ -1,20 +1,20 @@
odoo.define('kpi_dashboard.AbstractWidget', function (require) { odoo.define("kpi_dashboard.AbstractWidget", function(require) {
"use strict"; "use strict";
var Widget = require('web.Widget'); var Widget = require("web.Widget");
var field_utils = require('web.field_utils'); var field_utils = require("web.field_utils");
var time = require('web.time'); var time = require("web.time");
var ajax = require('web.ajax'); var ajax = require("web.ajax");
var registry = require('kpi_dashboard.widget_registry'); var registry = require("kpi_dashboard.widget_registry");
var AbstractWidget = Widget.extend({ var AbstractWidget = Widget.extend({
template: 'kpi_dashboard.base_widget', // Template used by the widget template: "kpi_dashboard.base_widget", // Template used by the widget
cssLibs: [], // Specific css of the widget cssLibs: [], // Specific css of the widget
jsLibs: [], // Specific Javascript libraries of the widget jsLibs: [], // Specific Javascript libraries of the widget
events: { events: {
'click .o_kpi_dashboard_toggle_button': '_onClickToggleButton', "click .o_kpi_dashboard_toggle_button": "_onClickToggleButton",
'click .direct_action': '_onClickDirectAction', "click .direct_action": "_onClickDirectAction",
}, },
init: function (parent, kpi_values) { init: function(parent, kpi_values) {
this._super(parent); this._super(parent);
this.col = kpi_values.col; this.col = kpi_values.col;
this.row = kpi_values.row; this.row = kpi_values.row;
@ -29,66 +29,70 @@ odoo.define('kpi_dashboard.AbstractWidget', function (require) {
this.prefix = kpi_values.prefix; this.prefix = kpi_values.prefix;
this.suffix = kpi_values.suffix; this.suffix = kpi_values.suffix;
this.actions = kpi_values.actions; this.actions = kpi_values.actions;
this.widget_size_x = this.widget_dimension_x * this.sizex + this.widget_size_x =
(this.sizex - 1) * this.margin_x; this.widget_dimension_x * this.sizex + (this.sizex - 1) * this.margin_x;
this.widget_size_y = this.widget_dimension_y * this.sizey + this.widget_size_y =
(this.sizey - 1) * this.margin_y; this.widget_dimension_y * this.sizey + (this.sizey - 1) * this.margin_y;
}, },
willStart: function () { willStart: function() {
// We need to load the libraries before the start // We need to load the libraries before the start
return $.when(ajax.loadLibs(this), this._super.apply(this, arguments)); return $.when(ajax.loadLibs(this), this._super.apply(this, arguments));
}, },
start: function () { start: function() {
var self = this; var self = this;
return this._super.apply(this, arguments).then(function () { return this._super.apply(this, arguments).then(function() {
self._fillWidget(self.values); self._fillWidget(self.values);
}); });
}, },
_onClickToggleButton: function (event) { _onClickToggleButton: function(event) {
event.preventDefault(); event.preventDefault();
this.$el.toggleClass('o_dropdown_open'); this.$el.toggleClass("o_dropdown_open");
}, },
_fillWidget: function (values) { _fillWidget: function(values) {
// This function fills the widget values // This function fills the widget values
if (this.$el === undefined) if (this.$el === undefined) return;
return;
this.fillWidget(values); this.fillWidget(values);
var item = this.$el.find('[data-bind="value_last_update_display"]'); var item = this.$el.find('[data-bind="value_last_update_display"]');
if (item && ! values.compute_on_fly && values.value_last_update !== undefined) { if (
item &&
!values.compute_on_fly &&
values.value_last_update !== undefined
) {
var value = field_utils.parse.datetime(values.value_last_update); var value = field_utils.parse.datetime(values.value_last_update);
item.text(value.clone().add( item.text(
this.getSession().getTZOffset(value), 'minutes').format( value
time.getLangDatetimeFormat() .clone()
)); .add(this.getSession().getTZOffset(value), "minutes")
.format(time.getLangDatetimeFormat())
);
} }
var $manage = this.$el.find('.o_kpi_dashboard_manage'); var $manage = this.$el.find(".o_kpi_dashboard_manage");
if ($manage && this.showManagePanel(values)) if ($manage && this.showManagePanel(values))
$manage.toggleClass('hidden', false); $manage.toggleClass("hidden", false);
}, },
showManagePanel: function (values) { showManagePanel: function(values) {
// Hook for extensions // Hook for extensions
return (values.actions !== undefined); return values.actions !== undefined;
}, },
fillWidget: function (values) { fillWidget: function(values) {
// Specific function that will be changed by specific widget // Specific function that will be changed by specific widget
var value = values.value; var value = values.value;
var self = this; var self = this;
_.each(value, function (val, key) { _.each(value, function(val, key) {
var item = self.$el.find('[data-bind=' + key + ']') var item = self.$el.find("[data-bind=" + key + "]");
if (item) if (item) item.text(val);
item.text(val); });
})
}, },
_onClickDirectAction: function(event) { _onClickDirectAction: function(event) {
event.preventDefault(); event.preventDefault();
var $data = $(event.currentTarget).closest('a'); var $data = $(event.currentTarget).closest("a");
var action = this.actions[$($data).data('id')]; var action = this.actions[$($data).data("id")];
return this.do_action(action.id, { return this.do_action(action.id, {
additional_context: action.context additional_context: action.context,
}); });
} },
}); });
registry.add('abstract', AbstractWidget); registry.add("abstract", AbstractWidget);
return AbstractWidget; return AbstractWidget;
}); });

View File

@ -1,14 +1,14 @@
odoo.define('kpi_dashboard.CounterWidget', function (require) { odoo.define("kpi_dashboard.CounterWidget", function(require) {
"use strict"; "use strict";
var IntegerWidget = require('kpi_dashboard.IntegerWidget'); var IntegerWidget = require("kpi_dashboard.IntegerWidget");
var registry = require('kpi_dashboard.widget_registry'); var registry = require("kpi_dashboard.widget_registry");
var field_utils = require('web.field_utils'); var field_utils = require("web.field_utils");
var CounterWidget = IntegerWidget.extend({ var CounterWidget = IntegerWidget.extend({
shortList: [], shortList: [],
}); });
registry.add('counter', CounterWidget); registry.add("counter", CounterWidget);
return CounterWidget; return CounterWidget;
}); });

View File

@ -1,40 +1,39 @@
odoo.define('kpi_dashboard.GraphWidget', function (require) { odoo.define("kpi_dashboard.GraphWidget", function(require) {
"use strict"; "use strict";
var AbstractWidget = require('kpi_dashboard.AbstractWidget'); var AbstractWidget = require("kpi_dashboard.AbstractWidget");
var registry = require('kpi_dashboard.widget_registry'); var registry = require("kpi_dashboard.widget_registry");
var core = require('web.core'); var core = require("web.core");
var qweb = core.qweb; var qweb = core.qweb;
var GraphWidget = AbstractWidget.extend({ var GraphWidget = AbstractWidget.extend({
template: 'kpi_dashboard.graph', template: "kpi_dashboard.graph",
jsLibs: [ jsLibs: [
'/web/static/lib/nvd3/d3.v3.js', "/web/static/lib/nvd3/d3.v3.js",
'/web/static/lib/nvd3/nv.d3.js', "/web/static/lib/nvd3/nv.d3.js",
'/web/static/src/js/libs/nvd3.js', "/web/static/src/js/libs/nvd3.js",
], ],
cssLibs: [ cssLibs: ["/web/static/lib/nvd3/nv.d3.css"],
'/web/static/lib/nvd3/nv.d3.css', start: function() {
],
start: function () {
this._onResize = this._onResize.bind(this); this._onResize = this._onResize.bind(this);
nv.utils.windowResize(this._onResize); nv.utils.windowResize(this._onResize);
return this._super.apply(this, arguments); return this._super.apply(this, arguments);
}, },
destroy: function () { destroy: function() {
if ('nv' in window && nv.utils && nv.utils.offWindowResize) { if ("nv" in window && nv.utils && nv.utils.offWindowResize) {
// if the widget is destroyed before the lazy loaded libs (nv) are // If the widget is destroyed before the lazy loaded libs (nv) are
// actually loaded (i.e. after the widget has actually started), // actually loaded (i.e. after the widget has actually started),
// nv is undefined, but the handler isn't bound yet anyway // nv is undefined, but the handler isn't bound yet anyway
nv.utils.offWindowResize(this._onResize); nv.utils.offWindowResize(this._onResize);
} }
this._super.apply(this, arguments); this._super.apply(this, arguments);
}, },
_getChartOptions: function (values) { _getChartOptions: function(values) {
return { return {
x: function (d, u) { return u; }, x: function(d, u) {
margin: {'left': 0, 'right': 0, 'top': 5, 'bottom': 0}, return u;
},
margin: {left: 0, right: 0, top: 5, bottom: 0},
showYAxis: false, showYAxis: false,
showXAxis: false, showXAxis: false,
showLegend: false, showLegend: false,
@ -42,60 +41,58 @@ odoo.define('kpi_dashboard.GraphWidget', function (require) {
width: this.widget_size_x - 20, width: this.widget_size_x - 20,
}; };
}, },
_chartConfiguration: function (values) { _chartConfiguration: function(values) {
this.chart.forceY([0]); this.chart.forceY([0]);
this.chart.xAxis.tickFormat(function (d) { this.chart.xAxis.tickFormat(function(d) {
var label = ''; var label = "";
_.each(values.value.graphs, function (v) { _.each(values.value.graphs, function(v) {
if (v.values[d] && v.values[d].x) { if (v.values[d] && v.values[d].x) {
label = v.values[d].x; label = v.values[d].x;
} }
}); });
return label; return label;
}); });
this.chart.yAxis.tickFormat(d3.format(',.2f')); this.chart.yAxis.tickFormat(d3.format(",.2f"));
this.chart.tooltip.contentGenerator(function (key) { this.chart.tooltip.contentGenerator(function(key) {
return qweb.render('GraphCustomTooltip', { return qweb.render("GraphCustomTooltip", {
'color': key.point.color, color: key.point.color,
'key': key.series[0].title, key: key.series[0].title,
'value': d3.format(',.2f')(key.point.y) value: d3.format(",.2f")(key.point.y),
}); });
}); });
}, },
_addGraph: function (values) { _addGraph: function(values) {
var data = values.value.graphs; var data = values.value.graphs;
this.$svg.addClass('o_graph_linechart'); this.$svg.addClass("o_graph_linechart");
this.chart = nv.models.lineChart(); this.chart = nv.models.lineChart();
this.chart.options( this.chart.options(this._getChartOptions(values));
this._getChartOptions(values)
);
this._chartConfiguration(values); this._chartConfiguration(values);
d3.select(this.$('svg')[0]) d3.select(this.$("svg")[0])
.datum(data) .datum(data)
.transition().duration(600) .transition()
.duration(600)
.call(this.chart); .call(this.chart);
this.$('svg').css('height', this.widget_size_y - 90); this.$("svg").css("height", this.widget_size_y - 90);
this._customizeChart(); this._customizeChart();
}, },
fillWidget: function (values) { fillWidget: function(values) {
var self = this; var self = this;
var element = this.$el.find('[data-bind="value"]'); var element = this.$el.find('[data-bind="value"]');
element.empty(); element.empty();
element.css('padding-left', 10).css('padding-right', 10); element.css("padding-left", 10).css("padding-right", 10);
this.chart = null; this.chart = null;
nv.addGraph(function () { nv.addGraph(function() {
self.$svg = self.$el.find( self.$svg = self.$el
'[data-bind="value"]' .find('[data-bind="value"]')
).append('<svg width=' + (self.widget_size_x - 20) + '>'); .append("<svg width=" + (self.widget_size_x - 20) + ">");
self._addGraph(values); self._addGraph(values);
}); });
}, },
_customizeChart: function () { _customizeChart: function() {
// Hook function // Hook function
}, },
_onResize: function () { _onResize: function() {
if (this.chart) { if (this.chart) {
this.chart.update(); this.chart.update();
this._customizeChart(); this._customizeChart();
@ -103,6 +100,6 @@ odoo.define('kpi_dashboard.GraphWidget', function (require) {
}, },
}); });
registry.add('graph', GraphWidget); registry.add("graph", GraphWidget);
return GraphWidget; return GraphWidget;
}); });

View File

@ -1,36 +1,38 @@
odoo.define('kpi_dashboard.IntegerWidget', function (require) { odoo.define("kpi_dashboard.IntegerWidget", function(require) {
"use strict"; "use strict";
var AbstractWidget = require('kpi_dashboard.AbstractWidget'); var AbstractWidget = require("kpi_dashboard.AbstractWidget");
var registry = require('kpi_dashboard.widget_registry'); var registry = require("kpi_dashboard.widget_registry");
var field_utils = require('web.field_utils'); var field_utils = require("web.field_utils");
var IntegerWidget = AbstractWidget.extend({ var IntegerWidget = AbstractWidget.extend({
template: 'kpi_dashboard.number', template: "kpi_dashboard.number",
digits: [3, 0], digits: [3, 0],
shortList: [ shortList: [
[1000000000000, 'T', [3, 1]], [1000000000000, "T", [3, 1]],
[1000000000, 'G', [3, 1]], [1000000000, "G", [3, 1]],
[1000000, 'M', [3, 1]], [1000000, "M", [3, 1]],
[1000, 'K', [3, 1]] [1000, "K", [3, 1]],
], ],
shortNumber: function (num) { shortNumber: function(num) {
var suffix = ''; var suffix = "";
var shortened = false; var shortened = false;
var digits = this.digits; var digits = this.digits;
_.each(this.shortList, function (shortItem) { _.each(this.shortList, function(shortItem) {
if (!shortened && Math.abs(num) >= shortItem[0]) { if (!shortened && Math.abs(num) >= shortItem[0]) {
shortened = true; shortened = true;
suffix = shortItem[1]; suffix = shortItem[1];
num = num / shortItem[0]; num /= shortItem[0];
digits = shortItem[2]; digits = shortItem[2];
} }
}); });
return field_utils.format.float(num, false, { return (
digits: digits}) + suffix; field_utils.format.float(num, false, {
digits: digits,
}) + suffix
);
}, },
fillWidget: function (values) { fillWidget: function(values) {
var widget = this.$el; var widget = this.$el;
var value = values.value.value; var value = values.value.value;
if (value === undefined) { if (value === undefined) {
@ -42,30 +44,31 @@ odoo.define('kpi_dashboard.IntegerWidget', function (require) {
} }
var previous = values.value.previous; var previous = values.value.previous;
var $change_rate = widget.find('.change-rate'); var $change_rate = widget.find(".change-rate");
if (previous === undefined) { if (previous === undefined) {
$change_rate.toggleClass('active', false); $change_rate.toggleClass("active", false);
} else { } else {
var difference = 0; var difference = 0;
if (previous !== 0) { if (previous !== 0) {
difference = field_utils.format.integer( difference =
(100 * value / previous) - 100) + '%'; field_utils.format.integer((100 * value) / previous - 100) +
"%";
} }
$change_rate.toggleClass('active', true); $change_rate.toggleClass("active", true);
var $difference = widget.find('[data-bind="difference"]'); var $difference = widget.find('[data-bind="difference"]');
$difference.text(difference); $difference.text(difference);
var $arrow = widget.find('[data-bind="arrow"]'); var $arrow = widget.find('[data-bind="arrow"]');
if (value < previous) { if (value < previous) {
$arrow.toggleClass('fa-arrow-up', false); $arrow.toggleClass("fa-arrow-up", false);
$arrow.toggleClass('fa-arrow-down', true); $arrow.toggleClass("fa-arrow-down", true);
} else { } else {
$arrow.toggleClass('fa-arrow-up', true); $arrow.toggleClass("fa-arrow-up", true);
$arrow.toggleClass('fa-arrow-down', false); $arrow.toggleClass("fa-arrow-down", false);
} }
} }
}, },
}); });
registry.add('integer', IntegerWidget); registry.add("integer", IntegerWidget);
return IntegerWidget; return IntegerWidget;
}); });

View File

@ -1,39 +1,34 @@
odoo.define('kpi_dashboard.MeterWidget', function (require) { odoo.define("kpi_dashboard.MeterWidget", function(require) {
"use strict"; "use strict";
var AbstractWidget = require('kpi_dashboard.AbstractWidget'); var AbstractWidget = require("kpi_dashboard.AbstractWidget");
var registry = require('kpi_dashboard.widget_registry'); var registry = require("kpi_dashboard.widget_registry");
var MeterWidget = AbstractWidget.extend({ var MeterWidget = AbstractWidget.extend({
template: 'kpi_dashboard.meter', template: "kpi_dashboard.meter",
jsLibs: [ jsLibs: ["/kpi_dashboard/static/lib/gauge/GaugeMeter.js"],
'/kpi_dashboard/static/lib/gauge/GaugeMeter.js', fillWidget: function(values) {
],
fillWidget: function (values) {
var input = this.$el.find('[data-bind="value"]'); var input = this.$el.find('[data-bind="value"]');
var options = this._getMeterOptions(values); var options = this._getMeterOptions(values);
var margin = (this.widget_dimension_x - options.size)/2; var margin = (this.widget_dimension_x - options.size) / 2;
input.gaugeMeter(options); input.gaugeMeter(options);
input.parent().css('padding-left', margin); input.parent().css("padding-left", margin);
}, },
_getMeterOptions: function (values) { _getMeterOptions: function(values) {
var size = Math.min( var size = Math.min(this.widget_size_x, this.widget_size_y - 40) - 10;
this.widget_size_x,
this.widget_size_y - 40) - 10;
return { return {
percent: values.value.value, percent: values.value.value,
style: 'Arch', style: "Arch",
width: 10, width: 10,
size: size, size: size,
prepend: values.prefix !== undefined ? values.prefix : '', prepend: values.prefix !== undefined ? values.prefix : "",
append: values.suffix !== undefined ? values.suffix : '', append: values.suffix !== undefined ? values.suffix : "",
color: values.font_color, color: values.font_color,
animate_text_colors: true, animate_text_colors: true,
}; };
}, },
}); });
registry.add('meter', MeterWidget); registry.add("meter", MeterWidget);
return MeterWidget; return MeterWidget;
}); });

View File

@ -1,21 +1,22 @@
odoo.define('kpi_dashboard.NumberWidget', function (require) { odoo.define("kpi_dashboard.NumberWidget", function(require) {
"use strict"; "use strict";
var IntegerWidget = require('kpi_dashboard.IntegerWidget'); var IntegerWidget = require("kpi_dashboard.IntegerWidget");
var registry = require('kpi_dashboard.widget_registry'); var registry = require("kpi_dashboard.widget_registry");
var field_utils = require('web.field_utils'); var field_utils = require("web.field_utils");
var NumberWidget = IntegerWidget.extend({ var NumberWidget = IntegerWidget.extend({
digits: [3, 1], digits: [3, 1],
shortNumber: function (num) { shortNumber: function(num) {
if (Math.abs(num) < 10) { if (Math.abs(num) < 10) {
return field_utils.format.float(num, false, { return field_utils.format.float(num, false, {
digits: [3, 2]}); digits: [3, 2],
});
} }
return this._super.apply(this, arguments) return this._super.apply(this, arguments);
}, },
}); });
registry.add('number', NumberWidget); registry.add("number", NumberWidget);
return NumberWidget; return NumberWidget;
}); });

View File

@ -1,17 +1,16 @@
odoo.define('kpi_dashboard.TextWidget', function (require) { odoo.define("kpi_dashboard.TextWidget", function(require) {
"use strict"; "use strict";
var AbstractWidget = require('kpi_dashboard.AbstractWidget'); var AbstractWidget = require("kpi_dashboard.AbstractWidget");
var registry = require('kpi_dashboard.widget_registry'); var registry = require("kpi_dashboard.widget_registry");
var TextWidget = AbstractWidget.extend({ var TextWidget = AbstractWidget.extend({
template: 'kpi_dashboard.base_text', template: "kpi_dashboard.base_text",
fillWidget: function () { fillWidget: function() {
return; return;
}, },
}); });
registry.add('base_text', TextWidget); registry.add("base_text", TextWidget);
return TextWidget; return TextWidget;
}); });

View File

@ -1,7 +1,7 @@
odoo.define('kpi_dashboard.widget_registry', function (require) { odoo.define("kpi_dashboard.widget_registry", function(require) {
"use strict"; "use strict";
var Registry = require('web.Registry'); var Registry = require("web.Registry");
return new Registry(); return new Registry();
}); });

View File

@ -1,11 +1,14 @@
.o_dashboard_view { .o_dashboard_view {
height: 100%; height: 100%;
@include o-webclient-padding($top: $o-horizontal-padding/2, $bottom: $o-horizontal-padding/2); @include o-webclient-padding(
$top: $o-horizontal-padding/2,
$bottom: $o-horizontal-padding/2
);
display: flex; display: flex;
>.gridster { > .gridster {
margin: 0 auto; margin: 0 auto;
>ul { > ul {
>li { > li {
text-align: center; text-align: center;
list-style: none outside none; list-style: none outside none;
} }
@ -56,7 +59,7 @@
max-width: 400px; max-width: 400px;
} }
.o_kpi_dashboard_manage_section { .o_kpi_dashboard_manage_section {
border-bottom: 1px solid gray('300'); border-bottom: 1px solid gray("300");
margin-bottom: 10px; margin-bottom: 10px;
} }
> div { > div {
@ -71,7 +74,7 @@
} }
.o_kpi_dashboard_toggle_button { .o_kpi_dashboard_toggle_button {
background: white; background: white;
border-color: gray('400'); border-color: gray("400");
z-index: $zindex-dropdown + 1; z-index: $zindex-dropdown + 1;
} }
} }
@ -82,8 +85,9 @@
right: 0; right: 0;
overflow: hidden; overflow: hidden;
cursor: default; cursor: default;
span, b{ span,
margin: 0 23%; b {
margin: 0 23%;
width: 54%; width: 54%;
position: absolute; position: absolute;
text-align: center; text-align: center;
@ -93,20 +97,20 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
[data-style="Semi"] B{ [data-style="Semi"] b {
Margin: 0 10%; margin: 0 10%;
Width: 80%; width: 80%;
} }
S, U{ s,
Text-Decoration:None; u {
text-decoration: None;
font-height: 100; font-height: 100;
} }
B{ b {
Color: Black; color: Black;
Font-Weight: 200; font-weight: 200;
Font-Size: 0.85em; font-size: 0.85em;
Opacity: .8; opacity: 0.8;
} }
} }
} }

View File

@ -2,11 +2,12 @@
<template> <template>
<t t-name="dashboard_kpi.dashboard"> <t t-name="dashboard_kpi.dashboard">
<div class="gridster kpi_dashboard"> <div class="gridster kpi_dashboard">
<ul/> <ul />
</div> </div>
</t> </t>
<t t-name="kpi_dashboard.kpi"> <t t-name="kpi_dashboard.kpi">
<li t-att-data-row="widget.row" <li
t-att-data-row="widget.row"
t-att-data-col="widget.col" t-att-data-col="widget.col"
t-att-data-sizex="widget.sizex" t-att-data-sizex="widget.sizex"
t-att-data-sizey="widget.sizey" t-att-data-sizey="widget.sizey"
@ -14,15 +15,21 @@
</t> </t>
<t t-name="kpi_dashboard.base_text"> <t t-name="kpi_dashboard.base_text">
<div class="kpi"> <div class="kpi">
<h1 class="title" t-esc="widget.values.name"/> <h1 class="title" t-esc="widget.values.name" />
</div> </div>
</t> </t>
<t t-name="kpi_dashboard.ManagePanel"> <t t-name="kpi_dashboard.ManagePanel">
<t t-if="widget.actions" > <t t-if="widget.actions">
<t t-foreach="widget.actions" t-as="action_id"> <t t-foreach="widget.actions" t-as="action_id">
<t t-set="action" t-value="widget.actions[action_id]"/> <t t-set="action" t-value="widget.actions[action_id]" />
<div role="menuitem" class=""> <div role="menuitem" class="">
<a role="menuitem" href="#" class="direct_action" t-att-data-id="action_id" t-att-data-type="action.type">Go to <t t-esc="action.name"/></a> <a
role="menuitem"
href="#"
class="direct_action"
t-att-data-id="action_id"
t-att-data-type="action.type"
>Go to <t t-esc="action.name" /></a>
</div> </div>
</t> </t>
</t> </t>
@ -31,45 +38,55 @@
<div class="kpi"> <div class="kpi">
<div class="o_kpi_dashboard_manage hidden"> <div class="o_kpi_dashboard_manage hidden">
<a class="o_kpi_dashboard_toggle_button" href="#"> <a class="o_kpi_dashboard_toggle_button" href="#">
<i class="fa fa-ellipsis-v" aria-label="Selection" role="img" title="Selection"/> <i
class="fa fa-ellipsis-v"
aria-label="Selection"
role="img"
title="Selection"
/>
</a> </a>
</div> </div>
<h1 class="title" t-esc="widget.values.name"/> <h1 class="title" t-esc="widget.values.name" />
<p class="updated_at" data-bind="value_last_update_display"/> <p class="updated_at" data-bind="value_last_update_display" />
<div class="container o_kpi_dashboard_manage_panel dropdown-menu"> <div class="container o_kpi_dashboard_manage_panel dropdown-menu">
<t t-call="kpi_dashboard.ManagePanel"/> <t t-call="kpi_dashboard.ManagePanel" />
</div> </div>
</div> </div>
</t> </t>
<t t-name="kpi_dashboard.number" t-extend="kpi_dashboard.base_widget"> <t t-name="kpi_dashboard.number" t-extend="kpi_dashboard.base_widget">
<t t-jquery="h1" t-operation="after"> <t t-jquery="h1" t-operation="after">
<h2 class="numbervalue"> <h2 class="numbervalue">
<span t-esc="widget.prefix"/><span data-bind="value"/><span t-esc="widget.suffix"/> <span t-esc="widget.prefix" />
<span data-bind="value" />
<span t-esc="widget.suffix" />
</h2> </h2>
<p class="change-rate"> <p class="change-rate">
<i class="fa" data-bind="arrow"/> <i class="fa" data-bind="arrow" />
<span data-bind="difference"/> <span data-bind="difference" />
</p> </p>
</t> </t>
</t> </t>
<t t-name="kpi_dashboard.meter" t-extend="kpi_dashboard.base_widget"> <t t-name="kpi_dashboard.meter" t-extend="kpi_dashboard.base_widget">
<t t-jquery="h1" t-operation="after"> <t t-jquery="h1" t-operation="after">
<div class="centered"> <div class="centered">
<div class="GaugeMeter" data-bind="value"/> <div class="GaugeMeter" data-bind="value" />
</div> </div>
</t> </t>
</t> </t>
<t t-name="kpi_dashboard.graph" t-extend="kpi_dashboard.base_widget"> <t t-name="kpi_dashboard.graph" t-extend="kpi_dashboard.base_widget">
<t t-jquery="h1" t-operation="after"> <t t-jquery="h1" t-operation="after">
<div class="centered"> <div class="centered">
<div data-bind="value"/> <div data-bind="value" />
</div> </div>
</t> </t>
</t> </t>
<t t-name="kpi_dashboard.buttons"> <t t-name="kpi_dashboard.buttons">
<div class="o_dashboard_buttons" role="toolbar" aria-label="Main actions"> <div class="o_dashboard_buttons" role="toolbar" aria-label="Main actions">
<button type="button" <button
class="btn btn-primary o_dashboard_button_add" accesskey="d"> type="button"
class="btn btn-primary o_dashboard_button_add"
accesskey="d"
>
Add to Dashboard Add to Dashboard
</button> </button>
</div> </div>

View File

@ -1,28 +1,68 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<template
<template id="assets_backend" id="assets_backend"
name="Backend Assets (used in backend interface)" name="Backend Assets (used in backend interface)"
inherit_id="web.assets_backend"> inherit_id="web.assets_backend"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<script
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget_registry.js"/> type="text/javascript"
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget/abstract_widget.js"/> src="/kpi_dashboard/static/src/js/widget_registry.js"
/>
<script type="text/javascript" src="/kpi_dashboard/static/src/js/dashboard_renderer.js"/> <script
<script type="text/javascript" src="/kpi_dashboard/static/src/js/dashboard_model.js"/> type="text/javascript"
<script type="text/javascript" src="/kpi_dashboard/static/src/js/dashboard_controller.js"/> src="/kpi_dashboard/static/src/js/widget/abstract_widget.js"
<script type="text/javascript" src="/kpi_dashboard/static/src/js/dashboard_view.js"/> />
<script
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget/integer_widget.js"/> type="text/javascript"
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget/number_widget.js"/> src="/kpi_dashboard/static/src/js/dashboard_renderer.js"
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget/counter_widget.js"/> />
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget/meter_widget.js"/> <script
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget/graph_widget.js"/> type="text/javascript"
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget/text_widget.js"/> src="/kpi_dashboard/static/src/js/dashboard_model.js"
<script type="text/javascript" src="/kpi_dashboard/static/src/js/field_widget.js"/> />
<script
<link rel="stylesheet" type="text/scss" href="/kpi_dashboard/static/src/scss/kpi_dashboard.scss"/> type="text/javascript"
src="/kpi_dashboard/static/src/js/dashboard_controller.js"
/>
<script
type="text/javascript"
src="/kpi_dashboard/static/src/js/dashboard_view.js"
/>
<script
type="text/javascript"
src="/kpi_dashboard/static/src/js/widget/integer_widget.js"
/>
<script
type="text/javascript"
src="/kpi_dashboard/static/src/js/widget/number_widget.js"
/>
<script
type="text/javascript"
src="/kpi_dashboard/static/src/js/widget/counter_widget.js"
/>
<script
type="text/javascript"
src="/kpi_dashboard/static/src/js/widget/meter_widget.js"
/>
<script
type="text/javascript"
src="/kpi_dashboard/static/src/js/widget/graph_widget.js"
/>
<script
type="text/javascript"
src="/kpi_dashboard/static/src/js/widget/text_widget.js"
/>
<script
type="text/javascript"
src="/kpi_dashboard/static/src/js/field_widget.js"
/>
<link
rel="stylesheet"
type="text/scss"
href="/kpi_dashboard/static/src/scss/kpi_dashboard.scss"
/>
</xpath> </xpath>
</template> </template>
</odoo> </odoo>

View File

@ -1,19 +1,15 @@
# Copyright 2020 Creu Blanca # Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase
class TestFormula(TransactionCase): class TestFormula(TransactionCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.kpi = self.env["kpi.kpi"].create( self.kpi = self.env["kpi.kpi"].create(
{ {"name": "DEMO KPI", "widget": "number", "computation_method": "code",}
"name": "DEMO KPI",
"widget": "number",
"computation_method": "code",
}
) )
def test_forbidden_words_01(self): def test_forbidden_words_01(self):
@ -45,10 +41,10 @@ self.env.cr.execute("CoMMiT")
self.kpi.compute() self.kpi.compute()
self.assertEqual(self.kpi.value, {}) self.assertEqual(self.kpi.value, {})
self.kpi.code = """ self.kpi.code = """
result = {} result = {{}}
result['value'] = len(model.search([('id', '=', %s)])) result['value'] = len(model.search([('id', '=', {})]))
result['previous'] = len(model.search([('id', '!=', %s)])) result['previous'] = len(model.search([('id', '!=', {})]))
""" % ( """.format(
self.kpi.id, self.kpi.id,
self.kpi.id, self.kpi.id,
) )
@ -66,10 +62,10 @@ result['previous'] = len(model.search([('id', '!=', %s)]))
self.assertTrue(self.kpi.history_ids) self.assertTrue(self.kpi.history_ids)
self.assertEqual(self.kpi.value, {}) self.assertEqual(self.kpi.value, {})
self.kpi.code = """ self.kpi.code = """
result = {} result = {{}}
result['value'] = len(model.search([('id', '=', %s)])) result['value'] = len(model.search([('id', '=', {})]))
result['previous'] = len(model.search([('id', '!=', %s)])) result['previous'] = len(model.search([('id', '!=', {})]))
""" % ( """.format(
self.kpi.id, self.kpi.id,
self.kpi.id, self.kpi.id,
) )

View File

@ -1,85 +1,92 @@
# Copyright 2020 Creu Blanca # Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError
from odoo.tests.common import Form
from mock import patch from mock import patch
from odoo.exceptions import ValidationError
from odoo.tests.common import Form, TransactionCase
class TestKpiDashboard(TransactionCase): class TestKpiDashboard(TransactionCase):
def setUp(self): def setUp(self):
super(TestKpiDashboard, self).setUp() super(TestKpiDashboard, self).setUp()
self.kpi_01 = self.env['kpi.kpi'].create({ self.kpi_01 = self.env["kpi.kpi"].create(
'name': 'KPI 01', {
'computation_method': 'function', "name": "KPI 01",
'widget': 'number', "computation_method": "function",
'function': 'test_demo_number' "widget": "number",
}) "function": "test_demo_number",
self.kpi_02 = self.env['kpi.kpi'].create({ }
'name': 'KPI 02', )
'computation_method': 'function', self.kpi_02 = self.env["kpi.kpi"].create(
'widget': 'number', {
'function': 'test_demo_number' "name": "KPI 02",
}) "computation_method": "function",
self.dashboard = self.env['kpi.dashboard'].create({ "widget": "number",
'name': 'Dashboard', "function": "test_demo_number",
'number_of_columns': 4, }
'widget_dimension_x': 250, )
'widget_dimension_y': 250, self.dashboard = self.env["kpi.dashboard"].create(
}) {
self.env['kpi.dashboard.item'].create({ "name": "Dashboard",
'dashboard_id': self.dashboard.id, "number_of_columns": 4,
'kpi_id': self.kpi_01.id, "widget_dimension_x": 250,
'name': self.kpi_01.name, "widget_dimension_y": 250,
'row': 1, }
'column': 1, )
}) self.env["kpi.dashboard.item"].create(
self.env['kpi.dashboard.item'].create({ {
'dashboard_id': self.dashboard.id, "dashboard_id": self.dashboard.id,
'name': self.kpi_02.name, "kpi_id": self.kpi_01.id,
'kpi_id': self.kpi_02.id, "name": self.kpi_01.name,
'row': 1, "row": 1,
'column': 2, "column": 1,
}) }
self.env['kpi.dashboard.item'].create({ )
'dashboard_id': self.dashboard.id, self.env["kpi.dashboard.item"].create(
'name': 'TITLE', {
'row': 2, "dashboard_id": self.dashboard.id,
'column': 1, "name": self.kpi_02.name,
}) "kpi_id": self.kpi_02.id,
"row": 1,
"column": 2,
}
)
self.env["kpi.dashboard.item"].create(
{"dashboard_id": self.dashboard.id, "name": "TITLE", "row": 2, "column": 1,}
)
def test_constrains_01(self): def test_constrains_01(self):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.kpi_01.dashboard_item_ids.write({'size_x': 2}) self.kpi_01.dashboard_item_ids.write({"size_x": 2})
def test_constrains_02(self): def test_constrains_02(self):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.kpi_02.dashboard_item_ids.write({'size_x': 4}) self.kpi_02.dashboard_item_ids.write({"size_x": 4})
def test_constrains_03(self): def test_constrains_03(self):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.kpi_01.dashboard_item_ids.write({'size_y': 11}) self.kpi_01.dashboard_item_ids.write({"size_y": 11})
def test_menu(self): def test_menu(self):
self.assertFalse(self.dashboard.menu_id) self.assertFalse(self.dashboard.menu_id)
wzd = self.env['kpi.dashboard.menu'].create({ wzd = self.env["kpi.dashboard.menu"].create(
'dashboard_id': self.dashboard.id, {
'menu_id': self.env['ir.ui.menu'].search([], limit=1).id, "dashboard_id": self.dashboard.id,
}) "menu_id": self.env["ir.ui.menu"].search([], limit=1).id,
}
)
wzd.generate_menu() wzd.generate_menu()
self.assertTrue(self.dashboard.menu_id) self.assertTrue(self.dashboard.menu_id)
self.assertFalse(self.dashboard.menu_id.groups_id) self.assertFalse(self.dashboard.menu_id.groups_id)
self.dashboard.write({ self.dashboard.write(
'group_ids': [ {"group_ids": [(6, 0, self.env["res.groups"].search([], limit=1).ids)]}
(6, 0, self.env['res.groups'].search([], limit=1).ids)] )
})
self.assertTrue(self.dashboard.menu_id.groups_id) self.assertTrue(self.dashboard.menu_id.groups_id)
def test_onchange(self): def test_onchange(self):
with Form(self.env['kpi.dashboard']) as dashboard: with Form(self.env["kpi.dashboard"]) as dashboard:
dashboard.name = 'New Dashboard' dashboard.name = "New Dashboard"
with dashboard.item_ids.new() as item: with dashboard.item_ids.new() as item:
item.kpi_id = self.kpi_01 item.kpi_id = self.kpi_01
self.assertTrue(item.name) self.assertTrue(item.name)
@ -88,40 +95,38 @@ class TestKpiDashboard(TransactionCase):
data = self.dashboard.read_dashboard() data = self.dashboard.read_dashboard()
title_found = False title_found = False
actions = 0 actions = 0
for item in data['item_ids']: for item in data["item_ids"]:
if not item.get('kpi_id'): if not item.get("kpi_id"):
title_found = True title_found = True
if item.get('actions', False): if item.get("actions", False):
actions += len(item['actions']) actions += len(item["actions"])
self.assertTrue(title_found) self.assertTrue(title_found)
self.assertEqual(0, actions) self.assertEqual(0, actions)
act01 = self.env['ir.actions.act_window'].search( act01 = self.env["ir.actions.act_window"].search([], limit=1)
[], limit=1) self.env["kpi.kpi.action"].create(
self.env['kpi.kpi.action'].create({ {"kpi_id": self.kpi_01.id, "action": "{},{}".format(act01._name, act01.id)}
'kpi_id': self.kpi_01.id, )
'action': '%s,%s' % (act01._name, act01.id) act02 = self.env["ir.actions.act_url"].search([], limit=1)
}) self.env["kpi.kpi.action"].create(
act02 = self.env['ir.actions.act_url'].search( {"kpi_id": self.kpi_01.id, "action": "{},{}".format(act02._name, act02.id)}
[], limit=1) )
self.env['kpi.kpi.action'].create({
'kpi_id': self.kpi_01.id,
'action': '%s,%s' % (act02._name, act02.id)
})
data = self.dashboard.read_dashboard() data = self.dashboard.read_dashboard()
title_found = False title_found = False
actions = 0 actions = 0
for item in data['item_ids']: for item in data["item_ids"]:
if not item.get('kpi_id'): if not item.get("kpi_id"):
title_found = True title_found = True
if item.get('actions', False): if item.get("actions", False):
actions += len(item['actions']) actions += len(item["actions"])
self.assertTrue(title_found) self.assertTrue(title_found)
self.assertEqual(2, actions) self.assertEqual(2, actions)
self.assertFalse(data.get("action_id", False)) self.assertFalse(data.get("action_id", False))
wzd = self.env['kpi.dashboard.menu'].create({ wzd = self.env["kpi.dashboard.menu"].create(
'dashboard_id': self.dashboard.id, {
'menu_id': self.env['ir.ui.menu'].search([], limit=1).id, "dashboard_id": self.dashboard.id,
}) "menu_id": self.env["ir.ui.menu"].search([], limit=1).id,
}
)
wzd.generate_menu() wzd.generate_menu()
data = self.dashboard.read_dashboard() data = self.dashboard.read_dashboard()
self.assertTrue(data.get("action_id", False)) self.assertTrue(data.get("action_id", False))
@ -129,8 +134,8 @@ class TestKpiDashboard(TransactionCase):
def test_compute(self): def test_compute(self):
self.assertFalse(self.kpi_01.value_last_update) self.assertFalse(self.kpi_01.value_last_update)
with patch( with patch(
"odoo.addons.kpi_dashboard.models.kpi_kpi." "odoo.addons.kpi_dashboard.models.kpi_kpi." "KpiKpi.test_demo_number",
"KpiKpi.test_demo_number", create=True create=True,
) as f: ) as f:
f.return_value = {"value": 0} f.return_value = {"value": 0}
self.kpi_01.compute() self.kpi_01.compute()
@ -138,10 +143,9 @@ class TestKpiDashboard(TransactionCase):
def test_compute_model(self): def test_compute_model(self):
self.assertFalse(self.kpi_01.value_last_update) self.assertFalse(self.kpi_01.value_last_update)
self.kpi_01.model_id = self.env.ref('base.model_res_partner') self.kpi_01.model_id = self.env.ref("base.model_res_partner")
with patch( with patch(
"odoo.addons.base.models.res_partner.Partner.test_demo_number", "odoo.addons.base.models.res_partner.Partner.test_demo_number", create=True
create=True
) as f: ) as f:
f.return_value = {"value": 0} f.return_value = {"value": 0}
self.kpi_01.compute() self.kpi_01.compute()
@ -153,8 +157,8 @@ class TestKpiDashboard(TransactionCase):
self.assertTrue(self.kpi_01.cron_id) self.assertTrue(self.kpi_01.cron_id)
self.assertFalse(self.kpi_01.value_last_update) self.assertFalse(self.kpi_01.value_last_update)
with patch( with patch(
"odoo.addons.kpi_dashboard.models.kpi_kpi." "odoo.addons.kpi_dashboard.models.kpi_kpi." "KpiKpi.test_demo_number",
"KpiKpi.test_demo_number", create=True create=True,
) as f: ) as f:
f.return_value = {"value": 0} f.return_value = {"value": 0}
self.kpi_01.cron_id.method_direct_trigger() self.kpi_01.cron_id.method_direct_trigger()

View File

@ -1,170 +1,179 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Creu Blanca <!-- Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<record model="ir.ui.view" id="kpi_dashboard_form_view"> <record model="ir.ui.view" id="kpi_dashboard_form_view">
<field name="name">kpi.dashboard.form (in kpi_dashboard)</field> <field name="name">kpi.dashboard.form (in kpi_dashboard)</field>
<field name="model">kpi.dashboard</field> <field name="model">kpi.dashboard</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<header/> <header />
<sheet> <sheet>
<div name="button_box" class="oe_button_box"> <div name="button_box" class="oe_button_box">
<button name="%(kpi_dashboard.kpi_dashboard_menu_act_window)d" <button
type="action" name="%(kpi_dashboard.kpi_dashboard_menu_act_window)d"
string="Generate menu" type="action"
icon="fa-folder-open-o" string="Generate menu"
context="{'default_dashboard_id': active_id}" icon="fa-folder-open-o"
attrs="{'invisible': [('menu_id', '!=', False)]}" context="{'default_dashboard_id': active_id}"
/> attrs="{'invisible': [('menu_id', '!=', False)]}"
/>
</div> </div>
<group> <group>
<field name="name"/> <field name="name" />
<field name="menu_id" attrs="{'invisible': [('menu_id', '=', False)]}"/> <field
name="menu_id"
attrs="{'invisible': [('menu_id', '=', False)]}"
/>
</group> </group>
<notebook> <notebook>
<page name="item" string="KPIs"> <page name="item" string="KPIs">
<field name="item_ids"> <field name="item_ids">
<tree editable="bottom"> <tree editable="bottom">
<field name="name"/> <field name="name" />
<field name="kpi_id"/> <field name="kpi_id" />
<field name="column"/> <field name="column" />
<field name="row"/> <field name="row" />
<field name="size_x"/> <field name="size_x" />
<field name="size_y"/> <field name="size_y" />
<field name="color" widget="color"/> <field name="color" widget="color" />
<field name="font_color" widget="color"/> <field name="font_color" widget="color" />
<button name="technical_config" string="" <button
type="object" icon="fa-edit" name="technical_config"
groups="base.group_no_one"/> string=""
type="object"
icon="fa-edit"
groups="base.group_no_one"
/>
</tree> </tree>
</field> </field>
</page> </page>
<page name="widget" string="Widget configuration"> <page name="widget" string="Widget configuration">
<group> <group>
<group name="margin"> <group name="margin">
<field name="margin_x"/> <field name="margin_x" />
<field name="margin_y"/> <field name="margin_y" />
</group> </group>
<group name="dimension"> <group name="dimension">
<field name="widget_dimension_x"/> <field name="widget_dimension_x" />
<field name="widget_dimension_y"/> <field name="widget_dimension_y" />
<field name="number_of_columns"/> <field name="number_of_columns" />
<field name="width"/> <field name="width" />
</group> </group>
<group name="color"> <group name="color">
<field name="background_color" widget="color"/> <field name="background_color" widget="color" />
</group> </group>
<group name="config"> <group name="config">
<field name="compute_on_fly_refresh"/> <field name="compute_on_fly_refresh" />
</group> </group>
</group> </group>
</page> </page>
<page name="group" string="Groups"> <page name="group" string="Groups">
<field name="group_ids"/> <field name="group_ids" />
</page> </page>
</notebook> </notebook>
</sheet> </sheet>
</form> </form>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="kpi_dashboard_search_view"> <record model="ir.ui.view" id="kpi_dashboard_search_view">
<field name="name">kpi.dashboard.search (in kpi_dashboard)</field> <field name="name">kpi.dashboard.search (in kpi_dashboard)</field>
<field name="model">kpi.dashboard</field> <field name="model">kpi.dashboard</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search> <search>
<field name="name"/> <field name="name" />
</search> </search>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="kpi_dashboard_tree_view"> <record model="ir.ui.view" id="kpi_dashboard_tree_view">
<field name="name">kpi.dashboard.tree (in kpi_dashboard)</field> <field name="name">kpi.dashboard.tree (in kpi_dashboard)</field>
<field name="model">kpi.dashboard</field> <field name="model">kpi.dashboard</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="name"/> <field name="name" />
</tree> </tree>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="kpi_dashboard_dashboard_view"> <record model="ir.ui.view" id="kpi_dashboard_dashboard_view">
<field name="name">kpi.dashboard.dashboard (in kpi_dashboard)</field> <field name="name">kpi.dashboard.dashboard (in kpi_dashboard)</field>
<field name="model">kpi.dashboard</field> <field name="model">kpi.dashboard</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<dashboard/> <dashboard />
</field> </field>
</record> </record>
<record model="ir.actions.act_window" id="kpi_dashboard_act_window"> <record model="ir.actions.act_window" id="kpi_dashboard_act_window">
<field name="name">Kpi Dashboard</field> <!-- TODO --> <field name="name">Kpi Dashboard</field>
<!-- TODO -->
<field name="res_model">kpi.dashboard</field> <field name="res_model">kpi.dashboard</field>
<field name="view_mode">tree,form,dashboard</field> <field name="view_mode">tree,form,dashboard</field>
<field name="domain">[]</field> <field name="domain">[]</field>
<field name="context">{}</field> <field name="context">{}</field>
</record> </record>
<record model="ir.ui.menu" id="kpi_dashboard_menu"> <record model="ir.ui.menu" id="kpi_dashboard_menu">
<field name="name">Configure Dashboard</field> <field name="name">Configure Dashboard</field>
<field name="parent_id" ref="menu_configuration_kpi_dashboards"/> <!-- TODO --> <field name="parent_id" ref="menu_configuration_kpi_dashboards" />
<field name="action" ref="kpi_dashboard_act_window"/> <!-- TODO -->
<field name="sequence" eval="16"/> <!-- TODO --> <field name="action" ref="kpi_dashboard_act_window" />
<field name="sequence" eval="16" />
<!-- TODO -->
</record> </record>
<record model="ir.ui.view" id="kpi_dashboard_item_form_view"> <record model="ir.ui.view" id="kpi_dashboard_item_form_view">
<field name="name">kpi.dashboard.item.form (in kpi_dashboard)</field> <field name="name">kpi.dashboard.item.form (in kpi_dashboard)</field>
<field name="model">kpi.dashboard.item</field> <field name="model">kpi.dashboard.item</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<header/> <header />
<sheet> <sheet>
<div name="button_box" class="oe_button_box"/> <div name="button_box" class="oe_button_box" />
<group> <group>
<field name="name"/> <field name="name" />
<field name="dashboard_id"/> <field name="dashboard_id" />
<field name="kpi_id"/> <field name="kpi_id" />
<field name="column"/> <field name="column" />
<field name="row"/> <field name="row" />
<field name="size_x"/> <field name="size_x" />
<field name="size_y"/> <field name="size_y" />
<field name="color" widget="color"/> <field name="color" widget="color" />
<field name="font_color" widget="color"/> <field name="font_color" widget="color" />
</group> </group>
</sheet> </sheet>
</form> </form>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="kpi_dashboard_item_config_form_view"> <record model="ir.ui.view" id="kpi_dashboard_item_config_form_view">
<field name="name">kpi.dashboard.item.form (in kpi_dashboard)</field> <field name="name">kpi.dashboard.item.form (in kpi_dashboard)</field>
<field name="model">kpi.dashboard.item</field> <field name="model">kpi.dashboard.item</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<header/> <header />
<sheet> <sheet>
<group> <group>
<field name="modify_context"/> <field name="modify_context" />
<field name="modify_context_expression" <field
attrs="{'invisible': [('modify_context', '=', False)]}" name="modify_context_expression"
widget="ace" options="{'mode': 'python'}"/> attrs="{'invisible': [('modify_context', '=', False)]}"
<field name="modify_color"/> widget="ace"
<field name="modify_color_expression" options="{'mode': 'python'}"
attrs="{'invisible': [('modify_color', '=', False)]}" />
widget="ace" options="{'mode': 'python'}"/> <field name="modify_color" />
<field
name="modify_color_expression"
attrs="{'invisible': [('modify_color', '=', False)]}"
widget="ace"
options="{'mode': 'python'}"
/>
</group> </group>
</sheet> </sheet>
<footer> <footer>
<button name="write" <button
string="Save" type="object" name="write"
class="oe_highlight"/> string="Save"
<button special="cancel" string="Cancel" type="object"
class="oe_link"/> class="oe_highlight"
/>
<button special="cancel" string="Cancel" class="oe_link" />
</footer> </footer>
</form> </form>
</field> </field>
</record> </record>
</odoo> </odoo>

View File

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Creu Blanca <!-- Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<record model="ir.actions.act_window" id="kpi_kpi_history_act_window"> <record model="ir.actions.act_window" id="kpi_kpi_history_act_window">
<field name="name">Kpi History</field> <field name="name">Kpi History</field>
<field name="res_model">kpi.kpi.history</field> <field name="res_model">kpi.kpi.history</field>
@ -11,149 +9,199 @@
<field name="domain">[('kpi_id', '=', active_id)]</field> <field name="domain">[('kpi_id', '=', active_id)]</field>
<field name="context">{}</field> <field name="context">{}</field>
</record> </record>
<record model="ir.ui.view" id="kpi_kpi_history_widget_form_view"> <record model="ir.ui.view" id="kpi_kpi_history_widget_form_view">
<field name="name">kpi.kpi.history.raw.form (in kpi_dashboard)</field> <field name="name">kpi.kpi.history.raw.form (in kpi_dashboard)</field>
<field name="model">kpi.kpi.history</field> <field name="model">kpi.kpi.history</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<field name="create_date" invisible="1"/> <field name="create_date" invisible="1" />
<field name="widget" invisible="1"/> <field name="widget" invisible="1" />
<field name="name" invisible="1"/> <field name="name" invisible="1" />
<field name="value" <field
widget="kpi" name="value"
options="{'date': 'create_date', 'widget': 'widget', 'name': 'name'}"/> widget="kpi"
options="{'date': 'create_date', 'widget': 'widget', 'name': 'name'}"
/>
</form> </form>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="kpi_kpi_history_raw_form_view"> <record model="ir.ui.view" id="kpi_kpi_history_raw_form_view">
<field name="name">kpi.kpi.history.raw.form (in kpi_dashboard)</field> <field name="name">kpi.kpi.history.raw.form (in kpi_dashboard)</field>
<field name="model">kpi.kpi.history</field> <field name="model">kpi.kpi.history</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<field name="raw_value"/> <field name="raw_value" />
</form> </form>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="kpi_kpi_history_tree_view"> <record model="ir.ui.view" id="kpi_kpi_history_tree_view">
<field name="name">kpi.kpi.history.tree (in kpi_dashboard)</field> <field name="name">kpi.kpi.history.tree (in kpi_dashboard)</field>
<field name="model">kpi.kpi.history</field> <field name="model">kpi.kpi.history</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree create="0" delete="0"> <tree create="0" delete="0">
<field name="create_date"/> <field name="create_date" />
<button name="show_form" string="Show value" type="object" <button
context="{'form_id': %(kpi_dashboard.kpi_kpi_history_widget_form_view)d}" name="show_form"
string="Show value"
type="object"
context="{'form_id': %(kpi_dashboard.kpi_kpi_history_widget_form_view)d}"
/> />
<button name="show_form" string="Raw value" type="object" <button
context="{'form_id': %(kpi_dashboard.kpi_kpi_history_raw_form_view)d}" name="show_form"
string="Raw value"
type="object"
context="{'form_id': %(kpi_dashboard.kpi_kpi_history_raw_form_view)d}"
/> />
</tree> </tree>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="kpi_kpi_form_view"> <record model="ir.ui.view" id="kpi_kpi_form_view">
<field name="name">kpi.kpi.form (in kpi_dashboard)</field> <field name="name">kpi.kpi.form (in kpi_dashboard)</field>
<field name="model">kpi.kpi</field> <field name="model">kpi.kpi</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<header> <header>
<button name="generate_cron" string="Generate cron" type="object" <button
attrs="{'invisible': ['|', ('cron_id', '!=',False), ('compute_on_fly', '=', True)]}"/> name="generate_cron"
<button name="compute" string="Compute now" type="object" attrs="{'invisible': [('compute_on_fly', '=', True)]}"/> string="Generate cron"
type="object"
attrs="{'invisible': ['|', ('cron_id', '!=',False), ('compute_on_fly', '=', True)]}"
/>
<button
name="compute"
string="Compute now"
type="object"
attrs="{'invisible': [('compute_on_fly', '=', True)]}"
/>
</header> </header>
<sheet> <sheet>
<div class="oe_button_box" name="button_box"> <div class="oe_button_box" name="button_box">
<button name="%(kpi_dashboard.kpi_kpi_history_act_window)d" <button
string="Show history" name="%(kpi_dashboard.kpi_kpi_history_act_window)d"
type="action" string="Show history"
attrs="{'invisible': ['|', ('store_history', '=', False), ('compute_on_fly', '=', True)]}" type="action"
icon="fa-history"/> attrs="{'invisible': ['|', ('store_history', '=', False), ('compute_on_fly', '=', True)]}"
<button string="Show value" type="object" name="show_value" icon="fa-history"
icon="fa-paint-brush" />
<button
string="Show value"
type="object"
name="show_value"
icon="fa-paint-brush"
/> />
</div> </div>
<h2> <h2>
<field name="name"/> <field name="name" />
</h2> </h2>
<group> <group>
<group> <group>
<field name="computation_method"/> <field name="computation_method" />
<field name="widget"/> <field name="widget" />
<field name="store_history" attrs="{'invisible': [('compute_on_fly', '=', True)]}"/> <field
<field name="store_history_interval" attrs="{'invisible': [('store_history', '=', False)]}"/> name="store_history"
<field name="store_history_interval_number" attrs="{'invisible': [('store_history', '=', False)]}"/> attrs="{'invisible': [('compute_on_fly', '=', True)]}"
<field name="compute_on_fly" attrs="{'invisible': [('store_history', '=', True)]}"/> />
<field name="model_id" attrs="{'invisible': [('computation_method', '!=', 'function')]}"/> <field
<field name="function" attrs="{'required': [('computation_method', '=', 'function')], 'invisible': [('computation_method', '!=', 'function')]}"/> name="store_history_interval"
<field name="args" attrs="{'invisible': [('computation_method', '!=', 'function')]}"/> attrs="{'invisible': [('store_history', '=', False)]}"
<field name="kwargs" attrs="{'invisible': [('computation_method', '!=', 'function')]}"/> />
<field
name="store_history_interval_number"
attrs="{'invisible': [('store_history', '=', False)]}"
/>
<field
name="compute_on_fly"
attrs="{'invisible': [('store_history', '=', True)]}"
/>
<field
name="model_id"
attrs="{'invisible': [('computation_method', '!=', 'function')]}"
/>
<field
name="function"
attrs="{'required': [('computation_method', '=', 'function')], 'invisible': [('computation_method', '!=', 'function')]}"
/>
<field
name="args"
attrs="{'invisible': [('computation_method', '!=', 'function')]}"
/>
<field
name="kwargs"
attrs="{'invisible': [('computation_method', '!=', 'function')]}"
/>
</group> </group>
<group> <group>
<field name="cron_id" attrs="{'invisible': [('cron_id', '=',False)]}" readonly="True"/> <field
name="cron_id"
attrs="{'invisible': [('cron_id', '=',False)]}"
readonly="True"
/>
</group> </group>
<group> <group>
<field name="suffix"/> <field name="suffix" />
<field name="prefix"/> <field name="prefix" />
</group> </group>
</group> </group>
<notebook> <notebook>
<page name="action" string="Actions"> <page name="action" string="Actions">
<field name="action_ids"> <field name="action_ids">
<tree editable="bottom"> <tree editable="bottom">
<field name="action"/> <field name="action" />
<field name="context"/> <field name="context" />
</tree> </tree>
</field> </field>
</page> </page>
<page name="code" string="Code" attrs="{'invisible': [('computation_method', '!=', 'code')]}"> <page
<field name="code" widget="ace" name="code"
options="{'mode': 'python'}" string="Code"
placeholder="Enter Python code here."/> attrs="{'invisible': [('computation_method', '!=', 'code')]}"
>
<field
name="code"
widget="ace"
options="{'mode': 'python'}"
placeholder="Enter Python code here."
/>
</page> </page>
</notebook> </notebook>
</sheet> </sheet>
</form> </form>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="kpi_kpi_widget_form_view"> <record model="ir.ui.view" id="kpi_kpi_widget_form_view">
<field name="name">kpi.kpi.raw.form (in kpi_dashboard)</field> <field name="name">kpi.kpi.raw.form (in kpi_dashboard)</field>
<field name="model">kpi.kpi</field> <field name="model">kpi.kpi</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<field name="computed_date" invisible="1"/> <field name="computed_date" invisible="1" />
<field name="widget" invisible="1"/> <field name="widget" invisible="1" />
<field name="name" invisible="1"/> <field name="name" invisible="1" />
<field name="computed_value" <field
widget="kpi" name="computed_value"
options="{'date': 'computed_date', 'widget': 'widget', 'name': 'name'}"/> widget="kpi"
options="{'date': 'computed_date', 'widget': 'widget', 'name': 'name'}"
/>
</form> </form>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="kpi_kpi_search_view"> <record model="ir.ui.view" id="kpi_kpi_search_view">
<field name="name">kpi.kpi.search (in kpi_dashboard)</field> <field name="name">kpi.kpi.search (in kpi_dashboard)</field>
<field name="model">kpi.kpi</field> <field name="model">kpi.kpi</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search> <search>
<field name="name"/> <field name="name" />
</search> </search>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="kpi_kpi_tree_view"> <record model="ir.ui.view" id="kpi_kpi_tree_view">
<field name="name">kpi.kpi.tree (in kpi_dashboard)</field> <field name="name">kpi.kpi.tree (in kpi_dashboard)</field>
<field name="model">kpi.kpi</field> <field name="model">kpi.kpi</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="name"/> <field name="name" />
</tree> </tree>
</field> </field>
</record> </record>
<record model="ir.actions.act_window" id="kpi_kpi_act_window"> <record model="ir.actions.act_window" id="kpi_kpi_act_window">
<field name="name">Kpi</field> <field name="name">Kpi</field>
<field name="res_model">kpi.kpi</field> <field name="res_model">kpi.kpi</field>
@ -161,12 +209,10 @@
<field name="domain">[]</field> <field name="domain">[]</field>
<field name="context">{}</field> <field name="context">{}</field>
</record> </record>
<record model="ir.ui.menu" id="kpi_kpi_menu"> <record model="ir.ui.menu" id="kpi_kpi_menu">
<field name="name">Configure Kpi</field> <field name="name">Configure Kpi</field>
<field name="parent_id" ref="menu_configuration_kpi_dashboards"/> <field name="parent_id" ref="menu_configuration_kpi_dashboards" />
<field name="action" ref="kpi_kpi_act_window"/> <field name="action" ref="kpi_kpi_act_window" />
<field name="sequence" eval="20"/> <field name="sequence" eval="20" />
</record> </record>
</odoo> </odoo>

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Creu Blanca <!-- Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<!-- CONFIGURATION --> <!-- CONFIGURATION -->
<menuitem id="menu_configuration_kpi_dashboards" <menuitem
name="KPI Dashboards" id="menu_configuration_kpi_dashboards"
parent="base.menu_reporting_config" name="KPI Dashboards"
groups="kpi_dashboard.group_kpi_dashboard_manager" parent="base.menu_reporting_config"
sequence="10"/> groups="kpi_dashboard.group_kpi_dashboard_manager"
sequence="10"
/>
</odoo> </odoo>

View File

@ -1,31 +1,28 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Creu Blanca <!-- Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<record model="ir.ui.view" id="kpi_dashboard_menu_form_view"> <record model="ir.ui.view" id="kpi_dashboard_menu_form_view">
<field name="name">kpi.dashboard.menu.form (in kpi_dashboard)</field> <field name="name">kpi.dashboard.menu.form (in kpi_dashboard)</field>
<field name="model">kpi.dashboard.menu</field> <field name="model">kpi.dashboard.menu</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Generate Menu"> <form string="Generate Menu">
<group> <group>
<field name="dashboard_id" invisible="1"/> <field name="dashboard_id" invisible="1" />
<field name="menu_id"/> <field name="menu_id" />
</group> </group>
<footer> <footer>
<button name="generate_menu" <button
string="Generate" name="generate_menu"
class="btn-primary" string="Generate"
type="object"/> class="btn-primary"
<button string="Cancel" type="object"
class="btn-default" />
special="cancel"/> <button string="Cancel" class="btn-default" special="cancel" />
</footer> </footer>
</form> </form>
</field> </field>
</record> </record>
<record model="ir.actions.act_window" id="kpi_dashboard_menu_act_window"> <record model="ir.actions.act_window" id="kpi_dashboard_menu_act_window">
<field name="name">Kpi Dashboard Menu</field> <field name="name">Kpi Dashboard Menu</field>
<field name="res_model">kpi.dashboard.menu</field> <field name="res_model">kpi.dashboard.menu</field>
@ -33,6 +30,4 @@
<field name="context">{}</field> <field name="context">{}</field>
<field name="target">new</field> <field name="target">new</field>
</record> </record>
</odoo> </odoo>