[REF] gamification: add batch mode, improve some views, remove inprogess_update state (replaced with boolean 'to update')
bzr revid: mat@openerp.com-20140411142257-zpaul4sq3t5j5r64
This commit is contained in:
parent
0152bea512
commit
21022c5472
|
@ -13,8 +13,6 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<p>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.</p>
|
<p>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.</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>
|
]]></field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,8 @@ class gamification_badge_user(osv.Model):
|
||||||
_columns = {
|
_columns = {
|
||||||
'user_id': fields.many2one('res.users', string="User", required=True),
|
'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"),
|
'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'),
|
'comment': fields.text('Comment'),
|
||||||
'badge_name': fields.related('badge_id', 'name', type="char", string="Badge Name"),
|
'badge_name': fields.related('badge_id', 'name', type="char", string="Badge Name"),
|
||||||
'create_date': fields.datetime('Created', readonly=True),
|
'create_date': fields.datetime('Created', readonly=True),
|
||||||
|
@ -263,7 +264,7 @@ class gamification_badge(osv.Model):
|
||||||
|
|
||||||
def check_progress(self, cr, uid, context=None):
|
def check_progress(self, cr, uid, context=None):
|
||||||
try:
|
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:
|
except ValueError:
|
||||||
return True
|
return True
|
||||||
badge_user_obj = self.pool.get('gamification.badge.user')
|
badge_user_obj = self.pool.get('gamification.badge.user')
|
||||||
|
|
|
@ -163,6 +163,8 @@ class gamification_challenge(osv.Model):
|
||||||
'reward_second_id': fields.many2one('gamification.badge', string="For 2nd user"),
|
'reward_second_id': fields.many2one('gamification.badge', string="For 2nd user"),
|
||||||
'reward_third_id': fields.many2one('gamification.badge', string="For 3rd user"),
|
'reward_third_id': fields.many2one('gamification.badge', string="For 3rd user"),
|
||||||
'reward_failure': fields.boolean('Reward Bests if not Succeeded?'),
|
'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([
|
'visibility_mode': fields.selection([
|
||||||
('personal', 'Individual Goals'),
|
('personal', 'Individual Goals'),
|
||||||
|
@ -257,7 +259,7 @@ class gamification_challenge(osv.Model):
|
||||||
|
|
||||||
elif vals.get('state') == 'draft':
|
elif vals.get('state') == 'draft':
|
||||||
# resetting progress
|
# 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.")
|
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)
|
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)
|
- Create the missing goals (eg: modified the challenge to add lines)
|
||||||
- Update every running challenge
|
- Update every running challenge
|
||||||
"""
|
"""
|
||||||
# start planned challenges
|
# start scheduled challenges
|
||||||
planned_challenge_ids = self.search(cr, uid, [
|
planned_challenge_ids = self.search(cr, uid, [
|
||||||
('state', '=', 'draft'),
|
('state', '=', 'draft'),
|
||||||
('start_date', '<=', fields.date.today())])
|
('start_date', '<=', fields.date.today())])
|
||||||
self.write(cr, uid, planned_challenge_ids, {'state': 'inprogress'}, context=context)
|
self.write(cr, uid, planned_challenge_ids, {'state': 'inprogress'}, context=context)
|
||||||
|
|
||||||
# close planned challenges
|
# close scheduled challenges
|
||||||
planned_challenge_ids = self.search(cr, uid, [
|
planned_challenge_ids = self.search(cr, uid, [
|
||||||
('state', '=', 'inprogress'),
|
('state', '=', 'inprogress'),
|
||||||
('end_date', '>=', fields.date.today())])
|
('end_date', '>=', fields.date.today())])
|
||||||
|
@ -312,7 +314,7 @@ class gamification_challenge(osv.Model):
|
||||||
goal_ids = goal_obj.search(cr, uid, [
|
goal_ids = goal_obj.search(cr, uid, [
|
||||||
('challenge_id', 'in', ids),
|
('challenge_id', 'in', ids),
|
||||||
'|',
|
'|',
|
||||||
('state', 'in', ('inprogress', 'inprogress_update')),
|
('state', '=', 'inprogress'),
|
||||||
'&',
|
'&',
|
||||||
('state', 'in', ('reached', 'failed')),
|
('state', 'in', ('reached', 'failed')),
|
||||||
'|',
|
'|',
|
||||||
|
@ -328,6 +330,8 @@ class gamification_challenge(osv.Model):
|
||||||
self.write(cr, uid, [challenge.id], {'user_ids': [(4, user.id) for user in challenge.autojoin_group_id.users]}, context=context)
|
self.write(cr, uid, [challenge.id], {'user_ids': [(4, user.id) for user in challenge.autojoin_group_id.users]}, context=context)
|
||||||
self.generate_goals_from_challenge(cr, uid, [challenge.id], context=context)
|
self.generate_goals_from_challenge(cr, uid, [challenge.id], context=context)
|
||||||
|
|
||||||
|
# goal_group = goal_obj.read_group(cr, uid, [('challenge_id', '=', challenge.id), ('closed', '=', False)], fields=['id', 'line_id', 'target_goal'], groupby=['line_id'], context=context)
|
||||||
|
|
||||||
# goals closed but still opened at the last report date
|
# goals closed but still opened at the last report date
|
||||||
closed_goals_to_report = goal_obj.search(cr, uid, [
|
closed_goals_to_report = goal_obj.search(cr, uid, [
|
||||||
('challenge_id', '=', challenge.id),
|
('challenge_id', '=', challenge.id),
|
||||||
|
@ -349,6 +353,7 @@ class gamification_challenge(osv.Model):
|
||||||
"""Update all the goals of a challenge, no generation of new goals"""
|
"""Update all the goals of a challenge, no generation of new goals"""
|
||||||
goal_ids = self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', '=', challenge_id)], context=context)
|
goal_ids = self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', '=', challenge_id)], context=context)
|
||||||
self.pool.get('gamification.goal').update(cr, uid, goal_ids, context=context)
|
self.pool.get('gamification.goal').update(cr, uid, goal_ids, context=context)
|
||||||
|
print self.pool.get('gamification.goal').read_group(cr, uid, [('challenge_id', '=', challenge_id), ('closed', '=', False)], fields=['id', 'line_id', 'target_goal'], groupby=['line_id'], context=context)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -377,6 +382,7 @@ class gamification_challenge(osv.Model):
|
||||||
can be called after each change in the list of users or lines.
|
can be called after each change in the list of users or lines.
|
||||||
:param list(int) ids: the list of challenge concerned"""
|
:param list(int) ids: the list of challenge concerned"""
|
||||||
|
|
||||||
|
to_update = []
|
||||||
for challenge in self.browse(cr, uid, ids, context):
|
for challenge in self.browse(cr, uid, ids, context):
|
||||||
(start_date, end_date) = start_end_date_for_period(challenge.period)
|
(start_date, end_date) = start_end_date_for_period(challenge.period)
|
||||||
|
|
||||||
|
@ -403,7 +409,7 @@ class gamification_challenge(osv.Model):
|
||||||
canceled_goal_ids = goal_obj.search(cr, uid, domain, context=context)
|
canceled_goal_ids = goal_obj.search(cr, uid, domain, context=context)
|
||||||
if canceled_goal_ids:
|
if canceled_goal_ids:
|
||||||
goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context)
|
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
|
# skip to next user
|
||||||
continue
|
continue
|
||||||
|
@ -425,8 +431,9 @@ class gamification_challenge(osv.Model):
|
||||||
values['remind_update_delay'] = 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)
|
||||||
|
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
|
return True
|
||||||
|
|
||||||
|
@ -460,7 +467,7 @@ class gamification_challenge(osv.Model):
|
||||||
'rank': <user ranking>,
|
'rank': <user ranking>,
|
||||||
'user_id': <res.users id>,
|
'user_id': <res.users id>,
|
||||||
'name': <res.users name>,
|
'name': <res.users name>,
|
||||||
'state': <gamification.goal state {draft,inprogress,inprogress_update,reached,failed,canceled}>,
|
'state': <gamification.goal state {draft,inprogress,reached,failed,canceled}>,
|
||||||
'completeness': <percentage>,
|
'completeness': <percentage>,
|
||||||
'current': <current value>,
|
'current': <current value>,
|
||||||
}
|
}
|
||||||
|
@ -478,7 +485,7 @@ class gamification_challenge(osv.Model):
|
||||||
'action': <{True,False}>,
|
'action': <{True,False}>,
|
||||||
'display_mode': <{progress,boolean}>,
|
'display_mode': <{progress,boolean}>,
|
||||||
'target': <challenge line target>,
|
'target': <challenge line target>,
|
||||||
'state': <gamification.goal state {draft,inprogress,inprogress_update,reached,failed,canceled}>,
|
'state': <gamification.goal state {draft,inprogress,reached,failed,canceled}>,
|
||||||
'completeness': <percentage>,
|
'completeness': <percentage>,
|
||||||
'current': <current value>,
|
'current': <current value>,
|
||||||
}
|
}
|
||||||
|
@ -545,7 +552,7 @@ class gamification_challenge(osv.Model):
|
||||||
if user_id and goal.user_id.id == user_id:
|
if user_id and goal.user_id.id == user_id:
|
||||||
line_data['own_goal_id'] = goal.id
|
line_data['own_goal_id'] = goal.id
|
||||||
elif restrict_top and ranking > restrict_top:
|
elif restrict_top and ranking > restrict_top:
|
||||||
# not own goal, over top, skipping
|
# not own goal and too low to be in top
|
||||||
continue
|
continue
|
||||||
|
|
||||||
line_data['goals'].append({
|
line_data['goals'].append({
|
||||||
|
|
|
@ -88,8 +88,16 @@ class gamification_goal_definition(osv.Model):
|
||||||
string='Date Field',
|
string='Date Field',
|
||||||
help='The date to use for the time period evaluated'),
|
help='The date to use for the time period evaluated'),
|
||||||
'domain': fields.char("Filter Domain",
|
'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),
|
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',
|
'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."),
|
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([
|
'condition': fields.selection([
|
||||||
|
@ -102,7 +110,7 @@ class gamification_goal_definition(osv.Model):
|
||||||
'action_id': fields.many2one('ir.actions.act_window', string="Action",
|
'action_id': fields.many2one('ir.actions.act_window', string="Action",
|
||||||
help="The action that will be called to update the goal value."),
|
help="The action that will be called to update the goal value."),
|
||||||
'res_id_field': fields.char("ID Field of user",
|
'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 = {
|
_defaults = {
|
||||||
|
@ -158,7 +166,7 @@ class gamification_goal(osv.Model):
|
||||||
_columns = {
|
_columns = {
|
||||||
'definition_id': fields.many2one('gamification.goal.definition', string='Goal Definition', required=True, ondelete="cascade"),
|
'definition_id': fields.many2one('gamification.goal.definition', string='Goal Definition', required=True, ondelete="cascade"),
|
||||||
'user_id': fields.many2one('res.users', string='User', required=True),
|
'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',
|
'challenge_id': fields.related('line_id', 'challenge_id',
|
||||||
string="Challenge",
|
string="Challenge",
|
||||||
type='many2one',
|
type='many2one',
|
||||||
|
@ -175,7 +183,6 @@ class gamification_goal(osv.Model):
|
||||||
'state': fields.selection([
|
'state': fields.selection([
|
||||||
('draft', 'Draft'),
|
('draft', 'Draft'),
|
||||||
('inprogress', 'In progress'),
|
('inprogress', 'In progress'),
|
||||||
('inprogress_update', 'In progress (to update)'),
|
|
||||||
('reached', 'Reached'),
|
('reached', 'Reached'),
|
||||||
('failed', 'Failed'),
|
('failed', 'Failed'),
|
||||||
('canceled', 'Canceled'),
|
('canceled', 'Canceled'),
|
||||||
|
@ -183,6 +190,8 @@ class gamification_goal(osv.Model):
|
||||||
string='State',
|
string='State',
|
||||||
required=True,
|
required=True,
|
||||||
track_visibility='always'),
|
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"),
|
'computation_mode': fields.related('definition_id', 'computation_mode', type='char', string="Computation mode"),
|
||||||
'remind_update_delay': fields.integer('Remind delay',
|
'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:
|
if goal.remind_update_delay and goal.last_update:
|
||||||
delta_max = timedelta(days=goal.remind_update_delay)
|
delta_max = timedelta(days=goal.remind_update_delay)
|
||||||
last_update = datetime.strptime(goal.last_update, DF).date()
|
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
|
# generate a remind report
|
||||||
temp_obj = self.pool.get('email.template')
|
temp_obj = self.pool.get('email.template')
|
||||||
template_id = self.pool['ir.model.data'].get_object(cr, uid, 'gamification', 'email_template_goal_reminder', context)
|
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)
|
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')
|
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 {}
|
return {}
|
||||||
|
|
||||||
def update(self, cr, uid, ids, context=None):
|
def update(self, cr, uid, ids, context=None):
|
||||||
|
@ -233,71 +242,123 @@ class gamification_goal(osv.Model):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
|
|
||||||
|
goals_by_definition = {}
|
||||||
|
goals_to_write = {}
|
||||||
|
all_goals = {}
|
||||||
for goal in self.browse(cr, uid, ids, context=context):
|
for goal in self.browse(cr, uid, ids, context=context):
|
||||||
towrite = {}
|
|
||||||
if goal.state in ('draft', 'canceled'):
|
if goal.state in ('draft', 'canceled'):
|
||||||
# skip if goal draft or canceled
|
# draft or canceled goals should not be recomputed
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if goal.definition_id.computation_mode == 'manually':
|
goals_by_definition.setdefault(goal.definition_id, []).append(goal)
|
||||||
towrite.update(self._check_remind_delay(cr, uid, goal, context))
|
goals_to_write[goal.id] = {}
|
||||||
|
all_goals[goal.id] = goal
|
||||||
|
|
||||||
elif goal.definition_id.computation_mode == 'python':
|
for definition, goals in goals_by_definition.items():
|
||||||
# execute the chosen method
|
if definition.computation_mode == 'manually':
|
||||||
cxt = {
|
for goal in goals:
|
||||||
'self': self.pool.get('gamification.goal'),
|
goals_to_write[goal.id].update(self._check_remind_delay(cr, uid, goal, context))
|
||||||
'object': goal,
|
elif definition.computation_mode == 'python':
|
||||||
'pool': self.pool,
|
# TODO batch execution
|
||||||
'cr': cr,
|
for goal in goals:
|
||||||
'context': dict(context), # copy context to prevent side-effects of eval
|
# execute the chosen method
|
||||||
'uid': uid,
|
cxt = {
|
||||||
'result': False,
|
'self': self.pool.get('gamification.goal'),
|
||||||
'date': date, 'datetime': datetime, 'timedelta': timedelta, 'time': time
|
'object': goal,
|
||||||
}
|
'pool': self.pool,
|
||||||
code = goal.definition_id.compute_code.strip()
|
'cr': cr,
|
||||||
safe_eval(code, cxt, mode="exec", nocopy=True)
|
'context': dict(context), # copy context to prevent side-effects of eval
|
||||||
# the result of the evaluated codeis put in the 'result' local variable, propagated to the context
|
'uid': uid,
|
||||||
result = cxt.get('result', False)
|
'result': False,
|
||||||
if result and type(result) in (float, int, long):
|
'date': date, 'datetime': datetime, 'timedelta': timedelta, 'time': time
|
||||||
if result != goal.current:
|
}
|
||||||
towrite['current'] = result
|
code = definition.compute_code.strip()
|
||||||
else:
|
safe_eval(code, cxt, mode="exec", nocopy=True)
|
||||||
_logger.exception(_('Invalid return content from the evaluation of %s' % code))
|
# the result of the evaluated codeis put in the 'result' local variable, propagated to the context
|
||||||
|
result = cxt.get('result', False)
|
||||||
|
if result 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
|
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
|
obj = self.pool.get(definition.model_id.model)
|
||||||
domain = safe_eval(goal.definition_id.domain, {'user': goal.user_id})
|
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 definition.computation_mode == 'count' and definition.batch_mode:
|
||||||
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 goal.definition_id.computation_mode == 'sum':
|
general_domain = safe_eval(definition.domain)
|
||||||
field_name = goal.definition_id.field_id.name
|
# goal_distinct_values = {goal.id: safe_eval(definition.batch_user_expression, {'user': goal.user_id}) for goal in goals}
|
||||||
res = obj.read_group(cr, uid, domain, [field_name], [field_name], context=context)
|
field_name = definition.batch_distinctive_field.name
|
||||||
new_value = res and res[0][field_name] or 0.0
|
# 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
|
for (start_date, end_date), query_goals in subqueries.items():
|
||||||
new_value = obj.search(cr, uid, domain, context=context, count=True)
|
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
|
user_values = obj.read_group(cr, uid, subquery_domain, fields=[field_name], groupby=[field_name], context=context)
|
||||||
if new_value != goal.current:
|
|
||||||
towrite['current'] = new_value
|
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
|
# 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):
|
if (goal.definition_condition == 'higher' and value.get('current', goal.current) >= goal.target_goal) \
|
||||||
towrite['state'] = 'reached'
|
or (goal.definition_condition == 'lower' and value.get('current', goal.current) <= goal.target_goal):
|
||||||
|
value['state'] = 'reached'
|
||||||
|
|
||||||
# check goal failure
|
# check goal failure
|
||||||
elif goal.end_date and fields.date.today() > goal.end_date:
|
elif goal.end_date and fields.date.today() > goal.end_date:
|
||||||
towrite['state'] = 'failed'
|
value['state'] = 'failed'
|
||||||
if towrite:
|
value['closed'] = True
|
||||||
self.write(cr, uid, [goal.id], towrite, context=context)
|
if value:
|
||||||
|
self.write(cr, uid, [goal.id], value, context=context)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def action_start(self, cr, uid, ids, context=None):
|
def action_start(self, cr, uid, ids, context=None):
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 62 KiB |
|
@ -49,21 +49,6 @@ openerp.gamification = function(instance) {
|
||||||
self.get_goal_todo_info();
|
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() {
|
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({
|
instance.mail.Widget.include({
|
||||||
start: function() {
|
start: function() {
|
||||||
this._super();
|
this._super();
|
||||||
|
|
|
@ -45,8 +45,8 @@
|
||||||
<header>
|
<header>
|
||||||
<button string="Start goal" type="object" name="action_start" states="draft" class="oe_highlight"/>
|
<button string="Start goal" type="object" name="action_start" states="draft" class="oe_highlight"/>
|
||||||
|
|
||||||
<button string="Goal Reached" type="object" name="action_reach" states="inprogress,inprogress_update" />
|
<button string="Goal Reached" type="object" name="action_reach" states="inprogress" />
|
||||||
<button string="Goal Failed" type="object" name="action_fail" states="inprogress,inprogress_update"/>
|
<button string="Goal Failed" type="object" name="action_fail" states="inprogress"/>
|
||||||
<button string="Reset Completion" type="object" name="action_cancel" states="failed,reached" groups="base.group_no_one" />
|
<button string="Reset Completion" type="object" name="action_cancel" states="failed,reached" groups="base.group_no_one" />
|
||||||
<field name="state" widget="statusbar" statusbar_visible="draft,inprogress,reached" />
|
<field name="state" widget="statusbar" statusbar_visible="draft,inprogress,reached" />
|
||||||
</header>
|
</header>
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
<filter name="inprogress" string="Current"
|
<filter name="inprogress" string="Current"
|
||||||
domain="[
|
domain="[
|
||||||
'|',
|
'|',
|
||||||
('state', 'in', ('inprogress', 'inprogress_update')),
|
('state', '=', 'inprogress'),
|
||||||
('end_date', '>=', context_today().strftime('%%Y-%%m-%%d'))
|
('end_date', '>=', context_today().strftime('%%Y-%%m-%%d'))
|
||||||
]"/>
|
]"/>
|
||||||
<filter name="closed" string="Passed" domain="[('state', 'in', ('reached', 'failed'))]"/>
|
<filter name="closed" string="Passed" domain="[('state', 'in', ('reached', 'failed'))]"/>
|
||||||
|
@ -157,7 +157,7 @@
|
||||||
<t t-if="record.definition_display.raw_value == 'boolean'">
|
<t t-if="record.definition_display.raw_value == 'boolean'">
|
||||||
<div class="oe_goal_state oe_e">
|
<div class="oe_goal_state oe_e">
|
||||||
<t t-if="record.state.raw_value=='reached'"><span class="oe_green" title="Goal Reached">W</span></t>
|
<t t-if="record.state.raw_value=='reached'"><span class="oe_green" title="Goal Reached">W</span></t>
|
||||||
<t t-if="record.state.raw_value=='inprogress' || record.state.raw_value=='inprogress_update'"><span title="Goal in Progress">N</span></t>
|
<t t-if="record.state.raw_value=='inprogress'"><span title="Goal in Progress">N</span></t>
|
||||||
<t t-if="record.state.raw_value=='failed'"><span class="oe_red" title="Goal Failed">X</span></t>
|
<t t-if="record.state.raw_value=='failed'"><span class="oe_red" title="Goal Failed">X</span></t>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
|
@ -241,15 +241,23 @@
|
||||||
|
|
||||||
<!-- Hide the fields below if manually -->
|
<!-- Hide the fields below if manually -->
|
||||||
<field name="model_id" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))], 'required':[('computation_mode','in',('sum', 'count'))]}" class="oe_inline"/>
|
<field name="model_id" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))], 'required':[('computation_mode','in',('sum', 'count'))]}" class="oe_inline"/>
|
||||||
<field name="field_id" attrs="{'invisible':[('computation_mode','!=','sum')], 'required':[('computation_mode','=','sum')]}" domain="[('model_id','=',model_id)]" class="oe_inline"/>
|
<field name="field_id" attrs="{'invisible':[('computation_mode','!=','sum')], 'required':[('computation_mode','=','sum')]}" domain="[('model_id', '=', model_id)]" class="oe_inline"/>
|
||||||
<field name="field_date_id" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))]}" domain="[('ttype', 'in', ('date', 'datetime')), ('model_id','=',model_id)]" class="oe_inline"/>
|
<field name="field_date_id" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))]}" domain="[('ttype', 'in', ('date', 'datetime')), ('model_id', '=', model_id)]" class="oe_inline"/>
|
||||||
<field name="domain" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))], 'required':[('computation_mode','in',('sum', 'count'))]}" class="oe_inline"/>
|
<field name="domain" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))], 'required':[('computation_mode','in',('sum', 'count'))]}" class="oe_inline"/>
|
||||||
<field name="compute_code" attrs="{'invisible':[('computation_mode','!=','python')], 'required':[('computation_mode','=','python')]}" placeholder="e.g. result = pool.get('mail.followers').search(cr, uid, [('res_model', '=', 'mail.group'), ('partner_id', '=', object.user_id.partner_id.id)], count=True, context=context)"/>
|
<field name="compute_code" attrs="{'invisible':[('computation_mode','!=','python')], 'required':[('computation_mode','=','python')]}" placeholder="e.g. result = pool.get('mail.followers').search(cr, uid, [('res_model', '=', 'mail.group'), ('partner_id', '=', object.user_id.partner_id.id)], count=True, context=context)"/>
|
||||||
<field name="condition" widget="radio"/>
|
<field name="condition" widget="radio"/>
|
||||||
</group>
|
</group>
|
||||||
|
<group string="Optimisation" attrs="{'invisible': [('computation_mode', '!=', 'count')]}">
|
||||||
|
<field name="batch_mode" />
|
||||||
|
<div colspan="4">In batch mode, the domain is evaluated globally. If enabled, do not use keyword 'user' in above filter domain.</div>
|
||||||
|
<field name="batch_distinctive_field" attrs="{'invisible': [('batch_mode', '=', False)], 'required': [('batch_mode', '=', True)]}"
|
||||||
|
domain="[('model_id', '=', model_id)]" class="oe_inline" />
|
||||||
|
<field name="batch_user_expression" attrs="{'invisible': [('batch_mode', '=', False)], 'required': [('batch_mode', '=', True)]}" class="oe_inline"
|
||||||
|
placeholder="e.g. user.partner_id.id"/>
|
||||||
|
</group>
|
||||||
<group string="Formating Options">
|
<group string="Formating Options">
|
||||||
<field name="display_mode" widget="radio" />
|
<field name="display_mode" widget="radio" />
|
||||||
<field name="suffix" placeholder="e.g. days"/>
|
<field name="suffix" placeholder="e.g. days" class="oe_inline"/>
|
||||||
<field name="monetary"/>
|
<field name="monetary"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Clickable Goals" attrs="{'invisible': [('computation_mode', '=', 'manually')]}">
|
<group string="Clickable Goals" attrs="{'invisible': [('computation_mode', '=', 'manually')]}">
|
||||||
|
|
|
@ -38,6 +38,7 @@ class goal_manual_wizard(osv.TransientModel):
|
||||||
towrite = {
|
towrite = {
|
||||||
'current': wiz.current,
|
'current': wiz.current,
|
||||||
'goal_id': wiz.goal_id.id,
|
'goal_id': wiz.goal_id.id,
|
||||||
|
'to_update': False,
|
||||||
}
|
}
|
||||||
goal_obj.write(cr, uid, [wiz.goal_id.id], towrite, context=context)
|
goal_obj.write(cr, uid, [wiz.goal_id.id], towrite, context=context)
|
||||||
goal_obj.update(cr, uid, [wiz.goal_id.id], context=context)
|
goal_obj.update(cr, uid, [wiz.goal_id.id], context=context)
|
||||||
|
|
Loading…
Reference in New Issue