From bb17b02de508526a90172def7f299636a655eb24 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Thu, 6 Jun 2013 14:47:08 +0200 Subject: [PATCH] [IMP] gamification: more flexible domain, using email_templates bzr revid: mat@openerp.com-20130606124708-3occhsgwkaxwzyp3 --- addons/gamification/__openerp__.py | 2 +- addons/gamification/badge.py | 16 ++- addons/gamification/badge_data.xml | 18 +++ addons/gamification/goal.py | 60 +++++----- addons/gamification/goal_base_data.xml | 110 +++++++++++++++++- addons/gamification/plan.py | 23 ++-- addons/gamification/res_users.py | 24 +++- addons/gamification/templates.py | 1 - addons/gamification/templates/reminder.mako | 3 +- .../gamification_sale_crm/sale_crm_goals.xml | 22 ++-- 10 files changed, 222 insertions(+), 57 deletions(-) diff --git a/addons/gamification/__openerp__.py b/addons/gamification/__openerp__.py index 0143f266d35..aaa4565cf43 100644 --- a/addons/gamification/__openerp__.py +++ b/addons/gamification/__openerp__.py @@ -23,7 +23,7 @@ 'version': '1.0', 'author': 'OpenERP SA', 'category': 'Human Ressources', - 'depends': ['mail'], + 'depends': ['mail', 'email_template'], 'description': """ Gamification process ==================== diff --git a/addons/gamification/badge.py b/addons/gamification/badge.py index 53e7eb84b05..2c44fd2f182 100644 --- a/addons/gamification/badge.py +++ b/addons/gamification/badge.py @@ -23,7 +23,7 @@ from openerp.osv import fields, osv from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF from openerp.tools.translate import _ -from templates import TemplateHelper +# from templates import TemplateHelper from datetime import date import logging @@ -194,12 +194,20 @@ class gamification_badge(osv.Model): :param user_from: optional id of the res.users object that has sent the badge """ badge = self.browse(cr, uid, badge_id, context=context) - template_env = TemplateHelper() + # template_env = TemplateHelper() res = None + temp_obj = self.pool.get('email.template') + template_id = self.pool['ir.model.data'].get_object(cr, uid, 'gamification', 'email_template_badge_received', context) + ctx = context.copy() for badge_user in self.pool.get('gamification.badge.user').browse(cr, uid, badge_user_ids, context=context): - values = {'badge_user': badge_user, 'user_from': user_from} - body_html = template_env.get_template('badge_received.mako').render(values) + + ctx.update({'user_from': self.pool.get('res.users').browse(cr, uid, user_from).name}) + + body_html = temp_obj.render_template(cr, uid, template_id.body_html, 'gamification.badge.user', badge_user.id, context=ctx) + + # values = {'badge_user': badge_user, 'user_from': user_from} + # body_html = template_env.get_template('badge_received.mako').render(values) res = self.message_post(cr, uid, badge.id, body=body_html, type='comment', subtype='mt_comment', context=context) return res diff --git a/addons/gamification/badge_data.xml b/addons/gamification/badge_data.xml index 8bd60d72dec..bedd9916c16 100644 --- a/addons/gamification/badge_data.xml +++ b/addons/gamification/badge_data.xml @@ -884,4 +884,22 @@ AABJRU5ErkJggg== + + + + + + Received Badge + Congratulation, you have received the badge ${object.badge_id.name} ! + % if ctx["user_from"] + This badge was granted by ${ctx["user_from"]}. + % endif +

+ + % if object.comment +

${object.comment}

