diff --git a/addons/gamification/goal.py b/addons/gamification/goal.py index 8fce8d43753..60b39c2e5a0 100644 --- a/addons/gamification/goal.py +++ b/addons/gamification/goal.py @@ -282,6 +282,6 @@ class gamification_goal(osv.Model): if goal.planline_id and goal.planline_id.plan_id.report_message_frequency == 'onchange': plan_obj = self.pool.get('gamification.goal.plan') - plan_obj.report_progress(cr, uid, [goal.planline_id.plan_id.id], users=[goal.user_id], context=context) + plan_obj.report_progress(cr, uid, goal.planline_id.plan_id, users=[goal.user_id], context=context) return super(gamification_goal, self).write(cr, uid, ids, vals, context=context) diff --git a/addons/gamification/plan.py b/addons/gamification/plan.py index 2a5e955c828..881e08fdea5 100644 --- a/addons/gamification/plan.py +++ b/addons/gamification/plan.py @@ -69,6 +69,26 @@ class gamification_goal_plan(osv.Model): _description = 'Gamification goal plan' _inherit = 'mail.thread' + 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 + period. Return a string in isoformat.""" + res = {} + for plan in self.browse(cr, uid, ids, context): + last = datetime.strptime(plan.last_report_date,'%Y-%m-%d').date() + if plan.report_message_frequency == 'daily': + next = last + timedelta(days=1) + elif plan.report_message_frequency == 'weekly': + next = last + timedelta(days=7) + elif plan.report_message_frequency == 'monthly': + month_range = calendar.monthrange(last.year, last.month) + next = last.replace(day=month_range[1]) + timedelta(days=1) + elif plan.report_message_frequency == 'yearly': + next = last.replace(year=last.year + 1) + else: # frequency == 'once': + next = False + res[plan.id] = next.isoformat() + return res + _columns = { 'name' : fields.char('Plan Name', required=True, translate=True), 'user_ids' : fields.many2many('res.users', @@ -121,7 +141,11 @@ class gamification_goal_plan(osv.Model): help='Group that will receive a copy of the report in addition to the user'), 'report_header' : fields.text('Report Header'), 'remind_update_delay' : fields.integer('Remind delay', - help="The number of days after which the user assigned to a manual goal will be reminded. Never reminded if no value or zero is specified.") + help="The number of days after which the user assigned to a manual goal will be reminded. Never reminded if no value or zero is specified."), + 'last_report_date': fields.date('Last Report Date'), + 'next_report_date': fields.function(_get_next_report_date, + type='date', + string='Next Report Date'), } _defaults = { @@ -129,6 +153,7 @@ class gamification_goal_plan(osv.Model): 'state': 'draft', 'visibility_mode' : 'progressbar', 'report_message_frequency' : 'onchange', + 'last_report_date' : fields.date.today } def _check_nonzero_planline(self, cr, uid, ids, context=None): @@ -167,25 +192,44 @@ class gamification_goal_plan(osv.Model): Create the goals for planlines not linked to goals (eg: modified the plan to add planlines) :param list(int) ids: the ids of the plans to update, if False will - update only goals in progress.""" + update only plans in progress.""" if not context: context = {} if not ids: ids = self.search(cr, uid, [('state', '=', 'inprogress')]) self.generate_goals_from_plan(cr, uid, ids, context=context) - # update goals goal_obj = self.pool.get('gamification.goal') + # we use yesterday to update the goals that just ended + yesterday = date.today() - timedelta(days=1) goal_ids = goal_obj.search(cr, uid, [ '&', ('state', 'in', ('inprogress','inprogress_update', 'reached')), '|', - ('end_date', '>=', fields.date.today()), + ('end_date', '>=', yesterday.isoformat()), ('end_date', '=', False) ], context=context) goal_obj.update(cr, uid, goal_ids, context=context) - self.report_progress(cr, uid, ids, context=context) + for plan in self.browse(cr, uid, ids, context=context): + # goals closed but still opened at the last report date + closed_goals_to_report = [] + for planline in plan.planline_ids: + closed_goals_to_report.extend( goal_obj.search(cr, uid, [ + ('planline_id','=',planline.id), + ('start_date', '>=', plan.last_report_date), + ('end_date', '<=', plan.last_report_date) + ]) ) + + if len(closed_goals_to_report) > 0: + # some goals need a final report + self.report_progress(cr, uid, plan, subset_goal_ids=closed_goals_to_report, context=context) + self.write(cr, uid, plan.id, {'last_report_date':fields.date.today}, context=context) + + if fields.date.today() == plan.next_report_date: + self.report_progress(cr, uid, plan, context=context) + self.write(cr, uid, plan.id, {'last_report_date':fields.date.today}, context=context) + def action_start(self, cr, uid, ids, context=None): """Start a draft goal plan @@ -199,7 +243,7 @@ class gamification_goal_plan(osv.Model): Create goals that haven't been created yet (eg: if added users of planlines) Recompute the current value for each goal related""" - return self._update_all(cr, uid, ids, context=context) + return self._update_all(cr, uid, ids=ids, context=context) def action_close(self, cr, uid, ids, context=None): @@ -254,6 +298,12 @@ class gamification_goal_plan(osv.Model): res['domain'] = [('id','in', related_goal_ids)] return res + def action_report_progress(self, cr, uid, ids, context=None): + """Manual report of a goal, does not influence automatic report frequency""" + for plan in self.browse(cr, uid, ids, context): + self.report_progress(cr, uid, plan, context=context) + return True + def generate_goals_from_plan(self, cr, uid, ids, context=None): """Generate the list of goals linked to a plan. @@ -324,55 +374,112 @@ class gamification_goal_plan(osv.Model): return True - def report_progress(self, cr, uid, ids, context=None, users=False): + def report_progress(self, cr, uid, plan, context=None, users=False, subset_goal_ids=False): """Post report about the progress of the goals - :param list(int) ids: the list of plan ids that need to be reported + :param plan: the plan object that need to be reported :param list(res.users) users: the list of users that are concerned by the report. If False, will send the report to every user concerned (goal users and group that recieves a copy). Only used for plan with - a visibility mode set to 'personal'.""" + a visibility mode set to 'personal'. + :param list(int) goal_ids: the list of goal ids linked to the plan for + the report. If not specified, use the goals for the current plan + period. This parameter can be used to produce report for previous plan + periods.""" context = context or {} goal_obj = self.pool.get('gamification.goal') template_env = TemplateHelper() - for plan in self.browse(cr, uid, ids, context=context): + (start_date, end_date) = start_end_date_for_period(plan.period) - if plan.visibility_mode == 'board': - # generate a shared report - planlines_boards = [] + if plan.visibility_mode == 'board': + # generate a shared report + planlines_boards = [] + + for planline in plan.planline_ids: + + domain = [ + ('planline_id', '=', planline.id), + ('state', 'in', ('inprogress', 'inprogress_update', + 'reached', 'failed')), + ] + + if subset_goal_ids: + goal_ids = goal_obj.search(cr, uid, domain, context=context) + common_goal_ids = [goal.id for goal in goal_ids if goal in subset_goal_ids] + else: + # if no subset goals, use the dates for restriction + if start_date: + domain.append(('start_date', '=', start_date.isoformat())) + if end_date: + domain.append(('end_date', '=', end_date.isoformat())) + common_goal_ids = goal_obj.search(cr, uid, domain, context=context) + + board_goals = [] + for goal in goal_obj.browse(cr, uid, common_goal_ids, context=context): + board_goals.append({ + 'user': goal.user_id, + 'current':goal.current, + 'target_goal':goal.target_goal, + 'completeness':goal.completeness, + }) + + # most complete first, current if same percentage (eg: if several 100%) + sorted_board = enumerate(sorted(board_goals, key=lambda k: (k['completeness'], k['current']), reverse=True)) + planlines_boards.append({'goal_type':planline.type_id.name, 'board_goals':sorted_board}) + + body_html = template_env.get_template('group_progress.mako').render({'object':plan, 'planlines_boards':planlines_boards}) + + self.message_post(cr, uid, plan.id, + body=body_html, + partner_ids=[(6, 0, [user.partner_id.id for user in plan.user_ids])], + context=context, + subtype='mail.mt_comment') + if plan.report_message_group_id: + self.pool.get('mail.group').message_post(cr, uid, plan.report_message_group_id.id, + body=body_html, + context=context, + subtype='mail.mt_comment') + + else: + # generate individual reports + for user in users or plan.user_ids: + related_goal_ids = [] for planline in plan.planline_ids: - - (start_date, end_date) = start_end_date_for_period(plan.period) domain = [ ('planline_id', '=', planline.id), + ('user_id', '=', user.id), ('state', 'in', ('inprogress', 'inprogress_update', 'reached', 'failed')), ] - if start_date: - domain.append(('start_date', '=', start_date.isoformat())) + if subset_goal_ids: + goal_ids = goal_obj.search(cr, uid, domain, context=context) + related_goal_ids.extend( [goal.id for goal in goal_ids if goal in subset_goal_ids] ) - board_goals = [] - goal_ids = goal_obj.search(cr, uid, domain, context=context) - for goal in goal_obj.browse(cr, uid, goal_ids, context=context): - board_goals.append({ - 'user': goal.user_id, - 'current':goal.current, - 'target_goal':goal.target_goal, - 'completeness':goal.completeness, - }) + else: + # if no subset goals, use the dates for restriction + if start_date: + domain.append(('start_date', '=', start_date.isoformat())) + if end_date: + domain.append(('end_date', '=', end_date.isoformat())) - # most complete first, current if same percentage (eg: if several 100%) - sorted_board = enumerate(sorted(board_goals, key=lambda k: (k['completeness'], k['current']), reverse=True)) - planlines_boards.append({'goal_type':planline.type_id.name, 'board_goals':sorted_board}) + related_goal_ids.extend( goal_obj.search(cr, uid, domain, context=context) ) - body_html = template_env.get_template('group_progress.mako').render({'object':plan, 'planlines_boards':planlines_boards}) + if len(related_goal_ids) == 0: + continue + + variables = { + 'object':plan, + 'user':user, + 'goals':goal_obj.browse(cr, uid, related_goal_ids, context=context) + } + body_html = template_env.get_template('personal_progress.mako').render(variables) self.message_post(cr, uid, plan.id, body=body_html, - partner_ids=[(6, 0, [user.partner_id.id for user in plan.user_ids])], + partner_ids=[(6, 0, [user.partner_id.id])], context=context, subtype='mail.mt_comment') if plan.report_message_group_id: @@ -380,60 +487,8 @@ class gamification_goal_plan(osv.Model): body=body_html, context=context, subtype='mail.mt_comment') - - else: - # generate individual reports - for user in users or plan.user_ids: - goal_ids = self.get_current_related_goals(cr, uid, plan.id, user.id, context=context) - if len(goal_ids) == 0: - continue - - variables = { - 'object':plan, - 'user':user, - 'goals':goal_obj.browse(cr, uid, goal_ids, context=context) - } - body_html = template_env.get_template('personal_progress.mako').render(variables) - - self.message_post(cr, uid, plan.id, - body=body_html, - partner_ids=[(6, 0, [user.partner_id.id])], - context=context, - subtype='mail.mt_comment') - if plan.report_message_group_id: - self.pool.get('mail.group').message_post(cr, uid, plan.report_message_group_id.id, - body=body_html, - context=context, - subtype='mail.mt_comment') return True - - - def get_current_related_goals(self, cr, uid, plan_id, user_id, context=None): - """Get the ids of goals linked to a plan for the current instance - - If several goals are linked to the same planline and user, only the - latest instance of the plan is checked (eg: if the plan is monthly, - return the goals started the 1st of this month). - """ - - plan = self.browse(cr, uid, plan_id, context=context) - (start_date, end_date) = start_end_date_for_period(plan.period) - - goal_obj = self.pool.get('gamification.goal') - related_goal_ids = [] - - for planline in plan.planline_ids: - domain = [('planline_id', '=', planline.id), - ('user_id', '=', user_id), - ('state','in',('inprogress','inprogress_update','reached'))] - - if start_date: - domain.append(('start_date', '=', start_date.isoformat())) - - goal_ids = goal_obj.search(cr, uid, domain, context=context) - related_goal_ids.extend(goal_ids) - - return related_goal_ids + class gamification_goal_planline(osv.Model): """Gamification goal planline @@ -477,4 +532,4 @@ class gamification_goal_planline(osv.Model): store={ 'gamification.goal.type': (_get_planline_types, ['sequence'], 10), }), - } \ No newline at end of file + } diff --git a/addons/gamification/plan_view.xml b/addons/gamification/plan_view.xml index 2c9efc5342f..cf95b6f690f 100644 --- a/addons/gamification/plan_view.xml +++ b/addons/gamification/plan_view.xml @@ -49,7 +49,7 @@