diff --git a/addons/gamification/data/goal_base.xml b/addons/gamification/data/goal_base.xml
index 66dd7be7431..428cc357047 100644
--- a/addons/gamification/data/goal_base.xml
+++ b/addons/gamification/data/goal_base.xml
@@ -13,8 +13,6 @@
You have not updated your progress for the goal ${object.definition_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.
]]>
diff --git a/addons/gamification/models/badge.py b/addons/gamification/models/badge.py
index 4f52fd72066..4ecc116000c 100644
--- a/addons/gamification/models/badge.py
+++ b/addons/gamification/models/badge.py
@@ -40,7 +40,8 @@ class gamification_badge_user(osv.Model):
_columns = {
'user_id': fields.many2one('res.users', string="User", required=True),
'sender_id': fields.many2one('res.users', string="Sender", help="The user who has send the badge"),
- 'badge_id': fields.many2one('gamification.badge', string='Badge', required=True),
+ 'badge_id': fields.many2one('gamification.badge', string='Badge', required=True, ondelete="cascade"),
+ 'challenge_id': fields.many2one('gamification.challenge', string='Challenge originating', help="If this badge was rewarded through a challenge"),
'comment': fields.text('Comment'),
'badge_name': fields.related('badge_id', 'name', type="char", string="Badge Name"),
'create_date': fields.datetime('Created', readonly=True),
@@ -264,7 +265,7 @@ class gamification_badge(osv.Model):
def check_progress(self, cr, uid, context=None):
try:
- model, res_id = template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'badge_hidden')
+ model, res_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'badge_hidden')
except ValueError:
return True
badge_user_obj = self.pool.get('gamification.badge.user')
diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/challenge.py
index 06d0c44baac..3ab12ff66e7 100644
--- a/addons/gamification/models/challenge.py
+++ b/addons/gamification/models/challenge.py
@@ -83,7 +83,7 @@ class gamification_challenge(osv.Model):
:return: a string in DEFAULT_SERVER_DATE_FORMAT representing the date"""
res = {}
- for challenge in self.browse(cr, uid, ids, context):
+ for challenge in self.browse(cr, uid, ids, context=context):
last = datetime.strptime(challenge.last_report_date, DF).date()
if challenge.report_message_frequency == 'daily':
next = last + timedelta(days=1)
@@ -163,6 +163,8 @@ class gamification_challenge(osv.Model):
'reward_second_id': fields.many2one('gamification.badge', string="For 2nd user"),
'reward_third_id': fields.many2one('gamification.badge', string="For 3rd user"),
'reward_failure': fields.boolean('Reward Bests if not Succeeded?'),
+ 'reward_realtime': fields.boolean('Reward as soon as every goal is reached',
+ help="With this option enabled, a user can receive a badge only once. The top 3 badges are still rewarded only at the end of the challenge."),
'visibility_mode': fields.selection([
('personal', 'Individual Goals'),
@@ -257,7 +259,7 @@ class gamification_challenge(osv.Model):
elif vals.get('state') == 'draft':
# resetting progress
- if self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', 'in', ids), ('state', 'in', ['inprogress', 'inprogress_update'])], context=context):
+ if self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', 'in', ids), ('state', '=', 'inprogress')], context=context):
raise osv.except_osv("Error", "You can not reset a challenge with unfinished goals.")
write_res = super(gamification_challenge, self).write(cr, uid, ids, vals, context=context)
@@ -280,13 +282,13 @@ class gamification_challenge(osv.Model):
- Create the missing goals (eg: modified the challenge to add lines)
- Update every running challenge
"""
- # start planned challenges
+ # start scheduled challenges
planned_challenge_ids = self.search(cr, uid, [
('state', '=', 'draft'),
('start_date', '<=', fields.date.today())])
self.write(cr, uid, planned_challenge_ids, {'state': 'inprogress'}, context=context)
- # close planned challenges
+ # close scheduled challenges
planned_challenge_ids = self.search(cr, uid, [
('state', '=', 'inprogress'),
('end_date', '>=', fields.date.today())])
@@ -312,7 +314,7 @@ class gamification_challenge(osv.Model):
goal_ids = goal_obj.search(cr, uid, [
('challenge_id', 'in', ids),
'|',
- ('state', 'in', ('inprogress', 'inprogress_update')),
+ ('state', '=', 'inprogress'),
'&',
('state', 'in', ('reached', 'failed')),
'|',
@@ -363,7 +365,7 @@ class gamification_challenge(osv.Model):
"""Manual report of a goal, does not influence automatic report frequency"""
if isinstance(ids, (int,long)):
ids = [ids]
- for challenge in self.browse(cr, uid, ids, context):
+ for challenge in self.browse(cr, uid, ids, context=context):
self.report_progress(cr, uid, challenge, context=context)
return True
@@ -377,7 +379,8 @@ class gamification_challenge(osv.Model):
can be called after each change in the list of users or lines.
:param list(int) ids: the list of challenge concerned"""
- for challenge in self.browse(cr, uid, ids, context):
+ to_update = []
+ for challenge in self.browse(cr, uid, ids, context=context):
(start_date, end_date) = start_end_date_for_period(challenge.period)
# if no periodicity, use challenge dates
@@ -403,7 +406,7 @@ class gamification_challenge(osv.Model):
canceled_goal_ids = goal_obj.search(cr, uid, domain, context=context)
if canceled_goal_ids:
goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context)
- goal_obj.update(cr, uid, canceled_goal_ids, context=context)
+ to_update.extend(canceled_goal_ids)
# skip to next user
continue
@@ -424,9 +427,10 @@ class gamification_challenge(osv.Model):
if challenge.remind_update_delay:
values['remind_update_delay'] = challenge.remind_update_delay
- new_goal_id = goal_obj.create(cr, uid, values, context)
+ new_goal_id = goal_obj.create(cr, uid, values, context=context)
+ to_update.append(new_goal_id)
- goal_obj.update(cr, uid, [new_goal_id], context=context)
+ goal_obj.update(cr, uid, to_update, context=context)
return True
@@ -460,7 +464,7 @@ class gamification_challenge(osv.Model):
'rank': ,
'user_id': ,
'name': ,
- 'state': ,
+ 'state': ,
'completeness': ,
'current': ,
}
@@ -478,7 +482,7 @@ class gamification_challenge(osv.Model):
'action': <{True,False}>,
'display_mode': <{progress,boolean}>,
'target': ,
- 'state': ,
+ 'state': ,
'completeness': ,
'current': ,
}
@@ -545,7 +549,7 @@ class gamification_challenge(osv.Model):
if user_id and goal.user_id.id == user_id:
line_data['own_goal_id'] = goal.id
elif restrict_top and ranking > restrict_top:
- # not own goal, over top, skipping
+ # not own goal and too low to be in top
continue
line_data['goals'].append({
@@ -669,52 +673,63 @@ class gamification_challenge(osv.Model):
"""
if isinstance(ids, (int,long)):
ids = [ids]
- context = context or {}
for challenge in self.browse(cr, uid, ids, context=context):
(start_date, end_date) = start_end_date_for_period(challenge.period, challenge.start_date, challenge.end_date)
yesterday = date.today() - timedelta(days=1)
- if end_date == yesterday.strftime(DF) or force:
+
+ rewarded_users = []
+ challenge_ended = end_date == yesterday.strftime(DF) or force
+ if challenge.reward_id and challenge_ended or challenge.reward_realtime:
+ for user in challenge.user_ids:
+ reached_goal_ids = self.pool.get('gamification.goal').search(cr, uid, [
+ ('challenge_id', '=', challenge.id),
+ ('user_id', '=', user.id),
+ ('start_date', '=', start_date),
+ ('end_date', '=', end_date),
+ ('state', '=', 'reached')
+ ], context=context)
+ if len(reached_goal_ids) == len(challenge.line_ids):
+ # the user has succeeded every assigned goal
+ if challenge.reward_realtime:
+ badges = self.pool['gamification.badge.user'].search(cr, uid, [
+ ('challenge_id', '=', challenge.id),
+ ('badge_id', '=', challenge.reward_id.id),
+ ('user_id', '=', user.id),
+ ], count=True, context=context)
+ if badges > 0:
+ # has already recieved the badge for this challenge
+ continue
+ self.reward_user(cr, uid, user.id, challenge.reward_id.id, challenge.id, context=context)
+ rewarded_users.append(user)
+
+ if challenge_ended:
# open chatter message
message_body = _("The challenge %s is finished." % challenge.name)
- # reward for everybody succeeding
- rewarded_users = []
- if challenge.reward_id:
- for user in challenge.user_ids:
- reached_goal_ids = self.pool.get('gamification.goal').search(cr, uid, [
- ('challenge_id', '=', challenge.id),
- ('user_id', '=', user.id),
- ('start_date', '=', start_date),
- ('end_date', '=', end_date),
- ('state', '=', 'reached')
- ], context=context)
- if len(reached_goal_ids) == len(challenge.line_ids):
- self.reward_user(cr, uid, user.id, challenge.reward_id.id, context)
- rewarded_users.append(user)
-
- if rewarded_users:
- message_body += _("
Reward (badge %s) for every succeeding user was sent to %s." % (challenge.reward_id.name, ", ".join([user.name for user in rewarded_users])))
- else:
- message_body += _("
Nobody has succeeded to reach every goal, no badge is rewared for this challenge.")
+ if rewarded_users:
+ message_body += _("
Reward (badge %s) for every succeeding user was sent to %s." % (challenge.reward_id.name, ", ".join([user.name for user in rewarded_users])))
+ else:
+ message_body += _("
Nobody has succeeded to reach every goal, no badge is rewared for this challenge.")
# reward bests
if challenge.reward_first_id:
- (first_user, second_user, third_user) = self.get_top3_users(cr, uid, challenge, context)
+ (first_user, second_user, third_user) = self.get_top3_users(cr, uid, challenge, context=context)
if first_user:
- self.reward_user(cr, uid, first_user.id, challenge.reward_first_id.id, context)
+ self.reward_user(cr, uid, first_user.id, challenge.reward_first_id.id, challenge.id, context=context)
message_body += _("
Special rewards were sent to the top competing users. The ranking for this challenge is :")
message_body += "
1. %s - %s" % (first_user.name, challenge.reward_first_id.name)
else:
message_body += _("Nobody reached the required conditions to receive special badges.")
if second_user and challenge.reward_second_id:
- self.reward_user(cr, uid, second_user.id, challenge.reward_second_id.id, context)
+ self.reward_user(cr, uid, second_user.id, challenge.reward_second_id.id, challenge.id, context=context)
message_body += "
2. %s - %s" % (second_user.name, challenge.reward_second_id.name)
if third_user and challenge.reward_third_id:
- self.reward_user(cr, uid, third_user.id, challenge.reward_second_id.id, context)
+ self.reward_user(cr, uid, third_user.id, challenge.reward_second_id.id, challenge.id, context=context)
message_body += "
3. %s - %s" % (third_user.name, challenge.reward_third_id.name)
self.message_post(cr, uid, challenge.id, body=message_body, context=context)
+
return True
def get_top3_users(self, cr, uid, challenge, context=None):
@@ -768,14 +783,14 @@ class gamification_challenge(osv.Model):
return (sorted_challengers[0]['user'], sorted_challengers[1]['user'], False)
return (sorted_challengers[0]['user'], sorted_challengers[1]['user'], sorted_challengers[2]['user'])
- def reward_user(self, cr, uid, user_id, badge_id, context=None):
+ def reward_user(self, cr, uid, user_id, badge_id, challenge_id=False, context=None):
"""Create a badge user and send the badge to him
:param user_id: the user to reward
:param badge_id: the concerned badge
"""
badge_user_obj = self.pool.get('gamification.badge.user')
- user_badge_id = badge_user_obj.create(cr, uid, {'user_id': user_id, 'badge_id': badge_id}, context=context)
+ user_badge_id = badge_user_obj.create(cr, uid, {'user_id': user_id, 'badge_id': badge_id, 'challenge_id':challenge_id}, context=context)
return badge_user_obj._send_badge(cr, uid, [user_badge_id], context=context)
diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py
index 2408e58d3d7..6ab4259cca2 100644
--- a/addons/gamification/models/goal.py
+++ b/addons/gamification/models/goal.py
@@ -88,8 +88,16 @@ class gamification_goal_definition(osv.Model):
string='Date Field',
help='The date to use for the time period evaluated'),
'domain': fields.char("Filter Domain",
- help="Domain for filtering records. The rule can contain reference to 'user' that is a browse record of the current user, e.g. [('user_id', '=', user.id)].",
+ 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),
+
+ 'batch_mode': fields.boolean('Batch Mode',
+ help="Evaluate the expression in batch instead of once for each user"),
+ 'batch_distinctive_field': fields.many2one('ir.model.fields',
+ string="Distinctive field for batch user",
+ help="In batch mode, this indicates which field distinct one user form the other, e.g. user_id, partner_id..."),
+ 'batch_user_expression': fields.char("Evaluted expression for batch mode",
+ help="The value to compare with the distinctive field. The expression can contain reference to 'user' which is a browse record of the current user, e.g. user.id, user.partner_id.id..."),
'compute_code': fields.text('Python Code',
help="Python code to be executed for each user. 'result' should contains the new current value. Evaluated user can be access through object.user_id."),
'condition': fields.selection([
@@ -102,7 +110,7 @@ class gamification_goal_definition(osv.Model):
'action_id': fields.many2one('ir.actions.act_window', string="Action",
help="The action that will be called to update the goal value."),
'res_id_field': fields.char("ID Field of user",
- help="The field name on the user profile (res.users) containing the value for res_id for action.")
+ help="The field name on the user profile (res.users) containing the value for res_id for action."),
}
_defaults = {
@@ -158,7 +166,7 @@ class gamification_goal(osv.Model):
_columns = {
'definition_id': fields.many2one('gamification.goal.definition', string='Goal Definition', required=True, ondelete="cascade"),
'user_id': fields.many2one('res.users', string='User', required=True),
- 'line_id': fields.many2one('gamification.challenge.line', string='Goal Line', ondelete="cascade"),
+ 'line_id': fields.many2one('gamification.challenge.line', string='Challenge Line', ondelete="cascade"),
'challenge_id': fields.related('line_id', 'challenge_id',
string="Challenge",
type='many2one',
@@ -175,7 +183,6 @@ class gamification_goal(osv.Model):
'state': fields.selection([
('draft', 'Draft'),
('inprogress', 'In progress'),
- ('inprogress_update', 'In progress (to update)'),
('reached', 'Reached'),
('failed', 'Failed'),
('canceled', 'Canceled'),
@@ -183,6 +190,8 @@ class gamification_goal(osv.Model):
string='State',
required=True,
track_visibility='always'),
+ 'to_update': fields.boolean('To update'),
+ 'closed': fields.boolean('Closed goal', help="These goals will not be recomputed."),
'computation_mode': fields.related('definition_id', 'computation_mode', type='char', string="Computation mode"),
'remind_update_delay': fields.integer('Remind delay',
@@ -212,14 +221,14 @@ class gamification_goal(osv.Model):
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':
+ if date.today() - last_update > delta_max:
# 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 {'to_update': True}
return {}
def update(self, cr, uid, ids, context=None):
@@ -233,70 +242,122 @@ class gamification_goal(osv.Model):
if context is None:
context = {}
+ goals_by_definition = {}
+ goals_to_write = {}
+ all_goals = {}
for goal in self.browse(cr, uid, ids, context=context):
- towrite = {}
if goal.state in ('draft', 'canceled'):
- # skip if goal draft or canceled
+ # draft or canceled goals should not be recomputed
continue
- if goal.definition_id.computation_mode == 'manually':
- towrite.update(self._check_remind_delay(cr, uid, goal, context))
+ goals_by_definition.setdefault(goal.definition_id, []).append(goal)
+ goals_to_write[goal.id] = {}
+ all_goals[goal.id] = goal
- elif goal.definition_id.computation_mode == 'python':
- # execute the chosen method
- cxt = {
- 'self': self.pool.get('gamification.goal'),
- 'object': goal,
- 'pool': self.pool,
- 'cr': cr,
- 'context': dict(context), # copy context to prevent side-effects of eval
- 'uid': uid,
- 'date': date, 'datetime': datetime, 'timedelta': timedelta, 'time': time
- }
- code = goal.definition_id.compute_code.strip()
- safe_eval(code, cxt, mode="exec", nocopy=True)
- # the result of the evaluated codeis put in the 'result' local variable, propagated to the context
- result = cxt.get('result')
- if result is not None and type(result) in (float, int, long):
- if result != goal.current:
- towrite['current'] = result
- else:
- _logger.exception(_('Invalid return content from the evaluation of %s' % code))
+ for definition, goals in goals_by_definition.items():
+ if definition.computation_mode == 'manually':
+ for goal in goals:
+ goals_to_write[goal.id].update(self._check_remind_delay(cr, uid, goal, context))
+ elif definition.computation_mode == 'python':
+ # TODO batch execution
+ for goal in goals:
+ # execute the chosen method
+ cxt = {
+ 'self': self.pool.get('gamification.goal'),
+ 'object': goal,
+ 'pool': self.pool,
+ 'cr': cr,
+ 'context': dict(context), # copy context to prevent side-effects of eval
+ 'uid': uid,
+ 'date': date, 'datetime': datetime, 'timedelta': timedelta, 'time': time
+ }
+ code = definition.compute_code.strip()
+ safe_eval(code, cxt, mode="exec", nocopy=True)
+ # the result of the evaluated codeis put in the 'result' local variable, propagated to the context
+ result = cxt.get('result')
+ if result is not None and type(result) in (float, int, long):
+ if result != goal.current:
+ goals_to_write[goal.id]['current'] = result
+ else:
+ _logger.exception(_('Invalid return content from the evaluation of code for definition %s' % definition.name))
else: # count or sum
- obj = self.pool.get(goal.definition_id.model_id.model)
- field_date_name = goal.definition_id.field_date_id.name
- # eval the domain with user replaced by goal user object
- domain = safe_eval(goal.definition_id.domain, {'user': goal.user_id})
+ obj = self.pool.get(definition.model_id.model)
+ field_date_name = definition.field_date_id and definition.field_date_id.name or False
- # add temporal clause(s) to the domain if fields are filled on the goal
- if goal.start_date and field_date_name:
- domain.append((field_date_name, '>=', goal.start_date))
- if goal.end_date and field_date_name:
- domain.append((field_date_name, '<=', goal.end_date))
+ if definition.computation_mode == 'count' and definition.batch_mode:
- if goal.definition_id.computation_mode == 'sum':
- field_name = goal.definition_id.field_id.name
- res = obj.read_group(cr, uid, domain, [field_name], [field_name], context=context)
- new_value = res and res[0][field_name] or 0.0
+ general_domain = safe_eval(definition.domain)
+ # goal_distinct_values = {goal.id: safe_eval(definition.batch_user_expression, {'user': goal.user_id}) for goal in goals}
+ field_name = definition.batch_distinctive_field.name
+ # general_domain.append((field_name, 'in', list(set(goal_distinct_values.keys()))))
+ subqueries = {}
+ for goal in goals:
+ start_date = field_date_name and goal.start_date or False
+ end_date = field_date_name and goal.end_date or False
+ subqueries.setdefault((start_date, end_date), {}).update({goal.id:safe_eval(definition.batch_user_expression, {'user': goal.user_id})})
- else: # computation mode = count
- new_value = obj.search(cr, uid, domain, context=context, count=True)
+ for (start_date, end_date), query_goals in subqueries.items():
+ subquery_domain = list(general_domain)
+ subquery_domain.append((field_name, 'in', list(set(query_goals.values()))))
+ if start_date:
+ subquery_domain.append((field_date_name, '>=', start_date))
+ if end_date:
+ subquery_domain.append((field_date_name, '>=', end_date))
- # avoid useless write if the new value is the same as the old one
- if new_value != goal.current:
- towrite['current'] = new_value
+ user_values = obj.read_group(cr, uid, subquery_domain, fields=[field_name], groupby=[field_name], context=context)
+
+ for goal in [g for g in goals if g.id in query_goals.keys()]:
+ for user_value in user_values:
+ # return format of read_group: [{'partner_id': 42, 'partner_id_count': 3},...]
+ queried_value = field_name in user_value and user_value[field_name] or False
+ if isinstance(queried_value, tuple) and len(queried_value) == 2 and isinstance(queried_value[0], (int, long)):
+ queried_value = queried_value[0]
+ if queried_value == query_goals[goal.id]:
+ new_value = user_value.get(field_name+'_count', goal.current)
+ if new_value != goal.current:
+ goals_to_write[goal.id]['current'] = new_value
+
+ else:
+ for goal in goals:
+ # eval the domain with user replaced by goal user object
+ domain = safe_eval(definition.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:
+ domain.append((field_date_name, '>=', goal.start_date))
+ if goal.end_date and field_date_name:
+ domain.append((field_date_name, '<=', goal.end_date))
+
+ if definition.computation_mode == 'sum':
+ field_name = definition.field_id.name
+ res = obj.read_group(cr, uid, domain, [field_name], [field_name], context=context)
+ new_value = res and res[0][field_name] or 0.0
+
+ else: # computation mode = count
+ new_value = obj.search(cr, uid, domain, context=context, count=True)
+
+ # avoid useless write if the new value is the same as the old one
+ if new_value != goal.current:
+ goals_to_write[goal.id]['current'] = new_value
+
+ for goal_id, value in goals_to_write.items():
+ if not value:
+ continue
+ goal = all_goals[goal_id]
# check goal target reached
- if (goal.definition_condition == 'higher' and towrite.get('current', goal.current) >= goal.target_goal) or (goal.definition_condition == 'lower' and towrite.get('current', goal.current) <= goal.target_goal):
- towrite['state'] = 'reached'
+ if (goal.definition_condition == 'higher' and value.get('current', goal.current) >= goal.target_goal) \
+ or (goal.definition_condition == 'lower' and value.get('current', goal.current) <= goal.target_goal):
+ value['state'] = 'reached'
# check goal failure
elif goal.end_date and fields.date.today() > goal.end_date:
- towrite['state'] = 'failed'
- if towrite:
- self.write(cr, uid, [goal.id], towrite, context=context)
+ value['state'] = 'failed'
+ value['closed'] = True
+ if value:
+ self.write(cr, uid, [goal.id], value, context=context)
return True
def action_start(self, cr, uid, ids, context=None):
diff --git a/addons/gamification/static/img/badge_hidden-image.png b/addons/gamification/static/img/badge_hidden-image.png
index 2b9040d5764..1c5cf7b6f5d 100644
Binary files a/addons/gamification/static/img/badge_hidden-image.png and b/addons/gamification/static/img/badge_hidden-image.png differ
diff --git a/addons/gamification/static/src/js/gamification.js b/addons/gamification/static/src/js/gamification.js
index 2a8db5f369d..242f67d8759 100644
--- a/addons/gamification/static/src/js/gamification.js
+++ b/addons/gamification/static/src/js/gamification.js
@@ -49,21 +49,6 @@ openerp.gamification = function(instance) {
self.get_goal_todo_info();
});
});
- },
- 'click .oe_goal h4': function(event) {
- var self = this;
- this.kkeys = [];
- $(document).on('keydown.klistener', function(event) {
- if ("37,38,39,40,65,66".indexOf(event.keyCode) < 0) {
- $(document).off('keydown.klistener');
- } else {
- self.kkeys.push(event.keyCode);
- if (self.kkeys.toString().indexOf("38,38,40,40,37,39,37,39,66,65") >= 0) {
- new instance.web.Model('gamification.badge').call('check_progress', []);
- $(document).off('keydown.klistener');
- }
- }
- });
}
},
start: function() {
@@ -126,6 +111,13 @@ openerp.gamification = function(instance) {
}
});
+ instance.web.WebClient.include({
+ to_kitten: function() {
+ this._super();
+ new instance.web.Model('gamification.badge').call('check_progress', []);
+ }
+ });
+
instance.mail.Widget.include({
start: function() {
this._super();
diff --git a/addons/gamification/views/challenge.xml b/addons/gamification/views/challenge.xml
index 075afcaccfa..b37ef0eb0d8 100644
--- a/addons/gamification/views/challenge.xml
+++ b/addons/gamification/views/challenge.xml
@@ -86,6 +86,7 @@
+
Badges are granted when a challenge is finished. This is either at the end of a running period (eg: end of the month for a monthly challenge), at the end date of a challenge (if no periodicity is set) or when the challenge is manually closed.
diff --git a/addons/gamification/views/goal.xml b/addons/gamification/views/goal.xml
index 1c408e3ab11..528d5db0511 100644
--- a/addons/gamification/views/goal.xml
+++ b/addons/gamification/views/goal.xml
@@ -45,8 +45,8 @@
@@ -105,7 +105,7 @@
@@ -157,7 +157,7 @@
W
- N
+ N
X
@@ -241,15 +241,23 @@
-
-
+
+
+
+
+ In batch mode, the domain is evaluated globally. If enabled, do not use keyword 'user' in above filter domain.
+
+
+
-
+
diff --git a/addons/gamification/wizard/update_goal.py b/addons/gamification/wizard/update_goal.py
index cbda3fefbce..95ba76299ec 100644
--- a/addons/gamification/wizard/update_goal.py
+++ b/addons/gamification/wizard/update_goal.py
@@ -38,6 +38,7 @@ class goal_manual_wizard(osv.TransientModel):
towrite = {
'current': wiz.current,
'goal_id': wiz.goal_id.id,
+ 'to_update': False,
}
goal_obj.write(cr, uid, [wiz.goal_id.id], towrite, context=context)
goal_obj.update(cr, uid, [wiz.goal_id.id], context=context)