+ % endif]]>
+
+
diff --git a/addons/gamification/goal.py b/addons/gamification/goal.py index 896b476f158..32a6bd55512 100644 --- a/addons/gamification/goal.py +++ b/addons/gamification/goal.py @@ -19,12 +19,12 @@ # ############################################################################## +from openerp import SUPERUSER_ID from openerp.osv import fields, osv +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF from openerp.tools.safe_eval import safe_eval from openerp.tools.translate import _ -from templates import TemplateHelper - from datetime import date, datetime, timedelta import logging @@ -88,8 +88,10 @@ class gamification_goal_type(osv.Model): 'field_date_id': fields.many2one('ir.model.fields', string='Date Field', help='The date to use for the time period evaluated'), + #TODO: actually it would be better to use 'user.id' in the domain definition, because it means user is a browse record and it's more flexible (i can do '[(country_id,=,user.partner_id.country_id.id)]) + 'domain': fields.char("Filter Domain", - help="Technical filters rules to apply", + help="Technical filters rules to apply. Use 'user.id' (without marks) to limit the search to the evaluated user.", required=True), 'compute_code': fields.char('Compute Code', help="The name of the python method that will be executed to compute the current value. See the file gamification/goal_type_data.py for examples."), @@ -146,8 +148,6 @@ class gamification_goal(osv.Model): _columns = { 'type_id': fields.many2one('gamification.goal.type', string='Goal Type', required=True, ondelete="cascade"), - #TODO: add some tooltip/grey label in view to explain that people can use 'user_id' in their domain - #TODO: actually it would be better to use 'user.id' in the domain definition, because it means user is a browse record and it's more flexible (i can do '[(country_id,=,user.partner_id.country_id.id)]) 'user_id': fields.many2one('res.users', string='User', required=True), 'planline_id': fields.many2one('gamification.goal.planline', string='Goal Planline', ondelete="cascade"), 'plan_id': fields.related('planline_id', 'plan_id', @@ -194,6 +194,25 @@ class gamification_goal(osv.Model): } _order = 'create_date desc, end_date desc, type_id, id' + def _check_remind_delay(self, goal, context=None): + """Verify if a goal has not been updated for some time and send a + reminder message of needed. + + :return: data to write on the goal object + """ + if goal.remind_update_delay and goal.last_update: + delta_max = timedelta(days=goal.remind_update_delay) + last_update = datetime.strptime(goal.last_update, DF).date() + if date.today() - last_update > delta_max and goal.state == 'inprogress': + # generate a remind report + temp_obj = self.pool.get('email.template') + template_id = self.pool['ir.model.data'].get_object(cr, uid, 'gamification', 'email_template_goal_reminder', context) + body_html = temp_obj.render_template(cr, uid, template_id.body_html, 'gamification.goal', goal.id, context=context) + + self.message_post(cr, uid, goal.id, body=body_html, partner_ids=[goal.user_id.partner_id.id], context=context, subtype='mail.mt_comment') + return {'state': 'inprogress_update'} + return {} + def update(self, cr, uid, ids, context=None): """Update the goals to recomputes values and change of states @@ -211,19 +230,7 @@ class gamification_goal(osv.Model): continue if goal.type_id.computation_mode == 'manually': - - #TODO: put this code section in a separated method - # check for remind to update - if goal.remind_update_delay and goal.last_update: - delta_max = timedelta(days=goal.remind_update_delay) - last_update = datetime.strptime(goal.last_update, '%Y-%m-%d').date() - if date.today() - last_update > delta_max and goal.state == 'inprogress': - towrite['state'] = 'inprogress_update' - - # generate a remind report - template_env = TemplateHelper() - body_html = template_env.get_template('reminder.mako').render({'object': goal}) - self.message_post(cr, uid, goal.id, body=body_html, partner_ids=[goal.user_id.partner_id.id], context=context, subtype='mail.mt_comment') + towrite.update(self._check_remind_delay(goal, context)) elif goal.type_id.computation_mode == 'python': # execute the chosen method @@ -231,7 +238,7 @@ class gamification_goal(osv.Model): result = safe_eval(goal.type_id.compute_code, values, {}) if type(result) in (float, int, long) and result != goal.current: - towrite = {'current': result} + towrite['current'] = result else: _logger.exception(_('Unvalid return content from the evaluation of %s' % str(goal.type_id.compute_code))) # raise osv.except_osv(_('Error!'), _('Unvalid return content from the evaluation of %s' % str(goal.type_id.compute_code))) @@ -241,7 +248,7 @@ class gamification_goal(osv.Model): field_date_name = goal.type_id.field_date_id.name # eval the domain with user_id replaced by goal user - domain = safe_eval(goal.type_id.domain, {'user_id': goal.user_id.id}) # TODO: {'user_id': goal.user_id} (see above comment) + domain = safe_eval(goal.type_id.domain, {'user': goal.user_id}) #add temporal clause(s) to the domain if fields are filled on the goal if goal.start_date and field_date_name: @@ -259,11 +266,11 @@ class gamification_goal(osv.Model): #avoid useless write if the new value is the same as the old one if new_value != goal.current: - towrite = {'current': new_value} + towrite['current'] = new_value # check goal target reached #TODO: reached condition is wrong because it should check time constraints. - if (goal.type_id.condition == 'higher' and towrite.get('current', goal.current) >= goal.target_goal) or (goal.type_id.condition == 'lower' and towrite.get('current', goal.curren) <= goal.target_goal): + if (goal.type_id.condition == 'higher' and towrite.get('current', goal.current) >= goal.target_goal) or (goal.type_id.condition == 'lower' and towrite.get('current', goal.current) <= goal.target_goal): towrite['state'] = 'reached' # check goal failure @@ -302,10 +309,9 @@ class gamification_goal(osv.Model): return self.write(cr, uid, ids, {'state': 'inprogress'}, context=context) def create(self, cr, uid, vals, context=None): - """Overwrite the create method to add a 'just_created' field to True""" - #TODO: rename just_created into something more explicit (related to the effect, not to the cause) like 'avoid_onchange_log', for example + """Overwrite the create method to add a 'no_remind_goal' field to True""" context = context or {} - context['just_created'] = True + context['no_remind_goal'] = True return super(gamification_goal, self).create(cr, uid, vals, context=context) def write(self, cr, uid, ids, vals, context=None): @@ -322,13 +328,13 @@ class gamification_goal(osv.Model): raise osv.except_osv(_('Error!'), _('Can not modify a started goal')) if 'current' in vals: - if 'just_created' in context: + if 'no_remind_goal' in context: # new goals should not be reported continue if goal.plan_id and goal.plan_id.report_message_frequency == 'onchange': plan_obj = self.pool.get('gamification.goal.plan') - plan_obj.report_progress(cr, uid, goal.plan_id, users=[goal.user_id], context=context) + plan_obj.report_progress(cr, SUPERUSER_ID, goal.plan_id, users=[goal.user_id], context=context) return result def get_action(self, cr, uid, goal_id, context=None): diff --git a/addons/gamification/goal_base_data.xml b/addons/gamification/goal_base_data.xml index 17886a6144c..b0e21b977cb 100644 --- a/addons/gamification/goal_base_data.xml +++ b/addons/gamification/goal_base_data.xml @@ -9,7 +9,7 @@ count checkbox - [('id','=',user_id),('partner_id.tz', '!=', False)] + [('id','=',user.id),('partner_id.tz', '!=', False)] id @@ -31,7 +31,7 @@ count checkbox - [('user_ids', 'in', user_id), ('name', '!=', 'Your Company')] + [('user_ids', 'in', user.id), ('name', '!=', 'Your Company')] company_id.id @@ -41,7 +41,7 @@ count checkbox - [('user_ids', 'in', user_id),('logo', '!=', False)] + [('user_ids', 'in', user.id),('logo', '!=', False)] company_id.id @@ -63,7 +63,7 @@ checkbox count - [('id', '!=', user_id)] + [('id', '!=', user.id)] @@ -124,7 +124,109 @@ 1 + + + + + + + Reminder for Goal Update + + Reminder ${object.name} + +

