2013-02-28 13:53:29 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
2013-12-17 10:48:13 +00:00
# Copyright (C) 2013 OpenERP SA (<http://www.openerp.com>)
2013-02-28 13:53:29 +00:00
#
# 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/>
#
##############################################################################
2013-12-20 14:45:21 +00:00
from openerp import SUPERUSER_ID
2013-02-28 13:53:29 +00:00
from openerp . osv import fields , osv
2014-04-18 16:38:37 +00:00
from openerp . tools import ustr , DEFAULT_SERVER_DATE_FORMAT as DF
2014-04-16 15:10:26 +00:00
from openerp . tools . safe_eval import safe_eval as eval
2013-04-15 14:04:01 +00:00
from openerp . tools . translate import _
2013-02-28 13:53:29 +00:00
from datetime import date , datetime , timedelta
import calendar
2013-03-29 16:31:37 +00:00
import logging
_logger = logging . getLogger ( __name__ )
2013-02-28 13:53:29 +00:00
2013-12-20 14:45:21 +00:00
# display top 3 in ranking, could be db variable
MAX_VISIBILITY_RANKING = 3
2013-02-28 13:53:29 +00:00
2013-04-24 10:31:07 +00:00
def start_end_date_for_period ( period , default_start_date = False , default_end_date = False ) :
2013-02-28 13:53:29 +00:00
""" Return the start and end date for a goal period based on today
2015-04-03 08:10:53 +00:00
: param str default_start_date : string date in DEFAULT_SERVER_DATE_FORMAT format
: param str default_end_date : string date in DEFAULT_SERVER_DATE_FORMAT format
: return : ( start_date , end_date ) , dates in string format , False if the period is
2013-02-28 13:53:29 +00:00
not defined or unknown """
today = date . today ( )
if period == ' daily ' :
start_date = today
2013-03-14 15:09:55 +00:00
end_date = start_date
2013-02-28 13:53:29 +00:00
elif period == ' weekly ' :
delta = timedelta ( days = today . weekday ( ) )
start_date = today - delta
end_date = start_date + timedelta ( days = 7 )
elif period == ' monthly ' :
month_range = calendar . monthrange ( today . year , today . month )
2013-03-01 08:11:10 +00:00
start_date = today . replace ( day = 1 )
2013-02-28 13:53:29 +00:00
end_date = today . replace ( day = month_range [ 1 ] )
elif period == ' yearly ' :
start_date = today . replace ( month = 1 , day = 1 )
end_date = today . replace ( month = 12 , day = 31 )
2013-03-13 16:43:25 +00:00
else : # period == 'once':
2013-04-24 10:31:07 +00:00
start_date = default_start_date # for manual goal, start each time
end_date = default_end_date
2013-03-11 09:39:30 +00:00
2013-04-15 14:12:38 +00:00
return ( start_date , end_date )
2013-02-28 13:53:29 +00:00
2015-04-03 08:10:53 +00:00
return ( datetime . strftime ( start_date , DF ) , datetime . strftime ( end_date , DF ) )
2013-02-28 13:53:29 +00:00
2013-12-17 16:15:41 +00:00
class gamification_challenge ( osv . Model ) :
""" Gamification challenge
2013-02-28 13:53:29 +00:00
2013-12-17 16:15:41 +00:00
Set of predifined objectives assigned to people with rules for recurrence and
rewards
2013-02-28 13:53:29 +00:00
If ' user_ids ' is defined and ' period ' is different than ' one ' , the set will
2013-03-11 09:39:30 +00:00
be assigned to the users for each period ( eg : every 1 st of each month if
' monthly ' is selected )
2013-02-28 13:53:29 +00:00
"""
2013-12-17 16:15:41 +00:00
_name = ' gamification.challenge '
_description = ' Gamification challenge '
2013-02-28 13:53:29 +00:00
_inherit = ' mail.thread '
2013-03-04 11:27:12 +00:00
def _get_next_report_date ( self , cr , uid , ids , field_name , arg , context = None ) :
""" Return the next report date based on the last report date and report
2013-03-15 13:39:29 +00:00
period .
2013-12-16 15:43:17 +00:00
: return : a string in DEFAULT_SERVER_DATE_FORMAT representing the date """
2013-03-04 11:27:12 +00:00
res = { }
2014-04-11 15:19:17 +00:00
for challenge in self . browse ( cr , uid , ids , context = context ) :
2013-12-17 16:15:41 +00:00
last = datetime . strptime ( challenge . last_report_date , DF ) . date ( )
if challenge . report_message_frequency == ' daily ' :
2013-03-13 10:49:00 +00:00
next = last + timedelta ( days = 1 )
2013-12-17 16:15:41 +00:00
res [ challenge . id ] = next . strftime ( DF )
elif challenge . report_message_frequency == ' weekly ' :
2013-03-13 10:49:00 +00:00
next = last + timedelta ( days = 7 )
2013-12-17 16:15:41 +00:00
res [ challenge . id ] = next . strftime ( DF )
elif challenge . report_message_frequency == ' monthly ' :
2013-03-04 11:27:12 +00:00
month_range = calendar . monthrange ( last . year , last . month )
2013-03-13 10:49:00 +00:00
next = last . replace ( day = month_range [ 1 ] ) + timedelta ( days = 1 )
2013-12-17 16:15:41 +00:00
res [ challenge . id ] = next . strftime ( DF )
elif challenge . report_message_frequency == ' yearly ' :
res [ challenge . id ] = last . replace ( year = last . year + 1 ) . strftime ( DF )
2013-04-24 10:31:07 +00:00
# frequency == 'once', reported when closed only
else :
2013-12-17 16:15:41 +00:00
res [ challenge . id ] = False
2013-03-13 10:26:14 +00:00
2013-03-04 11:27:12 +00:00
return res
2013-12-24 14:45:14 +00:00
2013-12-17 17:02:32 +00:00
def _get_categories ( self , cr , uid , context = None ) :
2013-12-24 14:45:14 +00:00
return [
( ' hr ' , ' Human Ressources / Engagement ' ) ,
( ' other ' , ' Settings / Gamification Tools ' ) ,
]
def _get_report_template ( self , cr , uid , context = None ) :
try :
return self . pool . get ( ' ir.model.data ' ) . get_object_reference ( cr , uid , ' gamification ' , ' simple_report_template ' ) [ 1 ]
except ValueError :
return False
2013-03-04 11:27:12 +00:00
2013-12-20 14:45:21 +00:00
_order = ' end_date, start_date, name, id '
2013-02-28 13:53:29 +00:00
_columns = {
2013-04-15 15:50:14 +00:00
' name ' : fields . char ( ' Challenge Name ' , required = True , translate = True ) ,
2013-04-15 14:04:01 +00:00
' description ' : fields . text ( ' Description ' , translate = True ) ,
2013-04-08 13:17:29 +00:00
' state ' : fields . selection ( [
( ' draft ' , ' Draft ' ) ,
( ' inprogress ' , ' In Progress ' ) ,
( ' done ' , ' Done ' ) ,
2014-07-06 14:44:26 +00:00
] , copy = False ,
2013-12-24 14:54:49 +00:00
string = ' State ' , required = True , track_visibility = ' onchange ' ) ,
2013-04-08 13:17:29 +00:00
' manager_id ' : fields . many2one ( ' res.users ' ,
2013-04-15 15:50:14 +00:00
string = ' Responsible ' , help = " The user responsible for the challenge. " ) ,
2013-04-08 13:17:29 +00:00
2014-05-12 09:27:18 +00:00
' user_ids ' : fields . many2many ( ' res.users ' , ' gamification_challenge_users_rel ' ,
2013-02-28 13:53:29 +00:00
string = ' Users ' ,
2013-12-17 16:15:41 +00:00
help = " List of users participating to the challenge " ) ,
2014-04-16 15:10:26 +00:00
' user_domain ' : fields . char ( ' User domain ' , help = " Alternative to a list of users " ) ,
2013-04-08 13:17:29 +00:00
2013-04-22 09:32:36 +00:00
' period ' : fields . selection ( [
( ' once ' , ' Non recurring ' ) ,
( ' daily ' , ' Daily ' ) ,
( ' weekly ' , ' Weekly ' ) ,
( ' monthly ' , ' Monthly ' ) ,
( ' yearly ' , ' Yearly ' )
] ,
string = ' Periodicity ' ,
help = ' Period of automatic goal assigment. If none is selected, should be launched manually. ' ,
required = True ) ,
' start_date ' : fields . date ( ' Start Date ' ,
help = " The day a new challenge will be automatically started. If no periodicity is set, will use this date as the goal start date. " ) ,
' end_date ' : fields . date ( ' End Date ' ,
help = " The day a new challenge will be automatically closed. If no periodicity is set, will use this date as the goal end date. " ) ,
2014-05-12 09:27:18 +00:00
' invited_user_ids ' : fields . many2many ( ' res.users ' , ' gamification_invited_user_ids_rel ' ,
2013-04-18 10:32:29 +00:00
string = " Suggest to users " ) ,
2013-04-15 14:04:01 +00:00
2013-12-17 16:15:41 +00:00
' line_ids ' : fields . one2many ( ' gamification.challenge.line ' , ' challenge_id ' ,
string = ' Lines ' ,
2013-04-15 14:04:01 +00:00
help = " List of goals that will be set " ,
2014-07-06 14:44:26 +00:00
required = True , copy = True ) ,
2013-04-08 13:17:29 +00:00
2013-04-16 10:27:24 +00:00
' reward_id ' : fields . many2one ( ' gamification.badge ' , string = " For Every Succeding User " ) ,
' reward_first_id ' : fields . many2one ( ' gamification.badge ' , string = " For 1st user " ) ,
' reward_second_id ' : fields . many2one ( ' gamification.badge ' , string = " For 2nd user " ) ,
' reward_third_id ' : fields . many2one ( ' gamification.badge ' , string = " For 3rd user " ) ,
2013-04-19 10:22:45 +00:00
' reward_failure ' : fields . boolean ( ' Reward Bests if not Succeeded? ' ) ,
2014-04-11 14:22:57 +00:00
' 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. " ) ,
2013-04-15 14:04:01 +00:00
2013-03-13 10:26:14 +00:00
' visibility_mode ' : fields . selection ( [
2013-12-17 17:02:32 +00:00
( ' personal ' , ' Individual Goals ' ) ,
( ' ranking ' , ' Leader Board (Group Ranking) ' ) ,
2013-02-28 13:53:29 +00:00
] ,
2013-04-10 10:14:54 +00:00
string = " Display Mode " , required = True ) ,
2013-12-24 14:45:14 +00:00
2013-03-13 10:26:14 +00:00
' report_message_frequency ' : fields . selection ( [
2013-04-15 14:04:01 +00:00
( ' never ' , ' Never ' ) ,
( ' onchange ' , ' On change ' ) ,
( ' daily ' , ' Daily ' ) ,
( ' weekly ' , ' Weekly ' ) ,
( ' monthly ' , ' Monthly ' ) ,
2013-02-28 13:53:29 +00:00
( ' yearly ' , ' Yearly ' )
] ,
2013-04-10 10:14:54 +00:00
string = " Report Frequency " , required = True ) ,
2013-03-13 10:26:14 +00:00
' report_message_group_id ' : fields . many2one ( ' mail.group ' ,
2013-02-28 13:53:29 +00:00
string = ' Send a copy to ' ,
help = ' Group that will receive a copy of the report in addition to the user ' ) ,
2013-12-24 14:45:14 +00:00
' report_template_id ' : fields . many2one ( ' email.template ' , string = " Report Template " , required = True ) ,
2013-04-08 13:17:29 +00:00
' remind_update_delay ' : fields . integer ( ' Non-updated manual goals will be reminded after ' ,
help = " Never reminded if no value or zero is specified. " ) ,
2013-03-04 11:27:12 +00:00
' last_report_date ' : fields . date ( ' Last Report Date ' ) ,
' next_report_date ' : fields . function ( _get_next_report_date ,
2013-12-17 17:02:32 +00:00
type = ' date ' , string = ' Next Report Date ' , store = True ) ,
2013-04-10 10:14:54 +00:00
2013-12-17 17:02:32 +00:00
' category ' : fields . selection ( lambda s , * a , * * k : s . _get_categories ( * a , * * k ) ,
2013-04-15 14:04:01 +00:00
string = " Appears in " , help = " Define the visibility of the challenge through menus " , required = True ) ,
2013-02-28 13:53:29 +00:00
}
_defaults = {
' period ' : ' once ' ,
' state ' : ' draft ' ,
2013-12-17 17:02:32 +00:00
' visibility_mode ' : ' personal ' ,
2013-07-02 14:52:47 +00:00
' report_message_frequency ' : ' never ' ,
2013-04-08 13:17:29 +00:00
' last_report_date ' : fields . date . today ,
' manager_id ' : lambda s , cr , uid , c : uid ,
2013-04-10 10:14:54 +00:00
' category ' : ' hr ' ,
2013-04-15 14:04:01 +00:00
' reward_failure ' : False ,
2013-12-24 14:45:14 +00:00
' report_template_id ' : lambda s , * a , * * k : s . _get_report_template ( * a , * * k ) ,
2014-09-12 08:37:14 +00:00
' reward_realtime ' : True ,
2013-02-28 13:53:29 +00:00
}
2013-12-18 11:12:43 +00:00
def create ( self , cr , uid , vals , context = None ) :
""" Overwrite the create method to add the user of groups """
2014-04-16 15:10:26 +00:00
if vals . get ( ' user_domain ' ) :
user_ids = self . _get_challenger_users ( cr , uid , vals . get ( ' user_domain ' ) , context = context )
2013-12-18 11:12:43 +00:00
2013-12-23 16:12:29 +00:00
if not vals . get ( ' user_ids ' ) :
2013-12-18 11:12:43 +00:00
vals [ ' user_ids ' ] = [ ]
2014-04-16 15:10:26 +00:00
vals [ ' user_ids ' ] + = [ ( 4 , user_id ) for user_id in user_ids ]
2013-12-18 11:12:43 +00:00
2014-05-07 09:56:16 +00:00
return super ( gamification_challenge , self ) . create ( cr , uid , vals , context = context )
2013-12-18 11:12:43 +00:00
2013-02-28 13:53:29 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
2013-12-24 14:11:40 +00:00
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2014-04-18 16:10:53 +00:00
if vals . get ( ' user_domain ' ) :
user_ids = self . _get_challenger_users ( cr , uid , vals . get ( ' user_domain ' ) , context = context )
if not vals . get ( ' user_ids ' ) :
vals [ ' user_ids ' ] = [ ]
vals [ ' user_ids ' ] + = [ ( 4 , user_id ) for user_id in user_ids ]
2013-12-24 14:54:49 +00:00
2014-04-18 14:07:41 +00:00
write_res = super ( gamification_challenge , self ) . write ( cr , uid , ids , vals , context = context )
2013-12-24 14:11:40 +00:00
2014-05-06 11:52:45 +00:00
if vals . get ( ' report_message_frequency ' , ' never ' ) != ' never ' :
# _recompute_challenge_users do not set users for challenges with no reports, subscribing them now
for challenge in self . browse ( cr , uid , ids , context = context ) :
self . message_subscribe ( cr , uid , [ challenge . id ] , [ user . partner_id . id for user in challenge . user_ids ] , context = context )
2013-12-24 14:11:40 +00:00
if vals . get ( ' state ' ) == ' inprogress ' :
2014-04-18 15:06:18 +00:00
self . _recompute_challenge_users ( cr , uid , ids , context = context )
2014-04-22 07:52:47 +00:00
self . _generate_goals_from_challenge ( cr , uid , ids , context = context )
2013-12-24 14:11:40 +00:00
elif vals . get ( ' state ' ) == ' done ' :
self . check_challenge_reward ( cr , uid , ids , force = True , context = context )
elif vals . get ( ' state ' ) == ' draft ' :
# resetting progress
2014-04-11 14:22:57 +00:00
if self . pool . get ( ' gamification.goal ' ) . search ( cr , uid , [ ( ' challenge_id ' , ' in ' , ids ) , ( ' state ' , ' = ' , ' inprogress ' ) ] , context = context ) :
2013-12-24 14:11:40 +00:00
raise osv . except_osv ( " Error " , " You can not reset a challenge with unfinished goals. " )
2013-12-17 16:15:41 +00:00
2013-02-28 13:53:29 +00:00
return write_res
2013-12-17 16:15:41 +00:00
2013-04-15 14:04:01 +00:00
##### Update #####
2013-03-01 15:43:36 +00:00
2013-03-06 14:45:44 +00:00
def _cron_update ( self , cr , uid , context = None , ids = False ) :
""" Daily cron check.
2013-02-28 13:53:29 +00:00
2013-12-17 16:15:41 +00:00
- Start planned challenges ( in draft and with start_date = today )
- Create the missing goals ( eg : modified the challenge to add lines )
- Update every running challenge
2013-03-29 16:31:37 +00:00
"""
2014-05-06 11:38:31 +00:00
if context is None :
context = { }
2014-04-11 14:22:57 +00:00
# start scheduled challenges
2013-12-17 16:15:41 +00:00
planned_challenge_ids = self . search ( cr , uid , [
2013-03-12 14:15:31 +00:00
( ' state ' , ' = ' , ' draft ' ) ,
2013-04-02 09:59:18 +00:00
( ' start_date ' , ' <= ' , fields . date . today ( ) ) ] )
2014-04-24 11:03:41 +00:00
if planned_challenge_ids :
self . write ( cr , uid , planned_challenge_ids , { ' state ' : ' inprogress ' } , context = context )
2013-03-06 14:45:44 +00:00
2014-04-11 14:22:57 +00:00
# close scheduled challenges
2013-12-17 16:15:41 +00:00
planned_challenge_ids = self . search ( cr , uid , [
2013-04-15 14:04:01 +00:00
( ' state ' , ' = ' , ' inprogress ' ) ,
( ' end_date ' , ' >= ' , fields . date . today ( ) ) ] )
2014-04-24 11:03:41 +00:00
if planned_challenge_ids :
self . write ( cr , uid , planned_challenge_ids , { ' state ' : ' done ' } , context = context )
2013-04-15 14:04:01 +00:00
2013-02-28 13:53:29 +00:00
if not ids :
2013-04-19 15:05:23 +00:00
ids = self . search ( cr , uid , [ ( ' state ' , ' = ' , ' inprogress ' ) ] , context = context )
2013-02-28 13:53:29 +00:00
2014-05-06 11:38:31 +00:00
# in cron mode, will do intermediate commits
# TODO in trunk: replace by parameter
2014-07-06 14:44:26 +00:00
context = dict ( context , commit_gamification = True )
2013-03-06 14:45:44 +00:00
return self . _update_all ( cr , uid , ids , context = context )
def _update_all ( self , cr , uid , ids , context = None ) :
2013-12-17 16:15:41 +00:00
""" Update the challenges and related goals
2013-03-06 14:45:44 +00:00
2013-12-17 16:15:41 +00:00
: param list ( int ) ids : the ids of the challenges to update , if False will
update only challenges in progress . """
2014-10-07 16:14:32 +00:00
if not ids :
return True
2013-12-24 14:11:40 +00:00
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2013-03-07 09:12:01 +00:00
goal_obj = self . pool . get ( ' gamification.goal ' )
2015-01-20 11:04:59 +00:00
# include yesterday goals to update the goals that just ended
# exclude goals for users that did not connect since the last update
2013-04-12 12:19:13 +00:00
yesterday = date . today ( ) - timedelta ( days = 1 )
2015-01-20 11:04:59 +00:00
cr . execute ( """ SELECT gg.id
FROM gamification_goal as gg ,
gamification_challenge as gc ,
res_users as ru
WHERE gg . challenge_id = gc . id
AND gg . user_id = ru . id
AND gg . write_date < ru . login_date
AND gg . closed IS false
AND gc . id IN % s
AND ( gg . state = ' inprogress '
OR ( gg . state = ' reached '
AND ( gg . end_date > = % s OR gg . end_date IS NULL ) ) )
""" , (tuple(ids), yesterday.strftime(DF)))
2015-01-20 15:15:10 +00:00
goal_ids = [ res [ 0 ] for res in cr . fetchall ( ) ]
2013-12-17 16:15:41 +00:00
# update every running goal already generated linked to selected challenges
2013-04-12 12:19:13 +00:00
goal_obj . update ( cr , uid , goal_ids , context = context )
2014-04-18 15:06:18 +00:00
self . _recompute_challenge_users ( cr , uid , ids , context = context )
2014-04-22 07:52:47 +00:00
self . _generate_goals_from_challenge ( cr , uid , ids , context = context )
2014-04-16 15:10:26 +00:00
2014-04-18 14:07:41 +00:00
for challenge in self . browse ( cr , uid , ids , context = context ) :
2013-04-04 15:23:59 +00:00
2014-05-14 08:59:20 +00:00
if challenge . last_report_date != fields . date . today ( ) :
# goals closed but still opened at the last report date
closed_goals_to_report = goal_obj . search ( cr , uid , [
( ' challenge_id ' , ' = ' , challenge . id ) ,
( ' start_date ' , ' >= ' , challenge . last_report_date ) ,
( ' end_date ' , ' <= ' , challenge . last_report_date )
] )
2013-03-04 11:27:12 +00:00
2014-05-14 14:15:18 +00:00
if challenge . next_report_date and fields . date . today ( ) > = challenge . next_report_date :
2014-05-14 08:59:20 +00:00
self . report_progress ( cr , uid , challenge , context = context )
2013-03-04 11:27:12 +00:00
2014-05-14 08:59:20 +00:00
elif len ( closed_goals_to_report ) > 0 :
# some goals need a final report
self . report_progress ( cr , uid , challenge , subset_goal_ids = closed_goals_to_report , context = context )
2013-04-19 10:22:45 +00:00
2013-04-19 15:05:23 +00:00
self . check_challenge_reward ( cr , uid , ids , context = context )
2013-04-05 15:19:53 +00:00
return True
2013-03-04 11:27:12 +00:00
2013-12-17 16:15:41 +00:00
def quick_update ( self , cr , uid , challenge_id , context = None ) :
2014-04-18 14:07:41 +00:00
""" Update all the goals of a specific challenge, no generation of new goals """
2013-12-17 16:15:41 +00:00
goal_ids = self . pool . get ( ' gamification.goal ' ) . search ( cr , uid , [ ( ' challenge_id ' , ' = ' , challenge_id ) ] , context = context )
2013-03-25 16:15:27 +00:00
self . pool . get ( ' gamification.goal ' ) . update ( cr , uid , goal_ids , context = context )
return True
2014-04-18 14:07:41 +00:00
def _get_challenger_users ( self , cr , uid , domain , context = None ) :
2014-04-18 16:38:37 +00:00
user_domain = eval ( ustr ( domain ) )
2014-04-18 14:07:41 +00:00
return self . pool [ ' res.users ' ] . search ( cr , uid , user_domain , context = context )
def _recompute_challenge_users ( self , cr , uid , challenge_ids , context = None ) :
""" Recompute the domain to add new users and remove the one no longer matching the domain """
for challenge in self . browse ( cr , uid , challenge_ids , context = context ) :
if challenge . user_domain :
old_user_ids = [ user . id for user in challenge . user_ids ]
new_user_ids = self . _get_challenger_users ( cr , uid , challenge . user_domain , context = context )
to_remove_ids = list ( set ( old_user_ids ) - set ( new_user_ids ) )
to_add_ids = list ( set ( new_user_ids ) - set ( old_user_ids ) )
write_op = [ ( 3 , user_id ) for user_id in to_remove_ids ]
write_op + = [ ( 4 , user_id ) for user_id in to_add_ids ]
2014-04-22 08:38:56 +00:00
if write_op :
self . write ( cr , uid , [ challenge . id ] , { ' user_ids ' : write_op } , context = context )
2014-04-18 14:07:41 +00:00
return True
2014-09-12 08:37:14 +00:00
def action_start ( self , cr , uid , ids , context = None ) :
""" Start a challenge """
return self . write ( cr , uid , ids , { ' state ' : ' inprogress ' } , context = context )
2013-12-17 16:15:41 +00:00
2013-02-28 13:53:29 +00:00
def action_check ( self , cr , uid , ids , context = None ) :
2013-12-17 16:15:41 +00:00
""" Check a challenge
2013-02-28 13:53:29 +00:00
2013-12-17 16:15:41 +00:00
Create goals that haven ' t been created yet (eg: if added users)
2013-02-28 13:53:29 +00:00
Recompute the current value for each goal related """
2015-08-27 07:39:44 +00:00
goal_obj = self . pool [ ' gamification.goal ' ]
goal_ids = goal_obj . search ( cr , uid , [ ( ' challenge_id ' , ' in ' , ids ) , ( ' state ' , ' = ' , ' inprogress ' ) ] , context = context )
goal_obj . unlink ( cr , uid , goal_ids , context = context )
2013-03-06 14:45:44 +00:00
return self . _update_all ( cr , uid , ids = ids , context = context )
2013-02-28 13:53:29 +00:00
2013-03-04 11:27:12 +00:00
def action_report_progress ( self , cr , uid , ids , context = None ) :
""" Manual report of a goal, does not influence automatic report frequency """
2013-12-18 11:12:43 +00:00
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2014-04-11 15:19:17 +00:00
for challenge in self . browse ( cr , uid , ids , context = context ) :
2013-12-17 16:15:41 +00:00
self . report_progress ( cr , uid , challenge , context = context )
2013-03-04 11:27:12 +00:00
return True
2013-12-17 16:15:41 +00:00
2013-04-15 14:04:01 +00:00
##### Automatic actions #####
2014-04-18 14:07:41 +00:00
def _generate_goals_from_challenge ( self , cr , uid , ids , context = None ) :
2013-12-17 16:15:41 +00:00
""" Generate the goals for each line and user.
2013-03-01 11:27:20 +00:00
2013-12-17 16:15:41 +00:00
If goals already exist for this line and user , the line is skipped . This
can be called after each change in the list of users or lines .
: param list ( int ) ids : the list of challenge concerned """
2013-03-01 11:27:20 +00:00
2014-04-18 09:05:20 +00:00
goal_obj = self . pool . get ( ' gamification.goal ' )
2014-04-11 15:19:17 +00:00
for challenge in self . browse ( cr , uid , ids , context = context ) :
2013-12-17 16:15:41 +00:00
( start_date , end_date ) = start_end_date_for_period ( challenge . period )
2014-05-06 10:29:42 +00:00
to_update = [ ]
2013-03-12 14:15:31 +00:00
2013-12-17 16:15:41 +00:00
# if no periodicity, use challenge dates
if not start_date and challenge . start_date :
start_date = challenge . start_date
if not end_date and challenge . end_date :
end_date = challenge . end_date
2013-04-15 14:04:01 +00:00
2013-12-17 16:15:41 +00:00
for line in challenge . line_ids :
2013-03-12 14:15:31 +00:00
2014-04-18 15:06:18 +00:00
# there is potentially a lot of users
# detect the ones with no goal linked to this line
date_clause = " "
query_params = [ line . id ]
if start_date :
date_clause + = " AND g.start_date = %s "
query_params . append ( start_date )
if end_date :
date_clause + = " AND g.end_date = %s "
query_params . append ( end_date )
query = """ SELECT u.id AS user_id
FROM res_users u
LEFT JOIN gamification_goal g
ON ( u . id = g . user_id )
WHERE line_id = % s
{ date_clause }
""" .format(date_clause=date_clause)
2014-04-22 07:52:47 +00:00
2014-04-18 15:06:18 +00:00
cr . execute ( query , query_params )
user_with_goal_ids = cr . dictfetchall ( )
2014-05-07 09:56:16 +00:00
participant_user_ids = [ user . id for user in challenge . user_ids ]
user_without_goal_ids = list ( set ( participant_user_ids ) - set ( [ user [ ' user_id ' ] for user in user_with_goal_ids ] ) )
user_squating_challenge_ids = list ( set ( [ user [ ' user_id ' ] for user in user_with_goal_ids ] ) - set ( participant_user_ids ) )
if user_squating_challenge_ids :
# users that used to match the challenge
goal_to_remove_ids = goal_obj . search ( cr , uid , [ ( ' challenge_id ' , ' = ' , challenge . id ) , ( ' user_id ' , ' in ' , user_squating_challenge_ids ) ] , context = context )
goal_obj . unlink ( cr , uid , goal_to_remove_ids , context = context )
2014-04-18 15:06:18 +00:00
values = {
' definition_id ' : line . definition_id . id ,
' line_id ' : line . id ,
' target_goal ' : line . target_goal ,
' state ' : ' inprogress ' ,
}
2013-03-12 14:15:31 +00:00
2014-04-18 15:06:18 +00:00
if start_date :
values [ ' start_date ' ] = start_date
if end_date :
values [ ' end_date ' ] = end_date
2013-02-28 13:53:29 +00:00
2014-05-09 13:18:39 +00:00
# the goal is initialised over the limit to make sure we will compute it at least once
if line . condition == ' higher ' :
values [ ' current ' ] = line . target_goal - 1
else :
values [ ' current ' ] = line . target_goal + 1
2014-04-18 15:06:18 +00:00
if challenge . remind_update_delay :
values [ ' remind_update_delay ' ] = challenge . remind_update_delay
2013-02-28 13:53:29 +00:00
2014-04-18 15:06:18 +00:00
for user_id in user_without_goal_ids :
values . update ( { ' user_id ' : user_id } )
2014-04-22 07:52:47 +00:00
goal_id = goal_obj . create ( cr , uid , values , context = context )
to_update . append ( goal_id )
2013-03-12 14:15:31 +00:00
2014-04-11 14:22:57 +00:00
goal_obj . update ( cr , uid , to_update , context = context )
2013-02-28 13:53:29 +00:00
return True
2013-04-15 14:04:01 +00:00
##### JS utilities #####
2013-12-20 14:45:21 +00:00
def _get_serialized_challenge_lines ( self , cr , uid , challenge , user_id = False , restrict_goal_ids = False , restrict_top = False , context = None ) :
2014-02-18 13:31:38 +00:00
""" Return a serialised version of the goals information if the user has not completed every goal
2013-12-20 14:45:21 +00:00
: challenge : browse record of challenge to compute
: user_id : res . users id of the user retrieving progress ( False if no distinction , only for ranking challenges )
: restrict_goal_ids : < list ( int ) > compute only the results for this subset if gamification . goal ids , if False retrieve every goal of current running challenge
: restrict_top : < int > for challenge lines where visibility_mode == ' ranking ' , retrieve only these bests results and itself , if False retrieve all
restrict_goal_ids has priority over restrict_top
format list
# if visibility_mode == 'ranking'
{
' name ' : < gamification . goal . description name > ,
' description ' : < gamification . goal . description description > ,
' condition ' : < reach condition { lower , higher } > ,
' computation_mode ' : < target computation { manually , count , sum , python } > ,
' monetary ' : < { True , False } > ,
' suffix ' : < value suffix > ,
' action ' : < { True , False } > ,
' display_mode ' : < { progress , boolean } > ,
' target ' : < challenge line target > ,
' own_goal_id ' : < gamification . goal id where user_id == uid > ,
' goals ' : [
{
' id ' : < gamification . goal id > ,
' rank ' : < user ranking > ,
' user_id ' : < res . users id > ,
' name ' : < res . users name > ,
2014-04-11 14:22:57 +00:00
' state ' : < gamification . goal state { draft , inprogress , reached , failed , canceled } > ,
2013-12-20 14:45:21 +00:00
' completeness ' : < percentage > ,
' current ' : < current value > ,
}
]
} ,
# if visibility_mode == 'personal'
{
' id ' : < gamification . goal id > ,
' name ' : < gamification . goal . description name > ,
' description ' : < gamification . goal . description description > ,
' condition ' : < reach condition { lower , higher } > ,
' computation_mode ' : < target computation { manually , count , sum , python } > ,
' monetary ' : < { True , False } > ,
' suffix ' : < value suffix > ,
' action ' : < { True , False } > ,
' display_mode ' : < { progress , boolean } > ,
' target ' : < challenge line target > ,
2014-04-11 14:22:57 +00:00
' state ' : < gamification . goal state { draft , inprogress , reached , failed , canceled } > ,
2013-12-20 14:45:21 +00:00
' completeness ' : < percentage > ,
' current ' : < current value > ,
}
"""
2013-03-25 15:39:53 +00:00
goal_obj = self . pool . get ( ' gamification.goal ' )
2013-12-17 16:15:41 +00:00
( start_date , end_date ) = start_end_date_for_period ( challenge . period )
2013-03-25 15:39:53 +00:00
2013-12-20 14:45:21 +00:00
res_lines = [ ]
2014-02-18 13:31:38 +00:00
all_reached = True
2013-12-17 16:15:41 +00:00
for line in challenge . line_ids :
2013-12-20 14:45:21 +00:00
line_data = {
' name ' : line . definition_id . name ,
' description ' : line . definition_id . description ,
' condition ' : line . definition_id . condition ,
' computation_mode ' : line . definition_id . computation_mode ,
' monetary ' : line . definition_id . monetary ,
' suffix ' : line . definition_id . suffix ,
' action ' : True if line . definition_id . action_id else False ,
' display_mode ' : line . definition_id . display_mode ,
' target ' : line . target_goal ,
}
2013-03-25 15:39:53 +00:00
domain = [
2013-12-17 16:15:41 +00:00
( ' line_id ' , ' = ' , line . id ) ,
2013-12-20 14:45:21 +00:00
( ' state ' , ' != ' , ' draft ' ) ,
2013-03-25 15:39:53 +00:00
]
2013-12-20 14:45:21 +00:00
if restrict_goal_ids :
domain . append ( ( ' ids ' , ' in ' , restrict_goal_ids ) )
2013-03-25 15:39:53 +00:00
else :
# if no subset goals, use the dates for restriction
if start_date :
2013-04-15 14:12:38 +00:00
domain . append ( ( ' start_date ' , ' = ' , start_date ) )
2013-03-25 15:39:53 +00:00
if end_date :
2013-04-15 14:12:38 +00:00
domain . append ( ( ' end_date ' , ' = ' , end_date ) )
2013-03-25 15:39:53 +00:00
2013-12-20 14:45:21 +00:00
if challenge . visibility_mode == ' personal ' :
if not user_id :
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( " Retrieving progress for personal challenge without user information " ) )
domain . append ( ( ' user_id ' , ' = ' , user_id ) )
sorting = goal_obj . _order
limit = 1
2013-03-25 15:39:53 +00:00
else :
2013-12-20 14:45:21 +00:00
line_data . update ( {
' own_goal_id ' : False ,
' goals ' : [ ] ,
} )
sorting = " completeness desc, current desc "
limit = False
goal_ids = goal_obj . search ( cr , uid , domain , order = sorting , limit = limit , context = context )
ranking = 0
for goal in goal_obj . browse ( cr , uid , goal_ids , context = context ) :
if challenge . visibility_mode == ' personal ' :
# limit=1 so only one result
line_data . update ( {
' id ' : goal . id ,
' current ' : goal . current ,
' completeness ' : goal . completeness ,
' state ' : goal . state ,
} )
2014-02-18 13:31:38 +00:00
if goal . state != ' reached ' :
all_reached = False
2013-12-20 14:45:21 +00:00
else :
ranking + = 1
if user_id and goal . user_id . id == user_id :
line_data [ ' own_goal_id ' ] = goal . id
elif restrict_top and ranking > restrict_top :
2014-04-11 14:22:57 +00:00
# not own goal and too low to be in top
2013-12-20 14:45:21 +00:00
continue
2013-03-25 15:39:53 +00:00
2013-12-20 14:45:21 +00:00
line_data [ ' goals ' ] . append ( {
' id ' : goal . id ,
' user_id ' : goal . user_id . id ,
' name ' : goal . user_id . name ,
' rank ' : ranking ,
' current ' : goal . current ,
' completeness ' : goal . completeness ,
' state ' : goal . state ,
} )
2014-02-18 13:31:38 +00:00
if goal . state != ' reached ' :
all_reached = False
2014-02-07 09:36:43 +00:00
if goal_ids :
res_lines . append ( line_data )
2014-02-18 13:31:38 +00:00
if all_reached :
return [ ]
2013-12-20 14:45:21 +00:00
return res_lines
2013-03-25 15:39:53 +00:00
2013-04-15 14:04:01 +00:00
##### Reporting #####
2013-12-17 16:15:41 +00:00
def report_progress ( self , cr , uid , challenge , context = None , users = False , subset_goal_ids = False ) :
2013-02-28 13:53:29 +00:00
""" Post report about the progress of the goals
2013-12-17 16:15:41 +00:00
: param challenge : the challenge object that need to be reported
2013-03-12 14:15:31 +00:00
: param users : the list ( res . users ) of users that are concerned by
2013-02-28 13:53:29 +00:00
the report . If False , will send the report to every user concerned
2013-12-17 16:15:41 +00:00
( goal users and group that receive a copy ) . Only used for challenge with
2013-03-04 11:27:12 +00:00
a visibility mode set to ' personal ' .
2013-12-17 16:15:41 +00:00
: param goal_ids : the list ( int ) of goal ids linked to the challenge for
the report . If not specified , use the goals for the current challenge
period . This parameter can be used to produce report for previous challenge
2013-03-12 14:15:31 +00:00
periods .
: param subset_goal_ids : a list ( int ) of goal ids to restrict the report
"""
2013-12-17 16:46:19 +00:00
if context is None :
context = { }
2013-12-24 14:45:14 +00:00
2013-06-06 12:47:08 +00:00
temp_obj = self . pool . get ( ' email.template ' )
ctx = context . copy ( )
2013-12-17 17:02:32 +00:00
if challenge . visibility_mode == ' ranking ' :
2013-12-20 14:45:21 +00:00
lines_boards = self . _get_serialized_challenge_lines ( cr , uid , challenge , user_id = False , restrict_goal_ids = subset_goal_ids , restrict_top = False , context = context )
2013-03-12 14:15:31 +00:00
2013-12-20 14:45:21 +00:00
ctx . update ( { ' challenge_lines ' : lines_boards } )
2013-12-24 14:45:14 +00:00
body_html = temp_obj . render_template ( cr , uid , challenge . report_template_id . body_html , ' gamification.challenge ' , challenge . id , context = ctx )
2013-03-04 11:27:12 +00:00
2014-05-07 09:56:16 +00:00
# send to every follower and participant of the challenge
2013-12-17 16:15:41 +00:00
self . message_post ( cr , uid , challenge . id ,
2013-03-04 11:27:12 +00:00
body = body_html ,
2014-05-07 09:56:16 +00:00
partner_ids = [ user . partner_id . id for user in challenge . user_ids ] ,
2013-03-04 11:27:12 +00:00
context = context ,
subtype = ' mail.mt_comment ' )
2013-12-17 16:15:41 +00:00
if challenge . report_message_group_id :
self . pool . get ( ' mail.group ' ) . message_post ( cr , uid , challenge . report_message_group_id . id ,
2013-03-04 11:27:12 +00:00
body = body_html ,
context = context ,
subtype = ' mail.mt_comment ' )
2013-03-12 14:15:31 +00:00
2013-03-04 11:27:12 +00:00
else :
# generate individual reports
2013-12-17 16:15:41 +00:00
for user in users or challenge . user_ids :
2013-12-20 14:45:21 +00:00
goals = self . _get_serialized_challenge_lines ( cr , uid , challenge , user . id , restrict_goal_ids = subset_goal_ids , context = context )
2013-06-21 08:24:28 +00:00
if not goals :
2013-03-04 11:27:12 +00:00
continue
2013-12-20 14:45:21 +00:00
ctx . update ( { ' challenge_lines ' : goals } )
2013-12-24 14:45:14 +00:00
body_html = temp_obj . render_template ( cr , user . id , challenge . report_template_id . body_html , ' gamification.challenge ' , challenge . id , context = ctx )
2013-12-20 14:45:21 +00:00
# send message only to users, not on the challenge
2013-04-23 09:41:43 +00:00
self . message_post ( cr , uid , 0 ,
2013-03-12 14:15:31 +00:00
body = body_html ,
partner_ids = [ ( 4 , user . partner_id . id ) ] ,
context = context ,
subtype = ' mail.mt_comment ' )
2013-12-17 16:15:41 +00:00
if challenge . report_message_group_id :
self . pool . get ( ' mail.group ' ) . message_post ( cr , uid , challenge . report_message_group_id . id ,
2013-03-12 14:15:31 +00:00
body = body_html ,
context = context ,
subtype = ' mail.mt_comment ' )
2013-12-17 16:15:41 +00:00
return self . write ( cr , uid , challenge . id , { ' last_report_date ' : fields . date . today ( ) } , context = context )
2013-03-04 15:31:38 +00:00
2013-04-19 15:05:23 +00:00
##### Challenges #####
2014-02-18 13:51:01 +00:00
# TODO in trunk, remove unused parameter user_id
def accept_challenge ( self , cr , uid , challenge_ids , context = None , user_id = None ) :
2013-04-15 14:04:01 +00:00
""" The user accept the suggested challenge """
2014-02-18 13:51:01 +00:00
return self . _accept_challenge ( cr , uid , uid , challenge_ids , context = context )
2014-02-18 13:08:00 +00:00
def _accept_challenge ( self , cr , uid , user_id , challenge_ids , context = None ) :
2013-04-16 10:27:24 +00:00
user = self . pool . get ( ' res.users ' ) . browse ( cr , uid , user_id , context = context )
message = " %s has joined the challenge " % user . name
2014-02-18 13:51:01 +00:00
self . message_post ( cr , SUPERUSER_ID , challenge_ids , body = message , context = context )
self . write ( cr , SUPERUSER_ID , challenge_ids , { ' invited_user_ids ' : [ ( 3 , user_id ) ] , ' user_ids ' : [ ( 4 , user_id ) ] } , context = context )
2014-04-22 07:52:47 +00:00
return self . _generate_goals_from_challenge ( cr , SUPERUSER_ID , challenge_ids , context = context )
2013-04-15 14:04:01 +00:00
2014-02-18 13:51:01 +00:00
# TODO in trunk, remove unused parameter user_id
def discard_challenge ( self , cr , uid , challenge_ids , context = None , user_id = None ) :
2013-04-15 14:04:01 +00:00
""" The user discard the suggested challenge """
2014-02-18 13:51:01 +00:00
return self . _discard_challenge ( cr , uid , uid , challenge_ids , context = context )
2014-02-18 13:08:00 +00:00
def _discard_challenge ( self , cr , uid , user_id , challenge_ids , context = None ) :
2013-04-16 10:27:24 +00:00
user = self . pool . get ( ' res.users ' ) . browse ( cr , uid , user_id , context = context )
message = " %s has refused the challenge " % user . name
2014-02-18 13:51:01 +00:00
self . message_post ( cr , SUPERUSER_ID , challenge_ids , body = message , context = context )
return self . write ( cr , SUPERUSER_ID , challenge_ids , { ' invited_user_ids ' : ( 3 , user_id ) } , context = context )
2013-04-15 14:04:01 +00:00
2013-12-17 16:15:41 +00:00
def reply_challenge_wizard ( self , cr , uid , challenge_id , context = None ) :
2013-12-24 14:45:14 +00:00
result = self . pool . get ( ' ir.model.data ' ) . get_object_reference ( cr , uid , ' gamification ' , ' challenge_wizard ' )
2013-04-18 16:05:12 +00:00
id = result and result [ 1 ] or False
2013-12-24 14:45:14 +00:00
result = self . pool . get ( ' ir.actions.act_window ' ) . read ( cr , uid , [ id ] , context = context ) [ 0 ]
2013-12-17 16:15:41 +00:00
result [ ' res_id ' ] = challenge_id
2013-04-18 16:05:12 +00:00
return result
2013-04-15 14:04:01 +00:00
2013-12-18 11:12:43 +00:00
def check_challenge_reward ( self , cr , uid , ids , force = False , context = None ) :
2013-04-19 15:05:23 +00:00
""" Actions for the end of a challenge
If a reward was selected , grant it to the correct users .
2013-04-23 08:25:04 +00:00
Rewards granted at :
2013-04-19 15:05:23 +00:00
- the end date for a challenge with no periodicity
- the end of a period for challenge with periodicity
- when a challenge is manually closed
2013-04-23 08:25:04 +00:00
( if no end date , a running challenge is never rewarded )
2013-04-19 15:05:23 +00:00
"""
2013-12-18 11:12:43 +00:00
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2014-10-07 16:14:32 +00:00
commit = context . get ( ' commit_gamification ' , False )
2013-12-18 11:12:43 +00:00
for challenge in self . browse ( cr , uid , ids , context = context ) :
2013-12-17 16:15:41 +00:00
( start_date , end_date ) = start_end_date_for_period ( challenge . period , challenge . start_date , challenge . end_date )
2013-04-19 15:05:23 +00:00
yesterday = date . today ( ) - timedelta ( days = 1 )
2014-04-11 15:19:17 +00:00
rewarded_users = [ ]
challenge_ended = end_date == yesterday . strftime ( DF ) or force
2014-09-12 08:37:14 +00:00
if challenge . reward_id and ( challenge_ended or challenge . reward_realtime ) :
2014-05-06 13:48:05 +00:00
# not using start_date as intemportal goals have a start date but no end_date
reached_goals = self . pool . get ( ' gamification.goal ' ) . read_group ( cr , uid , [
( ' challenge_id ' , ' = ' , challenge . id ) ,
( ' end_date ' , ' = ' , end_date ) ,
( ' state ' , ' = ' , ' reached ' )
] , fields = [ ' user_id ' ] , groupby = [ ' user_id ' ] , context = context )
for reach_goals_user in reached_goals :
if reach_goals_user [ ' user_id_count ' ] == len ( challenge . line_ids ) :
2014-04-11 15:19:17 +00:00
# the user has succeeded every assigned goal
2014-05-06 13:48:05 +00:00
user_id = reach_goals_user [ ' user_id ' ] [ 0 ]
2014-04-11 15:19:17 +00:00
if challenge . reward_realtime :
badges = self . pool [ ' gamification.badge.user ' ] . search ( cr , uid , [
( ' challenge_id ' , ' = ' , challenge . id ) ,
( ' badge_id ' , ' = ' , challenge . reward_id . id ) ,
2014-05-06 13:48:05 +00:00
( ' user_id ' , ' = ' , user_id ) ,
2014-04-11 15:19:17 +00:00
] , count = True , context = context )
if badges > 0 :
# has already recieved the badge for this challenge
continue
2014-05-06 13:48:05 +00:00
self . reward_user ( cr , uid , user_id , challenge . reward_id . id , challenge . id , context = context )
rewarded_users . append ( user_id )
2014-10-07 16:14:32 +00:00
if commit :
cr . commit ( )
2014-04-11 15:19:17 +00:00
if challenge_ended :
2013-04-23 08:25:04 +00:00
# open chatter message
2013-12-17 16:15:41 +00:00
message_body = _ ( " The challenge %s is finished. " % challenge . name )
2013-04-23 08:25:04 +00:00
2014-04-11 15:19:17 +00:00
if rewarded_users :
2014-05-06 13:48:05 +00:00
user_names = self . pool [ ' res.users ' ] . name_get ( cr , uid , rewarded_users , context = context )
2014-05-06 14:38:34 +00:00
message_body + = _ ( " <br/>Reward (badge %s ) for every succeeding user was sent to %s . " % ( challenge . reward_id . name , " , " . join ( [ name for ( user_id , name ) in user_names ] ) ) )
2014-04-11 15:19:17 +00:00
else :
message_body + = _ ( " <br/>Nobody has succeeded to reach every goal, no badge is rewared for this challenge. " )
2013-04-23 08:25:04 +00:00
2013-04-19 15:05:23 +00:00
# reward bests
2013-12-17 16:15:41 +00:00
if challenge . reward_first_id :
2014-04-11 15:19:17 +00:00
( first_user , second_user , third_user ) = self . get_top3_users ( cr , uid , challenge , context = context )
2013-04-19 15:05:23 +00:00
if first_user :
2014-04-11 15:19:17 +00:00
self . reward_user ( cr , uid , first_user . id , challenge . reward_first_id . id , challenge . id , context = context )
2013-06-21 08:31:40 +00:00
message_body + = _ ( " <br/>Special rewards were sent to the top competing users. The ranking for this challenge is : " )
2013-12-17 16:15:41 +00:00
message_body + = " <br/> 1. %s - %s " % ( first_user . name , challenge . reward_first_id . name )
2013-04-23 08:25:04 +00:00
else :
2013-06-21 08:31:40 +00:00
message_body + = _ ( " Nobody reached the required conditions to receive special badges. " )
2013-04-23 08:25:04 +00:00
2013-12-17 16:15:41 +00:00
if second_user and challenge . reward_second_id :
2014-04-11 15:19:17 +00:00
self . reward_user ( cr , uid , second_user . id , challenge . reward_second_id . id , challenge . id , context = context )
2013-12-17 16:15:41 +00:00
message_body + = " <br/> 2. %s - %s " % ( second_user . name , challenge . reward_second_id . name )
if third_user and challenge . reward_third_id :
2014-04-11 15:19:17 +00:00
self . reward_user ( cr , uid , third_user . id , challenge . reward_second_id . id , challenge . id , context = context )
2013-12-17 16:15:41 +00:00
message_body + = " <br/> 3. %s - %s " % ( third_user . name , challenge . reward_third_id . name )
2013-04-19 15:05:23 +00:00
2014-05-07 09:56:16 +00:00
self . message_post ( cr , uid , challenge . id ,
partner_ids = [ user . partner_id . id for user in challenge . user_ids ] ,
body = message_body ,
context = context )
2014-10-07 16:14:32 +00:00
if commit :
cr . commit ( )
2014-04-11 15:19:17 +00:00
2013-04-19 15:05:23 +00:00
return True
2013-12-17 16:15:41 +00:00
def get_top3_users ( self , cr , uid , challenge , context = None ) :
""" Get the top 3 users for a defined challenge
2013-04-19 15:05:23 +00:00
Ranking criterias :
2013-04-23 08:25:04 +00:00
1. succeed every goal of the challenge
2. total completeness of each goal ( can be over 100 )
Top 3 is computed only for users succeeding every goal of the challenge ,
except if reward_failure is True , in which case every user is
considered .
: return : ( ' first ' , ' second ' , ' third ' ) , tuple containing the res . users
objects of the top 3 users . If no user meets the criterias for a rank ,
it is set to False . Nobody can receive a rank is noone receives the
higher one ( eg : if ' second ' == False , ' third ' will be False )
2013-04-19 15:05:23 +00:00
"""
goal_obj = self . pool . get ( ' gamification.goal ' )
2013-12-17 16:15:41 +00:00
( start_date , end_date ) = start_end_date_for_period ( challenge . period , challenge . start_date , challenge . end_date )
2013-04-19 15:05:23 +00:00
challengers = [ ]
2013-12-17 16:15:41 +00:00
for user in challenge . user_ids :
2013-04-19 15:05:23 +00:00
all_reached = True
total_completness = 0
# every goal of the user for the running period
goal_ids = goal_obj . search ( cr , uid , [
2013-12-17 16:15:41 +00:00
( ' challenge_id ' , ' = ' , challenge . id ) ,
2013-04-19 15:05:23 +00:00
( ' user_id ' , ' = ' , user . id ) ,
( ' start_date ' , ' = ' , start_date ) ,
( ' end_date ' , ' = ' , end_date )
] , context = context )
for goal in goal_obj . browse ( cr , uid , goal_ids , context = context ) :
if goal . state != ' reached ' :
all_reached = False
2013-12-23 16:12:29 +00:00
if goal . definition_condition == ' higher ' :
2013-04-19 15:05:23 +00:00
# can be over 100
total_completness + = 100.0 * goal . current / goal . target_goal
elif goal . state == ' reached ' :
# for lower goals, can not get percentage so 0 or 100
total_completness + = 100
challengers . append ( { ' user ' : user , ' all_reached ' : all_reached , ' total_completness ' : total_completness } )
sorted_challengers = sorted ( challengers , key = lambda k : ( k [ ' all_reached ' ] , k [ ' total_completness ' ] ) , reverse = True )
2013-04-23 08:25:04 +00:00
2013-12-17 16:15:41 +00:00
if len ( sorted_challengers ) == 0 or ( not challenge . reward_failure and not sorted_challengers [ 0 ] [ ' all_reached ' ] ) :
2013-04-19 15:05:23 +00:00
# nobody succeeded
return ( False , False , False )
2013-12-17 16:15:41 +00:00
if len ( sorted_challengers ) == 1 or ( not challenge . reward_failure and not sorted_challengers [ 1 ] [ ' all_reached ' ] ) :
2013-04-19 15:05:23 +00:00
# only one user succeeded
return ( sorted_challengers [ 0 ] [ ' user ' ] , False , False )
2013-12-17 16:15:41 +00:00
if len ( sorted_challengers ) == 2 or ( not challenge . reward_failure and not sorted_challengers [ 2 ] [ ' all_reached ' ] ) :
2013-04-19 15:05:23 +00:00
# only one user succeeded
return ( sorted_challengers [ 0 ] [ ' user ' ] , sorted_challengers [ 1 ] [ ' user ' ] , False )
return ( sorted_challengers [ 0 ] [ ' user ' ] , sorted_challengers [ 1 ] [ ' user ' ] , sorted_challengers [ 2 ] [ ' user ' ] )
2014-04-11 15:19:17 +00:00
def reward_user ( self , cr , uid , user_id , badge_id , challenge_id = False , context = None ) :
2013-12-18 13:00:39 +00:00
""" 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 ' )
2014-04-11 15:19:17 +00:00
user_badge_id = badge_user_obj . create ( cr , uid , { ' user_id ' : user_id , ' badge_id ' : badge_id , ' challenge_id ' : challenge_id } , context = context )
2013-12-23 15:54:04 +00:00
return badge_user_obj . _send_badge ( cr , uid , [ user_badge_id ] , context = context )
2013-04-19 15:05:23 +00:00
2013-02-28 13:53:29 +00:00
2013-12-17 16:15:41 +00:00
class gamification_challenge_line ( osv . Model ) :
""" Gamification challenge line
2013-02-28 13:53:29 +00:00
2013-12-17 16:15:41 +00:00
Predifined goal for ' gamification_challenge '
2013-02-28 13:53:29 +00:00
These are generic list of goals with only the target goal defined
2013-12-17 16:15:41 +00:00
Should only be created for the gamification_challenge object
2013-02-28 13:53:29 +00:00
"""
2013-12-17 16:15:41 +00:00
_name = ' gamification.challenge.line '
_description = ' Gamification generic goal for challenge '
_order = " sequence, id "
def on_change_definition_id ( self , cr , uid , ids , definition_id = False , context = None ) :
goal_definition = self . pool . get ( ' gamification.goal.definition ' )
if not definition_id :
return { ' value ' : { ' definition_id ' : False } }
goal_definition = goal_definition . browse ( cr , uid , definition_id , context = context )
ret = {
' value ' : {
2013-12-20 14:45:21 +00:00
' condition ' : goal_definition . condition ,
2013-12-17 16:15:41 +00:00
' definition_full_suffix ' : goal_definition . full_suffix
}
}
2013-04-23 15:55:32 +00:00
return ret
2013-02-28 13:53:29 +00:00
_columns = {
2014-08-01 04:40:03 +00:00
' name ' : fields . related ( ' definition_id ' , ' name ' , string = " Name " , type = " char " ) ,
2013-12-17 16:15:41 +00:00
' challenge_id ' : fields . many2one ( ' gamification.challenge ' ,
string = ' Challenge ' ,
2013-04-04 15:23:59 +00:00
required = True ,
2013-02-28 13:53:29 +00:00
ondelete = " cascade " ) ,
2013-12-17 16:15:41 +00:00
' definition_id ' : fields . many2one ( ' gamification.goal.definition ' ,
string = ' Goal Definition ' ,
2013-02-28 13:53:29 +00:00
required = True ,
ondelete = " cascade " ) ,
2013-03-12 14:15:31 +00:00
' target_goal ' : fields . float ( ' Target Value to Reach ' ,
2013-02-28 13:53:29 +00:00
required = True ) ,
2013-04-15 15:50:14 +00:00
' sequence ' : fields . integer ( ' Sequence ' ,
2013-04-16 12:45:58 +00:00
help = ' Sequence number for ordering ' ) ,
2013-12-20 14:45:21 +00:00
' condition ' : fields . related ( ' definition_id ' , ' condition ' , type = " selection " ,
2013-04-05 15:44:37 +00:00
readonly = True , string = " Condition " , selection = [ ( ' lower ' , ' <= ' ) , ( ' higher ' , ' >= ' ) ] ) ,
2013-12-17 16:15:41 +00:00
' definition_suffix ' : fields . related ( ' definition_id ' , ' suffix ' , type = " char " , readonly = True , string = " Unit " ) ,
' definition_monetary ' : fields . related ( ' definition_id ' , ' monetary ' , type = " boolean " , readonly = True , string = " Monetary " ) ,
' definition_full_suffix ' : fields . related ( ' definition_id ' , ' full_suffix ' , type = " char " , readonly = True , string = " Suffix " ) ,
2013-03-04 11:27:12 +00:00
}
2013-04-15 15:50:14 +00:00
_default = {
' sequence ' : 1 ,
}