From 507846838f4a40d9fada5fdd5c2527339581394c Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Thu, 8 Jan 2015 19:06:57 +0200 Subject: [PATCH 01/19] upload "dashboard" modules --- __init__.py | 1 + __openerp__.py | 19 +++ models.py | 220 +++++++++++++++++++++++++++++++++++ security/ir.model.access.csv | 2 + static/src/css/main.css | 23 ++++ static/src/js/main.js | 115 ++++++++++++++++++ static/src/xml/main.xml | 122 +++++++++++++++++++ views.xml | 103 ++++++++++++++++ 8 files changed, 605 insertions(+) create mode 100644 __init__.py create mode 100644 __openerp__.py create mode 100644 models.py create mode 100644 security/ir.model.access.csv create mode 100644 static/src/css/main.css create mode 100644 static/src/js/main.js create mode 100644 static/src/xml/main.xml create mode 100644 views.xml diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..bff786c --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +import models diff --git a/__openerp__.py b/__openerp__.py new file mode 100644 index 0000000..fbb65c2 --- /dev/null +++ b/__openerp__.py @@ -0,0 +1,19 @@ +{ + 'name' : 'Extra Widgets for mail wall', + 'version' : '1.0.0', + 'author' : 'Ivan Yelizariev', + 'category' : 'Custom', + 'website' : 'https://it-projects.info', + 'description': """ + +Tested on odoo 8.0 ab7b5d7732a7c222a0aea45bd173742acd47242d + """, + 'depends' : ['mail','gamification'], + 'data':[ + 'views.xml', + #'data.xml', + 'security/ir.model.access.csv', + ], + 'qweb': ['static/src/xml/main.xml'], + 'installable': True, +} diff --git a/models.py b/models.py new file mode 100644 index 0000000..8a40d0d --- /dev/null +++ b/models.py @@ -0,0 +1,220 @@ +from openerp.osv import osv,fields as old_fields +from openerp import api, models, fields, tools +from openerp.tools.safe_eval import safe_eval +from openerp.addons.email_template.email_template import mako_template_env +import copy +from openerp.tools.translate import _ + +class mail_wall_widgets_widget(models.Model): + _name = 'mail.wall.widgets.widget' + _order = "sequence, id" + + _columns = { + 'name': old_fields.char('Name', required=True, translate=True), + 'type': old_fields.selection(string='Type', selection=[ + ('list', 'List'), + ('funnel', 'Funnel'), + ('slice', 'Slice'), + #('', ''), + #('', ''), + #('', ''), + #('', ''), + ], help=''' +Slice - use "domain" for total and "won_domain" for target + '''), + + 'description': old_fields.text('Description', translate=True), + 'group_ids': old_fields.many2many('res.groups', relation='mail_wall_widgets_widget_group', column1='widget_id', column2='group_id', string='Groups', help="User groups to show widget"), + 'model_id': old_fields.many2one('ir.model', string='Model', help='The model object for the field to evaluate'), + 'domain': old_fields.char("Filter Domain", help="Domain for filtering records. General rule, not user depending, e.g. [('state', '=', 'done')]. The expression can contain reference to 'user' which is a browse record of the current user if not in batch mode.", required=True), + 'limit': old_fields.integer('Limit', help='Limit count of records to show'), + 'order': old_fields.char('Order', help='Order of records to show'), + 'value_field_id': old_fields.many2one('ir.model.fields', + string='Value field', + help='The field containing the value of record'), + 'stage_field_id': old_fields.many2one('ir.model.fields', + string='Stage field', + help='Field to split records in funnel. It can be selection type or many2one (the later should have "sequence" field)'), + #'stage_field_domain': old_fields.many2one('ir.model.fields', + # string='Stage field domain', + # help='(for many2one stage_field_id) Domain to find stage objects'), + 'won_domain': old_fields.char('Won domain', + help='Domain to find won objects'), + 'field_date_id': old_fields.many2one('ir.model.fields', + string='Date Field', + help='The date to use for the time period evaluated'), + 'start_date': old_fields.date('Start Date'), + 'end_date': old_fields.date('End Date'), # no start and end = always active + 'content': old_fields.char('Line template', help='Mako template to show content'), + 'value_field_monetary': old_fields.boolean('Value is monetary'), + 'cache': old_fields.boolean('Cache'), + 'active': old_fields.boolean('Active'), + 'sequence': old_fields.integer('Sequence', help='Sequence number for ordering'), + } + _defaults = { + 'active': True, + 'cache': False, + 'limit': None, + 'order': None, + } + + @api.one + def get_data(self, user): + + domain = safe_eval(self.domain, {'user': user}) + won_domain = safe_eval(self.won_domain or '[]', {'user': user}) + + field_date_name = self.field_date_id and self.field_date_id.name + if self.start_date and field_date_name: + domain.append((field_date_name, '>=', self.start_date)) + if self.end_date and field_date_name: + domain.append((field_date_name, '<=', self.end_date)) + + res = { + 'name': self.name, + 'type': self.type, + 'model': self.model_id.model, + 'domain': str(domain), + } + obj = self.env[self.model_id.model] + if self.type == 'list': + res.update({ + 'more': self.limit and self.limit < obj.search_count(domain), + 'lines': [], + }) + for r in obj.search(domain, limit=self.limit, order=self.order): + mako = mako_template_env.from_string(tools.ustr(self.content)) + content = mako.render({'record':r}) + r_json = { + 'id': r.id, + #'fields': dict( (f,getattr(r,f)) for f in fields), + 'display_mode': 'progress', + 'state': 'inprogress', + 'completeness': 0, + 'name': content, + 'description': '', + } + if self.value_field_id: + r_json['current'] = getattr(r, self.value_field_id.name) + res['lines'].append(r_json) + elif self.type == 'funnel': + stage_ids = [] # [key] + for group in obj.read_group(domain, [], [self.stage_field_id.name]): + key = group[self.stage_field_id.name] + if isinstance(key, (list, tuple)): + key = key[0] + stage_ids.append(key) + + stages = [] # [{'name':Name, 'id': key}] + if self.stage_field_id.ttype == 'selection': + d = dict (self.stage_field_id.selection) + stages = [ {'id':id, 'name':d[id]} for id in stage_ids ] + else: # many2one + stage_model = self.stage_field_id.relation + for r in self.env[stage_model].browse(stage_ids): + stages.append({'id': r.id, 'name':r.name_get()[0][1]}) + + value_field_name = self.value_field_id.name + for stage in stages: + d = copy.copy(domain) + d.append( (self.stage_field_id.name, '=', stage['id']) ) + result = obj.read_group(d, [value_field_name], []) + stage['closed_value'] = result and result[0][value_field_name] or 0.0 + stage['domain'] = str(d) + + # won value + d = domain + won_domain + result = obj.read_group(domain, [value_field_name], []) + won = {'name': _('Won'), + 'id':'__won__', + 'closed_value': result and result[0][value_field_name] or 0.0 + } + stages.append(won) + cur = 0 + for stage in reversed(stages): + cur += stage['closed_value'] + stage['abs_value'] = cur + total_value = stages[0]['abs_value'] + precision = 0.1 + for s in stages: + s['rel_value'] = round(100*s['abs_value']/total_value/precision)*precision if total_value else 100 + # dummy fields + s['display_mode'] = 'progress' + s['monetary'] = 1 + + res['stages'] = stages + res['won'] = won + res['conversion_rate'] = stages[-1]['rel_value'] + elif self.type == 'slice': + value_field_name = self.value_field_id.name + for f,d in [('total', domain), ('won', won_domain)]: + result = obj.read_group(d, [value_field_name], []) + res[f] = result and result[0][value_field_name] or 0.0 + + res['domain'] = str(domain) + res['won_domain'] = str(won_domain) + + precision = 10 + total_value = res['total'] + res['slice'] = round(100*res['won']/res['total']/precision)*precision if res['total'] else 100 + # dummy fields + res['display_mode'] = 'progress' + res['monetary'] = self.value_field_monetary + return res + +class mail_wall_widgets_cache(models.Model): + _name = 'mail.wall.widgets.cache' + + cache = fields.Text('Cached data') + res_id = fields.Integer('Resource ID') + res_model = fields.Integer('Resource Model') + user_id = fields.Many2one('res.users') + +class res_users(models.Model): + _inherit = 'res.users' + + @api.v7 + def get_serialised_mail_wall_widgets_summary(self, cr, uid, excluded_categories=None, context=None): + return self._get_serialised_mail_wall_widgets_summary(cr, uid, uid, excluded_categories=excluded_categories, context=context)[0] + + @api.one + def _get_serialised_mail_wall_widgets_summary(self, excluded_categories=None): + """ + [ + { + 'id': ..., + 'model': ..., + 'currency': , + 'data': (depend on model) + }, + ] + """ + user = self.env.user + res = [] + model = 'mail.wall.widgets.widget' + domain = [('group_ids', 'in', user.groups_id.ids), ('active', '=', True)] + for widget in self.env[model].search(domain, order='sequence'): + if widget.cache: + #TODO + continue + res.append({ + 'model': model, + 'id': widget.id, + 'currency': user.company_id.currency_id.id, + 'data': widget.get_data(user)[0], + }) + return res + + #def get_challenge_suggestions(self, cr, uid, context=None): + # """Return the list of challenges suggested to the user""" + # challenge_info = [] + # challenge_obj = self.pool.get('mail_wall_widgets.challenge') + # challenge_ids = challenge_obj.search(cr, uid, [('invited_user_ids', 'in', uid), ('state', '=', 'inprogress')], context=context) + # for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context): + # values = { + # 'id': challenge.id, + # 'name': challenge.name, + # 'description': challenge.description, + # } + # challenge_info.append(values) + # return challenge_info diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100644 index 0000000..17465e5 --- /dev/null +++ b/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_mail_wall_widgets,mail.wall.widgets.widget,model_mail_wall_widgets_widget,,1,1,1,1 \ No newline at end of file diff --git a/static/src/css/main.css b/static/src/css/main.css new file mode 100644 index 0000000..da328f4 --- /dev/null +++ b/static/src/css/main.css @@ -0,0 +1,23 @@ +.openerp .oe_mail_wall .oe_mail_wall_aside .oe_mail_wall_widgets{ + background-color: #ededf6; +} +.oe_open_record:hover, .oe_open_record_list:hover{ + cursor:pointer; +} +.oe_goal_outer_box .more{ + text-align:center; +} + +.openerp .oe_mail_wall .oe_goal .oe_goals_list.funnel .oe_cell.oe_goal_current { + font-size: 150%; + font-weight: bold; + min-width: 65px; + padding: 0 5px; +} +.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell.oe_goal_current span.small { + font-weight: normal; + font-size: 70%; +} +.oe_open_record:hover, .oe_open_record_list_funnel:hover{ + cursor:pointer; +} diff --git a/static/src/js/main.js b/static/src/js/main.js new file mode 100644 index 0000000..de7edf1 --- /dev/null +++ b/static/src/js/main.js @@ -0,0 +1,115 @@ +openerp.mail_wall_widgets = function(instance) { + var QWeb = instance.web.qweb; + var _t = instance.web._t; + + instance.mail_wall_widgets.Sidebar = instance.web.Widget.extend({ + template: 'mail_wall_widgets.UserWallSidebar', + init: function (parent, action) { + var self = this; + this._super(parent, action); + this.deferred = $.Deferred(); + //$(document).off('keydown.klistener'); + this.widget_templates = { + 'mail.wall.widgets.widget': "mail_wall_widgets.Widget" + } + }, + events: { + 'click .oe_open_record': function(event){ + var $t = $(event.currentTarget); + this.do_action({ + 'name': _t('Details'), + 'type': 'ir.actions.act_window', + 'res_model': $t.parent().attr('data-model'), + 'res_id': parseInt($t.attr('data-id')), + 'target': 'current', + 'views': [[false, 'form'],[false, 'list']], + 'domain': $t.parent().attr('data-domain'), + }) + }, + 'click .oe_open_record_list': function(event){ + var $t = $(event.currentTarget); + this.do_action({ + 'name': _t('More...'), + 'type': 'ir.actions.act_window', + 'res_model': $t.parent().attr('data-model'), + 'target': 'current', + 'views': [[false, 'list'],[false, 'form']], + 'domain': $t.parent().attr('data-domain'), + }) + }, + 'click .oe_open_record_list_funnel': function(event){ + var $t = $(event.currentTarget); + this.do_action({ + 'name': _t('More...'), + 'type': 'ir.actions.act_window', + 'res_model': $t.parent().attr('data-model'), + 'target': 'current', + 'views': [[false, 'list'],[false, 'form']], + 'domain': $t.attr('data-domain'), + }) + }, + }, + start: function() { + var self = this; + this._super.apply(this, arguments); + self.get_widgets_info(); + }, + get_widgets_info: function() { + var self = this; + new instance.web.Model('res.users').call('get_serialised_mail_wall_widgets_summary', []).then(function(result) { + if (result.length === 0) { + self.$el.find(".oe_mail_wall_widgets").hide(); + } else { + self.$el.find(".oe_mail_wall_widgets").empty(); + _.each(result, function(item){ + var $item = $(QWeb.render(self.widget_templates[item.model], {info: item})); + self.render_money_fields($item); + //self.render_user_avatars($item); + self.$el.find('.oe_mail_wall_widgets').append($item); + }); + } + }); + }, + render_money_fields: function(item) { + var self = this; + self.dfm = new instance.web.form.DefaultFieldManager(self); + // Generate a FieldMonetary for each .oe_goal_field_monetary + item.find(".oe_goal_field_monetary").each(function() { + var currency_id = parseInt( $(this).attr('data-id'), 10); + money_field = new instance.web.form.FieldMonetary(self.dfm, { + attrs: { + modifiers: '{"readonly": true}' + } + }); + money_field.set('currency', currency_id); + money_field.get_currency_info(); + money_field.set('value', parseInt($(this).text(), 10)); + money_field.replace($(this)); + }); + }, + render_user_avatars: function(item) { + var self = this; + item.find(".oe_user_avatar").each(function() { + var user_id = parseInt( $(this).attr('data-id'), 10); + var url = instance.session.url('/web/binary/image', {model: 'res.users', field: 'image_small', id: user_id}); + $(this).attr("src", url); + }); + } + }); + + instance.web.WebClient.include({ + to_kitten: function() { + this._super(); + new instance.web.Model('mail_wall_widgets.badge').call('check_progress', []); + } + }); + + instance.mail.Wall.include({ + start: function() { + this._super(); + var sidebar = new instance.mail_wall_widgets.Sidebar(this); + sidebar.appendTo($('.oe_mail_wall_aside')); + }, + }); + +}; diff --git a/static/src/xml/main.xml b/static/src/xml/main.xml new file mode 100644 index 0000000..d43a562 --- /dev/null +++ b/static/src/xml/main.xml @@ -0,0 +1,122 @@ + + +
+
+
+
+ +
+
+ e +

+
+ + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+ More... +
+
+
+
+ +
+
+ +
+
+
%
+
+
+ - + +
+
+
+
+ +
+ +
+ +
+
+
%
+
+
+ from + +
+
+
+
+ + +
+
+
+ +
+
+ + Target: + + + Target: <= + + +
+
+
+
+
+ +
+
+
+ +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ +
diff --git a/views.xml b/views.xml new file mode 100644 index 0000000..70c3a6e --- /dev/null +++ b/views.xml @@ -0,0 +1,103 @@ + + + + + + + + Widgets + mail.wall.widgets.widget + tree,form + +

+ Click to create a Widget. +

+

+ Widget allows to show some information at the inbox +

+
+
+ + + Widget list view + mail.wall.widgets.widget + + + + + + + + + + + + Widget form view + mail.wall.widgets.widget + +
+ + +
+
+
+ + + Widget Search + mail.wall.widgets.widget + + + + + + + + + + + + + + +
+
From 354012203ebbebd17af536ceb9df344e6195c407 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Fri, 9 Jan 2015 12:35:12 +0200 Subject: [PATCH 02/19] [IMP] show count of records --- models.py | 4 +++- static/src/xml/main.xml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/models.py b/models.py index 8a40d0d..4223ad8 100644 --- a/models.py +++ b/models.py @@ -78,8 +78,10 @@ Slice - use "domain" for total and "won_domain" for target } obj = self.env[self.model_id.model] if self.type == 'list': + total_count = obj.search_count(domain) res.update({ - 'more': self.limit and self.limit < obj.search_count(domain), + 'more': self.limit and self.limit < total_count, + 'total_count': total_count, 'lines': [], }) for r in obj.search(domain, limit=self.limit, order=self.order): diff --git a/static/src/xml/main.xml b/static/src/xml/main.xml index d43a562..c4c1f3e 100644 --- a/static/src/xml/main.xml +++ b/static/src/xml/main.xml @@ -8,7 +8,7 @@
e -

+

()

From 6b58a5c0942de7eb5416c0fdf695c744acf09e61 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Wed, 28 Jan 2015 14:37:30 +0200 Subject: [PATCH 03/19] [IMP] precision --- models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/models.py b/models.py index 4223ad8..d3da657 100644 --- a/models.py +++ b/models.py @@ -51,6 +51,7 @@ Slice - use "domain" for total and "won_domain" for target 'active': old_fields.boolean('Active'), 'sequence': old_fields.integer('Sequence', help='Sequence number for ordering'), } + precision = fields.Float('Precision', help='round(Value/precision) * precision. E.g. 12345,333333 will be rounded to 12345,33 for precision=0.01, and to 12000 for precision=1000', default=0.01) _defaults = { 'active': True, 'cache': False, @@ -137,7 +138,7 @@ Slice - use "domain" for total and "won_domain" for target cur += stage['closed_value'] stage['abs_value'] = cur total_value = stages[0]['abs_value'] - precision = 0.1 + precision = self.precision for s in stages: s['rel_value'] = round(100*s['abs_value']/total_value/precision)*precision if total_value else 100 # dummy fields @@ -156,7 +157,7 @@ Slice - use "domain" for total and "won_domain" for target res['domain'] = str(domain) res['won_domain'] = str(won_domain) - precision = 10 + precision = self.precision total_value = res['total'] res['slice'] = round(100*res['won']/res['total']/precision)*precision if res['total'] else 100 # dummy fields From 39dfc872bbf7aea537efb7ad13952f3b8eda2da0 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Wed, 28 Jan 2015 15:05:25 +0200 Subject: [PATCH 04/19] [FIX] monetary fields --- models.py | 2 ++ static/src/css/main.css | 9 +++++++++ static/src/xml/main.xml | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/models.py b/models.py index d3da657..0148386 100644 --- a/models.py +++ b/models.py @@ -99,6 +99,8 @@ Slice - use "domain" for total and "won_domain" for target } if self.value_field_id: r_json['current'] = getattr(r, self.value_field_id.name) + if self.value_field_monetary: + r_json['monetary'] = 1 res['lines'].append(r_json) elif self.type == 'funnel': stage_ids = [] # [key] diff --git a/static/src/css/main.css b/static/src/css/main.css index da328f4..6658f16 100644 --- a/static/src/css/main.css +++ b/static/src/css/main.css @@ -14,6 +14,15 @@ min-width: 65px; padding: 0 5px; } +.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell.oe_goal_current .oe_form_field_monetary{ + font-weight: normal; + font-size: 70%; +} +.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell.oe_goal_current .oe_form_field_monetary .oe_form_char_content { + font-weight: bold; + font-size: 142%; /* 1/.7 */ +} + .openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell.oe_goal_current span.small { font-weight: normal; font-size: 70%; diff --git a/static/src/xml/main.xml b/static/src/xml/main.xml index c4c1f3e..0eea01b 100644 --- a/static/src/xml/main.xml +++ b/static/src/xml/main.xml @@ -17,7 +17,7 @@
-
+
From 42819fe077fd039af0bccbe3dcc6cc7e557c6685 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Wed, 28 Jan 2015 18:10:23 +0200 Subject: [PATCH 05/19] [FIX] rounding --- models.py | 1 + static/src/css/main.css | 1 + static/src/js/main.js | 28 +++++++++++++++++++++++++--- static/src/xml/main.xml | 8 ++++---- views.xml | 1 + 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/models.py b/models.py index 0148386..f63c642 100644 --- a/models.py +++ b/models.py @@ -76,6 +76,7 @@ Slice - use "domain" for total and "won_domain" for target 'type': self.type, 'model': self.model_id.model, 'domain': str(domain), + 'precision': self.precision, } obj = self.env[self.model_id.model] if self.type == 'list': diff --git a/static/src/css/main.css b/static/src/css/main.css index 6658f16..add6083 100644 --- a/static/src/css/main.css +++ b/static/src/css/main.css @@ -17,6 +17,7 @@ .openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell.oe_goal_current .oe_form_field_monetary{ font-weight: normal; font-size: 70%; + white-space: nowrap; } .openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell.oe_goal_current .oe_form_field_monetary .oe_form_char_content { font-weight: bold; diff --git a/static/src/js/main.js b/static/src/js/main.js index de7edf1..a4ac00a 100644 --- a/static/src/js/main.js +++ b/static/src/js/main.js @@ -64,6 +64,7 @@ openerp.mail_wall_widgets = function(instance) { _.each(result, function(item){ var $item = $(QWeb.render(self.widget_templates[item.model], {info: item})); self.render_money_fields($item); + self.render_float_fields($item); //self.render_user_avatars($item); self.$el.find('.oe_mail_wall_widgets').append($item); }); @@ -76,17 +77,38 @@ openerp.mail_wall_widgets = function(instance) { // Generate a FieldMonetary for each .oe_goal_field_monetary item.find(".oe_goal_field_monetary").each(function() { var currency_id = parseInt( $(this).attr('data-id'), 10); - money_field = new instance.web.form.FieldMonetary(self.dfm, { + var precision = parseFloat( $(this).attr('data-precision') , 10) || 1; + var digits = [69,0]; + if (precision && precision<1) + digits[1] = ($(this).attr('data-precision') || '0.01').slice(2).indexOf('1')+1; + var money_field = new instance.web.form.FieldMonetary(self.dfm, { attrs: { - modifiers: '{"readonly": true}' + 'modifiers': '{"readonly": true}', + 'digits': digits } }); money_field.set('currency', currency_id); money_field.get_currency_info(); - money_field.set('value', parseInt($(this).text(), 10)); + money_field.set('value', parseInt(parseFloat($(this).text(), 10)/precision)*precision); money_field.replace($(this)); }); }, + render_float_fields: function(item) { + var self = this; + // Generate a FieldMonetary for each .oe_goal_field_monetary + item.find(".oe_goal_field_float").each(function() { + var value = $(this).text(); + if (!value) + return; + var precision = parseFloat( $(this).attr('data-precision'), 10) || 1; + var digits = [69,0]; + if (precision && precision<1) + digits[1] = ($(this).attr('data-precision') || '0.01').slice(2).indexOf('1')+1; + + value = instance.web.format_value(parseFloat(value), {type: "float", digits: digits}, '') + $(this).text(value) + }); + }, render_user_avatars: function(item) { var self = this; item.find(".oe_user_avatar").each(function() { diff --git a/static/src/xml/main.xml b/static/src/xml/main.xml index 0eea01b..5b632ae 100644 --- a/static/src/xml/main.xml +++ b/static/src/xml/main.xml @@ -17,7 +17,7 @@
-
+
@@ -37,10 +37,10 @@
-
%
+
%
- - + -
@@ -56,7 +56,7 @@
%
- from + from
diff --git a/views.xml b/views.xml index 70c3a6e..74ef54d 100644 --- a/views.xml +++ b/views.xml @@ -69,6 +69,7 @@ + From 39cee912d81d79759afab328475b969cdddd1f79 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Wed, 28 Jan 2015 18:29:59 +0200 Subject: [PATCH 06/19] [FIX] css --- static/src/css/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/static/src/css/main.css b/static/src/css/main.css index add6083..3b19118 100644 --- a/static/src/css/main.css +++ b/static/src/css/main.css @@ -6,6 +6,7 @@ } .oe_goal_outer_box .more{ text-align:center; + padding: 10px 5px; } .openerp .oe_mail_wall .oe_goal .oe_goals_list.funnel .oe_cell.oe_goal_current { From ad1e313413304b398d41a9a83352103641ab04e7 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Fri, 30 Jan 2015 17:10:21 +0200 Subject: [PATCH 07/19] [IMP] sort record list as daily agenda --- models.py | 53 ++++++++++++++++++++++++++++++++++++++--- static/src/css/main.css | 24 +++++++++++++++++++ static/src/js/main.js | 4 ++-- static/src/xml/main.xml | 11 ++++++++- views.xml | 3 ++- 5 files changed, 88 insertions(+), 7 deletions(-) diff --git a/models.py b/models.py index f63c642..29fe703 100644 --- a/models.py +++ b/models.py @@ -4,6 +4,9 @@ from openerp.tools.safe_eval import safe_eval from openerp.addons.email_template.email_template import mako_template_env import copy from openerp.tools.translate import _ +from datetime import date, datetime, timedelta +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT class mail_wall_widgets_widget(models.Model): _name = 'mail.wall.widgets.widget' @@ -22,7 +25,6 @@ class mail_wall_widgets_widget(models.Model): ], help=''' Slice - use "domain" for total and "won_domain" for target '''), - 'description': old_fields.text('Description', translate=True), 'group_ids': old_fields.many2many('res.groups', relation='mail_wall_widgets_widget_group', column1='widget_id', column2='group_id', string='Groups', help="User groups to show widget"), 'model_id': old_fields.many2one('ir.model', string='Model', help='The model object for the field to evaluate'), @@ -52,6 +54,7 @@ Slice - use "domain" for total and "won_domain" for target 'sequence': old_fields.integer('Sequence', help='Sequence number for ordering'), } precision = fields.Float('Precision', help='round(Value/precision) * precision. E.g. 12345,333333 will be rounded to 12345,33 for precision=0.01, and to 12000 for precision=1000', default=0.01) + agenda = fields.Boolean('Agenda', help='Split records by date: overdue, today, tomorrow, later') _defaults = { 'active': True, 'cache': False, @@ -81,10 +84,49 @@ Slice - use "domain" for total and "won_domain" for target obj = self.env[self.model_id.model] if self.type == 'list': total_count = obj.search_count(domain) + groups = [{'test': lambda r: True}] + if self.agenda: + today = date.today() + tomorrow = today + timedelta(days=1) + def r2date(r): + d = getattr(r, field_date_name) + d = datetime.strptime(d, self.field_date_id.ttype=='date' and DEFAULT_SERVER_DATE_FORMAT or DEFAULT_SERVER_DATETIME_FORMAT) + d = d.date() + return d + groups = [ + { + 'label': _('Overdue'), + 'class': 'overdue', + 'test': lambda r: r2date(r) < today, + 'mandatory': False, + }, + { + 'label': _('Today'), + 'class': 'today', + 'test': lambda r: r2date(r) == today, + 'mandatory': True, + }, + { + 'label': _('Tomorrow'), + 'class': 'tomorrow', + 'test': lambda r: r2date(r) == tomorrow, + 'mandatory': False, + }, + { + 'label': _('Later'), + 'class': 'later', + 'test': lambda r: r2date(r) == later, + 'mandatory': False, + }, + ] + for g in groups: + g['lines'] = [] + res.update({ 'more': self.limit and self.limit < total_count, 'total_count': total_count, - 'lines': [], + 'agenda': self.agenda, + 'groups': groups, }) for r in obj.search(domain, limit=self.limit, order=self.order): mako = mako_template_env.from_string(tools.ustr(self.content)) @@ -102,7 +144,12 @@ Slice - use "domain" for total and "won_domain" for target r_json['current'] = getattr(r, self.value_field_id.name) if self.value_field_monetary: r_json['monetary'] = 1 - res['lines'].append(r_json) + for g in groups: + if g['test'](r): + g['lines'].append(r_json) + break + for g in groups: + del g['test'] elif self.type == 'funnel': stage_ids = [] # [key] for group in obj.read_group(domain, [], [self.stage_field_id.name]): diff --git a/static/src/css/main.css b/static/src/css/main.css index 3b19118..ffd78b8 100644 --- a/static/src/css/main.css +++ b/static/src/css/main.css @@ -32,3 +32,27 @@ .oe_open_record:hover, .oe_open_record_list_funnel:hover{ cursor:pointer; } + +.openerp .oe_mail_wall .list_group{ + margin-bottom:0.6em; +} +.openerp .oe_mail_wall .list_group .group_label{ + padding: 0 0 0.1em 1em; + font-size:120%; +} +.openerp .oe_mail_wall .list_group.overdue .group_label{ + color:red; +} +.openerp .oe_mail_wall .list_group.today .group_label{ + font-weight:bold; +} +.openerp .oe_mail_wall .list_group.later .group_label{ + color:grey; +} + +.openerp .oe_mail_wall .list_group .empty_list{ + background-color:white; + padding:10px 0; + color:grey; + text-align:center; +} \ No newline at end of file diff --git a/static/src/js/main.js b/static/src/js/main.js index a4ac00a..1e96555 100644 --- a/static/src/js/main.js +++ b/static/src/js/main.js @@ -19,11 +19,11 @@ openerp.mail_wall_widgets = function(instance) { this.do_action({ 'name': _t('Details'), 'type': 'ir.actions.act_window', - 'res_model': $t.parent().attr('data-model'), + 'res_model': $t.parent().parent().attr('data-model'), 'res_id': parseInt($t.attr('data-id')), 'target': 'current', 'views': [[false, 'form'],[false, 'list']], - 'domain': $t.parent().attr('data-domain'), + 'domain': $t.parent().parent().attr('data-domain'), }) }, 'click .oe_open_record_list': function(event){ diff --git a/static/src/xml/main.xml b/static/src/xml/main.xml index 5b632ae..c9de717 100644 --- a/static/src/xml/main.xml +++ b/static/src/xml/main.xml @@ -13,7 +13,11 @@
-
+
+
+ +
+
@@ -23,6 +27,11 @@
+ +
No records
+
+
+
diff --git a/views.xml b/views.xml index 74ef54d..2c2c4e8 100644 --- a/views.xml +++ b/views.xml @@ -58,7 +58,7 @@ - + @@ -73,6 +73,7 @@ + From a4ab50df89c3761c49a966242a054531f0716959 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Fri, 30 Jan 2015 17:16:51 +0200 Subject: [PATCH 08/19] [FIX] --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models.py b/models.py index 29fe703..82b6863 100644 --- a/models.py +++ b/models.py @@ -115,7 +115,7 @@ Slice - use "domain" for total and "won_domain" for target { 'label': _('Later'), 'class': 'later', - 'test': lambda r: r2date(r) == later, + 'test': lambda r: r2date(r) > tomorrow, 'mandatory': False, }, ] From 1dc395bb8aadc957c66e4eba6f685d0f4e17580f Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Fri, 30 Jan 2015 20:15:53 +0200 Subject: [PATCH 09/19] [FIX] floating blocks --- static/src/css/main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/src/css/main.css b/static/src/css/main.css index ffd78b8..d89196d 100644 --- a/static/src/css/main.css +++ b/static/src/css/main.css @@ -1,5 +1,5 @@ .openerp .oe_mail_wall .oe_mail_wall_aside .oe_mail_wall_widgets{ - background-color: #ededf6; + background-color:inherit; } .oe_open_record:hover, .oe_open_record_list:hover{ cursor:pointer; From 35f0d84609304f82fe140c40b3e12c09c166ec87 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Mon, 2 Feb 2015 12:13:08 +0200 Subject: [PATCH 10/19] [FIX] oldest lead [FIX] at mail_wall_widgets: date = date or today --- models.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/models.py b/models.py index 82b6863..1cf64c6 100644 --- a/models.py +++ b/models.py @@ -90,8 +90,11 @@ Slice - use "domain" for total and "won_domain" for target tomorrow = today + timedelta(days=1) def r2date(r): d = getattr(r, field_date_name) - d = datetime.strptime(d, self.field_date_id.ttype=='date' and DEFAULT_SERVER_DATE_FORMAT or DEFAULT_SERVER_DATETIME_FORMAT) - d = d.date() + if d: + d = datetime.strptime(d, self.field_date_id.ttype=='date' and DEFAULT_SERVER_DATE_FORMAT or DEFAULT_SERVER_DATETIME_FORMAT) + d = d.date() + else: + d = date.today() return d groups = [ { From 86a5afb2fd9b14c5d560e6e07b5c83ee743a9a20 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Mon, 2 Feb 2015 13:25:50 +0200 Subject: [PATCH 11/19] [IMP] cache currency info --- static/src/js/main.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/static/src/js/main.js b/static/src/js/main.js index 1e96555..1980093 100644 --- a/static/src/js/main.js +++ b/static/src/js/main.js @@ -8,6 +8,9 @@ openerp.mail_wall_widgets = function(instance) { var self = this; this._super(parent, action); this.deferred = $.Deferred(); + self.money_df = $.Deferred() + self.money_df.resolve(); + self.money_cache = {} //$(document).off('keydown.klistener'); this.widget_templates = { 'mail.wall.widgets.widget': "mail_wall_widgets.Widget" @@ -56,6 +59,7 @@ openerp.mail_wall_widgets = function(instance) { }, get_widgets_info: function() { var self = this; + self.dfm = new instance.web.form.DefaultFieldManager(self); new instance.web.Model('res.users').call('get_serialised_mail_wall_widgets_summary', []).then(function(result) { if (result.length === 0) { self.$el.find(".oe_mail_wall_widgets").hide(); @@ -73,7 +77,6 @@ openerp.mail_wall_widgets = function(instance) { }, render_money_fields: function(item) { var self = this; - self.dfm = new instance.web.form.DefaultFieldManager(self); // Generate a FieldMonetary for each .oe_goal_field_monetary item.find(".oe_goal_field_monetary").each(function() { var currency_id = parseInt( $(this).attr('data-id'), 10); @@ -88,9 +91,24 @@ openerp.mail_wall_widgets = function(instance) { } }); money_field.set('currency', currency_id); - money_field.get_currency_info(); money_field.set('value', parseInt(parseFloat($(this).text(), 10)/precision)*precision); money_field.replace($(this)); + + self.money_df = + self.money_df.then(function(){ + var callback = function(){ + money_field.set({'currency_info': self.money_cache[currency_id]}) + } + if (self.money_cache[currency_id]){ + callback(); + return; + } + var req = new instance.web.Model("res.currency").query(["symbol", "position"]).filter([["id", "=", currency_id]]).first() + return req.then(function(res){ + self.money_cache[currency_id] = res; + callback(); + }) + }) }); }, render_float_fields: function(item) { From a33366693823115d4b53427e86ee7383075b1551 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Mon, 2 Feb 2015 13:53:31 +0200 Subject: [PATCH 12/19] [FIX] cache currency info --- static/src/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/src/js/main.js b/static/src/js/main.js index 1980093..ac91048 100644 --- a/static/src/js/main.js +++ b/static/src/js/main.js @@ -90,7 +90,7 @@ openerp.mail_wall_widgets = function(instance) { 'digits': digits } }); - money_field.set('currency', currency_id); + //money_field.set('currency', currency_id); money_field.set('value', parseInt(parseFloat($(this).text(), 10)/precision)*precision); money_field.replace($(this)); From ff058f7bb921ac20b8893d395fc2d086f23b199e Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Thu, 19 Feb 2015 19:36:02 +0200 Subject: [PATCH 13/19] new website https://yelizariev.github.io/ --- __openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__openerp__.py b/__openerp__.py index fbb65c2..31a2ce3 100644 --- a/__openerp__.py +++ b/__openerp__.py @@ -3,7 +3,7 @@ 'version' : '1.0.0', 'author' : 'Ivan Yelizariev', 'category' : 'Custom', - 'website' : 'https://it-projects.info', + 'website' : 'https://yelizariev.github.io', 'description': """ Tested on odoo 8.0 ab7b5d7732a7c222a0aea45bd173742acd47242d From 601c888c71b75c0e1682c22927e1bf2f694c43dd Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Thu, 2 Jul 2015 16:35:12 +0500 Subject: [PATCH 14/19] [DOC] add icon --- static/description/icon.png | Bin 0 -> 2140 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/description/icon.png diff --git a/static/description/icon.png b/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..79f7d8fe294f838cf2996940c40dc08dde60642f GIT binary patch literal 2140 zcmV-i2&4CjP)=D+Wys1fe}G$Q#Z2832St25n<}h#$%L7sUBHn zgVlR~4gtNOs+KA#@5q|?_&of{fCW3h;+eI-@is^))^C>Ap=O06Yq-$?c7APo?&VKv z))(zizAhAwtYInZ&irDhM>SGBFM>?d${LzoxNVMo=er+dj|dVX82(itvPy&nJI7R} zxal|{b!VXeY~=?NW24HG!g#W&q%^mz9C_k)O~vB$l!nR>c7Azkc41B0?h`G%re>$6 zR3XK_wP$L$`wA`+vfQ*It2kJ&bC8N#=Y*z`?T#poH4<6rMKBSth@G$Anh#P6geeX3 z66#s7^ZuFn%%U9v!&L5`a;i`|jy4C6tV1;wvTsBRf(1LrR7N-{7FL;Ru=vP|@loCg zW(jHMC_PwJ+DqF+1@u$zm8o8m$Y1m0V>0T?5;C37EIq_^W|6qn&f)G(Em&B}uDzZf z*)+}sw{t=Y4awlQ*0UI=qn@0G^f!D}&U?lzXXnrqYS25*U`_+Qpa)L`slkiwNPnpt zbLhf?onzn&_-r^=Y+@25re#c%ToI(Yb5h;e6h}ke&V&2_vM3~m`<859t@^AmkXN#4 zp3=nL641^=Hb@NJ0y10#e8NYUd#FM=yd}$j-ZOZiJuPn=y})eY?Y#H?ythe(9+=(P z=pO{n_Ag1zK#!ilNhilfXs;Am_;fC9#?QGvtNfjZ8h);==7&rFJ5QsnusaH>>SjHY zMC`QRjtOGt!fz}(jiG1vi5A$ZVuAvl9f!Vzpm3p#doq~Eg#RkU_3sSy1kxiU%8{*-oYZcOz=#QLgi{iP%6NR;q2W-(ziFHtAxczhQ%>g55VsxmVxu{OgA(z8>)V_eL6l3DJe$G5=tJp1|FDThBVI=FE7j=3tSDuEI+K+>VJ0yjG=W@S`oh zY;UN?#paSycm6BhAH$yR>Hg=JAHz8+5-u#Cw5f387dA))IagN=LE)YiRtGqa4{&&- z?Akk2Q!(?$O{HOxfl1vpCei`ivon?ZK`rFDC~Jz{*?3agX(WqeLDAmP6g%rA0{0<> zeqtzKYj)#fyZMfN2OiuS5UVdK(E;HAX;YO|@Z}oGtA&1=LO(l>HnWdoj~#w9CnQPu zz$YVGP~7xI^+ijcUcstRfTEHe{7dZ7Q=w&u@tA}I)g3C3b#`)vCi?y*=jMs3M4_;7 z>sC2k0T-cEZ{)}r0)&rGk$e2;VuY_meg`{ogL|Bk!JluaKhH%NZ8kn?5uft;?gWym z*MCY|fYs06&r)EvXweNU2#O3lwsaSN3LjB|<)5GbI&(^F8Ja$Saf5Dn zfe8N*NS_HTjLe(YbWiuKO3@!C&=suZc=@z@*Skh)DrGicOE-M=A%!7Zj9>aSC(&v#nWVj-o?-d4Br_8Z zDP85P;LTw*XT(K^Ln=~W$+dIA7^DnX@{`gzERF6PYOuNDqSwU8{M+dh76b)Z$^Np8 zK`L@#(IcfdSX4>r$FmU4GvlLe(Pp~uq7PD-^kM2p`;8cbRHVRCk`&s%0t^5+Kg1eL SSUIi$0000 Date: Wed, 22 Jul 2015 17:34:44 +0500 Subject: [PATCH 15/19] [FIX] put import in try block --- models.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/models.py b/models.py index 1cf64c6..1f8f600 100644 --- a/models.py +++ b/models.py @@ -1,7 +1,14 @@ from openerp.osv import osv,fields as old_fields from openerp import api, models, fields, tools from openerp.tools.safe_eval import safe_eval -from openerp.addons.email_template.email_template import mako_template_env +try: + from openerp.addons.email_template.email_template import mako_template_env +except ImportError: + try: + from openerp.addons.mail.mail_template import mako_template_env + except ImportError: + pass + import copy from openerp.tools.translate import _ from datetime import date, datetime, timedelta From fb4681898438dc95b7b9940ce0e60509d3888022 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Sun, 27 Sep 2015 10:22:56 +0500 Subject: [PATCH 16/19] [DOC] add IT-Projects LLC to authors --- __openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__openerp__.py b/__openerp__.py index 31a2ce3..2476aec 100644 --- a/__openerp__.py +++ b/__openerp__.py @@ -1,7 +1,7 @@ { 'name' : 'Extra Widgets for mail wall', 'version' : '1.0.0', - 'author' : 'Ivan Yelizariev', + 'author' : 'IT-Projects LLC, Ivan Yelizariev', 'category' : 'Custom', 'website' : 'https://yelizariev.github.io', 'description': """ From f374e4df03d789046858ef9f0b538be9011e2caf Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Tue, 3 Nov 2015 10:22:20 +0500 Subject: [PATCH 17/19] [DOC] add license tag --- __openerp__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/__openerp__.py b/__openerp__.py index 2476aec..780a9ac 100644 --- a/__openerp__.py +++ b/__openerp__.py @@ -2,6 +2,7 @@ 'name' : 'Extra Widgets for mail wall', 'version' : '1.0.0', 'author' : 'IT-Projects LLC, Ivan Yelizariev', + 'license': 'LGPL-3', 'category' : 'Custom', 'website' : 'https://yelizariev.github.io', 'description': """ From 126cce4c7ef2673d92d80234c121775af10ceec6 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Mon, 9 Nov 2015 11:26:31 +0500 Subject: [PATCH 18/19] update license to GPL-3 --- __openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__openerp__.py b/__openerp__.py index 780a9ac..ff6e779 100644 --- a/__openerp__.py +++ b/__openerp__.py @@ -2,7 +2,7 @@ 'name' : 'Extra Widgets for mail wall', 'version' : '1.0.0', 'author' : 'IT-Projects LLC, Ivan Yelizariev', - 'license': 'LGPL-3', + 'license': 'GPL-3', 'category' : 'Custom', 'website' : 'https://yelizariev.github.io', 'description': """ From f637a780c9db09f1f60a06923c10b2ce52fe0b72 Mon Sep 17 00:00:00 2001 From: Ildar Nasyrov Date: Sun, 27 Mar 2016 16:24:15 +0500 Subject: [PATCH 19/19] [MOV] module -- mail_wall_widgets --- __init__.py => mail_wall_widgets/__init__.py | 0 __openerp__.py => mail_wall_widgets/__openerp__.py | 0 models.py => mail_wall_widgets/models.py | 0 .../security}/ir.model.access.csv | 0 .../static}/description/icon.png | Bin .../static}/src/css/main.css | 0 {static => mail_wall_widgets/static}/src/js/main.js | 0 .../static}/src/xml/main.xml | 0 views.xml => mail_wall_widgets/views.xml | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename __init__.py => mail_wall_widgets/__init__.py (100%) rename __openerp__.py => mail_wall_widgets/__openerp__.py (100%) rename models.py => mail_wall_widgets/models.py (100%) rename {security => mail_wall_widgets/security}/ir.model.access.csv (100%) rename {static => mail_wall_widgets/static}/description/icon.png (100%) rename {static => mail_wall_widgets/static}/src/css/main.css (100%) rename {static => mail_wall_widgets/static}/src/js/main.js (100%) rename {static => mail_wall_widgets/static}/src/xml/main.xml (100%) rename views.xml => mail_wall_widgets/views.xml (100%) diff --git a/__init__.py b/mail_wall_widgets/__init__.py similarity index 100% rename from __init__.py rename to mail_wall_widgets/__init__.py diff --git a/__openerp__.py b/mail_wall_widgets/__openerp__.py similarity index 100% rename from __openerp__.py rename to mail_wall_widgets/__openerp__.py diff --git a/models.py b/mail_wall_widgets/models.py similarity index 100% rename from models.py rename to mail_wall_widgets/models.py diff --git a/security/ir.model.access.csv b/mail_wall_widgets/security/ir.model.access.csv similarity index 100% rename from security/ir.model.access.csv rename to mail_wall_widgets/security/ir.model.access.csv diff --git a/static/description/icon.png b/mail_wall_widgets/static/description/icon.png similarity index 100% rename from static/description/icon.png rename to mail_wall_widgets/static/description/icon.png diff --git a/static/src/css/main.css b/mail_wall_widgets/static/src/css/main.css similarity index 100% rename from static/src/css/main.css rename to mail_wall_widgets/static/src/css/main.css diff --git a/static/src/js/main.js b/mail_wall_widgets/static/src/js/main.js similarity index 100% rename from static/src/js/main.js rename to mail_wall_widgets/static/src/js/main.js diff --git a/static/src/xml/main.xml b/mail_wall_widgets/static/src/xml/main.xml similarity index 100% rename from static/src/xml/main.xml rename to mail_wall_widgets/static/src/xml/main.xml diff --git a/views.xml b/mail_wall_widgets/views.xml similarity index 100% rename from views.xml rename to mail_wall_widgets/views.xml