${object.report_header or ''}

+ +

You have not updated your progress for the goal ${object.type_id.name} (currently reached at ${object.completeness}%) for at least ${object.remind_update_delay} days. Do not forget to do it.

+ +

If you have not changed your score yet, you can use the button "The current value is up to date" to indicate so.

+ ]]>
+
+ + + Personal Goal Progress + + ${object.name} + +

${object.report_header or ''}

+ + + + + + + + + % for goal in ${ctx["goals"]}: + = 100: + style="font-weight:bold;" + % endif + > + + + + + + % endfor +
GoalTargetCurrentCompleteness
${goal.type_id.name}${goal.target_goal} + % if goal.type_suffix: + ${goal.type_suffix} + % endif + ${goal.current} + % if goal.type_suffix: + ${goal.type_suffix} + % endif + ${goal.completeness} %
]]>
+
+ + + Group Goal Progress + + ${object.name} + +

${object.report_header or ''}

+ + % for planline in ctx['planlines_boards']: + + + + + + + + + + + % for idx, goal in planline.board_goals: + % if idx < 3 or goal.user_id.id == user.id: + = 100: + style="font-weight:bold;" + % endif + > + + + + + + % endif + % endfor +
${planline.goal_type.name}
#PersonCompletenessCurrent
${idx+1}${goal.user_id.name}${goal.completeness}%${goal.current}/${goal.target_goal} + % if goal.type_suffix: + ${goal.type_suffix} + % endif +
+ +

