commit
05d37e24fb
|
@ -0,0 +1,118 @@
|
|||
=============
|
||||
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`
|
||||
|
||||
#. In order to compute the KPI you can use a predefined function from a model or
|
||||
use the code to directly compute it.
|
||||
|
||||
Using KPI with code
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Define the code directly on the code field. You can use `self` and `model` as the kpi element
|
||||
The script should create a variable called `result` as a dictionary that
|
||||
will be stored as the value.
|
||||
For example, we can use::
|
||||
|
||||
result = {}
|
||||
result['value'] = len(model.search([('id', '=', %s)]))
|
||||
result['previous'] = len(model.search([('id', '!=', %s)]))
|
||||
|
||||
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.
|
||||
|
||||
.. |maintainer-etobella| image:: https://github.com/etobella.png?size=40px
|
||||
:target: https://github.com/etobella
|
||||
:alt: etobella
|
||||
|
||||
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|
||||
|
||||
|maintainer-etobella|
|
||||
|
||||
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,25 @@
|
|||
# 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": "13.0.1.0.0",
|
||||
"license": "AGPL-3",
|
||||
"author": "Creu Blanca,Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/reporting-engine",
|
||||
"depends": ["bus", "board", "base_sparse_field"],
|
||||
"qweb": ["static/src/xml/dashboard.xml"],
|
||||
"data": [
|
||||
"wizards/kpi_dashboard_menu.xml",
|
||||
"security/security.xml",
|
||||
"security/ir.model.access.csv",
|
||||
"templates/assets.xml",
|
||||
"views/kpi_menu.xml",
|
||||
"views/kpi_kpi.xml",
|
||||
"views/kpi_dashboard.xml",
|
||||
],
|
||||
"demo": ["demo/demo_dashboard.xml"],
|
||||
"maintainers": ["etobella"],
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
<?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>
|
||||
<field name="compute_on_fly_refresh">30</field>
|
||||
</record>
|
||||
<record id="widget_number_01" model="kpi.kpi">
|
||||
<field name="name">Number 01</field>
|
||||
<field name="prefix">$</field>
|
||||
<field name="computation_method">code</field>
|
||||
<field name="widget">number</field>
|
||||
<field name="code">
|
||||
result = {"value": 10000,"previous": 12000}
|
||||
</field>
|
||||
</record>
|
||||
<record id="widget_number_02" model="kpi.kpi">
|
||||
<field name="name">Number 02</field>
|
||||
<field name="suffix">€</field>
|
||||
<field name="computation_method">code</field>
|
||||
<field name="widget">number</field>
|
||||
<field name="code">
|
||||
result = {"value": 12000,"previous": 10000}
|
||||
</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">code</field>
|
||||
<field name="widget">meter</field>
|
||||
<field name="code">
|
||||
result = {"min": 0, "max": 100, "value": 90}
|
||||
</field>
|
||||
</record>
|
||||
<record id="widget_meter_02" model="kpi.kpi">
|
||||
<field name="name">Meter 02</field>
|
||||
<field name="prefix">$</field>
|
||||
<field name="computation_method">code</field>
|
||||
<field name="widget">meter</field>
|
||||
<field name="code">
|
||||
result = {"min": 0, "max": 100, "value": 40}
|
||||
</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">code</field>
|
||||
<field name="widget">graph</field>
|
||||
<field name="code">
|
||||
result = {"graphs": [
|
||||
{
|
||||
"values": [
|
||||
{"x": i, "y": i * 1000}
|
||||
for i in range(1, 12)
|
||||
],
|
||||
"title": "Current Year",
|
||||
"key": "current",
|
||||
"area": True,
|
||||
"color": "ffffff",
|
||||
},
|
||||
{
|
||||
"values": [
|
||||
{"x": i, "y": 1000 * (12-i)}
|
||||
for i in range(1, 12)
|
||||
],
|
||||
"title": "Previous Year",
|
||||
"key": "previous",
|
||||
"area": True,
|
||||
"color": "000000",
|
||||
},
|
||||
]}
|
||||
</field>
|
||||
</record>
|
||||
<function model="kpi.kpi" name="compute" eval="[[ref('widget_graph')]]" />
|
||||
<record id="widget_integer" model="kpi.kpi">
|
||||
<field name="name">Integer counter</field>
|
||||
<field name="computation_method">code</field>
|
||||
<field name="widget">integer</field>
|
||||
<field name="compute_on_fly" eval="True" />
|
||||
<field name="code">
|
||||
result = {"value": self.env.context.get('counter', 990)}
|
||||
</field>
|
||||
</record>
|
||||
<record id="widget_counter" model="kpi.kpi">
|
||||
<field name="name">Counter</field>
|
||||
<field name="computation_method">code</field>
|
||||
<field name="widget">counter</field>
|
||||
<field name="compute_on_fly" eval="True" />
|
||||
<field name="code">
|
||||
result = {"value": self.env.context.get('counter', 990)}
|
||||
</field>
|
||||
</record>
|
||||
<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_add_counter" model="kpi.dashboard.item">
|
||||
<field name="name">+1 to Counter</field>
|
||||
<field name="dashboard_id" ref="demo_dashboard" />
|
||||
<field name="column">3</field>
|
||||
<field name="row">10</field>
|
||||
<field name="size_y">1</field>
|
||||
<field name="size_x">2</field>
|
||||
<field name="color">#B41F1F</field>
|
||||
<field name="font_color">#EEBF77</field>
|
||||
<field name="modify_context" eval="True" />
|
||||
<field
|
||||
name="modify_context_expression"
|
||||
>{'counter': (context.counter or 990) + 1}</field>
|
||||
<field name="modify_color" eval="True" />
|
||||
<field
|
||||
name="modify_color_expression"
|
||||
>check_if(((context.counter or 990) + 1) % 2, '#ff0000', '#00ff00')</field>
|
||||
</record>
|
||||
<record id="dashboard_widget_counter" model="kpi.dashboard.item">
|
||||
<field name="name">Counter</field>
|
||||
<field name="dashboard_id" ref="demo_dashboard" />
|
||||
<field name="kpi_id" ref="widget_counter" />
|
||||
<field name="column">3</field>
|
||||
<field name="row">11</field>
|
||||
<field name="size_y">3</field>
|
||||
<field name="color">#4B0082</field>
|
||||
<field name="font_color">#ffffff</field>
|
||||
</record>
|
||||
<record id="dashboard_widget_integer" model="kpi.dashboard.item">
|
||||
<field name="name">Integer</field>
|
||||
<field name="dashboard_id" ref="demo_dashboard" />
|
||||
<field name="kpi_id" ref="widget_integer" />
|
||||
<field name="column">4</field>
|
||||
<field name="row">11</field>
|
||||
<field name="size_y">3</field>
|
||||
<field name="color">#ffffff</field>
|
||||
<field name="font_color">#4B0082</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,724 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * kpi_dashboard
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 12.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: code:addons/kpi_dashboard/models/kpi_kpi.py:153
|
||||
#, python-format
|
||||
msgid " or "
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#. openerp-web
|
||||
#: code:addons/kpi_dashboard/static/src/js/dashboard_controller.js:72
|
||||
#, python-format
|
||||
msgid "'%s' added to dashboard"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__action_ids
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_action__action
|
||||
msgid "Action"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model,name:kpi_dashboard.model_ir_actions_act_window_view
|
||||
msgid "Action Window View"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_kpi_form_view
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,help:kpi_dashboard.field_kpi_kpi__action_ids
|
||||
msgid "Actions that can be opened from the KPI"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__active
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__active
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:ir.actions.act_window.view,view_mode:0
|
||||
#: selection:ir.ui.view,type:0
|
||||
msgid "Activity"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#. openerp-web
|
||||
#: code:addons/kpi_dashboard/static/src/xml/dashboard.xml:72
|
||||
#, python-format
|
||||
msgid "Add to Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:kpi.kpi,widget:0
|
||||
msgid "Altair"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__args
|
||||
msgid "Args"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__background_color
|
||||
msgid "Background Color"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:kpi.kpi,widget:0
|
||||
msgid "Bokeh"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:ir.actions.act_window.view,view_mode:0
|
||||
#: selection:ir.ui.view,type:0
|
||||
msgid "Calendar"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_dashboard_item_config_form_view
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_dashboard_menu_form_view
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__code
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_kpi_form_view
|
||||
#: selection:kpi.kpi,computation_method:0
|
||||
msgid "Code"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__color
|
||||
msgid "Color"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__column
|
||||
msgid "Column"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__computation_method
|
||||
msgid "Computation Method"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__compute_on_fly
|
||||
msgid "Compute On Fly"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__compute_on_fly_refresh
|
||||
msgid "Compute On Fly Refresh"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_kpi_form_view
|
||||
msgid "Compute now"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__computed_date
|
||||
msgid "Computed Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__computed_value
|
||||
msgid "Computed Value"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.ui.menu,name:kpi_dashboard.kpi_dashboard_menu
|
||||
msgid "Configure Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.ui.menu,name:kpi_dashboard.kpi_kpi_menu
|
||||
msgid "Configure Kpi"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_action__context
|
||||
msgid "Context"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#. openerp-web
|
||||
#: code:addons/kpi_dashboard/static/src/js/dashboard_controller.js:76
|
||||
#, python-format
|
||||
msgid "Could not add KPI dashboard to dashboard"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:kpi.kpi,widget:0
|
||||
msgid "Counter"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model,name:kpi_dashboard.model_kpi_dashboard_menu
|
||||
msgid "Create a Menu for a Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__create_uid
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__create_uid
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_menu__create_uid
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__create_uid
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_action__create_uid
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history__create_uid
|
||||
msgid "Created by"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__create_date
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__create_date
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_menu__create_date
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__create_date
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_action__create_date
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history__create_date
|
||||
msgid "Created on"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__cron_id
|
||||
msgid "Cron"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#. openerp-web
|
||||
#: code:addons/kpi_dashboard/static/src/js/dashboard_view.js:21
|
||||
#: selection:ir.actions.act_window.view,view_mode:0
|
||||
#: model:ir.model,name:kpi_dashboard.model_kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__dashboard_id
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_menu__dashboard_id
|
||||
#: selection:ir.ui.view,type:0
|
||||
#, python-format
|
||||
msgid "Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__dashboard_item_ids
|
||||
msgid "Dashboard Item"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model,name:kpi_dashboard.model_kpi_dashboard_item
|
||||
msgid "Dashboard Items"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:ir.ui.view,type:0
|
||||
msgid "Diagram"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__display_name
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__display_name
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_menu__display_name
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__display_name
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_action__display_name
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history__display_name
|
||||
msgid "Display Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__end_column
|
||||
msgid "End Column"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__end_row
|
||||
msgid "End Row"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_kpi_form_view
|
||||
msgid "Enter Python code here."
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#. openerp-web
|
||||
#: code:addons/kpi_dashboard/static/src/js/dashboard_controller.js:57
|
||||
#, python-format
|
||||
msgid "First you must create the Menu"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__font_color
|
||||
msgid "Font Color"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:ir.actions.act_window.view,view_mode:0
|
||||
#: selection:ir.ui.view,type:0
|
||||
msgid "Form"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__function
|
||||
#: selection:kpi.kpi,computation_method:0
|
||||
msgid "Function"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:ir.actions.act_window.view,view_mode:0
|
||||
#: selection:ir.ui.view,type:0
|
||||
msgid "Gantt"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_dashboard_menu_form_view
|
||||
msgid "Generate"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_dashboard_menu_form_view
|
||||
msgid "Generate Menu"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_kpi_form_view
|
||||
msgid "Generate cron"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_dashboard_form_view
|
||||
msgid "Generate menu"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#. openerp-web
|
||||
#: code:addons/kpi_dashboard/static/src/xml/dashboard.xml:25
|
||||
#, python-format
|
||||
msgid "Go to"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:ir.actions.act_window.view,view_mode:0
|
||||
#: selection:ir.ui.view,type:0
|
||||
#: selection:kpi.kpi,widget:0
|
||||
msgid "Graph"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__group_ids
|
||||
msgid "Group"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_dashboard_form_view
|
||||
msgid "Groups"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__history_ids
|
||||
msgid "History"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__id
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__id
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_menu__id
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__id
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_action__id
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history__id
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:kpi.kpi,widget:0
|
||||
msgid "Integer"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__item_ids
|
||||
msgid "Item"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.ui.menu,name:kpi_dashboard.menu_configuration_kpi_dashboards
|
||||
msgid "KPI Dashboards"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model,name:kpi_dashboard.model_kpi_kpi_action
|
||||
msgid "KPI action"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model,name:kpi_dashboard.model_kpi_kpi_history
|
||||
msgid "KPI history"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_dashboard_form_view
|
||||
msgid "KPIs"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:ir.actions.act_window.view,view_mode:0
|
||||
#: selection:ir.ui.view,type:0
|
||||
msgid "Kanban"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.actions.act_window,name:kpi_dashboard.kpi_kpi_act_window
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__kpi_id
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_action__kpi_id
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history__kpi_id
|
||||
msgid "Kpi"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.actions.act_window,name:kpi_dashboard.kpi_dashboard_act_window
|
||||
msgid "Kpi Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.actions.act_window,name:kpi_dashboard.kpi_dashboard_menu_act_window
|
||||
msgid "Kpi Dashboard Menu"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.actions.act_window,name:kpi_dashboard.kpi_kpi_history_act_window
|
||||
msgid "Kpi History"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model,name:kpi_dashboard.model_kpi_kpi
|
||||
msgid "Kpi Kpi"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__kwargs
|
||||
msgid "Kwargs"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard____last_update
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item____last_update
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_menu____last_update
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi____last_update
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_action____last_update
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__write_uid
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__write_uid
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_menu__write_uid
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__write_uid
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_action__write_uid
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__write_date
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__write_date
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_menu__write_date
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__write_date
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_action__write_date
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#. openerp-web
|
||||
#: code:addons/kpi_dashboard/static/src/xml/dashboard.xml:70
|
||||
#, python-format
|
||||
msgid "Main actions"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:res.groups,name:kpi_dashboard.group_kpi_dashboard_manager
|
||||
msgid "Manage KPI Dashboards"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__margin_x
|
||||
msgid "Margin X"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__margin_y
|
||||
msgid "Margin Y"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__menu_id
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_menu__menu_id
|
||||
msgid "Menu"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:kpi.kpi,widget:0
|
||||
msgid "Meter"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__model_id
|
||||
msgid "Model"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__modify_color
|
||||
msgid "Modify Color"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__modify_color_expression
|
||||
msgid "Modify Color Expression"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__modify_context
|
||||
msgid "Modify Context"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__modify_context_expression
|
||||
msgid "Modify Context Expression"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__name
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__name
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__name
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history__name
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:kpi.kpi,widget:0
|
||||
msgid "Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__number_of_columns
|
||||
msgid "Number Of Columns"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:ir.actions.act_window.view,view_mode:0
|
||||
#: selection:ir.ui.view,type:0
|
||||
msgid "Pivot"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#. openerp-web
|
||||
#: code:addons/kpi_dashboard/static/src/js/dashboard_controller.js:73
|
||||
#, python-format
|
||||
msgid "Please refresh your browser for the changes to take effect."
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__prefix
|
||||
msgid "Prefix"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:ir.ui.view,type:0
|
||||
msgid "QWeb"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history__raw_value
|
||||
msgid "Raw Value"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_kpi_history_tree_view
|
||||
msgid "Raw value"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__row
|
||||
msgid "Row"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_dashboard_item_config_form_view
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:ir.ui.view,type:0
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,help:kpi_dashboard.field_kpi_dashboard__compute_on_fly_refresh
|
||||
msgid "Seconds to refresh on fly elements"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#. openerp-web
|
||||
#: code:addons/kpi_dashboard/static/src/xml/dashboard.xml:34
|
||||
#, python-format
|
||||
msgid "Selection"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_kpi_form_view
|
||||
msgid "Show history"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_kpi_form_view
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_kpi_history_tree_view
|
||||
msgid "Show value"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__size_x
|
||||
msgid "Size X"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard_item__size_y
|
||||
msgid "Size Y"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: code:addons/kpi_dashboard/models/kpi_dashboard.py:139
|
||||
#, python-format
|
||||
msgid "Size Y of the widget cannot be bigger than 10"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__store_history
|
||||
msgid "Store History"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__store_history_interval
|
||||
msgid "Store History Interval"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__store_history_interval_number
|
||||
msgid "Store History Interval Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__suffix
|
||||
msgid "Suffix"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: code:addons/kpi_dashboard/models/kpi_kpi.py:155
|
||||
#, python-format
|
||||
msgid "The code cannot contain the following terms: %s."
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:ir.actions.act_window.view,view_mode:0
|
||||
#: selection:ir.ui.view,type:0
|
||||
msgid "Tree"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__value
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history__value
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__value_last_update
|
||||
msgid "Value Last Update"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model,name:kpi_dashboard.model_ir_ui_view
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_ir_actions_act_window_view__view_mode
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_ir_ui_view__type
|
||||
msgid "View Type"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi__widget
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_kpi_history__widget
|
||||
msgid "Widget"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: code:addons/kpi_dashboard/models/kpi_dashboard.py:160
|
||||
#, python-format
|
||||
msgid "Widget %s is bigger than expected"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__widget_dimension_x
|
||||
msgid "Widget Dimension X"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__widget_dimension_y
|
||||
msgid "Widget Dimension Y"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:kpi_dashboard.kpi_dashboard_form_view
|
||||
msgid "Widget configuration"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: code:addons/kpi_dashboard/models/kpi_dashboard.py:156
|
||||
#, python-format
|
||||
msgid "Widgets cannot be crossed by other widgets"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: model:ir.model.fields,field_description:kpi_dashboard.field_kpi_dashboard__width
|
||||
msgid "Width"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:kpi.kpi.action,action:0
|
||||
msgid "ir.actions.act_url"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:kpi.kpi.action,action:0
|
||||
msgid "ir.actions.act_window"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:kpi.kpi.action,action:0
|
||||
msgid "ir.actions.client"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:kpi.kpi.action,action:0
|
||||
msgid "ir.actions.report"
|
||||
msgstr ""
|
||||
|
||||
#. module: kpi_dashboard
|
||||
#: selection:kpi.kpi.action,action:0
|
||||
msgid "ir.actions.server"
|
||||
msgstr ""
|
||||
|
|
@ -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,229 @@
|
|||
# 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)
|
||||
compute_on_fly_refresh = fields.Integer(
|
||||
default=0, help="Seconds to refresh on fly elements"
|
||||
)
|
||||
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_on_fly(self):
|
||||
self.ensure_one()
|
||||
result = []
|
||||
for item in self.item_ids:
|
||||
if not item.kpi_id.compute_on_fly:
|
||||
continue
|
||||
result.append(item._read_dashboard())
|
||||
return result
|
||||
|
||||
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,
|
||||
"compute_on_fly_refresh": self.compute_on_fly_refresh,
|
||||
"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": "{},{}".format(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, ondelete="cascade")
|
||||
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()
|
||||
modify_context = fields.Boolean()
|
||||
modify_context_expression = fields.Char()
|
||||
modify_color = fields.Boolean()
|
||||
modify_color_expression = 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",
|
||||
"modify_context": self.modify_context,
|
||||
"modify_color": self.modify_color,
|
||||
}
|
||||
if self.modify_context:
|
||||
vals["modify_context_expression"] = self.modify_context_expression
|
||||
if self.modify_color:
|
||||
vals["modify_color_expression"] = self.modify_color_expression
|
||||
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 "",
|
||||
"compute_on_fly": self.kpi_id.compute_on_fly,
|
||||
}
|
||||
)
|
||||
if self.kpi_id.compute_on_fly:
|
||||
vals.update(
|
||||
{
|
||||
"value": self.kpi_id._compute_value(),
|
||||
"value_last_update": fields.Datetime.now(),
|
||||
}
|
||||
)
|
||||
else:
|
||||
vals.update(
|
||||
{
|
||||
"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
|
||||
|
||||
def technical_config(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"name": self.display_name,
|
||||
"res_model": self._name,
|
||||
"res_id": self.id,
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "form",
|
||||
"target": "new",
|
||||
"view_id": self.env.ref(
|
||||
"kpi_dashboard.kpi_dashboard_item_config_form_view"
|
||||
).id,
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
# Copyright 2020 Creu Blanca
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import ast
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
|
||||
from dateutil import relativedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools.float_utils import float_compare
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
from odoo.addons.base.models.ir_cron import _intervalTypes
|
||||
|
||||
|
||||
class KpiKpi(models.Model):
|
||||
_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"), ("code", "Code")], 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(
|
||||
[
|
||||
("integer", "Integer"),
|
||||
("number", "Number"),
|
||||
("meter", "Meter"),
|
||||
("counter", "Counter"),
|
||||
("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",
|
||||
)
|
||||
code = fields.Text("Code")
|
||||
store_history = fields.Boolean()
|
||||
store_history_interval = fields.Selection(
|
||||
selection=lambda self: self.env["ir.cron"]._fields["interval_type"].selection,
|
||||
)
|
||||
store_history_interval_number = fields.Integer()
|
||||
compute_on_fly = fields.Boolean()
|
||||
history_ids = fields.One2many("kpi.kpi.history", inverse_name="kpi_id")
|
||||
computed_value = fields.Serialized(compute="_compute_computed_value")
|
||||
computed_date = fields.Datetime(compute="_compute_computed_value")
|
||||
|
||||
@api.depends("value", "value_last_update", "compute_on_fly")
|
||||
def _compute_computed_value(self):
|
||||
for record in self:
|
||||
if record.compute_on_fly:
|
||||
record.computed_value = record._compute_value()
|
||||
record.computed_date = fields.Datetime.now()
|
||||
else:
|
||||
record.computed_value = record.value
|
||||
record.computed_date = record.value_last_update
|
||||
|
||||
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 _generate_history_vals(self, value):
|
||||
return {
|
||||
"kpi_id": self.id,
|
||||
"value": value,
|
||||
"widget": self.widget,
|
||||
}
|
||||
|
||||
def _compute_value(self):
|
||||
return getattr(self, "_compute_value_%s" % self.computation_method)()
|
||||
|
||||
def _compute(self):
|
||||
value = self._compute_value()
|
||||
self.write({"value": value})
|
||||
if self.store_history:
|
||||
last = self.env["kpi.kpi.history"].search(
|
||||
[("kpi_id", "=", self.id)], limit=1
|
||||
)
|
||||
if (
|
||||
not last
|
||||
or not self.store_history_interval
|
||||
or last.create_date
|
||||
+ _intervalTypes[self.store_history_interval](
|
||||
self.store_history_interval_number
|
||||
)
|
||||
< fields.Datetime.now()
|
||||
):
|
||||
self.env["kpi.kpi.history"].create(self._generate_history_vals(value))
|
||||
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())
|
||||
|
||||
def write(self, vals):
|
||||
if "value" in vals:
|
||||
vals["value_last_update"] = fields.Datetime.now()
|
||||
return super().write(vals)
|
||||
|
||||
def _get_code_input_dict(self):
|
||||
return {
|
||||
"self": self,
|
||||
"model": self.browse(),
|
||||
"datetime": datetime,
|
||||
"float_compare": float_compare,
|
||||
"relativedelta": relativedelta.relativedelta,
|
||||
}
|
||||
|
||||
def _forbidden_code(self):
|
||||
return ["commit", "rollback", "getattr", "execute"]
|
||||
|
||||
def _compute_value_code(self):
|
||||
forbidden = self._forbidden_code()
|
||||
search_terms = "(" + ("|".join(forbidden)) + ")"
|
||||
if re.search(search_terms, (self.code or "").lower()):
|
||||
message = ", ".join(forbidden[:-1]) or ""
|
||||
if len(message) > 0:
|
||||
message += _(" or ")
|
||||
message += forbidden[-1]
|
||||
raise ValidationError(
|
||||
_("The code cannot contain the following terms: %s.") % message
|
||||
)
|
||||
results = self._get_code_input_dict()
|
||||
savepoint = "kpi_formula_%s" % self.id
|
||||
# pylint: disable=E8103
|
||||
self.env.cr.execute("savepoint %s" % savepoint)
|
||||
safe_eval(self.code or "", results, mode="exec", nocopy=True)
|
||||
# pylint: disable=E8103
|
||||
self.env.cr.execute("rollback to %s" % savepoint)
|
||||
return results.get("result", {})
|
||||
|
||||
def show_value(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref("kpi_dashboard.kpi_kpi_act_window")
|
||||
result = action.read()[0]
|
||||
result.update(
|
||||
{
|
||||
"res_id": self.id,
|
||||
"target": "new",
|
||||
"view_mode": "form",
|
||||
"views": [
|
||||
(self.env.ref("kpi_dashboard.kpi_kpi_widget_form_view").id, "form")
|
||||
],
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
context = fields.Char()
|
||||
|
||||
def read_dashboard(self):
|
||||
result = {}
|
||||
for r in self:
|
||||
result[r.id] = {
|
||||
"id": r.action.id,
|
||||
"type": r.action._name,
|
||||
"name": r.action.name,
|
||||
"context": safe_eval(r.context or "{}"),
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
class KpiKpiHistory(models.Model):
|
||||
_name = "kpi.kpi.history"
|
||||
_description = "KPI history"
|
||||
_order = "create_date DESC"
|
||||
|
||||
kpi_id = fields.Many2one(
|
||||
"kpi.kpi", required=True, ondelete="cascade", readonly=True
|
||||
)
|
||||
value = fields.Serialized(readonly=True)
|
||||
raw_value = fields.Char(compute="_compute_raw_value")
|
||||
name = fields.Char(related="kpi_id.name")
|
||||
widget = fields.Selection(
|
||||
selection=lambda self: self.env["kpi.kpi"]._fields["widget"].selection,
|
||||
required=True,
|
||||
)
|
||||
|
||||
@api.depends("value")
|
||||
def _compute_raw_value(self):
|
||||
for record in self:
|
||||
record.raw_value = json.dumps(record.value)
|
||||
|
||||
def show_form(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref("kpi_dashboard.kpi_kpi_history_act_window")
|
||||
result = action.read()[0]
|
||||
result.update(
|
||||
{
|
||||
"res_id": self.id,
|
||||
"target": "new",
|
||||
"view_mode": "form",
|
||||
"views": [(self.env.context.get("form_id"), "form")],
|
||||
}
|
||||
)
|
||||
return result
|
|
@ -0,0 +1,33 @@
|
|||
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`
|
||||
|
||||
#. In order to compute the KPI you can use a predefined function from a model or
|
||||
use the code to directly compute it.
|
||||
|
||||
Using KPI with code
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Define the code directly on the code field. You can use `self` and `model` as the kpi element
|
||||
The script should create a variable called `result` as a dictionary that
|
||||
will be stored as the value.
|
||||
For example, we can use::
|
||||
|
||||
result = {}
|
||||
result['value'] = len(model.search([('id', '=', %s)]))
|
||||
result['previous'] = len(model.search([('id', '!=', %s)]))
|
||||
|
||||
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,11 @@
|
|||
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
|
||||
access_kpi_kpi_history,access_kpi_kpi_history,model_kpi_kpi_history,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
|
||||
manage_kpi_kpi_history,manage_kpi_kpi_history,model_kpi_kpi_history,group_kpi_dashboard_manager,1,1,1,1
|
|
|
@ -0,0 +1,24 @@
|
|||
<?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>
|
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
|
@ -0,0 +1,466 @@
|
|||
<?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.15.1: 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="#using-kpi-with-code" id="id3">Using KPI with code</a></li>
|
||||
<li><a class="reference internal" href="#configure-dashboards" id="id4">Configure dashboards</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="id5">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="id6">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="id7">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="id8">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="id9">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>
|
||||
<li>In order to compute the KPI you can use a predefined function from a model or
|
||||
use the code to directly compute it.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="section" id="using-kpi-with-code">
|
||||
<h2><a class="toc-backref" href="#id3">Using KPI with code</a></h2>
|
||||
<p>Define the code directly on the code field. You can use <cite>self</cite> and <cite>model</cite> as the kpi element
|
||||
The script should create a variable called <cite>result</cite> as a dictionary that
|
||||
will be stored as the value.
|
||||
For example, we can use:</p>
|
||||
<pre class="literal-block">
|
||||
result = {}
|
||||
result['value'] = len(model.search([('id', '=', %s)]))
|
||||
result['previous'] = len(model.search([('id', '!=', %s)]))
|
||||
</pre>
|
||||
</div>
|
||||
<div class="section" id="configure-dashboards">
|
||||
<h2><a class="toc-backref" href="#id4">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="#id5">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="#id6">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#id7">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Creu Blanca</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
<h2><a class="toc-backref" href="#id8">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="#id9">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>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
|
||||
<p><a class="reference external" href="https://github.com/etobella"><img alt="etobella" src="https://github.com/etobella.png?size=40px" /></a></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}
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,427 @@
|
|||
/* nvd3 version 1.8.2-dev (https://github.com/novus/nvd3) 2016-02-08 */
|
||||
.nvd3 .nv-axis {
|
||||
pointer-events:none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis path {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
stroke-opacity: .75;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis path.domain {
|
||||
stroke-opacity: .75;
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis.nv-x path.domain {
|
||||
stroke-opacity: 0;
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis line {
|
||||
fill: none;
|
||||
stroke: #e5e5e5;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis .zero line,
|
||||
/*this selector may not be necessary*/ .nvd3 .nv-axis line.zero {
|
||||
stroke-opacity: .75;
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis .nv-axisMaxMin text {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nvd3 .x .nv-axis .nv-axisMaxMin text,
|
||||
.nvd3 .x2 .nv-axis .nv-axisMaxMin text,
|
||||
.nvd3 .x3 .nv-axis .nv-axisMaxMin text {
|
||||
text-anchor: middle
|
||||
}
|
||||
|
||||
.nvd3 .nv-axis.nv-disabled {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* scatter */
|
||||
.nvd3 .nv-groups .nv-point.hover {
|
||||
stroke-width: 20px;
|
||||
stroke-opacity: .5;
|
||||
}
|
||||
|
||||
.nvd3 .nv-scatter .nv-point.hover {
|
||||
fill-opacity: 1;
|
||||
}
|
||||
.nv-noninteractive {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.nv-distx, .nv-disty {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.nvtooltip {
|
||||
position: absolute;
|
||||
background-color: rgba(255,255,255,1.0);
|
||||
color: rgba(0,0,0,1.0);
|
||||
padding: 1px;
|
||||
border: 1px solid rgba(0,0,0,.2);
|
||||
z-index: 10000;
|
||||
display: block;
|
||||
|
||||
font-family: Arial;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
pointer-events: none;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.nvtooltip {
|
||||
background: rgba(255,255,255, 0.8);
|
||||
border: 1px solid rgba(0,0,0,0.5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/*Give tooltips that old fade in transition by
|
||||
putting a "with-transitions" class on the container div.
|
||||
*/
|
||||
.nvtooltip.with-transitions, .with-transitions .nvtooltip {
|
||||
transition: opacity 50ms linear;
|
||||
-moz-transition: opacity 50ms linear;
|
||||
-webkit-transition: opacity 50ms linear;
|
||||
|
||||
transition-delay: 200ms;
|
||||
-moz-transition-delay: 200ms;
|
||||
-webkit-transition-delay: 200ms;
|
||||
}
|
||||
|
||||
.nvtooltip.x-nvtooltip,
|
||||
.nvtooltip.y-nvtooltip {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.nvtooltip h3 {
|
||||
margin: 0;
|
||||
padding: 4px 14px;
|
||||
line-height: 18px;
|
||||
font-weight: normal;
|
||||
background-color: rgba(247,247,247,0.75);
|
||||
color: rgba(0,0,0,1.0);
|
||||
text-align: center;
|
||||
|
||||
border-bottom: 1px solid #ebebeb;
|
||||
|
||||
-webkit-border-radius: 5px 5px 0 0;
|
||||
-moz-border-radius: 5px 5px 0 0;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
.nvtooltip p {
|
||||
margin: 0;
|
||||
padding: 5px 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nvtooltip span {
|
||||
display: inline-block;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.nvtooltip table {
|
||||
margin: 6px;
|
||||
border-spacing:0;
|
||||
}
|
||||
|
||||
|
||||
.nvtooltip table td {
|
||||
padding: 2px 9px 2px 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.nvtooltip table td.key {
|
||||
font-weight: normal;
|
||||
}
|
||||
.nvtooltip table td.key.total {
|
||||
font-weight: bold;
|
||||
}
|
||||
.nvtooltip table td.value {
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nvtooltip table tr.highlight td {
|
||||
padding: 1px 9px 1px 0;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.nvtooltip table td.legend-color-guide div {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.nvtooltip table td.legend-color-guide div {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
.nvtooltip .footer {
|
||||
padding: 3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nvtooltip-pending-removal {
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/****
|
||||
Interactive Layer
|
||||
*/
|
||||
.nvd3 .nv-interactiveGuideLine {
|
||||
pointer-events:none;
|
||||
}
|
||||
.nvd3 line.nv-guideline {
|
||||
stroke: #ccc;
|
||||
}
|
||||
|
||||
.nvd3 .nv-bars rect {
|
||||
fill-opacity: .75;
|
||||
|
||||
transition: fill-opacity 250ms linear;
|
||||
-moz-transition: fill-opacity 250ms linear;
|
||||
-webkit-transition: fill-opacity 250ms linear;
|
||||
}
|
||||
|
||||
.nvd3 .nv-bars rect.hover {
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
.nvd3 .nv-bars .hover rect {
|
||||
fill: lightblue;
|
||||
}
|
||||
|
||||
.nvd3 .nv-bars text {
|
||||
fill: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
.nvd3 .nv-bars .hover text {
|
||||
fill: rgba(0,0,0,1);
|
||||
}
|
||||
|
||||
.nvd3 .nv-multibar .nv-groups rect,
|
||||
.nvd3 .nv-multibarHorizontal .nv-groups rect,
|
||||
.nvd3 .nv-discretebar .nv-groups rect {
|
||||
stroke-opacity: 0;
|
||||
|
||||
transition: fill-opacity 250ms linear;
|
||||
-moz-transition: fill-opacity 250ms linear;
|
||||
-webkit-transition: fill-opacity 250ms linear;
|
||||
}
|
||||
|
||||
.nvd3 .nv-multibar .nv-groups rect:hover,
|
||||
.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,
|
||||
.nvd3 .nv-candlestickBar .nv-ticks rect:hover,
|
||||
.nvd3 .nv-discretebar .nv-groups rect:hover {
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
.nvd3 .nv-discretebar .nv-groups text,
|
||||
.nvd3 .nv-multibarHorizontal .nv-groups text {
|
||||
font-weight: bold;
|
||||
fill: rgba(0,0,0,1);
|
||||
stroke: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
.nvd3 .nv-groups path.nv-line {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.nvd3 .nv-groups path.nv-area {
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point {
|
||||
fill-opacity: 0;
|
||||
stroke-opacity: 0;
|
||||
}
|
||||
|
||||
.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point {
|
||||
fill-opacity: .5 !important;
|
||||
stroke-opacity: .5 !important;
|
||||
}
|
||||
|
||||
|
||||
.with-transitions .nvd3 .nv-groups .nv-point {
|
||||
transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
-moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
-webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
|
||||
}
|
||||
|
||||
.nvd3.nv-scatter .nv-groups .nv-point.hover,
|
||||
.nvd3 .nv-groups .nv-point.hover {
|
||||
stroke-width: 7px;
|
||||
fill-opacity: .95 !important;
|
||||
stroke-opacity: .95 !important;
|
||||
}
|
||||
|
||||
|
||||
.nvd3 .nv-point-paths path {
|
||||
stroke: #aaa;
|
||||
stroke-opacity: 0;
|
||||
fill: #eee;
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.nvd3 .nv-indexLine {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
/********************
|
||||
* SVG CSS
|
||||
*/
|
||||
|
||||
/********************
|
||||
Default CSS for an svg element nvd3 used
|
||||
*/
|
||||
svg.nvd3-svg {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
display: block;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
/********************
|
||||
Box shadow and border radius styling
|
||||
*/
|
||||
.nvtooltip.with-3d-shadow, .with-3d-shadow .nvtooltip {
|
||||
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||
box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
.nvd3 text {
|
||||
font: normal 12px Arial;
|
||||
}
|
||||
|
||||
.nvd3 .title {
|
||||
font: bold 14px Arial;
|
||||
}
|
||||
|
||||
.nvd3 .nv-background {
|
||||
fill: white;
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
.nvd3.nv-noData {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/**********
|
||||
* Brush
|
||||
*/
|
||||
|
||||
.nv-brush .extent {
|
||||
fill-opacity: .125;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
.nv-brush .resize path {
|
||||
fill: #eee;
|
||||
stroke: #666;
|
||||
}
|
||||
|
||||
|
||||
/**********
|
||||
* Legend
|
||||
*/
|
||||
|
||||
.nvd3 .nv-legend .nv-series {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nvd3 .nv-legend .nv-disabled circle {
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
/* focus */
|
||||
.nvd3 .nv-brush .extent {
|
||||
fill-opacity: 0 !important;
|
||||
}
|
||||
|
||||
.nvd3 .nv-brushBackground rect {
|
||||
stroke: #000;
|
||||
stroke-width: .4;
|
||||
fill: #fff;
|
||||
fill-opacity: .7;
|
||||
}
|
||||
|
||||
|
||||
.nvd3.nv-pie path {
|
||||
stroke-opacity: 0;
|
||||
transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
-moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
-webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
|
||||
|
||||
}
|
||||
|
||||
.nvd3.nv-pie .nv-pie-title {
|
||||
font-size: 24px;
|
||||
fill: rgba(19, 196, 249, 0.59);
|
||||
}
|
||||
|
||||
.nvd3.nv-pie .nv-slice text {
|
||||
stroke: #000;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.nvd3.nv-pie path {
|
||||
stroke: #fff;
|
||||
stroke-width: 1px;
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
|
||||
.nvd3.nv-pie path {
|
||||
fill-opacity: .7;
|
||||
}
|
||||
.nvd3.nv-pie .hover path {
|
||||
fill-opacity: 1;
|
||||
}
|
||||
.nvd3.nv-pie .nv-label {
|
||||
pointer-events: none;
|
||||
}
|
||||
.nvd3.nv-pie .nv-label rect {
|
||||
fill-opacity: 0;
|
||||
stroke-opacity: 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
global py
|
||||
*/
|
||||
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({
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.dashboard_context = {};
|
||||
this.dashboard_color_data = [];
|
||||
},
|
||||
custom_events: _.extend({}, BasicController.prototype.custom_events, {
|
||||
addDashboard: "_addDashboard",
|
||||
refresh_on_fly: "_refreshOnFly",
|
||||
modify_context: "_modifyContext",
|
||||
add_modify_color: "_addModifyColor",
|
||||
refresh_colors: "_refreshColors",
|
||||
}),
|
||||
_refreshOnFly: function() {
|
||||
var self = this;
|
||||
this._rpc({
|
||||
model: this.modelName,
|
||||
method: "read_dashboard_on_fly",
|
||||
args: [[this.renderer.state.res_id]],
|
||||
context: this._getContext(),
|
||||
}).then(function(data) {
|
||||
_.each(data, function(item) {
|
||||
// We will follow the same logic used on Bus Notifications
|
||||
self.renderer._onNotification([
|
||||
["kpi_dashboard_" + self.renderer.state.res_id, item],
|
||||
]);
|
||||
});
|
||||
});
|
||||
},
|
||||
renderPager: function($node, options) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
options = _.extend({}, options, {
|
||||
validate: this.canBeDiscarded.bind(this),
|
||||
});
|
||||
this._super($node, options);
|
||||
},
|
||||
_pushState: function(state) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
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);
|
||||
},
|
||||
_getContext: function() {
|
||||
return _.extend(
|
||||
{},
|
||||
this.model.get(this.handle, {raw: true}).getContext(),
|
||||
{bin_size: true},
|
||||
this.dashboard_context
|
||||
);
|
||||
},
|
||||
_modifyContext: function(event) {
|
||||
var ctx = this._getContext();
|
||||
this.dashboard_context = _.extend(
|
||||
this.dashboard_context,
|
||||
py.eval(event.data.context, {
|
||||
context: _.extend(
|
||||
ctx,
|
||||
{
|
||||
__getattr__: function() {
|
||||
return false;
|
||||
},
|
||||
}
|
||||
// We need to add this in order to allow to use undefined
|
||||
// context items
|
||||
),
|
||||
})
|
||||
);
|
||||
this._refreshOnFly(event);
|
||||
this._refreshColors();
|
||||
},
|
||||
_addModifyColor: function(event) {
|
||||
this.dashboard_color_data.push([
|
||||
event.data.element_id,
|
||||
event.data.expression,
|
||||
]);
|
||||
},
|
||||
_refreshColors: function() {
|
||||
var self = this;
|
||||
var ctx = this._getContext();
|
||||
_.each(this.dashboard_color_data, function(data) {
|
||||
var color = py.eval(data[1], {
|
||||
context: _.extend(ctx, {
|
||||
__getattr__: function() {
|
||||
return false;
|
||||
},
|
||||
}),
|
||||
check_if: function(args) {
|
||||
if (args[0].toJSON()) {
|
||||
return args[1];
|
||||
}
|
||||
return args[2];
|
||||
},
|
||||
});
|
||||
var $element = self.renderer.$el.find("#" + data[0]);
|
||||
$element.css("background-color", color);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return DashboardController;
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
odoo.define("kpi_dashboard.DashboardModel", function(require) {
|
||||
"use strict";
|
||||
|
||||
var BasicModel = require("web.BasicModel");
|
||||
|
||||
var DashboardModel = BasicModel.extend({
|
||||
_fetchRecord: function(record) {
|
||||
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,108 @@
|
|||
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 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;
|
||||
},
|
||||
_onClickModifyContext: function(modify_context_expression, event) {
|
||||
this.trigger_up("modify_context", {
|
||||
context: modify_context_expression,
|
||||
event: event,
|
||||
});
|
||||
},
|
||||
_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);
|
||||
element.attr("id", _.uniqueId("kpi_"));
|
||||
self.$grid.append(element);
|
||||
if (kpi.modify_color) {
|
||||
self.trigger_up("add_modify_color", {
|
||||
element_id: element.attr("id"),
|
||||
expression: kpi.modify_color_expression,
|
||||
});
|
||||
}
|
||||
if (kpi.modify_context) {
|
||||
element.on(
|
||||
"click",
|
||||
self._onClickModifyContext.bind(
|
||||
self,
|
||||
kpi.modify_context_expression
|
||||
)
|
||||
);
|
||||
element.css("cursor", "pointer");
|
||||
// We want to set it show as clickable
|
||||
}
|
||||
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);
|
||||
if (this.state.specialData.compute_on_fly_refresh > 0) {
|
||||
// Setting the refresh interval
|
||||
this.on_fly_interval = setInterval(function() {
|
||||
self.trigger_up("refresh_on_fly");
|
||||
}, this.state.specialData.compute_on_fly_refresh * 1000);
|
||||
}
|
||||
this.trigger_up("refresh_colors");
|
||||
this.trigger_up("refresh_on_fly");
|
||||
// We need to refreshs data in order compute with the current
|
||||
// context
|
||||
return $.when();
|
||||
},
|
||||
on_detach_callback: function() {
|
||||
// We want to clear the refresh interval once we exit the view
|
||||
if (this.on_fly_interval) {
|
||||
clearInterval(this.on_fly_interval);
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
_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,40 @@
|
|||
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,61 @@
|
|||
odoo.define("kpi_dashboard.KpiFieldWidget", function(require) {
|
||||
"use strict";
|
||||
|
||||
var basic_fields = require("web.basic_fields");
|
||||
var field_registry = require("web.field_registry");
|
||||
var core = require("web.core");
|
||||
var qweb = core.qweb;
|
||||
var registry = require("kpi_dashboard.widget_registry");
|
||||
|
||||
var KpiFieldWidget = basic_fields.FieldChar.extend({
|
||||
jsLibs: ["/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.js"],
|
||||
cssLibs: ["/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.css"],
|
||||
className: "o_dashboard_view",
|
||||
_renderReadonly: function() {
|
||||
this.$el.html($(qweb.render("dashboard_kpi.dashboard")));
|
||||
var marginx = 0;
|
||||
var marginy = 0;
|
||||
var widgetx = 400;
|
||||
var widgety = 400;
|
||||
this.$el.find(".gridster").css("width", widgety);
|
||||
this.$grid = this.$el.find(".gridster ul");
|
||||
var widgetVals = {
|
||||
value: this.value,
|
||||
col: 1,
|
||||
row: 1,
|
||||
sizex: 1,
|
||||
sizey: 1,
|
||||
name: this.recordData[this.nodeOptions.name],
|
||||
value_last_update: this.recordData[this.nodeOptions.date],
|
||||
};
|
||||
var Widget = registry.getAny([
|
||||
this.recordData[this.nodeOptions.widget],
|
||||
"abstract",
|
||||
]);
|
||||
this.state = {
|
||||
specialData: {
|
||||
margin_x: marginx,
|
||||
margin_y: marginy,
|
||||
widget_dimension_x: widgetx,
|
||||
widget_dimension_y: widgety,
|
||||
},
|
||||
};
|
||||
var widget = new Widget(this, widgetVals);
|
||||
var element = $(qweb.render("kpi_dashboard.kpi", {widget: widgetVals}));
|
||||
element.css("background-color", "white");
|
||||
element.css("color", "black");
|
||||
this.$grid.append(element);
|
||||
widget.appendTo(element);
|
||||
this.$grid
|
||||
.gridster({
|
||||
widget_margins: [marginx, marginy],
|
||||
widget_base_dimensions: [widgetx, widgety],
|
||||
cols: 1,
|
||||
})
|
||||
.data("gridster")
|
||||
.disable();
|
||||
},
|
||||
});
|
||||
field_registry.add("kpi", KpiFieldWidget);
|
||||
return KpiFieldWidget;
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
global nv
|
||||
*/
|
||||
odoo.define("web.nvd3.extensions", function() {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* The nvd3 library extensions and fixes should be done here to avoid patching
|
||||
* in place.
|
||||
*/
|
||||
|
||||
nv.dev = false;
|
||||
// Sets nvd3 library in production mode
|
||||
|
||||
// monkey patch nvd3 to allow removing eventhandler on windowresize events
|
||||
// see https://github.com/novus/nvd3/pull/396 for more details
|
||||
|
||||
// Adds a resize listener to the window.
|
||||
nv.utils.onWindowResize = function(fun) {
|
||||
if (fun === null) return;
|
||||
window.addEventListener("resize", fun);
|
||||
};
|
||||
|
||||
// Backwards compatibility with current API.
|
||||
nv.utils.windowResize = nv.utils.onWindowResize;
|
||||
|
||||
// Removes a resize listener from the window.
|
||||
nv.utils.offWindowResize = function(fun) {
|
||||
if (fun === null) return;
|
||||
window.removeEventListener("resize", fun);
|
||||
};
|
||||
|
||||
// Monkey patch nvd3 to prevent crashes when user changes view and nvd3
|
||||
// tries to remove tooltips after 500 ms... seriously nvd3, what were you
|
||||
// thinking?
|
||||
nv.tooltip.cleanup = function() {
|
||||
$(".nvtooltip").remove();
|
||||
};
|
||||
|
||||
// Monkey patch nvd3 to prevent it to display a tooltip (position: absolute)
|
||||
// with a negative `top`; with this patch the highest tooltip's position is
|
||||
// still in the graph
|
||||
var originalCalcTooltipPosition = nv.tooltip.calcTooltipPosition;
|
||||
nv.tooltip.calcTooltipPosition = function() {
|
||||
var container = originalCalcTooltipPosition.apply(this, arguments);
|
||||
container.style.top =
|
||||
container.style.top.split("px")[0] < 0 ? 0 + "px" : container.style.top;
|
||||
return container;
|
||||
};
|
||||
});
|
|
@ -0,0 +1,101 @@
|
|||
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 used by the widget
|
||||
template: "kpi_dashboard.base_widget",
|
||||
// Specific css of the widget
|
||||
cssLibs: [],
|
||||
// Specific Javascript libraries of the widget
|
||||
jsLibs: [],
|
||||
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.compute_on_fly &&
|
||||
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");
|
||||
var action = this.actions[$($data).data("id")];
|
||||
return this.do_action(action.id, {
|
||||
additional_context: action.context,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
registry.add("abstract", AbstractWidget);
|
||||
return AbstractWidget;
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
odoo.define("kpi_dashboard.CounterWidget", function(require) {
|
||||
"use strict";
|
||||
|
||||
var IntegerWidget = require("kpi_dashboard.IntegerWidget");
|
||||
var registry = require("kpi_dashboard.widget_registry");
|
||||
|
||||
var CounterWidget = IntegerWidget.extend({
|
||||
shortList: [],
|
||||
});
|
||||
|
||||
registry.add("counter", CounterWidget);
|
||||
return CounterWidget;
|
||||
});
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
global nv, d3
|
||||
*/
|
||||
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: [
|
||||
"/kpi_dashboard/static/lib/nvd3/d3.v3.js",
|
||||
"/kpi_dashboard/static/lib/nvd3/nv.d3.js",
|
||||
"/kpi_dashboard/static/src/js/lib/nvd3.js",
|
||||
],
|
||||
cssLibs: ["/kpi_dashboard/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() {
|
||||
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,75 @@
|
|||
odoo.define("kpi_dashboard.IntegerWidget", 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 IntegerWidget = AbstractWidget.extend({
|
||||
template: "kpi_dashboard.number",
|
||||
digits: [3, 0],
|
||||
shortList: [
|
||||
[1000000000000, "T", [3, 1]],
|
||||
[1000000000, "G", [3, 1]],
|
||||
[1000000, "M", [3, 1]],
|
||||
[1000, "K", [3, 1]],
|
||||
],
|
||||
shortNumber: function(num) {
|
||||
var suffix = "";
|
||||
var shortened = false;
|
||||
var digits = this.digits;
|
||||
var result = num;
|
||||
_.each(this.shortList, function(shortItem) {
|
||||
if (!shortened && Math.abs(num) >= shortItem[0]) {
|
||||
shortened = true;
|
||||
suffix = shortItem[1];
|
||||
result /= shortItem[0];
|
||||
digits = shortItem[2];
|
||||
}
|
||||
});
|
||||
return (
|
||||
field_utils.format.float(result, false, {
|
||||
digits: digits,
|
||||
}) + suffix
|
||||
);
|
||||
},
|
||||
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("integer", IntegerWidget);
|
||||
return IntegerWidget;
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
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,22 @@
|
|||
odoo.define("kpi_dashboard.NumberWidget", function(require) {
|
||||
"use strict";
|
||||
|
||||
var IntegerWidget = require("kpi_dashboard.IntegerWidget");
|
||||
var registry = require("kpi_dashboard.widget_registry");
|
||||
var field_utils = require("web.field_utils");
|
||||
|
||||
var NumberWidget = IntegerWidget.extend({
|
||||
digits: [3, 1],
|
||||
shortNumber: function(num) {
|
||||
if (Math.abs(num) < 10) {
|
||||
return field_utils.format.float(num, false, {
|
||||
digits: [3, 2],
|
||||
});
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
registry.add("number", NumberWidget);
|
||||
return NumberWidget;
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
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,116 @@
|
|||
.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: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
<?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_id">
|
||||
<t t-set="action" t-value="widget.actions[action_id]" />
|
||||
<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,68 @@
|
|||
<?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/integer_widget.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/kpi_dashboard/static/src/js/widget/number_widget.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/kpi_dashboard/static/src/js/widget/counter_widget.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/kpi_dashboard/static/src/js/widget/meter_widget.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/kpi_dashboard/static/src/js/widget/graph_widget.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/kpi_dashboard/static/src/js/widget/text_widget.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/kpi_dashboard/static/src/js/field_widget.js"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/scss"
|
||||
href="/kpi_dashboard/static/src/scss/kpi_dashboard.scss"
|
||||
/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
|
@ -0,0 +1,2 @@
|
|||
from . import test_formula
|
||||
from . import test_kpi_dashboard
|
|
@ -0,0 +1,75 @@
|
|||
# Copyright 2020 Creu Blanca
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestFormula(TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.kpi = self.env["kpi.kpi"].create(
|
||||
{"name": "DEMO KPI", "widget": "number", "computation_method": "code"}
|
||||
)
|
||||
|
||||
def test_forbidden_words_01(self):
|
||||
self.kpi.code = """
|
||||
result = {"value": 0}
|
||||
self.env.cr.commit()
|
||||
"""
|
||||
with self.assertRaises(ValidationError):
|
||||
self.kpi.compute()
|
||||
|
||||
def test_forbidden_words_02(self):
|
||||
self.kpi.code = """
|
||||
result = {"value": 0}
|
||||
self.env.cr.rollback()
|
||||
"""
|
||||
with self.assertRaises(ValidationError):
|
||||
self.kpi.compute()
|
||||
|
||||
def test_forbidden_words_03(self):
|
||||
self.kpi.code = """
|
||||
result = {"value": 0}
|
||||
self.env.cr.execute("CoMMiT")
|
||||
"""
|
||||
with self.assertRaises(ValidationError):
|
||||
self.kpi.compute()
|
||||
|
||||
def test_computation(self):
|
||||
self.assertFalse(self.kpi.value)
|
||||
self.kpi.compute()
|
||||
self.assertEqual(self.kpi.value, {})
|
||||
self.kpi.code = """
|
||||
result = {{}}
|
||||
result['value'] = len(model.search([('id', '=', {})]))
|
||||
result['previous'] = len(model.search([('id', '!=', {})]))
|
||||
""".format(
|
||||
self.kpi.id, self.kpi.id,
|
||||
)
|
||||
self.kpi.compute()
|
||||
value = self.kpi.value
|
||||
self.assertTrue(value.get("value"))
|
||||
self.assertEqual(value.get("value"), 1)
|
||||
self.assertEqual(value.get("previous"), self.kpi.search_count([]) - 1)
|
||||
self.assertFalse(self.kpi.history_ids)
|
||||
|
||||
def test_computation_history(self):
|
||||
self.assertFalse(self.kpi.value)
|
||||
self.kpi.store_history = True
|
||||
self.kpi.compute()
|
||||
self.assertTrue(self.kpi.history_ids)
|
||||
self.assertEqual(self.kpi.value, {})
|
||||
self.kpi.code = """
|
||||
result = {{}}
|
||||
result['value'] = len(model.search([('id', '=', {})]))
|
||||
result['previous'] = len(model.search([('id', '!=', {})]))
|
||||
""".format(
|
||||
self.kpi.id, self.kpi.id,
|
||||
)
|
||||
self.kpi.compute()
|
||||
value = self.kpi.value
|
||||
self.assertTrue(value.get("value"))
|
||||
self.assertEqual(value.get("value"), 1)
|
||||
self.assertEqual(value.get("previous"), self.kpi.search_count([]) - 1)
|
||||
self.assertTrue(self.kpi.history_ids)
|
|
@ -0,0 +1,167 @@
|
|||
# Copyright 2020 Creu Blanca
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from mock import patch
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import Form, TransactionCase
|
||||
|
||||
|
||||
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})
|
||||
self.kpi_01.flush()
|
||||
|
||||
def test_constrains_02(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
self.kpi_02.dashboard_item_ids.write({"size_x": 4})
|
||||
self.kpi_01.flush()
|
||||
|
||||
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": "{},{}".format(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": "{},{}".format(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)
|
||||
self.assertFalse(data.get("action_id", False))
|
||||
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()
|
||||
data = self.dashboard.read_dashboard()
|
||||
self.assertTrue(data.get("action_id", False))
|
||||
|
||||
def test_compute(self):
|
||||
self.assertFalse(self.kpi_01.value_last_update)
|
||||
with patch(
|
||||
"odoo.addons.kpi_dashboard.models.kpi_kpi." "KpiKpi.test_demo_number",
|
||||
create=True,
|
||||
) as f:
|
||||
f.return_value = {"value": 0}
|
||||
self.kpi_01.compute()
|
||||
self.assertTrue(self.kpi_01.value_last_update)
|
||||
|
||||
def test_compute_model(self):
|
||||
self.assertFalse(self.kpi_01.value_last_update)
|
||||
self.kpi_01.model_id = self.env.ref("base.model_res_partner")
|
||||
with patch(
|
||||
"odoo.addons.base.models.res_partner.Partner.test_demo_number", create=True
|
||||
) as f:
|
||||
f.return_value = {"value": 0}
|
||||
self.kpi_01.compute()
|
||||
self.assertTrue(self.kpi_01.value_last_update)
|
||||
|
||||
def test_generate_cron(self):
|
||||
self.assertFalse(self.kpi_01.cron_id)
|
||||
self.kpi_01.generate_cron()
|
||||
self.assertTrue(self.kpi_01.cron_id)
|
||||
self.assertFalse(self.kpi_01.value_last_update)
|
||||
with patch(
|
||||
"odoo.addons.kpi_dashboard.models.kpi_kpi." "KpiKpi.test_demo_number",
|
||||
create=True,
|
||||
) as f:
|
||||
f.return_value = {"value": 0}
|
||||
self.kpi_01.cron_id.method_direct_trigger()
|
||||
self.assertTrue(self.kpi_01.value_last_update)
|
|
@ -0,0 +1,179 @@
|
|||
<?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" />
|
||||
<button
|
||||
name="technical_config"
|
||||
string=""
|
||||
type="object"
|
||||
icon="fa-edit"
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
</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 name="config">
|
||||
<field name="compute_on_fly_refresh" />
|
||||
</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>
|
||||
<record model="ir.ui.view" id="kpi_dashboard_item_form_view">
|
||||
<field name="name">kpi.dashboard.item.form (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.dashboard.item</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<header />
|
||||
<sheet>
|
||||
<div name="button_box" class="oe_button_box" />
|
||||
<group>
|
||||
<field name="name" />
|
||||
<field name="dashboard_id" />
|
||||
<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" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="kpi_dashboard_item_config_form_view">
|
||||
<field name="name">kpi.dashboard.item.form (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.dashboard.item</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<header />
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="modify_context" />
|
||||
<field
|
||||
name="modify_context_expression"
|
||||
attrs="{'invisible': [('modify_context', '=', False)]}"
|
||||
widget="ace"
|
||||
options="{'mode': 'python'}"
|
||||
/>
|
||||
<field name="modify_color" />
|
||||
<field
|
||||
name="modify_color_expression"
|
||||
attrs="{'invisible': [('modify_color', '=', False)]}"
|
||||
widget="ace"
|
||||
options="{'mode': 'python'}"
|
||||
/>
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button
|
||||
name="write"
|
||||
string="Save"
|
||||
type="object"
|
||||
class="oe_highlight"
|
||||
/>
|
||||
<button special="cancel" string="Cancel" class="oe_link" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
|
@ -0,0 +1,218 @@
|
|||
<?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.actions.act_window" id="kpi_kpi_history_act_window">
|
||||
<field name="name">Kpi History</field>
|
||||
<field name="res_model">kpi.kpi.history</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="domain">[('kpi_id', '=', active_id)]</field>
|
||||
<field name="context">{}</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="kpi_kpi_history_widget_form_view">
|
||||
<field name="name">kpi.kpi.history.raw.form (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.kpi.history</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<field name="create_date" invisible="1" />
|
||||
<field name="widget" invisible="1" />
|
||||
<field name="name" invisible="1" />
|
||||
<field
|
||||
name="value"
|
||||
widget="kpi"
|
||||
options="{'date': 'create_date', 'widget': 'widget', 'name': 'name'}"
|
||||
/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="kpi_kpi_history_raw_form_view">
|
||||
<field name="name">kpi.kpi.history.raw.form (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.kpi.history</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<field name="raw_value" />
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="kpi_kpi_history_tree_view">
|
||||
<field name="name">kpi.kpi.history.tree (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.kpi.history</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree create="0" delete="0">
|
||||
<field name="create_date" />
|
||||
<button
|
||||
name="show_form"
|
||||
string="Show value"
|
||||
type="object"
|
||||
context="{'form_id': %(kpi_dashboard.kpi_kpi_history_widget_form_view)d}"
|
||||
/>
|
||||
<button
|
||||
name="show_form"
|
||||
string="Raw value"
|
||||
type="object"
|
||||
context="{'form_id': %(kpi_dashboard.kpi_kpi_history_raw_form_view)d}"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<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), ('compute_on_fly', '=', True)]}"
|
||||
/>
|
||||
<button
|
||||
name="compute"
|
||||
string="Compute now"
|
||||
type="object"
|
||||
attrs="{'invisible': [('compute_on_fly', '=', True)]}"
|
||||
/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button
|
||||
name="%(kpi_dashboard.kpi_kpi_history_act_window)d"
|
||||
string="Show history"
|
||||
type="action"
|
||||
attrs="{'invisible': ['|', ('store_history', '=', False), ('compute_on_fly', '=', True)]}"
|
||||
icon="fa-history"
|
||||
/>
|
||||
<button
|
||||
string="Show value"
|
||||
type="object"
|
||||
name="show_value"
|
||||
icon="fa-paint-brush"
|
||||
/>
|
||||
</div>
|
||||
<h2>
|
||||
<field name="name" />
|
||||
</h2>
|
||||
<group>
|
||||
<group>
|
||||
<field name="computation_method" />
|
||||
<field name="widget" />
|
||||
<field
|
||||
name="store_history"
|
||||
attrs="{'invisible': [('compute_on_fly', '=', True)]}"
|
||||
/>
|
||||
<field
|
||||
name="store_history_interval"
|
||||
attrs="{'invisible': [('store_history', '=', False)]}"
|
||||
/>
|
||||
<field
|
||||
name="store_history_interval_number"
|
||||
attrs="{'invisible': [('store_history', '=', False)]}"
|
||||
/>
|
||||
<field
|
||||
name="compute_on_fly"
|
||||
attrs="{'invisible': [('store_history', '=', True)]}"
|
||||
/>
|
||||
<field
|
||||
name="model_id"
|
||||
attrs="{'invisible': [('computation_method', '!=', 'function')]}"
|
||||
/>
|
||||
<field
|
||||
name="function"
|
||||
attrs="{'required': [('computation_method', '=', 'function')], 'invisible': [('computation_method', '!=', 'function')]}"
|
||||
/>
|
||||
<field
|
||||
name="args"
|
||||
attrs="{'invisible': [('computation_method', '!=', 'function')]}"
|
||||
/>
|
||||
<field
|
||||
name="kwargs"
|
||||
attrs="{'invisible': [('computation_method', '!=', 'function')]}"
|
||||
/>
|
||||
</group>
|
||||
<group>
|
||||
<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" />
|
||||
<field name="context" />
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page
|
||||
name="code"
|
||||
string="Code"
|
||||
attrs="{'invisible': [('computation_method', '!=', 'code')]}"
|
||||
>
|
||||
<field
|
||||
name="code"
|
||||
widget="ace"
|
||||
options="{'mode': 'python'}"
|
||||
placeholder="Enter Python code here."
|
||||
/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="kpi_kpi_widget_form_view">
|
||||
<field name="name">kpi.kpi.raw.form (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.kpi</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<field name="computed_date" invisible="1" />
|
||||
<field name="widget" invisible="1" />
|
||||
<field name="name" invisible="1" />
|
||||
<field
|
||||
name="computed_value"
|
||||
widget="kpi"
|
||||
options="{'date': 'computed_date', 'widget': 'widget', 'name': 'name'}"
|
||||
/>
|
||||
</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,13 @@
|
|||
<?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 @@
|
|||
from . import kpi_dashboard_menu
|
|
@ -0,0 +1,16 @@
|
|||
# Copyright 2020 Creu Blanca
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import 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")
|
||||
|
||||
def generate_menu(self):
|
||||
self.dashboard_id._generate_menu(self.menu_id)
|
|
@ -0,0 +1,33 @@
|
|||
<?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>
|
Loading…
Reference in New Issue