diff --git a/kpi_dashboard/__manifest__.py b/kpi_dashboard/__manifest__.py index 2046ee36..7ad4e156 100644 --- a/kpi_dashboard/__manifest__.py +++ b/kpi_dashboard/__manifest__.py @@ -15,8 +15,8 @@ "wizards/kpi_dashboard_menu.xml", "security/security.xml", "security/ir.model.access.csv", + "templates/assets.xml", "views/kpi_menu.xml", - "views/webclient_templates.xml", "views/kpi_kpi.xml", "views/kpi_dashboard.xml", ], diff --git a/kpi_dashboard/demo/demo_dashboard.xml b/kpi_dashboard/demo/demo_dashboard.xml index 958e1580..3a31344c 100644 --- a/kpi_dashboard/demo/demo_dashboard.xml +++ b/kpi_dashboard/demo/demo_dashboard.xml @@ -6,6 +6,7 @@ 50 250 #020202 + 30 @@ -87,6 +88,26 @@ result = {"graphs": [ + + Integer counter + code + integer + + +result = {"value": self.env.context.get('counter', 990)} + + + + + Counter + code + counter + + +result = {"value": self.env.context.get('counter', 990)} + + + Dashboard title @@ -141,6 +162,43 @@ result = {"graphs": [ #ffffff + + +1 to Counter + + 3 + 10 + 1 + 2 + #B41F1F + #EEBF77 + + {'counter': (context.counter or 990) + 1} + + check_if(((context.counter or 990) + 1) % 2, '#ff0000', '#00ff00') + + + + Counter + + + 3 + 11 + 3 + #4B0082 + #ffffff + + + + Integer + + + 4 + 11 + 3 + #ffffff + #4B0082 + + Graph diff --git a/kpi_dashboard/models/kpi_dashboard.py b/kpi_dashboard/models/kpi_dashboard.py index 770a2546..24d42301 100644 --- a/kpi_dashboard/models/kpi_dashboard.py +++ b/kpi_dashboard/models/kpi_dashboard.py @@ -117,6 +117,10 @@ class KpiDashboardItem(models.Model): 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): @@ -173,7 +177,13 @@ class KpiDashboardItem(models.Model): "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( { @@ -205,3 +215,16 @@ class KpiDashboardItem(models.Model): 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, + } diff --git a/kpi_dashboard/models/kpi_kpi.py b/kpi_dashboard/models/kpi_kpi.py index b67e506a..a16d613d 100644 --- a/kpi_dashboard/models/kpi_kpi.py +++ b/kpi_dashboard/models/kpi_kpi.py @@ -10,6 +10,7 @@ from odoo.tools.float_utils import float_compare import re import json import datetime +from dateutil import relativedelta class KpiKpi(models.Model): @@ -29,7 +30,8 @@ class KpiKpi(models.Model): args = fields.Char() kwargs = fields.Char() widget = fields.Selection( - [("number", "Number"), ("meter", "Meter"), ("graph", "Graph")], + [('integer', 'Integer'), ("number", "Number"), ("meter", "Meter"), + ('counter', 'Counter'), ("graph", "Graph")], required=True, default="number", ) @@ -136,6 +138,7 @@ class KpiKpi(models.Model): "model": self.browse(), "datetime": datetime, "float_compare": float_compare, + "relativedelta": relativedelta.relativedelta, } def _forbidden_code(self): diff --git a/kpi_dashboard/static/src/js/dashboard_controller.js b/kpi_dashboard/static/src/js/dashboard_controller.js index 952077bd..97db1765 100644 --- a/kpi_dashboard/static/src/js/dashboard_controller.js +++ b/kpi_dashboard/static/src/js/dashboard_controller.js @@ -8,9 +8,17 @@ odoo.define('kpi_dashboard.DashboardController', function (require) { 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 (event) { var self = this; @@ -18,11 +26,7 @@ odoo.define('kpi_dashboard.DashboardController', function (require) { model: this.modelName, method: 'read_dashboard_on_fly', args: [[this.renderer.state.res_id]], - context: _.extend( - {}, - this.model.get(this.handle, {raw: true}).getContext(), - {bin_size: true} - ), + context: this._getContext(), }).then(function (data) { _.each(data, function (item) { // We will follow the same logic used on Bus Notifications @@ -91,6 +95,54 @@ odoo.define('kpi_dashboard.DashboardController', function (require) { 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; diff --git a/kpi_dashboard/static/src/js/dashboard_renderer.js b/kpi_dashboard/static/src/js/dashboard_renderer.js index b2603771..6bc38c49 100644 --- a/kpi_dashboard/static/src/js/dashboard_renderer.js +++ b/kpi_dashboard/static/src/js/dashboard_renderer.js @@ -16,6 +16,12 @@ odoo.define('kpi_dashboard.DashboardRenderer', function (require) { 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( @@ -30,7 +36,20 @@ odoo.define('kpi_dashboard.DashboardRenderer', function (require) { '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); }); @@ -59,6 +78,10 @@ odoo.define('kpi_dashboard.DashboardRenderer', function (require) { 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 () { diff --git a/kpi_dashboard/static/src/js/widget/counter_widget.js b/kpi_dashboard/static/src/js/widget/counter_widget.js new file mode 100644 index 00000000..6005298f --- /dev/null +++ b/kpi_dashboard/static/src/js/widget/counter_widget.js @@ -0,0 +1,14 @@ +odoo.define('kpi_dashboard.CounterWidget', 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 CounterWidget = IntegerWidget.extend({ + shortList: [], + }); + + registry.add('counter', CounterWidget); + return CounterWidget; +}); diff --git a/kpi_dashboard/static/src/js/widget/integer_widget.js b/kpi_dashboard/static/src/js/widget/integer_widget.js new file mode 100644 index 00000000..7b9354d6 --- /dev/null +++ b/kpi_dashboard/static/src/js/widget/integer_widget.js @@ -0,0 +1,71 @@ +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; + _.each(this.shortList, function (shortItem) { + if (!shortened && Math.abs(num) >= shortItem[0]) { + shortened = true; + suffix = shortItem[1]; + num = num / shortItem[0]; + digits = shortItem[2]; + } + }); + return field_utils.format.float(num, 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; +}); diff --git a/kpi_dashboard/static/src/js/widget/number_widget.js b/kpi_dashboard/static/src/js/widget/number_widget.js index 987c612e..569f8cf2 100644 --- a/kpi_dashboard/static/src/js/widget/number_widget.js +++ b/kpi_dashboard/static/src/js/widget/number_widget.js @@ -1,72 +1,21 @@ odoo.define('kpi_dashboard.NumberWidget', function (require) { "use strict"; - var AbstractWidget = require('kpi_dashboard.AbstractWidget'); + var IntegerWidget = require('kpi_dashboard.IntegerWidget'); var registry = require('kpi_dashboard.widget_registry'); var field_utils = require('web.field_utils'); - - var NumberWidget = AbstractWidget.extend({ - template: 'kpi_dashboard.number', + var NumberWidget = IntegerWidget.extend({ + digits: [3, 1], shortNumber: function (num) { - if (Math.abs(num) >= 1000000000000) { - return field_utils.format.integer(num / 1000000000000, false, { - digits: [3, 1]}) + 'T'; - } - if (Math.abs(num) >= 1000000000) { - return field_utils.format.integer(num / 1000000000, false, { - digits: [3,1]}) + 'G'; - } - if (Math.abs(num) >= 1000000) { - return field_utils.format.integer(num / 1000000, false, { - digits: [3, 1]}) + 'M'; - } - if (Math.abs(num) >= 1000) { - return field_utils.format.float(num / 1000, false, { - digits: [3, 1]}) + 'K'; - } - if (Math.abs(num) >= 10) { + if (Math.abs(num) < 10) { return field_utils.format.float(num, false, { - digits: [3, 1]}); - } - return field_utils.format.float(num, false, { - digits: [3, 2]}); - }, - fillWidget: function (values) { - var widget = this.$el; - var value = values.value.value; - if (value === undefined) { - value = 0; - } - var item = widget.find('[data-bind="value"]'); - if (item) { - item.text(this.shortNumber(value)); - } - var previous = values.value.previous; - - var $change_rate = widget.find('.change-rate'); - if (previous === undefined) { - $change_rate.toggleClass('active', false); - } else { - var difference = 0; - if (previous !== 0) { - difference = field_utils.format.integer( - (100 * value / previous) - 100) + '%'; - } - $change_rate.toggleClass('active', true); - var $difference = widget.find('[data-bind="difference"]'); - $difference.text(difference); - var $arrow = widget.find('[data-bind="arrow"]'); - if (value < previous) { - $arrow.toggleClass('fa-arrow-up', false); - $arrow.toggleClass('fa-arrow-down', true); - } else { - $arrow.toggleClass('fa-arrow-up', true); - $arrow.toggleClass('fa-arrow-down', false); - } + digits: [3, 2]}); } + return this._super.apply(this, arguments) }, }); + registry.add('number', NumberWidget); return NumberWidget; }); diff --git a/kpi_dashboard/views/webclient_templates.xml b/kpi_dashboard/templates/assets.xml similarity index 87% rename from kpi_dashboard/views/webclient_templates.xml rename to kpi_dashboard/templates/assets.xml index e4263077..87b5e141 100644 --- a/kpi_dashboard/views/webclient_templates.xml +++ b/kpi_dashboard/templates/assets.xml @@ -14,7 +14,9 @@