+ + % endfor +]]>
+
diff --git a/addons/gamification/plan.py b/addons/gamification/plan.py index 2541d9e500b..4375474fd05 100644 --- a/addons/gamification/plan.py +++ b/addons/gamification/plan.py @@ -22,7 +22,7 @@ from openerp.osv import fields, osv from openerp.tools.translate import _ -from templates import TemplateHelper +# from templates import TemplateHelper from datetime import date, datetime, timedelta import calendar @@ -545,12 +545,17 @@ class gamification_goal_plan(osv.Model): context = context or {} goal_obj = self.pool.get('gamification.goal') - template_env = TemplateHelper() - + # template_env = TemplateHelper() + temp_obj = self.pool.get('email.template') + ctx = context.copy() if plan.visibility_mode == 'board': planlines_boards = self.get_board_goal_info(cr, uid, plan, subset_goal_ids, context) - body_html = template_env.get_template('group_progress.mako').render({'object': plan, 'planlines_boards': planlines_boards, 'uid': uid}) + ctx.update({'planlines_boards': planlines_boards}) + template_id = self.pool['ir.model.data'].get_object(cr, uid, 'gamification', 'email_template_goal_progress_group', context) + body_html = temp_obj.render_template(cr, uid, template_id.body_html, 'gamification.goal.plan', plan.id, context=context) + + # body_html = template_env.get_template('group_progress.mako').render({'object': plan, 'planlines_boards': planlines_boards, 'uid': uid}) # send to every follower of the plan self.message_post(cr, uid, plan.id, @@ -570,10 +575,14 @@ class gamification_goal_plan(osv.Model): if not values: continue - values['object'] = plan - values['user'] = user, + # values['object'] = plan + # values['user'] = user - body_html = template_env.get_template('personal_progress.mako').render(values) + ctx.update({'planlines_boards': planlines_boards}) + template_id = self.pool['ir.model.data'].get_object(cr, uid, 'gamification', 'email_template_goal_progress_perso', context) + body_html = temp_obj.render_template(cr, user.id, template_id.body_html, 'gamification.goal.plan', plan.id, context=context) + + # body_html = template_env.get_template('personal_progress.mako').render(values) # send message only to users self.message_post(cr, uid, 0, diff --git a/addons/gamification/res_users.py b/addons/gamification/res_users.py index c0458263e05..4962cb1b975 100644 --- a/addons/gamification/res_users.py +++ b/addons/gamification/res_users.py @@ -1,6 +1,27 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2010-Today OpenERP SA () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +############################################################################## + from openerp.osv import osv -#TODO add copyright and last line for vim users + class res_users_gamification_group(osv.Model): """ Update of res.users class - if adding groups to an user, check gamification.goal.plan linked to @@ -144,3 +165,4 @@ class res_groups_gamification_group(osv.Model): if plan_ids: goal_plan_obj.write(cr, uid, plan_ids, {'user_ids': [(4, user_id) for user_id in user_ids]}, context=context) return write_res +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/gamification/templates.py b/addons/gamification/templates.py index fb9c23c3e3d..b6d071c8f3a 100644 --- a/addons/gamification/templates.py +++ b/addons/gamification/templates.py @@ -40,4 +40,3 @@ class TemplateHelper(SandboxedEnvironment): 'quote': quote, 'urlencode': urlencode, }) - diff --git a/addons/gamification/templates/reminder.mako b/addons/gamification/templates/reminder.mako index f50d798a657..d3f8a584a03 100644 --- a/addons/gamification/templates/reminder.mako +++ b/addons/gamification/templates/reminder.mako @@ -13,4 +13,5 @@

