OCA-git-bot
5 years ago
49 changed files with 3058 additions and 0 deletions
-
96kpi_dashboard/README.rst
-
2kpi_dashboard/__init__.py
-
23kpi_dashboard/__manifest__.py
-
4kpi_dashboard/models/__init__.py
-
10kpi_dashboard/models/ir_actions_act_window_view.py
-
10kpi_dashboard/models/ir_ui_view.py
-
182kpi_dashboard/models/kpi_dashboard.py
-
110kpi_dashboard/models/kpi_kpi.py
-
19kpi_dashboard/readme/CONFIGURE.rst
-
1kpi_dashboard/readme/CONTRIBUTORS.rst
-
1kpi_dashboard/readme/DESCRIPTION.rst
-
9kpi_dashboard/security/ir.model.access.csv
-
22kpi_dashboard/security/security.xml
-
BINkpi_dashboard/static/description/icon.png
-
449kpi_dashboard/static/description/index.html
-
276kpi_dashboard/static/lib/gauge/GaugeMeter.js
-
2kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.css
-
2kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.js
-
76kpi_dashboard/static/src/js/dashboard_controller.js
-
23kpi_dashboard/static/src/js/dashboard_model.js
-
74kpi_dashboard/static/src/js/dashboard_renderer.js
-
44kpi_dashboard/static/src/js/dashboard_view.js
-
91kpi_dashboard/static/src/js/widget/abstract_widget.js
-
108kpi_dashboard/static/src/js/widget/graph_widget.js
-
39kpi_dashboard/static/src/js/widget/meter_widget.js
-
72kpi_dashboard/static/src/js/widget/number_widget.js
-
17kpi_dashboard/static/src/js/widget/text_widget.js
-
7kpi_dashboard/static/src/js/widget_registry.js
-
112kpi_dashboard/static/src/scss/kpi_dashboard.scss
-
76kpi_dashboard/static/src/xml/dashboard.xml
-
111kpi_dashboard/views/kpi_dashboard.xml
-
89kpi_dashboard/views/kpi_kpi.xml
-
12kpi_dashboard/views/kpi_menu.xml
-
25kpi_dashboard/views/webclient_templates.xml
-
1kpi_dashboard/wizards/__init__.py
-
17kpi_dashboard/wizards/kpi_dashboard_menu.py
-
38kpi_dashboard/wizards/kpi_dashboard_menu.xml
-
73kpi_dashboard_test/README.rst
-
1kpi_dashboard_test/__init__.py
-
14kpi_dashboard_test/__manifest__.py
-
126kpi_dashboard_test/demo/demo_dashboard.xml
-
1kpi_dashboard_test/models/__init__.py
-
48kpi_dashboard_test/models/kpi_kpi.py
-
1kpi_dashboard_test/readme/CONTRIBUTORS.rst
-
1kpi_dashboard_test/readme/DESCRIPTION.rst
-
BINkpi_dashboard_test/static/description/icon.png
-
419kpi_dashboard_test/static/description/index.html
-
1kpi_dashboard_test/tests/__init__.py
-
123kpi_dashboard_test/tests/test_kpi_dashboard.py
@ -0,0 +1,96 @@ |
|||
============= |
|||
Kpi Dashboard |
|||
============= |
|||
|
|||
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
|||
!! This file is generated by oca-gen-addon-readme !! |
|||
!! changes will be overwritten. !! |
|||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
|||
|
|||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png |
|||
:target: https://odoo-community.org/page/development-status |
|||
:alt: Beta |
|||
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png |
|||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Freporting--engine-lightgray.png?logo=github |
|||
:target: https://github.com/OCA/reporting-engine/tree/12.0/kpi_dashboard |
|||
:alt: OCA/reporting-engine |
|||
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png |
|||
:target: https://translation.odoo-community.org/projects/reporting-engine-12-0/reporting-engine-12-0-kpi_dashboard |
|||
:alt: Translate me on Weblate |
|||
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png |
|||
:target: https://runbot.odoo-community.org/runbot/143/12.0 |
|||
:alt: Try me on Runbot |
|||
|
|||
|badge1| |badge2| |badge3| |badge4| |badge5| |
|||
|
|||
This module adds new kinds of dashboards on a specific new type of view. |
|||
|
|||
**Table of contents** |
|||
|
|||
.. contents:: |
|||
:local: |
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
Configure KPIs |
|||
~~~~~~~~~~~~~~ |
|||
|
|||
#. Access `Dashboards > Configuration > KPI Dashboards > Configure KPI` |
|||
#. Create a new KPI specifying the computation method and the kpi type |
|||
|
|||
#. Number: result must contain a `value` and, if needed, a `previous` |
|||
#. Meter: result must contain `value`, `min` and `max` |
|||
#. Graph: result must contain a list on `graphs` containing `values`, `title` and `key` |
|||
|
|||
|
|||
Configure dashboards |
|||
~~~~~~~~~~~~~~~~~~~~ |
|||
|
|||
#. Access `Dashboards > Configuration > KPI Dashboards > Configure Dashboards` |
|||
#. Create a new dashboard and specify all the standard parameters on `Widget configuration` |
|||
#. Append elements on KPIs |
|||
#. You can preview the element using the dashboard view |
|||
#. You can create the menu entry directly using the `Generate menu` button |
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/reporting-engine/issues>`_. |
|||
In case of trouble, please check there if your issue has already been reported. |
|||
If you spotted it first, help us smashing it by providing a detailed and welcomed |
|||
`feedback <https://github.com/OCA/reporting-engine/issues/new?body=module:%20kpi_dashboard%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
|||
|
|||
Do not contact contributors directly about support or help with technical issues. |
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Authors |
|||
~~~~~~~ |
|||
|
|||
* Creu Blanca |
|||
|
|||
Contributors |
|||
~~~~~~~~~~~~ |
|||
|
|||
* Enric Tobella <etobella@creublanca.es> |
|||
|
|||
Maintainers |
|||
~~~~~~~~~~~ |
|||
|
|||
This module is maintained by the OCA. |
|||
|
|||
.. image:: https://odoo-community.org/logo.png |
|||
:alt: Odoo Community Association |
|||
:target: https://odoo-community.org |
|||
|
|||
OCA, or the Odoo Community Association, is a nonprofit organization whose |
|||
mission is to support the collaborative development of Odoo features and |
|||
promote its widespread use. |
|||
|
|||
This module is part of the `OCA/reporting-engine <https://github.com/OCA/reporting-engine/tree/12.0/kpi_dashboard>`_ project on GitHub. |
|||
|
|||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. |
@ -0,0 +1,2 @@ |
|||
from . import models |
|||
from . import wizards |
@ -0,0 +1,23 @@ |
|||
# Copyright 2020 Creu Blanca |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
{ |
|||
"name": "Kpi Dashboard", |
|||
"summary": """ |
|||
Create Dashboards using kpis""", |
|||
"version": "12.0.1.0.0", |
|||
"license": "AGPL-3", |
|||
"author": "Creu Blanca,Odoo Community Association (OCA)", |
|||
"website": "https://github.com/reporting-engine", |
|||
"depends": ["bus", "board", "base_sparse_field", "web_widget_color"], |
|||
"qweb": ["static/src/xml/dashboard.xml"], |
|||
"data": [ |
|||
"wizards/kpi_dashboard_menu.xml", |
|||
"security/security.xml", |
|||
"security/ir.model.access.csv", |
|||
"views/kpi_menu.xml", |
|||
"views/webclient_templates.xml", |
|||
"views/kpi_kpi.xml", |
|||
"views/kpi_dashboard.xml", |
|||
], |
|||
} |
@ -0,0 +1,4 @@ |
|||
from . import kpi_dashboard |
|||
from . import kpi_kpi |
|||
from . import ir_actions_act_window_view |
|||
from . import ir_ui_view |
@ -0,0 +1,10 @@ |
|||
# Copyright 2019 Creu Blanca |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from odoo import fields, models |
|||
|
|||
|
|||
class IrActionsActWindowView(models.Model): |
|||
_inherit = "ir.actions.act_window.view" |
|||
|
|||
view_mode = fields.Selection(selection_add=[("dashboard", "Dashboard")]) |
@ -0,0 +1,10 @@ |
|||
# Copyright 2019 Creu Blanca |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from odoo import fields, models |
|||
|
|||
|
|||
class IrUiView(models.Model): |
|||
_inherit = "ir.ui.view" |
|||
|
|||
type = fields.Selection(selection_add=[("dashboard", "Dashboard")]) |
@ -0,0 +1,182 @@ |
|||
# Copyright 2020 Creu Blanca |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from odoo import api, fields, models, _ |
|||
from odoo.exceptions import ValidationError |
|||
|
|||
|
|||
class KpiDashboard(models.Model): |
|||
|
|||
_name = "kpi.dashboard" |
|||
_description = "Dashboard" |
|||
|
|||
name = fields.Char(required=True) |
|||
active = fields.Boolean(default=True,) |
|||
item_ids = fields.One2many( |
|||
"kpi.dashboard.item", inverse_name="dashboard_id", copy=True, |
|||
) |
|||
number_of_columns = fields.Integer(default=5, required=True) |
|||
width = fields.Integer(compute="_compute_width") |
|||
margin_y = fields.Integer(default=10, required=True) |
|||
margin_x = fields.Integer(default=10, required=True) |
|||
widget_dimension_x = fields.Integer(default=250, required=True) |
|||
widget_dimension_y = fields.Integer(default=250, required=True) |
|||
background_color = fields.Char(required=True, default="#f9f9f9") |
|||
group_ids = fields.Many2many("res.groups",) |
|||
menu_id = fields.Many2one("ir.ui.menu", copy=False) |
|||
|
|||
def write(self, vals): |
|||
res = super().write(vals) |
|||
if "group_ids" in vals: |
|||
for rec in self: |
|||
if rec.menu_id: |
|||
rec.menu_id.write( |
|||
{"groups_id": [(6, 0, rec.group_ids.ids)]} |
|||
) |
|||
return res |
|||
|
|||
@api.depends("widget_dimension_x", "margin_x", "number_of_columns") |
|||
def _compute_width(self): |
|||
for rec in self: |
|||
rec.width = ( |
|||
rec.margin_x * (rec.number_of_columns + 1) |
|||
+ rec.widget_dimension_x * rec.number_of_columns |
|||
) |
|||
|
|||
def read_dashboard(self): |
|||
self.ensure_one() |
|||
result = { |
|||
"name": self.name, |
|||
"width": self.width, |
|||
"item_ids": self.item_ids.read_dashboard(), |
|||
"max_cols": self.number_of_columns, |
|||
"margin_x": self.margin_x, |
|||
"margin_y": self.margin_y, |
|||
"widget_dimension_x": self.widget_dimension_x, |
|||
"widget_dimension_y": self.widget_dimension_y, |
|||
"background_color": self.background_color, |
|||
} |
|||
if self.menu_id: |
|||
result["action_id"] = self.menu_id.action.id |
|||
return result |
|||
|
|||
def _generate_menu_vals(self, menu, action): |
|||
return { |
|||
"parent_id": menu.id or False, |
|||
"name": self.name, |
|||
"action": "%s,%s" % (action._name, action.id), |
|||
"groups_id": [(6, 0, self.group_ids.ids)], |
|||
} |
|||
|
|||
def _generate_action_vals(self, menu): |
|||
return { |
|||
"name": self.name, |
|||
"res_model": self._name, |
|||
"view_mode": "dashboard", |
|||
"res_id": self.id, |
|||
} |
|||
|
|||
def _generate_menu(self, menu): |
|||
action = self.env["ir.actions.act_window"].create( |
|||
self._generate_action_vals(menu) |
|||
) |
|||
self.menu_id = self.env["ir.ui.menu"].create( |
|||
self._generate_menu_vals(menu, action) |
|||
) |
|||
|
|||
|
|||
class KpiDashboardItem(models.Model): |
|||
_name = "kpi.dashboard.item" |
|||
_description = "Dashboard Items" |
|||
_order = "row asc, column asc" |
|||
|
|||
name = fields.Char(required=True) |
|||
kpi_id = fields.Many2one("kpi.kpi") |
|||
dashboard_id = fields.Many2one("kpi.dashboard", required=True,) |
|||
column = fields.Integer(required=True, default=1) |
|||
row = fields.Integer(required=True, default=1) |
|||
end_row = fields.Integer(store=True, compute='_compute_end_row') |
|||
end_column = fields.Integer(store=True, compute='_compute_end_column') |
|||
size_x = fields.Integer(required=True, default=1) |
|||
size_y = fields.Integer(required=True, default=1) |
|||
color = fields.Char() |
|||
font_color = fields.Char() |
|||
|
|||
@api.depends('row', 'size_y') |
|||
def _compute_end_row(self): |
|||
for r in self: |
|||
r.end_row = r.row + r.size_y - 1 |
|||
|
|||
@api.depends('column', 'size_x') |
|||
def _compute_end_column(self): |
|||
for r in self: |
|||
r.end_column = r.column + r.size_x - 1 |
|||
|
|||
@api.constrains('size_y') |
|||
def _check_size_y(self): |
|||
for rec in self: |
|||
if rec.size_y > 10: |
|||
raise ValidationError(_( |
|||
'Size Y of the widget cannot be bigger than 10')) |
|||
|
|||
def _check_size_domain(self): |
|||
return [ |
|||
('dashboard_id', '=', self.dashboard_id.id), |
|||
('id', '!=', self.id), |
|||
('row', '<=', self.end_row), |
|||
('end_row', '>=', self.row), |
|||
('column', '<=', self.end_column), |
|||
('end_column', '>=', self.column), |
|||
] |
|||
|
|||
@api.constrains('end_row', 'end_column', 'row', 'column') |
|||
def _check_size(self): |
|||
for r in self: |
|||
if self.search(r._check_size_domain(), limit=1): |
|||
raise ValidationError(_( |
|||
'Widgets cannot be crossed by other widgets' |
|||
)) |
|||
if r.end_column > r.dashboard_id.number_of_columns: |
|||
raise ValidationError(_( |
|||
'Widget %s is bigger than expected' |
|||
) % r.display_name) |
|||
|
|||
@api.onchange("kpi_id") |
|||
def _onchange_kpi(self): |
|||
for rec in self: |
|||
if not rec.name and rec.kpi_id: |
|||
rec.name = rec.kpi_id.name |
|||
|
|||
def _read_dashboard(self): |
|||
vals = { |
|||
"id": self.id, |
|||
"name": self.name, |
|||
"col": self.column, |
|||
"row": self.row, |
|||
"sizex": self.size_x, |
|||
"sizey": self.size_y, |
|||
"color": self.color, |
|||
"font_color": self.font_color or "000000", |
|||
} |
|||
if self.kpi_id: |
|||
vals.update( |
|||
{ |
|||
"widget": self.kpi_id.widget, |
|||
"kpi_id": self.kpi_id.id, |
|||
"suffix": self.kpi_id.suffix or "", |
|||
"prefix": self.kpi_id.prefix or "", |
|||
"value": self.kpi_id.value, |
|||
"value_last_update": self.kpi_id.value_last_update, |
|||
} |
|||
) |
|||
if self.kpi_id.action_ids: |
|||
vals["actions"] = self.kpi_id.action_ids.read_dashboard() |
|||
else: |
|||
vals["widget"] = "base_text" |
|||
return vals |
|||
|
|||
def read_dashboard(self): |
|||
result = [] |
|||
for kpi in self: |
|||
result.append(kpi._read_dashboard()) |
|||
return result |
@ -0,0 +1,110 @@ |
|||
# Copyright 2020 Creu Blanca |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from odoo import api, fields, models |
|||
import ast |
|||
|
|||
|
|||
class KpiKpi(models.Model): |
|||
_name = "kpi.kpi" |
|||
_description = "Kpi Kpi" |
|||
|
|||
name = fields.Char(required=True) |
|||
active = fields.Boolean(default=True) |
|||
cron_id = fields.Many2one("ir.cron", readonly=True, copy=False) |
|||
computation_method = fields.Selection( |
|||
[("function", "Function")], required=True |
|||
) |
|||
value = fields.Serialized() |
|||
dashboard_item_ids = fields.One2many("kpi.dashboard.item", inverse_name="kpi_id") |
|||
model_id = fields.Many2one("ir.model",) |
|||
function = fields.Char() |
|||
args = fields.Char() |
|||
kwargs = fields.Char() |
|||
widget = fields.Selection( |
|||
[("number", "Number"), ("meter", "Meter"), ("graph", "Graph")], |
|||
required=True, |
|||
default="number", |
|||
) |
|||
value_last_update = fields.Datetime(readonly=True) |
|||
prefix = fields.Char() |
|||
suffix = fields.Char() |
|||
action_ids = fields.One2many( |
|||
"kpi.kpi.action", |
|||
inverse_name='kpi_id', |
|||
help="Actions that can be opened from the KPI" |
|||
) |
|||
|
|||
def _cron_vals(self): |
|||
return { |
|||
"name": self.name, |
|||
"model_id": self.env.ref("kpi_dashboard.model_kpi_kpi").id, |
|||
"interval_number": 1, |
|||
"interval_type": "hours", |
|||
"state": "code", |
|||
"code": "model.browse(%s).compute()" % self.id, |
|||
"active": True, |
|||
} |
|||
|
|||
def compute(self): |
|||
for record in self: |
|||
record._compute() |
|||
return True |
|||
|
|||
def _compute(self): |
|||
self.write( |
|||
{ |
|||
"value": getattr( |
|||
self, "_compute_value_%s" % self.computation_method |
|||
)() |
|||
} |
|||
) |
|||
notifications = [] |
|||
for dashboard_item in self.dashboard_item_ids: |
|||
channel = "kpi_dashboard_%s" % dashboard_item.dashboard_id.id |
|||
notifications.append([channel, dashboard_item._read_dashboard()]) |
|||
if notifications: |
|||
self.env["bus.bus"].sendmany(notifications) |
|||
|
|||
def _compute_value_function(self): |
|||
obj = self |
|||
if self.model_id: |
|||
obj = self.env[self.model_id.model] |
|||
args = ast.literal_eval(self.args or "[]") |
|||
kwargs = ast.literal_eval(self.kwargs or "{}") |
|||
return getattr(obj, self.function)(*args, **kwargs) |
|||
|
|||
def generate_cron(self): |
|||
self.ensure_one() |
|||
self.cron_id = self.env["ir.cron"].create(self._cron_vals()) |
|||
|
|||
@api.multi |
|||
def write(self, vals): |
|||
if "value" in vals: |
|||
vals["value_last_update"] = fields.Datetime.now() |
|||
return super().write(vals) |
|||
|
|||
|
|||
class KpiKpiAction(models.Model): |
|||
_name = 'kpi.kpi.action' |
|||
_description = 'KPI action' |
|||
|
|||
kpi_id = fields.Many2one('kpi.kpi', required=True, ondelete='cascade') |
|||
action = fields.Reference( |
|||
selection=[('ir.actions.report', 'ir.actions.report'), |
|||
('ir.actions.act_window', 'ir.actions.act_window'), |
|||
('ir.actions.act_url', 'ir.actions.act_url'), |
|||
('ir.actions.server', 'ir.actions.server'), |
|||
('ir.actions.client', 'ir.actions.client')], |
|||
required=True, |
|||
) |
|||
|
|||
def read_dashboard(self): |
|||
result = [] |
|||
for r in self: |
|||
result.append({ |
|||
'id': r.action.id, |
|||
'type': r.action._name, |
|||
'name': r.action.name |
|||
}) |
|||
return result |
@ -0,0 +1,19 @@ |
|||
Configure KPIs |
|||
~~~~~~~~~~~~~~ |
|||
|
|||
#. Access `Dashboards > Configuration > KPI Dashboards > Configure KPI` |
|||
#. Create a new KPI specifying the computation method and the kpi type |
|||
|
|||
#. Number: result must contain a `value` and, if needed, a `previous` |
|||
#. Meter: result must contain `value`, `min` and `max` |
|||
#. Graph: result must contain a list on `graphs` containing `values`, `title` and `key` |
|||
|
|||
|
|||
Configure dashboards |
|||
~~~~~~~~~~~~~~~~~~~~ |
|||
|
|||
#. Access `Dashboards > Configuration > KPI Dashboards > Configure Dashboards` |
|||
#. Create a new dashboard and specify all the standard parameters on `Widget configuration` |
|||
#. Append elements on KPIs |
|||
#. You can preview the element using the dashboard view |
|||
#. You can create the menu entry directly using the `Generate menu` button |
@ -0,0 +1 @@ |
|||
* Enric Tobella <etobella@creublanca.es> |
@ -0,0 +1 @@ |
|||
This module adds new kinds of dashboards on a specific new type of view. |
@ -0,0 +1,9 @@ |
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
|||
access_kpi_dashboard,access_kpi_dashboard,model_kpi_dashboard,base.group_user,1,0,0,0 |
|||
access_kpi_dashboard_kpi,access_kpi_dashboard_kpi,model_kpi_dashboard_item,base.group_user,1,0,0,0 |
|||
access_kpi_kpi,access_kpi_kpi,model_kpi_kpi,base.group_user,1,0,0,0 |
|||
access_kpi_kpi_action,access_kpi_kpi_action,model_kpi_kpi_action,base.group_user,1,0,0,0 |
|||
manage_kpi_dashboard,manage_kpi_dashboard,model_kpi_dashboard,group_kpi_dashboard_manager,1,1,1,1 |
|||
manage_kpi_dashboard_kpi,manage_kpi_dashboard_kpi,model_kpi_dashboard_item,group_kpi_dashboard_manager,1,1,1,1 |
|||
manage_kpi_kpi,manage_kpi_kpi,model_kpi_kpi,group_kpi_dashboard_manager,1,1,1,1 |
|||
manage_kpi_kpi_action,manage_kpi_kpi_action,model_kpi_kpi_action,group_kpi_dashboard_manager,1,1,1,1 |
@ -0,0 +1,22 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<record id="group_kpi_dashboard_manager" model="res.groups"> |
|||
<field name="name">Manage KPI Dashboards</field> |
|||
<field name="category_id" ref="base.module_category_hidden"/> |
|||
<field name="users" eval="[(4, ref('base.user_admin'))]"/> |
|||
</record> |
|||
<data noupdate="1"> |
|||
<record id="rule_kpi_dashboard" model="ir.rule"> |
|||
<field name="name">KPI Dashboard: User</field> |
|||
<field name="model_id" ref="model_kpi_dashboard"/> |
|||
<field 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 id="rule_kpi_dashboard_all" model="ir.rule"> |
|||
<field name="name">KPI Dashboard: All</field> |
|||
<field name="model_id" ref="model_kpi_dashboard"/> |
|||
<field name="domain_force">[(1, '=', 1)]</field> |
|||
<field name="groups" eval="[(4, ref('group_kpi_dashboard_manager'))]"/> |
|||
</record> |
|||
</data> |
|||
</odoo> |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,449 @@ |
|||
<?xml version="1.0" encoding="utf-8" ?> |
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
|||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
|||
<head> |
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|||
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" /> |
|||
<title>Kpi Dashboard</title> |
|||
<style type="text/css"> |
|||
|
|||
/* |
|||
:Author: David Goodger (goodger@python.org) |
|||
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $ |
|||
:Copyright: This stylesheet has been placed in the public domain. |
|||
|
|||
Default cascading style sheet for the HTML output of Docutils. |
|||
|
|||
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to |
|||
customize this style sheet. |
|||
*/ |
|||
|
|||
/* used to remove borders from tables and images */ |
|||
.borderless, table.borderless td, table.borderless th { |
|||
border: 0 } |
|||
|
|||
table.borderless td, table.borderless th { |
|||
/* Override padding for "table.docutils td" with "! important". |
|||
The right padding separates the table cells. */ |
|||
padding: 0 0.5em 0 0 ! important } |
|||
|
|||
.first { |
|||
/* Override more specific margin styles with "! important". */ |
|||
margin-top: 0 ! important } |
|||
|
|||
.last, .with-subtitle { |
|||
margin-bottom: 0 ! important } |
|||
|
|||
.hidden { |
|||
display: none } |
|||
|
|||
.subscript { |
|||
vertical-align: sub; |
|||
font-size: smaller } |
|||
|
|||
.superscript { |
|||
vertical-align: super; |
|||
font-size: smaller } |
|||
|
|||
a.toc-backref { |
|||
text-decoration: none ; |
|||
color: black } |
|||
|
|||
blockquote.epigraph { |
|||
margin: 2em 5em ; } |
|||
|
|||
dl.docutils dd { |
|||
margin-bottom: 0.5em } |
|||
|
|||
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { |
|||
overflow: hidden; |
|||
} |
|||
|
|||
/* Uncomment (and remove this text!) to get bold-faced definition list terms |
|||
dl.docutils dt { |
|||
font-weight: bold } |
|||
*/ |
|||
|
|||
div.abstract { |
|||
margin: 2em 5em } |
|||
|
|||
div.abstract p.topic-title { |
|||
font-weight: bold ; |
|||
text-align: center } |
|||
|
|||
div.admonition, div.attention, div.caution, div.danger, div.error, |
|||
div.hint, div.important, div.note, div.tip, div.warning { |
|||
margin: 2em ; |
|||
border: medium outset ; |
|||
padding: 1em } |
|||
|
|||
div.admonition p.admonition-title, div.hint p.admonition-title, |
|||
div.important p.admonition-title, div.note p.admonition-title, |
|||
div.tip p.admonition-title { |
|||
font-weight: bold ; |
|||
font-family: sans-serif } |
|||
|
|||
div.attention p.admonition-title, div.caution p.admonition-title, |
|||
div.danger p.admonition-title, div.error p.admonition-title, |
|||
div.warning p.admonition-title, .code .error { |
|||
color: red ; |
|||
font-weight: bold ; |
|||
font-family: sans-serif } |
|||
|
|||
/* Uncomment (and remove this text!) to get reduced vertical space in |
|||
compound paragraphs. |
|||
div.compound .compound-first, div.compound .compound-middle { |
|||
margin-bottom: 0.5em } |
|||
|
|||
div.compound .compound-last, div.compound .compound-middle { |
|||
margin-top: 0.5em } |
|||
*/ |
|||
|
|||
div.dedication { |
|||
margin: 2em 5em ; |
|||
text-align: center ; |
|||
font-style: italic } |
|||
|
|||
div.dedication p.topic-title { |
|||
font-weight: bold ; |
|||
font-style: normal } |
|||
|
|||
div.figure { |
|||
margin-left: 2em ; |
|||
margin-right: 2em } |
|||
|
|||
div.footer, div.header { |
|||
clear: both; |
|||
font-size: smaller } |
|||
|
|||
div.line-block { |
|||
display: block ; |
|||
margin-top: 1em ; |
|||
margin-bottom: 1em } |
|||
|
|||
div.line-block div.line-block { |
|||
margin-top: 0 ; |
|||
margin-bottom: 0 ; |
|||
margin-left: 1.5em } |
|||
|
|||
div.sidebar { |
|||
margin: 0 0 0.5em 1em ; |
|||
border: medium outset ; |
|||
padding: 1em ; |
|||
background-color: #ffffee ; |
|||
width: 40% ; |
|||
float: right ; |
|||
clear: right } |
|||
|
|||
div.sidebar p.rubric { |
|||
font-family: sans-serif ; |
|||
font-size: medium } |
|||
|
|||
div.system-messages { |
|||
margin: 5em } |
|||
|
|||
div.system-messages h1 { |
|||
color: red } |
|||
|
|||
div.system-message { |
|||
border: medium outset ; |
|||
padding: 1em } |
|||
|
|||
div.system-message p.system-message-title { |
|||
color: red ; |
|||
font-weight: bold } |
|||
|
|||
div.topic { |
|||
margin: 2em } |
|||
|
|||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, |
|||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { |
|||
margin-top: 0.4em } |
|||
|
|||
h1.title { |
|||
text-align: center } |
|||
|
|||
h2.subtitle { |
|||
text-align: center } |
|||
|
|||
hr.docutils { |
|||
width: 75% } |
|||
|
|||
img.align-left, .figure.align-left, object.align-left, table.align-left { |
|||
clear: left ; |
|||
float: left ; |
|||
margin-right: 1em } |
|||
|
|||
img.align-right, .figure.align-right, object.align-right, table.align-right { |
|||
clear: right ; |
|||
float: right ; |
|||
margin-left: 1em } |
|||
|
|||
img.align-center, .figure.align-center, object.align-center { |
|||
display: block; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
} |
|||
|
|||
table.align-center { |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
} |
|||
|
|||
.align-left { |
|||
text-align: left } |
|||
|
|||
.align-center { |
|||
clear: both ; |
|||
text-align: center } |
|||
|
|||
.align-right { |
|||
text-align: right } |
|||
|
|||
/* reset inner alignment in figures */ |
|||
div.align-right { |
|||
text-align: inherit } |
|||
|
|||
/* div.align-center * { */ |
|||
/* text-align: left } */ |
|||
|
|||
.align-top { |
|||
vertical-align: top } |
|||
|
|||
.align-middle { |
|||
vertical-align: middle } |
|||
|
|||
.align-bottom { |
|||
vertical-align: bottom } |
|||
|
|||
ol.simple, ul.simple { |
|||
margin-bottom: 1em } |
|||
|
|||
ol.arabic { |
|||
list-style: decimal } |
|||
|
|||
ol.loweralpha { |
|||
list-style: lower-alpha } |
|||
|
|||
ol.upperalpha { |
|||
list-style: upper-alpha } |
|||
|
|||
ol.lowerroman { |
|||
list-style: lower-roman } |
|||
|
|||
ol.upperroman { |
|||
list-style: upper-roman } |
|||
|
|||
p.attribution { |
|||
text-align: right ; |
|||
margin-left: 50% } |
|||
|
|||
p.caption { |
|||
font-style: italic } |
|||
|
|||
p.credits { |
|||
font-style: italic ; |
|||
font-size: smaller } |
|||
|
|||
p.label { |
|||
white-space: nowrap } |
|||
|
|||
p.rubric { |
|||
font-weight: bold ; |
|||
font-size: larger ; |
|||
color: maroon ; |
|||
text-align: center } |
|||
|
|||
p.sidebar-title { |
|||
font-family: sans-serif ; |
|||
font-weight: bold ; |
|||
font-size: larger } |
|||
|
|||
p.sidebar-subtitle { |
|||
font-family: sans-serif ; |
|||
font-weight: bold } |
|||
|
|||
p.topic-title { |
|||
font-weight: bold } |
|||
|
|||
pre.address { |
|||
margin-bottom: 0 ; |
|||
margin-top: 0 ; |
|||
font: inherit } |
|||
|
|||
pre.literal-block, pre.doctest-block, pre.math, pre.code { |
|||
margin-left: 2em ; |
|||
margin-right: 2em } |
|||
|
|||
pre.code .ln { color: grey; } /* line numbers */ |
|||
pre.code, code { background-color: #eeeeee } |
|||
pre.code .comment, code .comment { color: #5C6576 } |
|||
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } |
|||
pre.code .literal.string, code .literal.string { color: #0C5404 } |
|||
pre.code .name.builtin, code .name.builtin { color: #352B84 } |
|||
pre.code .deleted, code .deleted { background-color: #DEB0A1} |
|||
pre.code .inserted, code .inserted { background-color: #A3D289} |
|||
|
|||
span.classifier { |
|||
font-family: sans-serif ; |
|||
font-style: oblique } |
|||
|
|||
span.classifier-delimiter { |
|||
font-family: sans-serif ; |
|||
font-weight: bold } |
|||
|
|||
span.interpreted { |
|||
font-family: sans-serif } |
|||
|
|||
span.option { |
|||
white-space: nowrap } |
|||
|
|||
span.pre { |
|||
white-space: pre } |
|||
|
|||
span.problematic { |
|||
color: red } |
|||
|
|||
span.section-subtitle { |
|||
/* font-size relative to parent (h1..h6 element) */ |
|||
font-size: 80% } |
|||
|
|||
table.citation { |
|||
border-left: solid 1px gray; |
|||
margin-left: 1px } |
|||
|
|||
table.docinfo { |
|||
margin: 2em 4em } |
|||
|
|||
table.docutils { |
|||
margin-top: 0.5em ; |
|||
margin-bottom: 0.5em } |
|||
|
|||
table.footnote { |
|||
border-left: solid 1px black; |
|||
margin-left: 1px } |
|||
|
|||
table.docutils td, table.docutils th, |
|||
table.docinfo td, table.docinfo th { |
|||
padding-left: 0.5em ; |
|||
padding-right: 0.5em ; |
|||
vertical-align: top } |
|||
|
|||
table.docutils th.field-name, table.docinfo th.docinfo-name { |
|||
font-weight: bold ; |
|||
text-align: left ; |
|||
white-space: nowrap ; |
|||
padding-left: 0 } |
|||
|
|||
/* "booktabs" style (no vertical lines) */ |
|||
table.docutils.booktabs { |
|||
border: 0px; |
|||
border-top: 2px solid; |
|||
border-bottom: 2px solid; |
|||
border-collapse: collapse; |
|||
} |
|||
table.docutils.booktabs * { |
|||
border: 0px; |
|||
} |
|||
table.docutils.booktabs th { |
|||
border-bottom: thin solid; |
|||
text-align: left; |
|||
} |
|||
|
|||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, |
|||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { |
|||
font-size: 100% } |
|||
|
|||
ul.auto-toc { |
|||
list-style-type: none } |
|||
|
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div class="document" id="kpi-dashboard"> |
|||
<h1 class="title">Kpi Dashboard</h1> |
|||
|
|||
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
|||
!! This file is generated by oca-gen-addon-readme !! |
|||
!! changes will be overwritten. !! |
|||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> |
|||
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/reporting-engine/tree/12.0/kpi_dashboard"><img alt="OCA/reporting-engine" src="https://img.shields.io/badge/github-OCA%2Freporting--engine-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/reporting-engine-12-0/reporting-engine-12-0-kpi_dashboard"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/143/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p> |
|||
<p>This module adds new kinds of dashboards on a specific new type of view.</p> |
|||
<p><strong>Table of contents</strong></p> |
|||
<div class="contents local topic" id="contents"> |
|||
<ul class="simple"> |
|||
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a><ul> |
|||
<li><a class="reference internal" href="#configure-kpis" id="id2">Configure KPIs</a></li> |
|||
<li><a class="reference internal" href="#configure-dashboards" id="id3">Configure dashboards</a></li> |
|||
</ul> |
|||
</li> |
|||
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li> |
|||
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul> |
|||
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li> |
|||
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li> |
|||
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li> |
|||
</ul> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
<div class="section" id="configuration"> |
|||
<h1><a class="toc-backref" href="#id1">Configuration</a></h1> |
|||
<div class="section" id="configure-kpis"> |
|||
<h2><a class="toc-backref" href="#id2">Configure KPIs</a></h2> |
|||
<ol class="arabic simple"> |
|||
<li>Access <cite>Dashboards > Configuration > KPI Dashboards > Configure KPI</cite></li> |
|||
<li>Create a new KPI specifying the computation method and the kpi type<ol class="arabic"> |
|||
<li>Number: result must contain a <cite>value</cite> and, if needed, a <cite>previous</cite></li> |
|||
<li>Meter: result must contain <cite>value</cite>, <cite>min</cite> and <cite>max</cite></li> |
|||
<li>Graph: result must contain a list on <cite>graphs</cite> containing <cite>values</cite>, <cite>title</cite> and <cite>key</cite></li> |
|||
</ol> |
|||
</li> |
|||
</ol> |
|||
</div> |
|||
<div class="section" id="configure-dashboards"> |
|||
<h2><a class="toc-backref" href="#id3">Configure dashboards</a></h2> |
|||
<ol class="arabic simple"> |
|||
<li>Access <cite>Dashboards > Configuration > KPI Dashboards > Configure Dashboards</cite></li> |
|||
<li>Create a new dashboard and specify all the standard parameters on <cite>Widget configuration</cite></li> |
|||
<li>Append elements on KPIs</li> |
|||
<li>You can preview the element using the dashboard view</li> |
|||
<li>You can create the menu entry directly using the <cite>Generate menu</cite> button</li> |
|||
</ol> |
|||
</div> |
|||
</div> |
|||
<div class="section" id="bug-tracker"> |
|||
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1> |
|||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/reporting-engine/issues">GitHub Issues</a>. |
|||
In case of trouble, please check there if your issue has already been reported. |
|||
If you spotted it first, help us smashing it by providing a detailed and welcomed |
|||
<a class="reference external" href="https://github.com/OCA/reporting-engine/issues/new?body=module:%20kpi_dashboard%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p> |
|||
<p>Do not contact contributors directly about support or help with technical issues.</p> |
|||
</div> |
|||
<div class="section" id="credits"> |
|||
<h1><a class="toc-backref" href="#id5">Credits</a></h1> |
|||
<div class="section" id="authors"> |
|||
<h2><a class="toc-backref" href="#id6">Authors</a></h2> |
|||
<ul class="simple"> |
|||
<li>Creu Blanca</li> |
|||
</ul> |
|||
</div> |
|||
<div class="section" id="contributors"> |
|||
<h2><a class="toc-backref" href="#id7">Contributors</a></h2> |
|||
<ul class="simple"> |
|||
<li>Enric Tobella <<a class="reference external" href="mailto:etobella@creublanca.es">etobella@creublanca.es</a>></li> |
|||
</ul> |
|||
</div> |
|||
<div class="section" id="maintainers"> |
|||
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2> |
|||
<p>This module is maintained by the OCA.</p> |
|||
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a> |
|||
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose |
|||
mission is to support the collaborative development of Odoo features and |
|||
promote its widespread use.</p> |
|||
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/reporting-engine/tree/12.0/kpi_dashboard">OCA/reporting-engine</a> project on GitHub.</p> |
|||
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
</html> |
@ -0,0 +1,276 @@ |
|||
; |
|||
/* |
|||
* AshAlom Gauge Meter. Version 2.0.0 |
|||
* Copyright AshAlom.com All rights reserved. |
|||
* https://github.com/AshAlom/GaugeMeter <- Deleted!
|
|||
* https://github.com/githubsrinath/GaugeMeter <- Backup original.
|
|||
* |
|||
* Original created by Dr Ash Alom |
|||
* |
|||
* This is a bug fixed and modified version of the AshAlom Gauge Meter. |
|||
* Copyright 2018 Michael Wolf (Mictronics) |
|||
* https://github.com/mictronics/GaugeMeter
|
|||
* |
|||
*/ |
|||
!function ($) { |
|||
$.fn.gaugeMeter = function (t) { |
|||
var defaults = $.extend({ |
|||
id: "", |
|||
percent: 0, |
|||
used: null, |
|||
min: null, |
|||
total: null, |
|||
size: 100, |
|||
prepend: "", |
|||
append: "", |
|||
theme: "Red-Gold-Green", |
|||
color: "", |
|||
back: "RGBa(0,0,0,.06)", |
|||
width: 3, |
|||
style: "Full", |
|||
stripe: "0", |
|||
animationstep: 1, |
|||
animate_gauge_colors: false, |
|||
animate_text_colors: false, |
|||
label: "", |
|||
label_color: "Black", |
|||
text: "", |
|||
text_size: 0.22, |
|||
fill: "", |
|||
showvalue: false |
|||
}, t); |
|||
return this.each(function () { |
|||
|
|||
function getThemeColor(e) { |
|||
var t = "#2C94E0"; |
|||
return e || (e = 1e-14), |
|||
"Red-Gold-Green" === option.theme && (e > 0 && (t = "#d90000"), e > 10 && (t = "#e32100"), e > 20 && (t = "#f35100"), e > 30 && (t = "#ff8700"), e > 40 && (t = "#ffb800"), e > 50 && (t = "#ffd900"), e > 60 && (t = "#dcd800"), e > 70 && (t = "#a6d900"), e > 80 && (t = "#69d900"), e > 90 && (t = "#32d900")), |
|||
"Green-Gold-Red" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#69d900"), e > 20 && (t = "#a6d900"), e > 30 && (t = "#dcd800"), e > 40 && (t = "#ffd900"), e > 50 && (t = "#ffb800"), e > 60 && (t = "#ff8700"), e > 70 && (t = "#f35100"), e > 80 && (t = "#e32100"), e > 90 && (t = "#d90000")), |
|||
"Green-Red" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#41c900"), e > 20 && (t = "#56b300"), e > 30 && (t = "#6f9900"), e > 40 && (t = "#8a7b00"), e > 50 && (t = "#a75e00"), e > 60 && (t = "#c24000"), e > 70 && (t = "#db2600"), e > 80 && (t = "#f01000"), e > 90 && (t = "#ff0000")), |
|||
"Red-Green" === option.theme && (e > 0 && (t = "#ff0000"), e > 10 && (t = "#f01000"), e > 20 && (t = "#db2600"), e > 30 && (t = "#c24000"), e > 40 && (t = "#a75e00"), e > 50 && (t = "#8a7b00"), e > 60 && (t = "#6f9900"), e > 70 && (t = "#56b300"), e > 80 && (t = "#41c900"), e > 90 && (t = "#32d900")), |
|||
"DarkBlue-LightBlue" === option.theme && (e > 0 && (t = "#2c94e0"), e > 10 && (t = "#2b96e1"), e > 20 && (t = "#2b99e4"), e > 30 && (t = "#2a9ce7"), e > 40 && (t = "#28a0e9"), e > 50 && (t = "#26a4ed"), e > 60 && (t = "#25a8f0"), e > 70 && (t = "#24acf3"), e > 80 && (t = "#23aff5"), e > 90 && (t = "#21b2f7")), |
|||
"LightBlue-DarkBlue" === option.theme && (e > 0 && (t = "#21b2f7"), e > 10 && (t = "#23aff5"), e > 20 && (t = "#24acf3"), e > 30 && (t = "#25a8f0"), e > 40 && (t = "#26a4ed"), e > 50 && (t = "#28a0e9"), e > 60 && (t = "#2a9ce7"), e > 70 && (t = "#2b99e4"), e > 80 && (t = "#2b96e1"), e > 90 && (t = "#2c94e0")), |
|||
"DarkRed-LightRed" === option.theme && (e > 0 && (t = "#d90000"), e > 10 && (t = "#dc0000"), e > 20 && (t = "#e00000"), e > 30 && (t = "#e40000"), e > 40 && (t = "#ea0000"), e > 50 && (t = "#ee0000"), e > 60 && (t = "#f30000"), e > 70 && (t = "#f90000"), e > 80 && (t = "#fc0000"), e > 90 && (t = "#ff0000")), |
|||
"LightRed-DarkRed" === option.theme && (e > 0 && (t = "#ff0000"), e > 10 && (t = "#fc0000"), e > 20 && (t = "#f90000"), e > 30 && (t = "#f30000"), e > 40 && (t = "#ee0000"), e > 50 && (t = "#ea0000"), e > 60 && (t = "#e40000"), e > 70 && (t = "#e00000"), e > 80 && (t = "#dc0000"), e > 90 && (t = "#d90000")), |
|||
"DarkGreen-LightGreen" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#33db00"), e > 20 && (t = "#34df00"), e > 30 && (t = "#34e200"), e > 40 && (t = "#36e700"), e > 50 && (t = "#37ec00"), e > 60 && (t = "#38f100"), e > 70 && (t = "#38f600"), e > 80 && (t = "#39f900"), e > 90 && (t = "#3afc00")), |
|||
"LightGreen-DarkGreen" === option.theme && (e > 0 && (t = "#3afc00"), e > 10 && (t = "#39f900"), e > 20 && (t = "#38f600"), e > 30 && (t = "#38f100"), e > 40 && (t = "#37ec00"), e > 50 && (t = "#36e700"), e > 60 && (t = "#34e200"), e > 70 && (t = "#34df00"), e > 80 && (t = "#33db00"), e > 90 && (t = "#32d900")), |
|||
"DarkGold-LightGold" === option.theme && (e > 0 && (t = "#ffb800"), e > 10 && (t = "#ffba00"), e > 20 && (t = "#ffbd00"), e > 30 && (t = "#ffc200"), e > 40 && (t = "#ffc600"), e > 50 && (t = "#ffcb00"), e > 60 && (t = "#ffcf00"), e > 70 && (t = "#ffd400"), e > 80 && (t = "#ffd600"), e > 90 && (t = "#ffd900")), |
|||
"LightGold-DarkGold" === option.theme && (e > 0 && (t = "#ffd900"), e > 10 && (t = "#ffd600"), e > 20 && (t = "#ffd400"), e > 30 && (t = "#ffcf00"), e > 40 && (t = "#ffcb00"), e > 50 && (t = "#ffc600"), e > 60 && (t = "#ffc200"), e > 70 && (t = "#ffbd00"), e > 80 && (t = "#ffba00"), e > 90 && (t = "#ffb800")), |
|||
"White" === option.theme && (t = "#fff"), |
|||
"Black" === option.theme && (t = "#000"), |
|||
t; |
|||
} |
|||
/* The label below gauge. */ |
|||
function createLabel(t, a) { |
|||
if(t.children("b").length === 0){ |
|||
$("<b></b>").appendTo(t).html(option.label).css({ |
|||
"line-height": option.size + 5 * a + "px", |
|||
color: option.label_color |
|||
}); |
|||
} |
|||
} |
|||
/* Prepend and append text, the gauge text or percentage value. */ |
|||
function createSpanTag(t) { |
|||
var fgcolor = ""; |
|||
if (option.animate_text_colors === true){ |
|||
fgcolor = option.fgcolor; |
|||
} |
|||
var child = t.children("span"); |
|||
if(child.length !== 0){ |
|||
child.html(r).css({color: fgcolor}); |
|||
return; |
|||
} |
|||
if(option.text_size <= 0.0 || Number.isNaN(option.text_size)){ |
|||
option.text_size = 0.22; |
|||
} |
|||
if(option.text_size > 0.5){ |
|||
option.text_size = 0.5; |
|||
} |
|||
$("<span></span>").appendTo(t).html(r).css({ |
|||
"line-height": option.size + "px", |
|||
"font-size": option.text_size * option.size + "px", |
|||
color: fgcolor |
|||
}); |
|||
} |
|||
/* Get data attributes as options from div tag. Fall back to defaults when not exists. */ |
|||
function getDataAttr(t) { |
|||
$.each(dataAttr, function (index, element) { |
|||
if(t.data(element) !== undefined && t.data(element) !== null){ |
|||
option[element] = t.data(element); |
|||
} else { |
|||
option[element] = $(defaults).attr(element); |
|||
} |
|||
|
|||
if(element === "fill"){ |
|||
s = option[element]; |
|||
} |
|||
|
|||
if((element === "size" || |
|||
element === "width" || |
|||
element === "animationstep" || |
|||
element === "stripe" |
|||
) && !Number.isInteger(option[element])){ |
|||
option[element] = parseInt(option[element]); |
|||
} |
|||
|
|||
if(element === "text_size"){ |
|||
option[element] = parseFloat(option[element]); |
|||
} |
|||
}); |
|||
} |
|||
/* Draws the gauge. */ |
|||
function drawGauge(a) { |
|||
if(M < 0) M = 0; |
|||
if(M > 100) M = 100; |
|||
var lw = option.width < 1 || isNaN(option.width) ? option.size / 20 : option.width; |
|||
g.clearRect(0, 0, b.width, b.height); |
|||
g.beginPath(); |
|||
g.arc(m, v, x, G, k, !1); |
|||
if(s){ |
|||
g.fillStyle = option.fill; |
|||
g.fill(); |
|||
} |
|||
g.lineWidth = lw; |
|||
g.strokeStyle = option.back; |
|||
option.stripe > parseInt(0) ? g.setLineDash([option.stripe], 1) : g.lineCap = "round"; |
|||
g.stroke(); |
|||
g.beginPath(); |
|||
g.arc(m, v, x, -I, P * a - I, !1); |
|||
g.lineWidth = lw; |
|||
g.strokeStyle = option.fgcolor; |
|||
g.stroke(); |
|||
c > M && (M += z, requestAnimationFrame(function(){ |
|||
drawGauge(Math.min(M, c) / 100); |
|||
}, p)); |
|||
} |
|||
|
|||
$(this).attr("data-id", $(this).attr("id")); |
|||
var r, |
|||
dataAttr = ["percent", |
|||
"used", |
|||
"min", |
|||
"total", |
|||
"size", |
|||
"prepend", |
|||
"append", |
|||
"theme", |
|||
"color", |
|||
"back", |
|||
"width", |
|||
"style", |
|||
"stripe", |
|||
"animationstep", |
|||
"animate_gauge_colors", |
|||
"animate_text_colors", |
|||
"label", |
|||
"label_color", |
|||
"text", |
|||
"text_size", |
|||
"fill", |
|||
"showvalue"], |
|||
option = {}, |
|||
c = 0, |
|||
p = $(this), |
|||
s = false; |
|||
p.addClass("gaugeMeter"); |
|||
getDataAttr(p); |
|||
|
|||
if(Number.isInteger(option.used) && Number.isInteger(option.total)){ |
|||
var u = option.used; |
|||
var t = option.total; |
|||
if(Number.isInteger(option.min)) { |
|||
if(option.min < 0) { |
|||
t -= option.min; |
|||
u -= option.min; |
|||
} |
|||
} |
|||
c = u / (t / 100); |
|||
} else { |
|||
if(Number.isInteger(option.percent)){ |
|||
c = option.percent; |
|||
} else { |
|||
c = parseInt(defaults.percent); |
|||
} |
|||
} |
|||
if(c < 0) c = 0; |
|||
if(c > 100) c = 100; |
|||
|
|||
if( option.text !== "" && option.text !== null && option.text !== undefined){ |
|||
if(option.append !== "" && option.append !== null && option.append !== undefined){ |
|||
r = option.text + "<u>" + option.append + "</u>"; |
|||
} else { |
|||
r = option.text; |
|||
} |
|||
if(option.prepend !== "" && option.prepend !== null && option.prepend !== undefined){ |
|||
r = "<s>" + option.prepend + "</s>" + r; |
|||
} |
|||
} else { |
|||
if(defaults.showvalue === true || option.showvalue === true){ |
|||
r = option.used; |
|||
} else { |
|||
r = c.toString(); |
|||
} |
|||
if(option.prepend !== "" && option.prepend !== null && option.prepend !== undefined){ |
|||
r = "<s>" + option.prepend + "</s>" + r; |
|||
} |
|||
|
|||
if(option.append !== "" && option.append !== null && option.append !== undefined){ |
|||
r = r + "<u>" + option.append + "</u>"; |
|||
} |
|||
} |
|||
|
|||
option.fgcolor = getThemeColor(c); |
|||
if(option.color !== "" && option.color !== null && option.color !== undefined){ |
|||
option.fgcolor = option.color; |
|||
} |
|||
|
|||
if(option.animate_gauge_colors === true){ |
|||
option.fgcolor = getThemeColor(c); |
|||
} |
|||
createSpanTag(p); |
|||
|
|||
if(option.style !== "" && option.style !== null && option.style !== undefined){ |
|||
createLabel(p, option.size / 13); |
|||
} |
|||
|
|||
$(this).width(option.size + "px"); |
|||
|
|||
var b = $("<canvas></canvas>").attr({width: option.size, height: option.size}).get(0), |
|||
g = b.getContext("2d"), |
|||
m = b.width / 2, |
|||
v = b.height / 2, |
|||
_ = 360 * option.percent, |
|||
x = (_ * (Math.PI / 180), b.width / 2.5), |
|||
k = 2.3 * Math.PI, |
|||
G = 0, |
|||
M = 0 === option.animationstep ? c : 0, |
|||
z = Math.max(option.animationstep, 0), |
|||
P = 2 * Math.PI, |
|||
I = Math.PI / 2, |
|||
R = option.style; |
|||
var child = $(this).children("canvas"); |
|||
if(child.length !== 0){ |
|||
/* Replace existing canvas when new percentage was written. */ |
|||
child.replaceWith(b); |
|||
} else { |
|||
/* Initially create canvas. */ |
|||
$(b).appendTo($(this)); |
|||
} |
|||
|
|||
if ("Semi" === R){ |
|||
k = 2 * Math.PI; |
|||
G = 3.13; |
|||
P = 1 * Math.PI; |
|||
I = Math.PI / .996; |
|||
} |
|||
if ("Arch" === R){ |
|||
k = 2.195 * Math.PI; |
|||
G = 1, G = 655.99999; |
|||
P = 1.4 * Math.PI; |
|||
I = Math.PI / .8335; |
|||
} |
|||
drawGauge(M / 100); |
|||
}); |
|||
}; |
|||
} |
|||
(jQuery); |
@ -0,0 +1,2 @@ |
|||
/*! gridster.js - v0.8.0 - 2019-01-10 - * https://dsmorse.github.io/gridster.js/ - Copyright (c) 2019 ducksboard; Licensed MIT */ |
|||
.gridster{position:relative}.gridster>*{-webkit-transition:height .4s,width .4s;-moz-transition:height .4s,width .4s;-o-transition:height .4s,width .4s;-ms-transition:height .4s,width .4s;transition:height .4s,width .4s}.gridster .gs-w{z-index:2;position:absolute}.gridster .preview-holder{z-index:1;position:absolute;background-color:#fff;border-color:#fff;opacity:.3}.gridster .player-revert{z-index:10!important;-webkit-transition:left .3s,top .3s!important;-moz-transition:left .3s,top .3s!important;-o-transition:left .3s,top .3s!important;transition:left .3s,top .3s!important}.gridster.collapsed{height:auto!important}.gridster.collapsed .gs-w{position:static!important}.ready .gs-w:not(.preview-holder),.ready .resize-preview-holder{-webkit-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-moz-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-o-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;transition:opacity .3s,left .3s,top .3s,width .3s,height .3s}.gridster .dragging,.gridster .resizing{z-index:10!important;-webkit-transition:all 0s!important;-moz-transition:all 0s!important;-o-transition:all 0s!important;transition:all 0s!important}.gs-resize-handle{position:absolute;z-index:1}.gs-resize-handle-both{width:20px;height:20px;bottom:-8px;right:-8px;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=);background-position:top left;background-repeat:no-repeat;cursor:se-resize;z-index:20}.gs-resize-handle-x{top:0;bottom:13px;right:-5px;width:10px;cursor:e-resize}.gs-resize-handle-y{left:0;right:13px;bottom:-5px;height:10px;cursor:s-resize}.gs-w:hover .gs-resize-handle,.resizing .gs-resize-handle{opacity:1}.gs-resize-handle,.gs-w.dragging .gs-resize-handle{opacity:0}.gs-resize-disabled .gs-resize-handle,[data-max-sizex="1"] .gs-resize-handle-x,[data-max-sizey="1"] .gs-resize-handle-y,[data-max-sizey="1"][data-max-sizex="1"] .gs-resize-handle{display:none!important} |
2
kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,76 @@ |
|||
odoo.define('kpi_dashboard.DashboardController', function (require) { |
|||
"use strict"; |
|||
|
|||
var BasicController = require('web.BasicController'); |
|||
var core = require('web.core'); |
|||
var qweb = core.qweb; |
|||
|
|||
var _t = core._t; |
|||
|
|||
var DashboardController = BasicController.extend({ |
|||
custom_events: _.extend({}, BasicController.prototype.custom_events, { |
|||
addDashboard: '_addDashboard', |
|||
}), |
|||
renderPager: function ($node, options) { |
|||
options = _.extend({}, options, { |
|||
validate: this.canBeDiscarded.bind(this), |
|||
}); |
|||
this._super($node, options); |
|||
}, |
|||
_pushState: function (state) { |
|||
state = state || {}; |
|||
var env = this.model.get(this.handle, {env: true}); |
|||
state.id = env.currentId; |
|||
this._super(state); |
|||
}, |
|||
_addDashboard: function () { |
|||
var self = this; |
|||
var action = self.initialState.specialData.action_id; |
|||
var name = self.initialState.specialData.name; |
|||
if (! action) { |
|||
self.do_warn(_t("First you must create the Menu")); |
|||
} |
|||
return self._rpc({ |
|||
route: '/board/add_to_dashboard', |
|||
params: { |
|||
action_id: action, |
|||
context_to_save: {'res_id': self.initialState.res_id}, |
|||
domain: [('id', '=', self.initialState.res_id)], |
|||
view_mode: 'dashboard', |
|||
name: name, |
|||
}, |
|||
}) |
|||
.then(function (r) { |
|||
if (r) { |
|||
self.do_notify( |
|||
_.str.sprintf(_t("'%s' added to dashboard"), name), |
|||
_t('Please refresh your browser for the changes to take effect.') |
|||
); |
|||
} else { |
|||
self.do_warn(_t("Could not add KPI dashboard to dashboard")); |
|||
} |
|||
}); |
|||
}, |
|||
_updateButtons: function () { |
|||
// HOOK Function
|
|||
this.$buttons.on( |
|||
'click', '.o_dashboard_button_add', |
|||
this._addDashboard.bind(this)); |
|||
}, |
|||
renderButtons: function ($node) { |
|||
if (! $node) { |
|||
return; |
|||
} |
|||
|
|||
this.$buttons = $('<div/>'); |
|||
this.$buttons.append(qweb.render( |
|||
"kpi_dashboard.buttons", {widget: this})); |
|||
|
|||
this._updateButtons(); |
|||
this.$buttons.appendTo($node); |
|||
}, |
|||
}); |
|||
|
|||
return DashboardController; |
|||
|
|||
}); |
@ -0,0 +1,23 @@ |
|||
odoo.define('kpi_dashboard.DashboardModel', function (require) { |
|||
"use strict"; |
|||
|
|||
var BasicModel = require('web.BasicModel'); |
|||
|
|||
var DashboardModel = BasicModel.extend({ |
|||
_fetchRecord: function (record, options) { |
|||
return this._rpc({ |
|||
model: record.model, |
|||
method: 'read_dashboard', |
|||
args: [[record.res_id]], |
|||
context: _.extend({}, record.getContext(), {bin_size: true}), |
|||
}) |
|||
.then(function (result) { |
|||
record.specialData = result; |
|||
return result |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
return DashboardModel; |
|||
|
|||
}); |
@ -0,0 +1,74 @@ |
|||
odoo.define('kpi_dashboard.DashboardRenderer', function (require) { |
|||
"use strict"; |
|||
|
|||
var BasicRenderer = require('web.BasicRenderer'); |
|||
var core = require('web.core'); |
|||
var registry = require('kpi_dashboard.widget_registry'); |
|||
var BusService = require('bus.BusService'); |
|||
var qweb = core.qweb; |
|||
|
|||
var DashboardRenderer= BasicRenderer.extend({ |
|||
className: "o_dashboard_view", |
|||
_getDashboardWidget: function (kpi) { |
|||
var Widget = registry.getAny([ |
|||
kpi.widget, 'abstract', |
|||
]); |
|||
var widget = new Widget(this, kpi); |
|||
return widget; |
|||
}, |
|||
_renderView: function () { |
|||
this.$el.html($(qweb.render('dashboard_kpi.dashboard'))); |
|||
this.$el.css( |
|||
'background-color', this.state.specialData.background_color); |
|||
this.$el.find('.gridster') |
|||
.css('width', this.state.specialData.width); |
|||
this.$grid = this.$el.find('.gridster ul'); |
|||
var self = this; |
|||
this.kpi_widget = {}; |
|||
_.each(this.state.specialData.item_ids, function (kpi) { |
|||
var element = $(qweb.render( |
|||
'kpi_dashboard.kpi', {widget: kpi})); |
|||
element.css('background-color', kpi.color); |
|||
element.css('color', kpi.font_color); |
|||
self.$grid.append(element); |
|||
self.kpi_widget[kpi.id] = self._getDashboardWidget(kpi); |
|||
self.kpi_widget[kpi.id].appendTo(element); |
|||
}); |
|||
this.$grid.gridster({ |
|||
widget_margins: [ |
|||
this.state.specialData.margin_x, |
|||
this.state.specialData.margin_y, |
|||
], |
|||
widget_base_dimensions: [ |
|||
this.state.specialData.widget_dimension_x, |
|||
this.state.specialData.widget_dimension_y, |
|||
], |
|||
cols: this.state.specialData.max_cols, |
|||
}).data('gridster').disable(); |
|||
this.channel = 'kpi_dashboard_' + this.state.res_id; |
|||
this.call( |
|||
'bus_service', 'addChannel', this.channel); |
|||
this.call('bus_service', 'startPolling'); |
|||
this.call( |
|||
'bus_service', 'onNotification', |
|||
this, this._onNotification |
|||
); |
|||
return $.when(); |
|||
}, |
|||
_onNotification: function (notifications) { |
|||
var self = this; |
|||
_.each(notifications, function (notification) { |
|||
var channel = notification[0]; |
|||
var message = notification[1]; |
|||
if (channel === self.channel && message) { |
|||
var widget = self.kpi_widget[message.id]; |
|||
if (widget !== undefined) { |
|||
widget._fillWidget(message); |
|||
} |
|||
} |
|||
}); |
|||
}, |
|||
}); |
|||
|
|||
return DashboardRenderer; |
|||
}); |
@ -0,0 +1,44 @@ |
|||
odoo.define('kpi_dashboard.DashboardView', function (require) { |
|||
"use strict"; |
|||
|
|||
var BasicView = require('web.BasicView'); |
|||
var DashboardController = require('kpi_dashboard.DashboardController'); |
|||
var DashboardModel = require('kpi_dashboard.DashboardModel'); |
|||
var DashboardRenderer = require('kpi_dashboard.DashboardRenderer'); |
|||
var view_registry = require('web.view_registry'); |
|||
var core = require('web.core'); |
|||
|
|||
var _lt = core._lt; |
|||
|
|||
var DashboardView = BasicView.extend({ |
|||
jsLibs: [ |
|||
'/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.js', |
|||
], |
|||
cssLibs: [ |
|||
'/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.css', |
|||
], |
|||
accesskey: "d", |
|||
display_name: _lt("Dashboard"), |
|||
icon: 'fa-tachometer', |
|||
viewType: 'dashboard', |
|||
config: _.extend({}, BasicView.prototype.config, { |
|||
Controller: DashboardController, |
|||
Renderer: DashboardRenderer, |
|||
Model: DashboardModel, |
|||
}), |
|||
multi_record: false, |
|||
searchable: false, |
|||
init: function () { |
|||
this._super.apply(this, arguments); |
|||
this.controllerParams.mode = 'readonly'; |
|||
this.loadParams.type = 'record'; |
|||
if (! this.loadParams.res_id && this.loadParams.context.res_id) { |
|||
this.loadParams.res_id = this.loadParams.context.res_id; |
|||
} |
|||
}, |
|||
}); |
|||
|
|||
view_registry.add('dashboard', DashboardView); |
|||
|
|||
return DashboardView; |
|||
}); |
@ -0,0 +1,91 @@ |
|||
odoo.define('kpi_dashboard.AbstractWidget', function (require) { |
|||
"use strict"; |
|||
var Widget = require('web.Widget'); |
|||
var field_utils = require('web.field_utils'); |
|||
var time = require('web.time'); |
|||
var ajax = require('web.ajax'); |
|||
var registry = require('kpi_dashboard.widget_registry'); |
|||
|
|||
var AbstractWidget = Widget.extend({ |
|||
template: 'kpi_dashboard.base_widget', // Template used by the widget
|
|||
cssLibs: [], // Specific css of the widget
|
|||
jsLibs: [], // Specific Javascript libraries of the widget
|
|||
events: { |
|||
'click .o_kpi_dashboard_toggle_button': '_onClickToggleButton', |
|||
'click .direct_action': '_onClickDirectAction', |
|||
}, |
|||
init: function (parent, kpi_values) { |
|||
this._super(parent); |
|||
this.col = kpi_values.col; |
|||
this.row = kpi_values.row; |
|||
this.sizex = kpi_values.sizex; |
|||
this.sizey = kpi_values.sizey; |
|||
this.color = kpi_values.color; |
|||
this.values = kpi_values; |
|||
this.margin_x = parent.state.specialData.margin_x; |
|||
this.margin_y = parent.state.specialData.margin_y; |
|||
this.widget_dimension_x = parent.state.specialData.widget_dimension_x; |
|||
this.widget_dimension_y = parent.state.specialData.widget_dimension_y; |
|||
this.prefix = kpi_values.prefix; |
|||
this.suffix = kpi_values.suffix; |
|||
this.actions = kpi_values.actions; |
|||
this.widget_size_x = this.widget_dimension_x * this.sizex + |
|||
(this.sizex - 1) * this.margin_x; |
|||
this.widget_size_y = this.widget_dimension_y * this.sizey + |
|||
(this.sizey - 1) * this.margin_y; |
|||
}, |
|||
willStart: function () { |
|||
// We need to load the libraries before the start
|
|||
return $.when(ajax.loadLibs(this), this._super.apply(this, arguments)); |
|||
}, |
|||
start: function () { |
|||
var self = this; |
|||
return this._super.apply(this, arguments).then(function () { |
|||
self._fillWidget(self.values); |
|||
}); |
|||
}, |
|||
_onClickToggleButton: function (event) { |
|||
event.preventDefault(); |
|||
this.$el.toggleClass('o_dropdown_open'); |
|||
}, |
|||
_fillWidget: function (values) { |
|||
// This function fills the widget values
|
|||
if (this.$el === undefined) |
|||
return; |
|||
this.fillWidget(values); |
|||
var item = this.$el.find('[data-bind="value_last_update_display"]'); |
|||
if (item && values.value_last_update !== undefined) { |
|||
var value = field_utils.parse.datetime(values.value_last_update); |
|||
item.text(value.clone().add( |
|||
this.getSession().getTZOffset(value), 'minutes').format( |
|||
time.getLangDatetimeFormat() |
|||
)); |
|||
} |
|||
var $manage = this.$el.find('.o_kpi_dashboard_manage'); |
|||
if ($manage && this.showManagePanel(values)) |
|||
$manage.toggleClass('hidden', false); |
|||
}, |
|||
showManagePanel: function (values) { |
|||
// Hook for extensions
|
|||
return (values.actions !== undefined); |
|||
}, |
|||
fillWidget: function (values) { |
|||
// Specific function that will be changed by specific widget
|
|||
var value = values.value; |
|||
var self = this; |
|||
_.each(value, function (val, key) { |
|||
var item = self.$el.find('[data-bind=' + key + ']') |
|||
if (item) |
|||
item.text(val); |
|||
}) |
|||
}, |
|||
_onClickDirectAction: function(event) { |
|||
event.preventDefault(); |
|||
var $data = $(event.currentTarget).closest('a'); |
|||
return this.do_action($($data).data('id')); |
|||
} |
|||
}); |
|||
|
|||
registry.add('abstract', AbstractWidget); |
|||
return AbstractWidget; |
|||
}); |
@ -0,0 +1,108 @@ |
|||
odoo.define('kpi_dashboard.GraphWidget', function (require) { |
|||
"use strict"; |
|||
|
|||
var AbstractWidget = require('kpi_dashboard.AbstractWidget'); |
|||
var registry = require('kpi_dashboard.widget_registry'); |
|||
var core = require('web.core'); |
|||
var qweb = core.qweb; |
|||
|
|||
|
|||
var GraphWidget = AbstractWidget.extend({ |
|||
template: 'kpi_dashboard.graph', |
|||
jsLibs: [ |
|||
'/web/static/lib/nvd3/d3.v3.js', |
|||
'/web/static/lib/nvd3/nv.d3.js', |
|||
'/web/static/src/js/libs/nvd3.js', |
|||
], |
|||
cssLibs: [ |
|||
'/web/static/lib/nvd3/nv.d3.css', |
|||
], |
|||
start: function () { |
|||
this._onResize = this._onResize.bind(this); |
|||
nv.utils.windowResize(this._onResize); |
|||
return this._super.apply(this, arguments); |
|||
}, |
|||
destroy: function () { |
|||
if ('nv' in window && nv.utils && nv.utils.offWindowResize) { |
|||
// if the widget is destroyed before the lazy loaded libs (nv) are
|
|||
// actually loaded (i.e. after the widget has actually started),
|
|||
// nv is undefined, but the handler isn't bound yet anyway
|
|||
nv.utils.offWindowResize(this._onResize); |
|||
} |
|||
this._super.apply(this, arguments); |
|||
}, |
|||
_getChartOptions: function (values) { |
|||
return { |
|||
x: function (d, u) { return u; }, |
|||
margin: {'left': 0, 'right': 0, 'top': 5, 'bottom': 0}, |
|||
showYAxis: false, |
|||
showXAxis: false, |
|||
showLegend: false, |
|||
height: this.widget_size_y - 90, |
|||
width: this.widget_size_x - 20, |
|||
}; |
|||
}, |
|||
_chartConfiguration: function (values) { |
|||
|
|||
this.chart.forceY([0]); |
|||
this.chart.xAxis.tickFormat(function (d) { |
|||
var label = ''; |
|||
_.each(values.value.graphs, function (v) { |
|||
if (v.values[d] && v.values[d].x) { |
|||
label = v.values[d].x; |
|||
} |
|||
}); |
|||
return label; |
|||
}); |
|||
this.chart.yAxis.tickFormat(d3.format(',.2f')); |
|||
|
|||
this.chart.tooltip.contentGenerator(function (key) { |
|||
return qweb.render('GraphCustomTooltip', { |
|||
'color': key.point.color, |
|||
'key': key.series[0].title, |
|||
'value': d3.format(',.2f')(key.point.y) |
|||
}); |
|||
}); |
|||
}, |
|||
_addGraph: function (values) { |
|||
var data = values.value.graphs; |
|||
this.$svg.addClass('o_graph_linechart'); |
|||
this.chart = nv.models.lineChart(); |
|||
this.chart.options( |
|||
this._getChartOptions(values) |
|||
); |
|||
this._chartConfiguration(values); |
|||
d3.select(this.$('svg')[0]) |
|||
.datum(data) |
|||
.transition().duration(600) |
|||
.call(this.chart); |
|||
this.$('svg').css('height', this.widget_size_y - 90); |
|||
this._customizeChart(); |
|||
}, |
|||
fillWidget: function (values) { |
|||
var self = this; |
|||
var element = this.$el.find('[data-bind="value"]'); |
|||
element.empty(); |
|||
element.css('padding-left', 10).css('padding-right', 10); |
|||
this.chart = null; |
|||
nv.addGraph(function () { |
|||
self.$svg = self.$el.find( |
|||
'[data-bind="value"]' |
|||
).append('<svg width=' + (self.widget_size_x - 20) + '>'); |
|||
self._addGraph(values); |
|||
}); |
|||
}, |
|||
_customizeChart: function () { |
|||
// Hook function
|
|||
}, |
|||
_onResize: function () { |
|||
if (this.chart) { |
|||
this.chart.update(); |
|||
this._customizeChart(); |
|||
} |
|||
}, |
|||
}); |
|||
|
|||
registry.add('graph', GraphWidget); |
|||
return GraphWidget; |
|||
}); |
@ -0,0 +1,39 @@ |
|||
odoo.define('kpi_dashboard.MeterWidget', function (require) { |
|||
"use strict"; |
|||
|
|||
var AbstractWidget = require('kpi_dashboard.AbstractWidget'); |
|||
var registry = require('kpi_dashboard.widget_registry'); |
|||
|
|||
|
|||
var MeterWidget = AbstractWidget.extend({ |
|||
template: 'kpi_dashboard.meter', |
|||
jsLibs: [ |
|||
'/kpi_dashboard/static/lib/gauge/GaugeMeter.js', |
|||
], |
|||
fillWidget: function (values) { |
|||
var input = this.$el.find('[data-bind="value"]'); |
|||
var options = this._getMeterOptions(values); |
|||
var margin = (this.widget_dimension_x - options.size)/2; |
|||
input.gaugeMeter(options); |
|||
input.parent().css('padding-left', margin); |
|||
}, |
|||
_getMeterOptions: function (values) { |
|||
var size = Math.min( |
|||
this.widget_size_x, |
|||
this.widget_size_y - 40) - 10; |
|||
return { |
|||
percent: values.value.value, |
|||
style: 'Arch', |
|||
width: 10, |
|||
size: size, |
|||
prepend: values.prefix !== undefined ? values.prefix : '', |
|||
append: values.suffix !== undefined ? values.suffix : '', |
|||
color: values.font_color, |
|||
animate_text_colors: true, |
|||
}; |
|||
}, |
|||
}); |
|||
|
|||
registry.add('meter', MeterWidget); |
|||
return MeterWidget; |
|||
}); |
@ -0,0 +1,72 @@ |
|||
odoo.define('kpi_dashboard.NumberWidget', function (require) { |
|||
"use strict"; |
|||
|
|||
var AbstractWidget = require('kpi_dashboard.AbstractWidget'); |
|||
var registry = require('kpi_dashboard.widget_registry'); |
|||
var field_utils = require('web.field_utils'); |
|||
|
|||
|
|||
var NumberWidget = AbstractWidget.extend({ |
|||
template: 'kpi_dashboard.number', |
|||
shortNumber: function (num) { |
|||
if (Math.abs(num) >= 1000000000000) { |
|||
return field_utils.format.integer(num / 1000000000000, false, { |
|||
digits: [3, 1]}) + 'T'; |
|||
} |
|||
if (Math.abs(num) >= 1000000000) { |
|||
return field_utils.format.integer(num / 1000000000, false, { |
|||
digits: [3,1]}) + 'G'; |
|||
} |
|||
if (Math.abs(num) >= 1000000) { |
|||
return field_utils.format.integer(num / 1000000, false, { |
|||
digits: [3, 1]}) + 'M'; |
|||
} |
|||
if (Math.abs(num) >= 1000) { |
|||
return field_utils.format.float(num / 1000, false, { |
|||
digits: [3, 1]}) + 'K'; |
|||
} |
|||
if (Math.abs(num) >= 10) { |
|||
return field_utils.format.float(num, false, { |
|||
digits: [3, 1]}); |
|||
} |
|||
return field_utils.format.float(num, false, { |
|||
digits: [3, 2]}); |
|||
}, |
|||
fillWidget: function (values) { |
|||
var widget = this.$el; |
|||
var value = values.value.value; |
|||
if (value === undefined) { |
|||
value = 0; |
|||
} |
|||
var item = widget.find('[data-bind="value"]'); |
|||
if (item) { |
|||
item.text(this.shortNumber(value)); |
|||
} |
|||
var previous = values.value.previous; |
|||
|
|||
var $change_rate = widget.find('.change-rate'); |
|||
if (previous === undefined) { |
|||
$change_rate.toggleClass('active', false); |
|||
} else { |
|||
var difference = 0; |
|||
if (previous !== 0) { |
|||
difference = field_utils.format.integer( |
|||
(100 * value / previous) - 100) + '%'; |
|||
} |
|||
$change_rate.toggleClass('active', true); |
|||
var $difference = widget.find('[data-bind="difference"]'); |
|||
$difference.text(difference); |
|||
var $arrow = widget.find('[data-bind="arrow"]'); |
|||
if (value < previous) { |
|||
$arrow.toggleClass('fa-arrow-up', false); |
|||
$arrow.toggleClass('fa-arrow-down', true); |
|||
} else { |
|||
$arrow.toggleClass('fa-arrow-up', true); |
|||
$arrow.toggleClass('fa-arrow-down', false); |
|||
} |
|||
} |
|||
}, |
|||
}); |
|||
registry.add('number', NumberWidget); |
|||
return NumberWidget; |
|||
}); |
@ -0,0 +1,17 @@ |
|||
odoo.define('kpi_dashboard.TextWidget', function (require) { |
|||
"use strict"; |
|||
|
|||
var AbstractWidget = require('kpi_dashboard.AbstractWidget'); |
|||
var registry = require('kpi_dashboard.widget_registry'); |
|||
|
|||
|
|||
var TextWidget = AbstractWidget.extend({ |
|||
template: 'kpi_dashboard.base_text', |
|||
fillWidget: function () { |
|||
return; |
|||
}, |
|||
}); |
|||
|
|||
registry.add('base_text', TextWidget); |
|||
return TextWidget; |
|||
}); |
@ -0,0 +1,7 @@ |
|||
odoo.define('kpi_dashboard.widget_registry', function (require) { |
|||
"use strict"; |
|||
|
|||
var Registry = require('web.Registry'); |
|||
|
|||
return new Registry(); |
|||
}); |
@ -0,0 +1,112 @@ |
|||
.o_dashboard_view { |
|||
height: 100%; |
|||
@include o-webclient-padding($top: $o-horizontal-padding/2, $bottom: $o-horizontal-padding/2); |
|||
display: flex; |
|||
>.gridster { |
|||
margin: 0 auto; |
|||
>ul { |
|||
>li { |
|||
text-align: center; |
|||
list-style: none outside none; |
|||
} |
|||
} |
|||
} |
|||
.updated_at { |
|||
font-size: 15px; |
|||
position: absolute; |
|||
bottom: 0px; |
|||
left: 0; |
|||
right: 0; |
|||
} |
|||
.gs-w { |
|||
padding: 10px; |
|||
} |
|||
.centered { |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
} |
|||
.numbervalue { |
|||
text-transform: uppercase; |
|||
font-size: 54px; |
|||
font-weight: 700; |
|||
} |
|||
.change-rate { |
|||
font-weight: 500; |
|||
font-size: 30px; |
|||
} |
|||
.hidden { |
|||
display: none; |
|||
} |
|||
.o_kpi_dashboard_toggle_button { |
|||
position: absolute; |
|||
right: 0px; |
|||
top: 0px; |
|||
margin: -1px -1px auto auto; |
|||
padding: 8px 16px; |
|||
border: 1px solid transparent; |
|||
border-bottom: none; |
|||
height: 35px; |
|||
} |
|||
.o_kpi_dashboard_manage_panel { |
|||
@include o-position-absolute($right: -1px, $top: 34px); |
|||
margin-top: -1px; |
|||
&.container { |
|||
width: 95%; |
|||
max-width: 400px; |
|||
} |
|||
.o_kpi_dashboard_manage_section { |
|||
border-bottom: 1px solid gray('300'); |
|||
margin-bottom: 10px; |
|||
} |
|||
> div { |
|||
padding: 3px 0 3px 20px; |
|||
visibility: visible; |
|||
margin-bottom: 5px; |
|||
} |
|||
} |
|||
.o_dropdown_open { |
|||
.o_kpi_dashboard_manage_panel { |
|||
display: block; |
|||
} |
|||
.o_kpi_dashboard_toggle_button { |
|||
background: white; |
|||
border-color: gray('400'); |
|||
z-index: $zindex-dropdown + 1; |
|||
} |
|||
} |
|||
.GaugeMeter { |
|||
position: relative; |
|||
text-align: center; |
|||
left: 0; |
|||
right: 0; |
|||
overflow: hidden; |
|||
cursor: default; |
|||
span, b{ |
|||
margin: 0 23%; |
|||
width: 54%; |
|||
position: absolute; |
|||
text-align: center; |
|||
display: inline-block; |
|||
font-height: 100; |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
} |
|||
[data-style="Semi"] B{ |
|||
Margin: 0 10%; |
|||
Width: 80%; |
|||
} |
|||
S, U{ |
|||
Text-Decoration:None; |
|||
font-height: 100; |
|||
} |
|||
B{ |
|||
Color: Black; |
|||
Font-Weight: 200; |
|||
Font-Size: 0.85em; |
|||
Opacity: .8; |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,76 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<template> |
|||
<t t-name="dashboard_kpi.dashboard"> |
|||
<div class="gridster kpi_dashboard"> |
|||
<ul/> |
|||
</div> |
|||
</t> |
|||
<t t-name="kpi_dashboard.kpi"> |
|||
<li t-att-data-row="widget.row" |
|||
t-att-data-col="widget.col" |
|||
t-att-data-sizex="widget.sizex" |
|||
t-att-data-sizey="widget.sizey" |
|||
/> |
|||
</t> |
|||
<t t-name="kpi_dashboard.base_text"> |
|||
<div class="kpi"> |
|||
<h1 class="title" t-esc="widget.values.name"/> |
|||
</div> |
|||
</t> |
|||
<t t-name="kpi_dashboard.ManagePanel"> |
|||
<t t-if="widget.actions" > |
|||
<t t-foreach="widget.actions" t-as="action"> |
|||
<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> |
|||
</div> |
|||
</t> |
|||
</t> |
|||
</t> |
|||
<t t-name="kpi_dashboard.base_widget"> |
|||
<div class="kpi"> |
|||
<div class="o_kpi_dashboard_manage hidden"> |
|||
<a class="o_kpi_dashboard_toggle_button" href="#"> |
|||
<i class="fa fa-ellipsis-v" aria-label="Selection" role="img" title="Selection"/> |
|||
</a> |
|||
</div> |
|||
<h1 class="title" t-esc="widget.values.name"/> |
|||
<p class="updated_at" data-bind="value_last_update_display"/> |
|||
<div class="container o_kpi_dashboard_manage_panel dropdown-menu"> |
|||
<t t-call="kpi_dashboard.ManagePanel"/> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
<t t-name="kpi_dashboard.number" t-extend="kpi_dashboard.base_widget"> |
|||
<t t-jquery="h1" t-operation="after"> |
|||
<h2 class="numbervalue"> |
|||
<span t-esc="widget.prefix"/><span data-bind="value"/><span t-esc="widget.suffix"/> |
|||
</h2> |
|||
<p class="change-rate"> |
|||
<i class="fa" data-bind="arrow"/> |
|||
<span data-bind="difference"/> |
|||
</p> |
|||
</t> |
|||
</t> |
|||
<t t-name="kpi_dashboard.meter" t-extend="kpi_dashboard.base_widget"> |
|||
<t t-jquery="h1" t-operation="after"> |
|||
<div class="centered"> |
|||
<div class="GaugeMeter" data-bind="value"/> |
|||
</div> |
|||
</t> |
|||
</t> |
|||
<t t-name="kpi_dashboard.graph" t-extend="kpi_dashboard.base_widget"> |
|||
<t t-jquery="h1" t-operation="after"> |
|||
<div class="centered"> |
|||
<div data-bind="value"/> |
|||
</div> |
|||
</t> |
|||
</t> |
|||
<t t-name="kpi_dashboard.buttons"> |
|||
<div class="o_dashboard_buttons" role="toolbar" aria-label="Main actions"> |
|||
<button type="button" |
|||
class="btn btn-primary o_dashboard_button_add" accesskey="d"> |
|||
Add to Dashboard |
|||
</button> |
|||
</div> |
|||
</t> |
|||
</template> |
@ -0,0 +1,111 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- Copyright 2020 Creu Blanca |
|||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
|||
|
|||
<odoo> |
|||
|
|||
<record model="ir.ui.view" id="kpi_dashboard_form_view"> |
|||
<field name="name">kpi.dashboard.form (in kpi_dashboard)</field> |
|||
<field name="model">kpi.dashboard</field> |
|||
<field name="arch" type="xml"> |
|||
<form> |
|||
<header/> |
|||
<sheet> |
|||
<div name="button_box" class="oe_button_box"> |
|||
<button name="%(kpi_dashboard.kpi_dashboard_menu_act_window)d" |
|||
type="action" |
|||
string="Generate menu" |
|||
icon="fa-folder-open-o" |
|||
context="{'default_dashboard_id': active_id}" |
|||
attrs="{'invisible': [('menu_id', '!=', False)]}" |
|||
/> |
|||
</div> |
|||
<group> |
|||
<field name="name"/> |
|||
<field name="menu_id" attrs="{'invisible': [('menu_id', '=', False)]}"/> |
|||
</group> |
|||
<notebook> |
|||
<page name="item" string="KPIs"> |
|||
<field name="item_ids"> |
|||
<tree editable="bottom"> |
|||
<field name="name"/> |
|||
<field name="kpi_id"/> |
|||
<field name="column"/> |
|||
<field name="row"/> |
|||
<field name="size_x"/> |
|||
<field name="size_y"/> |
|||
<field name="color" widget="color"/> |
|||
<field name="font_color" widget="color"/> |
|||
</tree> |
|||
</field> |
|||
</page> |
|||
<page name="widget" string="Widget configuration"> |
|||
<group> |
|||
<group name="margin"> |
|||
<field name="margin_x"/> |
|||
<field name="margin_y"/> |
|||
</group> |
|||
<group name="dimension"> |
|||
<field name="widget_dimension_x"/> |
|||
<field name="widget_dimension_y"/> |
|||
<field name="number_of_columns"/> |
|||
<field name="width"/> |
|||
</group> |
|||
<group name="color"> |
|||
<field name="background_color" widget="color"/> |
|||
</group> |
|||
</group> |
|||
</page> |
|||
<page name="group" string="Groups"> |
|||
<field name="group_ids"/> |
|||
</page> |
|||
</notebook> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.ui.view" id="kpi_dashboard_search_view"> |
|||
<field name="name">kpi.dashboard.search (in kpi_dashboard)</field> |
|||
<field name="model">kpi.dashboard</field> |
|||
<field name="arch" type="xml"> |
|||
<search> |
|||
<field name="name"/> |
|||
</search> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.ui.view" id="kpi_dashboard_tree_view"> |
|||
<field name="name">kpi.dashboard.tree (in kpi_dashboard)</field> |
|||
<field name="model">kpi.dashboard</field> |
|||
<field name="arch" type="xml"> |
|||
<tree> |
|||
<field name="name"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.ui.view" id="kpi_dashboard_dashboard_view"> |
|||
<field name="name">kpi.dashboard.dashboard (in kpi_dashboard)</field> |
|||
<field name="model">kpi.dashboard</field> |
|||
<field name="arch" type="xml"> |
|||
<dashboard/> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="kpi_dashboard_act_window"> |
|||
<field name="name">Kpi Dashboard</field> <!-- TODO --> |
|||
<field name="res_model">kpi.dashboard</field> |
|||
<field name="view_mode">tree,form,dashboard</field> |
|||
<field name="domain">[]</field> |
|||
<field name="context">{}</field> |
|||
</record> |
|||
|
|||
<record model="ir.ui.menu" id="kpi_dashboard_menu"> |
|||
<field name="name">Configure Dashboard</field> |
|||
<field name="parent_id" ref="menu_configuration_kpi_dashboards"/> <!-- TODO --> |
|||
<field name="action" ref="kpi_dashboard_act_window"/> |
|||
<field name="sequence" eval="16"/> <!-- TODO --> |
|||
</record> |
|||
|
|||
</odoo> |
@ -0,0 +1,89 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- Copyright 2020 Creu Blanca |
|||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
|||
|
|||
<odoo> |
|||
|
|||
<record model="ir.ui.view" id="kpi_kpi_form_view"> |
|||
<field name="name">kpi.kpi.form (in kpi_dashboard)</field> |
|||
<field name="model">kpi.kpi</field> |
|||
<field name="arch" type="xml"> |
|||
<form> |
|||
<header> |
|||
<button name="generate_cron" string="Generate cron" type="object" |
|||
attrs="{'invisible': [('cron_id', '!=',False)]}"/> |
|||
<button name="compute" string="Compute now" type="object"/> |
|||
</header> |
|||
<sheet> |
|||
<div class="oe_button_box" name="button_box"/> |
|||
<h2> |
|||
<field name="name"/> |
|||
</h2> |
|||
<group> |
|||
<group> |
|||
<field name="computation_method"/> |
|||
<field name="widget"/> |
|||
<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> |
|||
<field name="cron_id" attrs="{'invisible': [('cron_id', '=',False)]}" readonly="True"/> |
|||
</group> |
|||
<group> |
|||
<field name="suffix"/> |
|||
<field name="prefix"/> |
|||
</group> |
|||
</group> |
|||
|
|||
<notebook> |
|||
<page name="action" string="Actions"> |
|||
<field name="action_ids"> |
|||
<tree editable="bottom"> |
|||
<field name="action"/> |
|||
</tree> |
|||
</field> |
|||
</page> |
|||
</notebook> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.ui.view" id="kpi_kpi_search_view"> |
|||
<field name="name">kpi.kpi.search (in kpi_dashboard)</field> |
|||
<field name="model">kpi.kpi</field> |
|||
<field name="arch" type="xml"> |
|||
<search> |
|||
<field name="name"/> |
|||
</search> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.ui.view" id="kpi_kpi_tree_view"> |
|||
<field name="name">kpi.kpi.tree (in kpi_dashboard)</field> |
|||
<field name="model">kpi.kpi</field> |
|||
<field name="arch" type="xml"> |
|||
<tree> |
|||
<field name="name"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="kpi_kpi_act_window"> |
|||
<field name="name">Kpi</field> |
|||
<field name="res_model">kpi.kpi</field> |
|||
<field name="view_mode">tree,form</field> |
|||
<field name="domain">[]</field> |
|||
<field name="context">{}</field> |
|||
</record> |
|||
|
|||
<record model="ir.ui.menu" id="kpi_kpi_menu"> |
|||
<field name="name">Configure Kpi</field> |
|||
<field name="parent_id" ref="menu_configuration_kpi_dashboards"/> |
|||
<field name="action" ref="kpi_kpi_act_window"/> |
|||
<field name="sequence" eval="20"/> |
|||
</record> |
|||
|
|||
</odoo> |
@ -0,0 +1,12 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- Copyright 2020 Creu Blanca |
|||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
|||
|
|||
<odoo> |
|||
<!-- CONFIGURATION --> |
|||
<menuitem id="menu_configuration_kpi_dashboards" |
|||
name="KPI Dashboards" |
|||
parent="base.menu_reporting_config" |
|||
groups="kpi_dashboard.group_kpi_dashboard_manager" |
|||
sequence="10"/> |
|||
</odoo> |
@ -0,0 +1,25 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
|
|||
<template id="assets_backend" |
|||
name="Backend Assets (used in backend interface)" |
|||
inherit_id="web.assets_backend"> |
|||
<xpath expr="." position="inside"> |
|||
|
|||
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget_registry.js"/> |
|||
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget/abstract_widget.js"/> |
|||
|
|||
<script type="text/javascript" src="/kpi_dashboard/static/src/js/dashboard_renderer.js"/> |
|||
<script type="text/javascript" src="/kpi_dashboard/static/src/js/dashboard_model.js"/> |
|||
<script 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/number_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"/> |
|||
|
|||
<link rel="stylesheet" type="text/scss" href="/kpi_dashboard/static/src/scss/kpi_dashboard.scss"/> |
|||
</xpath> |
|||
</template> |
|||
</odoo> |
@ -0,0 +1 @@ |
|||
from . import kpi_dashboard_menu |
@ -0,0 +1,17 @@ |
|||
# Copyright 2020 Creu Blanca |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from odoo import api, fields, models |
|||
|
|||
|
|||
class KpiDashboardMenu(models.TransientModel): |
|||
|
|||
_name = "kpi.dashboard.menu" |
|||
_description = "Create a Menu for a Dashboard" |
|||
|
|||
dashboard_id = fields.Many2one("kpi.dashboard", required=True) |
|||
menu_id = fields.Many2one("ir.ui.menu") |
|||
|
|||
@api.multi |
|||
def generate_menu(self): |
|||
self.dashboard_id._generate_menu(self.menu_id) |
@ -0,0 +1,38 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- Copyright 2020 Creu Blanca |
|||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
|||
|
|||
<odoo> |
|||
|
|||
<record model="ir.ui.view" id="kpi_dashboard_menu_form_view"> |
|||
<field name="name">kpi.dashboard.menu.form (in kpi_dashboard)</field> |
|||
<field name="model">kpi.dashboard.menu</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="Generate Menu"> |
|||
<group> |
|||
<field name="dashboard_id" invisible="1"/> |
|||
<field name="menu_id"/> |
|||
</group> |
|||
<footer> |
|||
<button name="generate_menu" |
|||
string="Generate" |
|||
class="btn-primary" |
|||
type="object"/> |
|||
<button string="Cancel" |
|||
class="btn-default" |
|||
special="cancel"/> |
|||
</footer> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record model="ir.actions.act_window" id="kpi_dashboard_menu_act_window"> |
|||
<field name="name">Kpi Dashboard Menu</field> |
|||
<field name="res_model">kpi.dashboard.menu</field> |
|||
<field name="view_mode">form</field> |
|||
<field name="context">{}</field> |
|||
<field name="target">new</field> |
|||
</record> |
|||
|
|||
|
|||
</odoo> |
@ -0,0 +1,73 @@ |
|||
=================== |
|||
Kpi Dashboard: Test |
|||
=================== |
|||
|
|||
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
|||
!! This file is generated by oca-gen-addon-readme !! |
|||
!! changes will be overwritten. !! |
|||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
|||
|
|||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png |
|||
:target: https://odoo-community.org/page/development-status |
|||
:alt: Beta |
|||
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png |
|||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Freporting--engine-lightgray.png?logo=github |
|||
:target: https://github.com/OCA/reporting-engine/tree/12.0/kpi_dashboard_test |
|||
:alt: OCA/reporting-engine |
|||
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png |
|||
:target: https://translation.odoo-community.org/projects/reporting-engine-12-0/reporting-engine-12-0-kpi_dashboard_test |
|||
:alt: Translate me on Weblate |
|||
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png |
|||
:target: https://runbot.odoo-community.org/runbot/143/12.0 |
|||
:alt: Try me on Runbot |
|||
|
|||
|badge1| |badge2| |badge3| |badge4| |badge5| |
|||
|
|||
This module is used in order to test KPI Dashboard |
|||
|
|||
**Table of contents** |
|||
|
|||
.. contents:: |
|||
:local: |
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/reporting-engine/issues>`_. |
|||
In case of trouble, please check there if your issue has already been reported. |
|||
If you spotted it first, help us smashing it by providing a detailed and welcomed |
|||
`feedback <https://github.com/OCA/reporting-engine/issues/new?body=module:%20kpi_dashboard_test%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
|||
|
|||
Do not contact contributors directly about support or help with technical issues. |
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Authors |
|||
~~~~~~~ |
|||
|
|||
* Creu Blanca |
|||
|
|||
Contributors |
|||
~~~~~~~~~~~~ |
|||
|
|||
* Enric Tobella <etobella@creublanca.es> |
|||
|
|||
Maintainers |
|||
~~~~~~~~~~~ |
|||
|
|||
This module is maintained by the OCA. |
|||
|
|||
.. image:: https://odoo-community.org/logo.png |
|||
:alt: Odoo Community Association |
|||
:target: https://odoo-community.org |
|||
|
|||
OCA, or the Odoo Community Association, is a nonprofit organization whose |
|||
mission is to support the collaborative development of Odoo features and |
|||
promote its widespread use. |
|||
|
|||
This module is part of the `OCA/reporting-engine <https://github.com/OCA/reporting-engine/tree/12.0/kpi_dashboard_test>`_ project on GitHub. |
|||
|
|||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. |
@ -0,0 +1 @@ |
|||
from . import models |
@ -0,0 +1,14 @@ |
|||
# Copyright 2020 Creu Blanca |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
{ |
|||
"name": "Kpi Dashboard: Test", |
|||
"summary": """ |
|||
Testing KPI dashboard""", |
|||
"version": "12.0.1.0.0", |
|||
"license": "AGPL-3", |
|||
"author": "Creu Blanca,Odoo Community Association (OCA)", |
|||
"website": "https://github.com/reporting-engine", |
|||
"depends": ["kpi_dashboard"], |
|||
"demo": ["demo/demo_dashboard.xml"], |
|||
} |
@ -0,0 +1,126 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<odoo> |
|||
<record id="demo_dashboard" model="kpi.dashboard"> |
|||
<field name="name">Dashboard</field> |
|||
<field name="number_of_columns">4</field> |
|||
<field name="widget_dimension_y">50</field> |
|||
<field name="widget_dimension_x">250</field> |
|||
<field name="background_color">#020202</field> |
|||
</record> |
|||
|
|||
<record id="widget_number_01" model="kpi.kpi"> |
|||
<field name="name">Number 01</field> |
|||
<field name="prefix">$</field> |
|||
<field name="computation_method">function</field> |
|||
<field name="widget">number</field> |
|||
<field name="function">test_demo_number</field> |
|||
</record> |
|||
|
|||
<record id="widget_number_02" model="kpi.kpi"> |
|||
<field name="name">Number 02</field> |
|||
<field name="suffix">€</field> |
|||
<field name="computation_method">function</field> |
|||
<field name="widget">number</field> |
|||
<field name="function">test_demo_number</field> |
|||
</record> |
|||
|
|||
<function model="kpi.kpi" name="compute" |
|||
eval="[[ref('widget_number_01'), ref('widget_number_02')]]"/> |
|||
|
|||
<record id="widget_meter_01" model="kpi.kpi"> |
|||
<field name="name">Meter 01</field> |
|||
<field name="suffix">€</field> |
|||
<field name="computation_method">function</field> |
|||
<field name="widget">meter</field> |
|||
<field name="function">test_demo_meter</field> |
|||
</record> |
|||
|
|||
<record id="widget_meter_02" model="kpi.kpi"> |
|||
<field name="name">Meter 02</field> |
|||
<field name="prefix">$</field> |
|||
<field name="computation_method">function</field> |
|||
<field name="widget">meter</field> |
|||
<field name="function">test_demo_meter</field> |
|||
</record> |
|||
|
|||
<function model="kpi.kpi" name="compute" |
|||
eval="[[ref('widget_meter_01'), ref('widget_meter_02')]]"/> |
|||
|
|||
<record id="widget_graph" model="kpi.kpi"> |
|||
<field name="name">Graph</field> |
|||
<field name="computation_method">function</field> |
|||
<field name="widget">graph</field> |
|||
<field name="function">test_demo_graph</field> |
|||
</record> |
|||
|
|||
<function model="kpi.kpi" name="compute" |
|||
eval="[[ref('widget_graph')]]"/> |
|||
|
|||
|
|||
<record id="dashboard_widget_text" model="kpi.dashboard.item"> |
|||
<field name="name">Dashboard title</field> |
|||
<field name="dashboard_id" ref="demo_dashboard"/> |
|||
<field name="column">1</field> |
|||
<field name="row">1</field> |
|||
<field name="size_x">4</field> |
|||
<field name="color">#707070</field> |
|||
<field name="font_color">#000000</field> |
|||
</record> |
|||
|
|||
<record id="dashboard_widget_number_01" model="kpi.dashboard.item"> |
|||
<field name="name">Number 01</field> |
|||
<field name="dashboard_id" ref="demo_dashboard"/> |
|||
<field name="kpi_id" ref="widget_number_01"/> |
|||
<field name="column">1</field> |
|||
<field name="row">2</field> |
|||
<field name="size_y">4</field> |
|||
<field name="color">#47bbb3</field> |
|||
<field name="font_color">#ffffff</field> |
|||
</record> |
|||
|
|||
<record id="dashboard_widget_number_02" model="kpi.dashboard.item"> |
|||
<field name="name">Number 02</field> |
|||
<field name="dashboard_id" ref="demo_dashboard"/> |
|||
<field name="kpi_id" ref="widget_number_02"/> |
|||
<field name="column">1</field> |
|||
<field name="row">6</field> |
|||
<field name="size_y">4</field> |
|||
<field name="color">#ec663c</field> |
|||
<field name="font_color">#ffffff</field> |
|||
</record> |
|||
|
|||
<record id="dashboard_widget_meter_01" model="kpi.dashboard.item"> |
|||
<field name="name">Meter 01</field> |
|||
<field name="dashboard_id" ref="demo_dashboard"/> |
|||
<field name="kpi_id" ref="widget_meter_01"/> |
|||
<field name="column">2</field> |
|||
<field name="row">2</field> |
|||
<field name="size_y">4</field> |
|||
<field name="color">#9c4274</field> |
|||
<field name="font_color">#ffffff</field> |
|||
</record> |
|||
|
|||
<record id="dashboard_widget_meter_02" model="kpi.dashboard.item"> |
|||
<field name="name">Meter 02</field> |
|||
<field name="dashboard_id" ref="demo_dashboard"/> |
|||
<field name="kpi_id" ref="widget_meter_02"/> |
|||
<field name="column">2</field> |
|||
<field name="row">6</field> |
|||
<field name="size_y">4</field> |
|||
<field name="color">#12b0c5</field> |
|||
<field name="font_color">#ffffff</field> |
|||
</record> |
|||
|
|||
<record id="dashboard_widget_graph" model="kpi.dashboard.item"> |
|||
<field name="name">Graph</field> |
|||
<field name="dashboard_id" ref="demo_dashboard"/> |
|||
<field name="kpi_id" ref="widget_graph"/> |
|||
<field name="column">3</field> |
|||
<field name="row">2</field> |
|||
<field name="size_x">2</field> |
|||
<field name="size_y">8</field> |
|||
<field name="color">#ff9618</field> |
|||
<field name="font_color">#ffffff</field> |
|||
</record> |
|||
|
|||
</odoo> |
@ -0,0 +1 @@ |
|||
from . import kpi_kpi |
@ -0,0 +1,48 @@ |
|||
# Copyright 2020 Creu Blanca |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from odoo import models |
|||
import random |
|||
|
|||
|
|||
class KpiKpi(models.Model): |
|||
_inherit = "kpi.kpi" |
|||
|
|||
def test_demo_number(self): |
|||
return { |
|||
"value": random.random() * 10000, |
|||
"previous": random.random() * 10000, |
|||
} |
|||
|
|||
def test_demo_meter(self): |
|||
return { |
|||
"min": 0, |
|||
"max": 100, |
|||
"value": random.random() * 100, |
|||
} |
|||
|
|||
def test_demo_graph(self): |
|||
return { |
|||
"graphs": [ |
|||
{ |
|||
"values": [ |
|||
{"x": i, "y": random.random() * 1000} |
|||
for i in range(1, 12) |
|||
], |
|||
"title": "Current Year", |
|||
"key": "current", |
|||
"area": True, |
|||
"color": "ffffff", |
|||
}, |
|||
{ |
|||
"values": [ |
|||
{"x": i, "y": random.random() * 1000} |
|||
for i in range(1, 12) |
|||
], |
|||
"title": "Previous Year", |
|||
"key": "previous", |
|||
"area": True, |
|||
"color": "000000", |
|||
}, |
|||
] |
|||
} |
@ -0,0 +1 @@ |
|||
* Enric Tobella <etobella@creublanca.es> |
@ -0,0 +1 @@ |
|||
This module is used in order to test KPI Dashboard |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,419 @@ |
|||
<?xml version="1.0" encoding="utf-8" ?> |
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
|||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
|||
<head> |
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|||
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" /> |
|||
<title>Kpi Dashboard: Test</title> |
|||
<style type="text/css"> |
|||
|
|||
/* |
|||
:Author: David Goodger (goodger@python.org) |
|||
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $ |
|||
:Copyright: This stylesheet has been placed in the public domain. |
|||
|
|||
Default cascading style sheet for the HTML output of Docutils. |
|||
|
|||
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to |
|||
customize this style sheet. |
|||
*/ |
|||
|
|||
/* used to remove borders from tables and images */ |
|||
.borderless, table.borderless td, table.borderless th { |
|||
border: 0 } |
|||
|
|||
table.borderless td, table.borderless th { |
|||
/* Override padding for "table.docutils td" with "! important". |
|||
The right padding separates the table cells. */ |
|||
padding: 0 0.5em 0 0 ! important } |
|||
|
|||
.first { |
|||
/* Override more specific margin styles with "! important". */ |
|||
margin-top: 0 ! important } |
|||
|
|||
.last, .with-subtitle { |
|||
margin-bottom: 0 ! important } |
|||
|
|||
.hidden { |
|||
display: none } |
|||
|
|||
.subscript { |
|||
vertical-align: sub; |
|||
font-size: smaller } |
|||
|
|||
.superscript { |
|||
vertical-align: super; |
|||
font-size: smaller } |
|||
|
|||
a.toc-backref { |
|||
text-decoration: none ; |
|||
color: black } |
|||
|
|||
blockquote.epigraph { |
|||
margin: 2em 5em ; } |
|||
|
|||
dl.docutils dd { |
|||
margin-bottom: 0.5em } |
|||
|
|||
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { |
|||
overflow: hidden; |
|||
} |
|||
|
|||
/* Uncomment (and remove this text!) to get bold-faced definition list terms |
|||
dl.docutils dt { |
|||
font-weight: bold } |
|||
*/ |
|||
|
|||
div.abstract { |
|||
margin: 2em 5em } |
|||
|
|||
div.abstract p.topic-title { |
|||
font-weight: bold ; |
|||
text-align: center } |
|||
|
|||
div.admonition, div.attention, div.caution, div.danger, div.error, |
|||
div.hint, div.important, div.note, div.tip, div.warning { |
|||
margin: 2em ; |
|||
border: medium outset ; |
|||
padding: 1em } |
|||
|
|||
div.admonition p.admonition-title, div.hint p.admonition-title, |
|||
div.important p.admonition-title, div.note p.admonition-title, |
|||
div.tip p.admonition-title { |
|||
font-weight: bold ; |
|||
font-family: sans-serif } |
|||
|
|||
div.attention p.admonition-title, div.caution p.admonition-title, |
|||
div.danger p.admonition-title, div.error p.admonition-title, |
|||
div.warning p.admonition-title, .code .error { |
|||
color: red ; |
|||
font-weight: bold ; |
|||
font-family: sans-serif } |
|||
|
|||
/* Uncomment (and remove this text!) to get reduced vertical space in |
|||
compound paragraphs. |
|||
div.compound .compound-first, div.compound .compound-middle { |
|||
margin-bottom: 0.5em } |
|||
|
|||
div.compound .compound-last, div.compound .compound-middle { |
|||
margin-top: 0.5em } |
|||
*/ |
|||
|
|||
div.dedication { |
|||
margin: 2em 5em ; |
|||
text-align: center ; |
|||
font-style: italic } |
|||
|
|||
div.dedication p.topic-title { |
|||
font-weight: bold ; |
|||
font-style: normal } |
|||
|
|||
div.figure { |
|||
margin-left: 2em ; |
|||
margin-right: 2em } |
|||
|
|||
div.footer, div.header { |
|||
clear: both; |
|||
font-size: smaller } |
|||
|
|||
div.line-block { |
|||
display: block ; |
|||
margin-top: 1em ; |
|||
margin-bottom: 1em } |
|||
|
|||
div.line-block div.line-block { |
|||
margin-top: 0 ; |
|||
margin-bottom: 0 ; |
|||
margin-left: 1.5em } |
|||
|
|||
div.sidebar { |
|||
margin: 0 0 0.5em 1em ; |
|||
border: medium outset ; |
|||
padding: 1em ; |
|||
background-color: #ffffee ; |
|||
width: 40% ; |
|||
float: right ; |
|||
clear: right } |
|||
|
|||
div.sidebar p.rubric { |
|||
font-family: sans-serif ; |
|||
font-size: medium } |
|||
|
|||
div.system-messages { |
|||
margin: 5em } |
|||
|
|||
div.system-messages h1 { |
|||
color: red } |
|||
|
|||
div.system-message { |
|||
border: medium outset ; |
|||
padding: 1em } |
|||
|
|||
div.system-message p.system-message-title { |
|||
color: red ; |
|||
font-weight: bold } |
|||
|
|||
div.topic { |
|||
margin: 2em } |
|||
|
|||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, |
|||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { |
|||
margin-top: 0.4em } |
|||
|
|||
h1.title { |
|||
text-align: center } |
|||
|
|||
h2.subtitle { |
|||
text-align: center } |
|||
|
|||
hr.docutils { |
|||
width: 75% } |
|||
|
|||
img.align-left, .figure.align-left, object.align-left, table.align-left { |
|||
clear: left ; |
|||
float: left ; |
|||
margin-right: 1em } |
|||
|
|||
img.align-right, .figure.align-right, object.align-right, table.align-right { |
|||
clear: right ; |
|||
float: right ; |
|||
margin-left: 1em } |
|||
|
|||
img.align-center, .figure.align-center, object.align-center { |
|||
display: block; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
} |
|||
|
|||
table.align-center { |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
} |
|||
|
|||
.align-left { |
|||
text-align: left } |
|||
|
|||
.align-center { |
|||
clear: both ; |
|||
text-align: center } |
|||
|
|||
.align-right { |
|||
text-align: right } |
|||
|
|||
/* reset inner alignment in figures */ |
|||
div.align-right { |
|||
text-align: inherit } |
|||
|
|||
/* div.align-center * { */ |
|||
/* text-align: left } */ |
|||
|
|||
.align-top { |
|||
vertical-align: top } |
|||
|
|||
.align-middle { |
|||
vertical-align: middle } |
|||
|
|||
.align-bottom { |
|||
vertical-align: bottom } |
|||
|
|||
ol.simple, ul.simple { |
|||
margin-bottom: 1em } |
|||
|
|||
ol.arabic { |
|||
list-style: decimal } |
|||
|
|||
ol.loweralpha { |
|||
list-style: lower-alpha } |
|||
|
|||
ol.upperalpha { |
|||
list-style: upper-alpha } |
|||
|
|||
ol.lowerroman { |
|||
list-style: lower-roman } |
|||
|
|||
ol.upperroman { |
|||
list-style: upper-roman } |
|||
|
|||
p.attribution { |
|||
text-align: right ; |
|||
margin-left: 50% } |
|||
|
|||
p.caption { |
|||
font-style: italic } |
|||
|
|||
p.credits { |
|||
font-style: italic ; |
|||
font-size: smaller } |
|||
|
|||
p.label { |
|||
white-space: nowrap } |
|||
|
|||
p.rubric { |
|||
font-weight: bold ; |
|||
font-size: larger ; |
|||
color: maroon ; |
|||
text-align: center } |
|||
|
|||
p.sidebar-title { |
|||
font-family: sans-serif ; |
|||
font-weight: bold ; |
|||
font-size: larger } |
|||
|
|||
p.sidebar-subtitle { |
|||
font-family: sans-serif ; |
|||
font-weight: bold } |
|||
|
|||
p.topic-title { |
|||
font-weight: bold } |
|||
|
|||
pre.address { |
|||
margin-bottom: 0 ; |
|||
margin-top: 0 ; |
|||
font: inherit } |
|||
|
|||
pre.literal-block, pre.doctest-block, pre.math, pre.code { |
|||
margin-left: 2em ; |
|||
margin-right: 2em } |
|||
|
|||
pre.code .ln { color: grey; } /* line numbers */ |
|||
pre.code, code { background-color: #eeeeee } |
|||
pre.code .comment, code .comment { color: #5C6576 } |
|||
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } |
|||
pre.code .literal.string, code .literal.string { color: #0C5404 } |
|||
pre.code .name.builtin, code .name.builtin { color: #352B84 } |
|||
pre.code .deleted, code .deleted { background-color: #DEB0A1} |
|||
pre.code .inserted, code .inserted { background-color: #A3D289} |
|||
|
|||
span.classifier { |
|||
font-family: sans-serif ; |
|||
font-style: oblique } |
|||
|
|||
span.classifier-delimiter { |
|||
font-family: sans-serif ; |
|||
font-weight: bold } |
|||
|
|||
span.interpreted { |
|||
font-family: sans-serif } |
|||
|
|||
span.option { |
|||
white-space: nowrap } |
|||
|
|||
span.pre { |
|||
white-space: pre } |
|||
|
|||
span.problematic { |
|||
color: red } |
|||
|
|||
span.section-subtitle { |
|||
/* font-size relative to parent (h1..h6 element) */ |
|||
font-size: 80% } |
|||
|
|||
table.citation { |
|||
border-left: solid 1px gray; |
|||
margin-left: 1px } |
|||
|
|||
table.docinfo { |
|||
margin: 2em 4em } |
|||
|
|||
table.docutils { |
|||
margin-top: 0.5em ; |
|||
margin-bottom: 0.5em } |
|||
|
|||
table.footnote { |
|||
border-left: solid 1px black; |
|||
margin-left: 1px } |
|||
|
|||
table.docutils td, table.docutils th, |
|||
table.docinfo td, table.docinfo th { |
|||
padding-left: 0.5em ; |
|||
padding-right: 0.5em ; |
|||
vertical-align: top } |
|||
|
|||
table.docutils th.field-name, table.docinfo th.docinfo-name { |
|||
font-weight: bold ; |
|||
text-align: left ; |
|||
white-space: nowrap ; |
|||
padding-left: 0 } |
|||
|
|||
/* "booktabs" style (no vertical lines) */ |
|||
table.docutils.booktabs { |
|||
border: 0px; |
|||
border-top: 2px solid; |
|||
border-bottom: 2px solid; |
|||
border-collapse: collapse; |
|||
} |
|||
table.docutils.booktabs * { |
|||
border: 0px; |
|||
} |
|||
table.docutils.booktabs th { |
|||
border-bottom: thin solid; |
|||
text-align: left; |
|||
} |
|||
|
|||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, |
|||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { |
|||
font-size: 100% } |
|||
|
|||
ul.auto-toc { |
|||
list-style-type: none } |
|||
|
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div class="document" id="kpi-dashboard-test"> |
|||
<h1 class="title">Kpi Dashboard: Test</h1> |
|||
|
|||
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
|||
!! This file is generated by oca-gen-addon-readme !! |
|||
!! changes will be overwritten. !! |
|||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> |
|||
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/reporting-engine/tree/12.0/kpi_dashboard_test"><img alt="OCA/reporting-engine" src="https://img.shields.io/badge/github-OCA%2Freporting--engine-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/reporting-engine-12-0/reporting-engine-12-0-kpi_dashboard_test"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/143/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p> |
|||
<p>This module is used in order to test KPI Dashboard</p> |
|||
<p><strong>Table of contents</strong></p> |
|||
<div class="contents local topic" id="contents"> |
|||
<ul class="simple"> |
|||
<li><a class="reference internal" href="#bug-tracker" id="id1">Bug Tracker</a></li> |
|||
<li><a class="reference internal" href="#credits" id="id2">Credits</a><ul> |
|||
<li><a class="reference internal" href="#authors" id="id3">Authors</a></li> |
|||
<li><a class="reference internal" href="#contributors" id="id4">Contributors</a></li> |
|||
<li><a class="reference internal" href="#maintainers" id="id5">Maintainers</a></li> |
|||
</ul> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
<div class="section" id="bug-tracker"> |
|||
<h1><a class="toc-backref" href="#id1">Bug Tracker</a></h1> |
|||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/reporting-engine/issues">GitHub Issues</a>. |
|||
In case of trouble, please check there if your issue has already been reported. |
|||
If you spotted it first, help us smashing it by providing a detailed and welcomed |
|||
<a class="reference external" href="https://github.com/OCA/reporting-engine/issues/new?body=module:%20kpi_dashboard_test%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p> |
|||
<p>Do not contact contributors directly about support or help with technical issues.</p> |
|||
</div> |
|||
<div class="section" id="credits"> |
|||
<h1><a class="toc-backref" href="#id2">Credits</a></h1> |
|||
<div class="section" id="authors"> |
|||
<h2><a class="toc-backref" href="#id3">Authors</a></h2> |
|||
<ul class="simple"> |
|||
<li>Creu Blanca</li> |
|||
</ul> |
|||
</div> |
|||
<div class="section" id="contributors"> |
|||
<h2><a class="toc-backref" href="#id4">Contributors</a></h2> |
|||
<ul class="simple"> |
|||
<li>Enric Tobella <<a class="reference external" href="mailto:etobella@creublanca.es">etobella@creublanca.es</a>></li> |
|||
</ul> |
|||
</div> |
|||
<div class="section" id="maintainers"> |
|||
<h2><a class="toc-backref" href="#id5">Maintainers</a></h2> |
|||
<p>This module is maintained by the OCA.</p> |
|||
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a> |
|||
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose |
|||
mission is to support the collaborative development of Odoo features and |
|||
promote its widespread use.</p> |
|||
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/reporting-engine/tree/12.0/kpi_dashboard_test">OCA/reporting-engine</a> project on GitHub.</p> |
|||
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
</html> |
@ -0,0 +1 @@ |
|||
from . import test_kpi_dashboard |
@ -0,0 +1,123 @@ |
|||
# Copyright 2020 Creu Blanca |
|||
# 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 |
|||
|
|||
|
|||
class TestKpiDashboard(TransactionCase): |
|||
|
|||
def setUp(self): |
|||
super(TestKpiDashboard, self).setUp() |
|||
self.kpi_01 = self.env['kpi.kpi'].create({ |
|||
'name': 'KPI 01', |
|||
'computation_method': 'function', |
|||
'widget': 'number', |
|||
'function': 'test_demo_number' |
|||
}) |
|||
self.kpi_02 = self.env['kpi.kpi'].create({ |
|||
'name': 'KPI 02', |
|||
'computation_method': 'function', |
|||
'widget': 'number', |
|||
'function': 'test_demo_number' |
|||
}) |
|||
self.dashboard = self.env['kpi.dashboard'].create({ |
|||
'name': 'Dashboard', |
|||
'number_of_columns': 4, |
|||
'widget_dimension_x': 250, |
|||
'widget_dimension_y': 250, |
|||
}) |
|||
self.env['kpi.dashboard.item'].create({ |
|||
'dashboard_id': self.dashboard.id, |
|||
'kpi_id': self.kpi_01.id, |
|||
'name': self.kpi_01.name, |
|||
'row': 1, |
|||
'column': 1, |
|||
}) |
|||
self.env['kpi.dashboard.item'].create({ |
|||
'dashboard_id': self.dashboard.id, |
|||
'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): |
|||
with self.assertRaises(ValidationError): |
|||
self.kpi_01.dashboard_item_ids.write({'size_x': 2}) |
|||
|
|||
def test_constrains_02(self): |
|||
with self.assertRaises(ValidationError): |
|||
self.kpi_02.dashboard_item_ids.write({'size_x': 4}) |
|||
|
|||
def test_constrains_03(self): |
|||
with self.assertRaises(ValidationError): |
|||
self.kpi_01.dashboard_item_ids.write({'size_y': 11}) |
|||
|
|||
def test_menu(self): |
|||
self.assertFalse(self.dashboard.menu_id) |
|||
wzd = self.env['kpi.dashboard.menu'].create({ |
|||
'dashboard_id': self.dashboard.id, |
|||
'menu_id': self.env['ir.ui.menu'].search([], limit=1).id, |
|||
}) |
|||
wzd.generate_menu() |
|||
self.assertTrue(self.dashboard.menu_id) |
|||
self.assertFalse(self.dashboard.menu_id.groups_id) |
|||
self.dashboard.write({ |
|||
'group_ids': [ |
|||
(6, 0, self.env['res.groups'].search([], limit=1).ids)] |
|||
}) |
|||
self.assertTrue(self.dashboard.menu_id.groups_id) |
|||
|
|||
def test_onchange(self): |
|||
with Form(self.env['kpi.dashboard']) as dashboard: |
|||
dashboard.name = 'New Dashboard' |
|||
with dashboard.item_ids.new() as item: |
|||
item.kpi_id = self.kpi_01 |
|||
self.assertTrue(item.name) |
|||
|
|||
def test_read_dashboard(self): |
|||
data = self.dashboard.read_dashboard() |
|||
title_found = False |
|||
actions = 0 |
|||
for item in data['item_ids']: |
|||
if not item.get('kpi_id'): |
|||
title_found = True |
|||
if item.get('actions', False): |
|||
actions += len(item['actions']) |
|||
self.assertTrue(title_found) |
|||
self.assertEqual(0, actions) |
|||
act01 = self.env['ir.actions.act_window'].search( |
|||
[], limit=1) |
|||
self.env['kpi.kpi.action'].create({ |
|||
'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({ |
|||
'kpi_id': self.kpi_01.id, |
|||
'action': '%s,%s' % (act02._name, act02.id) |
|||
}) |
|||
data = self.dashboard.read_dashboard() |
|||
title_found = False |
|||
actions = 0 |
|||
for item in data['item_ids']: |
|||
if not item.get('kpi_id'): |
|||
title_found = True |
|||
if item.get('actions', False): |
|||
actions += len(item['actions']) |
|||
self.assertTrue(title_found) |
|||
self.assertEqual(2, actions) |
|||
|
|||
def test_compute(self): |
|||
self.assertFalse(self.kpi_01.value_last_update) |
|||
self.kpi_01.compute() |
|||
self.assertTrue(self.kpi_01.value_last_update) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue