[IMP] gamification: more flexible domain, using email_templates

bzr revid: mat@openerp.com-20130606124708-3occhsgwkaxwzyp3
This commit is contained in:
Martin Trigaux
2013-06-06 14:47:08 +02:00
parent aae3a5dd27
commit bb17b02de5
10 changed files with 222 additions and 57 deletions
+1 -1
View File
@@ -23,7 +23,7 @@
'version': '1.0',
'author': 'OpenERP SA',
'category': 'Human Ressources',
'depends': ['mail'],
'depends': ['mail', 'email_template'],
'description': """
Gamification process
====================
+12 -4
View File
@@ -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
+18
View File
@@ -884,4 +884,22 @@
AABJRU5ErkJggg==</field>
</record>
</data>
<data noupdate="0">
<record id="email_template_badge_received" model="email.template">
<field name="name">Received Badge</field>
<field name="body_html"><![CDATA[
<p>Congratulation, you have received the badge <strong>${object.badge_id.name}</strong> !
% if ctx["user_from"]
This badge was granted by <strong>${ctx["user_from"]}</strong>.
% endif
</p>
% if object.comment
<p><em>${object.comment}</em></p>
% endif]]></field>
</record>
</data>
</openerp>
+33 -27
View File
@@ -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):
+106 -4
View File
@@ -9,7 +9,7 @@
<field name="computation_mode">count</field>
<field name="display_mode">checkbox</field>
<field name="model_id" eval="ref('base.model_res_users')" />
<field name="domain">[('id','=',user_id),('partner_id.tz', '!=', False)]</field>
<field name="domain">[('id','=',user.id),('partner_id.tz', '!=', False)]</field>
<field name="action_id" eval="ref('base.action_res_users_my')" />
<field name="res_id_field">id</field>
</record>
@@ -31,7 +31,7 @@
<field name="computation_mode">count</field>
<field name="display_mode">checkbox</field>
<field name="model_id" eval="ref('base.model_res_company')" />
<field name="domain">[('user_ids', 'in', user_id), ('name', '!=', 'Your Company')]</field>
<field name="domain">[('user_ids', 'in', user.id), ('name', '!=', 'Your Company')]</field>
<field name="action_id" eval="ref('base.action_res_company_form')" />
<field name="res_id_field">company_id.id</field>
</record>
@@ -41,7 +41,7 @@
<field name="computation_mode">count</field>
<field name="display_mode">checkbox</field>
<field name="model_id" eval="ref('base.model_res_company')" />
<field name="domain">[('user_ids', 'in', user_id),('logo', '!=', False)]</field>
<field name="domain">[('user_ids', 'in', user.id),('logo', '!=', False)]</field>
<field name="action_id" eval="ref('base.action_res_company_form')" />
<field name="res_id_field">company_id.id</field>
</record>
@@ -63,7 +63,7 @@
<field name="display_mode">checkbox</field>
<field name="computation_mode">count</field>
<field name="model_id" eval="ref('base.model_res_users')" />
<field name="domain">[('id', '!=', user_id)]</field>
<field name="domain">[('id', '!=', user.id)]</field>
<field name="action_id" eval="ref('action_new_simplified_res_users')" />
</record>
@@ -124,7 +124,109 @@
<field name="target_goal">1</field>
<field name="plan_id" eval="ref('plan_base_configure')" />
</record>
</data>
<!-- Mail template is done in a NOUPDATE block
so users can freely customize/delete them -->
<data noupdate="0">
<!--Email template -->
<record id="email_template_goal_reminder" model="email.template">
<field name="name">Reminder for Goal Update</field>
<field name="body_html"><![CDATA[
<header>
<strong>Reminder ${object.name}</strong>
</header>
<p class="oe_grey">${object.report_header or ''}</p>
<p>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.</p>
<p>If you have not changed your score yet, you can use the button "The current value is up to date" to indicate so.</p>
]]></field>
</record>
<record id="email_template_goal_progress_perso" model="email.template">
<field name="name">Personal Goal Progress</field>
<field name="body_html"><![CDATA[
<header>
<strong>${object.name}</strong>
</header>
<p class="oe_grey">${object.report_header or ''}</p>
<table width="100%" border="1">
<tr>
<th>Goal</th>
<th>Target</th>
<th>Current</th>
<th>Completeness</th>
</tr>
% for goal in ${ctx["goals"]}:
<tr
% if goal.completeness >= 100:
style="font-weight:bold;"
% endif
>
<td>${goal.type_id.name}</td>
<td>${goal.target_goal}
% if goal.type_suffix:
${goal.type_suffix}
% endif
</td>
<td>${goal.current}
% if goal.type_suffix:
${goal.type_suffix}
% endif
</td>
<td>${goal.completeness} %</td>
</tr>
% endfor
</table>]]></field>
</record>
<record id="email_template_goal_progress_group" model="email.template">
<field name="name">Group Goal Progress</field>
<field name="body_html"><![CDATA[
<header>
<strong>${object.name}</strong>
</header>
<p class="oe_grey">${object.report_header or ''}</p>
% for planline in ctx['planlines_boards']:
<table width="100%" border="1">
<tr>
<th colspan="4">${planline.goal_type.name}</th>
</tr>
<tr>
<th>#</th>
<th>Person</th>
<th>Completeness</th>
<th>Current</th>
</tr>
% for idx, goal in planline.board_goals:
% if idx < 3 or goal.user_id.id == user.id:
<tr
% if goal.completeness >= 100:
style="font-weight:bold;"
% endif
>
<td>${idx+1}</td>
<td>${goal.user_id.name}</td>
<td>${goal.completeness}%</td>
<td>${goal.current}/${goal.target_goal}
% if goal.type_suffix:
${goal.type_suffix}
% endif
</td>
</tr>
% endif
% endfor
</table>
<br/><br/>
% endfor
]]></field>
</record>
</data>
</openerp>
+16 -7
View File
@@ -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,
+23 -1
View File
@@ -1,6 +1,27 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2010-Today OpenERP SA (<http://www.openerp.com>)
#
# 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 <http://www.gnu.org/licenses/>
#
##############################################################################
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:
-1
View File
@@ -40,4 +40,3 @@ class TemplateHelper(SandboxedEnvironment):
'quote': quote,
'urlencode': urlencode,
})
+2 -1
View File
@@ -13,4 +13,5 @@
<p>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.</p>
<p>If you have not changed your score yet, you can use the button "The current value is up to date" to indicate so.</p>
</body>
</body>
</html>
+11 -11
View File
@@ -11,7 +11,7 @@
<field name="model_id" eval="ref('account.model_account_invoice_report')" />
<field name="field_id" eval="ref('account.field_account_invoice_report_price_total')" />
<field name="field_date_id" eval="ref('account.field_account_invoice_report_day')" />
<field name="domain">[('state','!=','cancel'),('user_id','=',user_id),('type','=','out_invoice')]</field>
<field name="domain">[('state','!=','cancel'),('user_id','=',user.id),('type','=','out_invoice')]</field>
</record>
<record model="gamification.goal.type" id="type_crm_nbr_new_leads">
@@ -21,7 +21,7 @@
<field name="unit">leads</field>
<field name="model_id" eval="ref('crm.model_crm_lead')" />
<field name="field_date_id" eval="ref('crm.field_crm_lead_create_date')" />
<field name="domain">[('user_id','=',user_id),('type', '=', 'lead')]</field>
<field name="domain">[('user_id','=',user.id),('type', '=', 'lead')]</field>
</record>
<record model="gamification.goal.type" id="type_crm_lead_delay_open">
@@ -33,7 +33,7 @@
<field name="model_id" eval="ref('crm.model_crm_lead_report')" />
<field name="field_id" eval="ref('crm.field_crm_lead_report_delay_close')" />
<field name="field_date_id" eval="ref('crm.field_crm_lead_report_date_closed')" />
<field name="domain">[('user_id','=',user_id),('type', '=', 'lead')]</field>
<field name="domain">[('user_id','=',user.id),('type', '=', 'lead')]</field>
</record>
<record model="gamification.goal.type" id="type_crm_lead_delay_close">
@@ -45,7 +45,7 @@
<field name="model_id" eval="ref('crm.model_crm_lead_report')" />
<field name="field_id" eval="ref('crm.field_crm_lead_report_delay_open')" />
<field name="field_date_id" eval="ref('crm.field_crm_lead_report_opening_date')" />
<field name="domain">[('user_id','=',user_id)]</field>
<field name="domain">[('user_id','=',user.id)]</field>
</record>
@@ -56,7 +56,7 @@
<field name="unit">calls</field>
<field name="model_id" eval="ref('crm.model_crm_phonecall')" />
<field name="field_date_id" eval="ref('crm.field_crm_phonecall_date_closed')" />
<field name="domain">[('user_id','=',user_id),('state','=','done')]</field>
<field name="domain">[('user_id','=',user.id),('state','=','done')]</field>
</record>
<record model="gamification.goal.type" id="type_crm_nbr_new_opportunities">
@@ -66,7 +66,7 @@
<field name="unit">opportunities</field>
<field name="model_id" eval="ref('crm.model_crm_lead')" />
<field name="field_date_id" eval="ref('crm.field_crm_lead_date_open')" />
<field name="domain">[('user_id','=',user_id),('type','=','opportunity')]</field>
<field name="domain">[('user_id','=',user.id),('type','=','opportunity')]</field>
</record>
<record model="gamification.goal.type" id="type_crm_nbr_sale_order_created">
@@ -76,7 +76,7 @@
<field name="unit">orders</field>
<field name="model_id" eval="ref('sale.model_sale_order')" />
<field name="field_date_id" eval="ref('sale.field_sale_order_date_order')" />
<field name="domain">[('user_id','=',user_id),('state','not in',('draft', 'sent', 'cancel'))]</field>
<field name="domain">[('user_id','=',user.id),('state','not in',('draft', 'sent', 'cancel'))]</field>
</record>
<record model="gamification.goal.type" id="type_crm_nbr_paid_sale_order">
@@ -86,7 +86,7 @@
<field name="unit">orders</field>
<field name="model_id" eval="ref('account.model_account_invoice_report')" />
<field name="field_date_id" eval="ref('account.field_account_invoice_report_day')" />
<field name="domain">[('state','=','paid'),('user_id','=',user_id),('type','=','out_invoice')]</field>
<field name="domain">[('state','=','paid'),('user_id','=',user.id),('type','=','out_invoice')]</field>
</record>
<record model="gamification.goal.type" id="type_crm_tot_paid_sale_order">
<field name="name">Total Paid Sales Orders</field>
@@ -96,7 +96,7 @@
<field name="model_id" eval="ref('account.model_account_invoice_report')" />
<field name="field_id" eval="ref('account.field_account_invoice_report_price_total')" />
<field name="field_date_id" eval="ref('account.field_account_invoice_report_day')" />
<field name="domain">[('state','=','paid'),('user_id','=',user_id),('type','=','out_invoice')]</field>
<field name="domain">[('state','=','paid'),('user_id','=',user.id),('type','=','out_invoice')]</field>
</record>
@@ -108,7 +108,7 @@
<field name="unit">invoices</field>
<field name="model_id" eval="ref('account.model_account_invoice_report')" />
<field name="field_date_id" eval="ref('account.field_account_invoice_report_day')" />
<field name="domain">[('state','!=','cancel'),('user_id','=',user_id),('type','=','out_refund')]</field>
<field name="domain">[('state','!=','cancel'),('user_id','=',user.id),('type','=','out_refund')]</field>
</record>
<record model="gamification.goal.type" id="type_crm_tot_customer_refunds">
<field name="name">Total Customer Refunds</field>
@@ -119,7 +119,7 @@
<field name="model_id" eval="ref('account.model_account_invoice_report')" />
<field name="field_id" eval="ref('account.field_account_invoice_report_price_total')" />
<field name="field_date_id" eval="ref('account.field_account_invoice_report_day')" />
<field name="domain">[('state','!=','cancel'),('user_id','=',user_id),('type','=','out_refund')]</field>
<field name="domain">[('state','!=','cancel'),('user_id','=',user.id),('type','=','out_refund')]</field>
</record>