You have not updated your progress for the goal ${object.type_id.name} (currently reached at ${object.completeness}%) for at least ${object.remind_update_delay} days. Do not forget to do it.

If you have not changed your score yet, you can use the button "The current value is up to date" to indicate so.

- \ No newline at end of file + + diff --git a/addons/gamification_sale_crm/sale_crm_goals.xml b/addons/gamification_sale_crm/sale_crm_goals.xml index c5f85ef3273..6336f0e0654 100644 --- a/addons/gamification_sale_crm/sale_crm_goals.xml +++ b/addons/gamification_sale_crm/sale_crm_goals.xml @@ -11,7 +11,7 @@ - [('state','!=','cancel'),('user_id','=',user_id),('type','=','out_invoice')] + [('state','!=','cancel'),('user_id','=',user.id),('type','=','out_invoice')] @@ -21,7 +21,7 @@ leads - [('user_id','=',user_id),('type', '=', 'lead')] + [('user_id','=',user.id),('type', '=', 'lead')] @@ -33,7 +33,7 @@ - [('user_id','=',user_id),('type', '=', 'lead')] + [('user_id','=',user.id),('type', '=', 'lead')] @@ -45,7 +45,7 @@ - [('user_id','=',user_id)] + [('user_id','=',user.id)] @@ -56,7 +56,7 @@ calls - [('user_id','=',user_id),('state','=','done')] + [('user_id','=',user.id),('state','=','done')] @@ -66,7 +66,7 @@ opportunities - [('user_id','=',user_id),('type','=','opportunity')] + [('user_id','=',user.id),('type','=','opportunity')] @@ -76,7 +76,7 @@ orders - [('user_id','=',user_id),('state','not in',('draft', 'sent', 'cancel'))] + [('user_id','=',user.id),('state','not in',('draft', 'sent', 'cancel'))] @@ -86,7 +86,7 @@ orders - [('state','=','paid'),('user_id','=',user_id),('type','=','out_invoice')] + [('state','=','paid'),('user_id','=',user.id),('type','=','out_invoice')] Total Paid Sales Orders @@ -96,7 +96,7 @@ - [('state','=','paid'),('user_id','=',user_id),('type','=','out_invoice')] + [('state','=','paid'),('user_id','=',user.id),('type','=','out_invoice')] @@ -108,7 +108,7 @@ invoices - [('state','!=','cancel'),('user_id','=',user_id),('type','=','out_refund')] + [('state','!=','cancel'),('user_id','=',user.id),('type','=','out_refund')] Total Customer Refunds @@ -119,7 +119,7 @@ - [('state','!=','cancel'),('user_id','=',user_id),('type','=','out_refund')] + [('state','!=','cancel'),('user_id','=',user.id),('type','=','out_refund')]