diff --git a/addons/project/project.py b/addons/project/project.py index d83f4a8d424..c3a3ef7f5bf 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -84,35 +84,70 @@ class project(osv.osv): val['pricelist_id'] = pricelist_id return {'value': val} - def _progress_rate(self, cr, uid, ids, names, arg, context=None): - res = {}.fromkeys(ids, 0.0) - if not ids: - return res - cr.execute('''SELECT - project_id, sum(planned_hours), sum(total_hours), sum(effective_hours), SUM(remaining_hours) - FROM - project_task - WHERE - project_id in %s AND - state<>'cancelled' - GROUP BY - project_id''', (tuple(ids),)) - progress = dict(map(lambda x: (x[0], (x[1],x[2],x[3],x[4])), cr.fetchall())) - for project in self.browse(cr, uid, ids, context=context): - s = progress.get(project.id, (0.0,0.0,0.0,0.0)) - res[project.id] = { - 'planned_hours': s[0], - 'effective_hours': s[2], - 'total_hours': s[1], - 'progress_rate': s[1] and round(100.0*s[2]/s[1],2) or 0.0 - } + def _get_projects_from_tasks(self, cr, uid, task_ids, context=None): + tasks = self.pool.get('project.task').browse(cr, uid, task_ids, context=context) + project_ids = [task.project_id.id for task in tasks] + return self.pool.get('project.project')._get_project_and_parents(cr, uid, project_ids, context) + + def _get_project_and_parents(self, cr, uid, ids, context=None): + """ return the project ids and all their parent projects """ + res = set(ids) + while ids: + cr.execute(""" + SELECT DISTINCT parent.id + FROM project_project project, project_project parent, account_analytic_account account + WHERE project.analytic_account_id = account.id + AND parent.analytic_account_id = account.parent_id + AND project.id IN %s + """, (tuple(ids),)) + ids = [t[0] for t in cr.fetchall()] + res.update(ids) + return list(res) + + def _get_project_and_children(self, cr, uid, ids, context=None): + """ retrieve all children projects of project ids; + return a dictionary mapping each project to its parent project (or None) + """ + res = dict.fromkeys(ids, None) + while ids: + cr.execute(""" + SELECT project.id, parent.id + FROM project_project project, project_project parent, account_analytic_account account + WHERE project.analytic_account_id = account.id + AND parent.analytic_account_id = account.parent_id + AND parent.id IN %s + """, (tuple(ids),)) + dic = dict(cr.fetchall()) + res.update(dic) + ids = dic.keys() return res - def _get_project_task(self, cr, uid, ids, context=None): - result = {} - for task in self.pool.get('project.task').browse(cr, uid, ids, context=context): - if task.project_id: result[task.project_id.id] = True - return result.keys() + def _progress_rate(self, cr, uid, ids, names, arg, context=None): + child_parent = self._get_project_and_children(cr, uid, ids, context) + # compute planned_hours, total_hours, effective_hours specific to each project + cr.execute(""" + SELECT project_id, COALESCE(SUM(planned_hours), 0.0), + COALESCE(SUM(total_hours), 0.0), COALESCE(SUM(effective_hours), 0.0) + FROM project_task WHERE project_id IN %s AND state <> 'cancelled' + GROUP BY project_id + """, (tuple(child_parent.keys()),)) + # aggregate results into res + res = dict([(id, {'planned_hours':0.0,'total_hours':0.0,'effective_hours':0.0}) for id in ids]) + for id, planned, total, effective in cr.fetchall(): + # add the values specific to id to all parent projects of id in the result + while id: + if id in ids: + res[id]['planned_hours'] += planned + res[id]['total_hours'] += total + res[id]['effective_hours'] += effective + id = child_parent[id] + # compute progress rates + for id in ids: + if res[id]['total_hours']: + res[id]['progress_rate'] = round(100.0 * res[id]['effective_hours'] / res[id]['total_hours'], 2) + else: + res[id]['progress_rate'] = 0.0 + return res def unlink(self, cr, uid, ids, *args, **kwargs): for proj in self.browse(cr, uid, ids): @@ -133,17 +168,25 @@ class project(osv.osv): 'tasks': fields.one2many('project.task', 'project_id', "Project tasks"), 'planned_hours': fields.function(_progress_rate, multi="progress", string='Planned Time', help="Sum of planned hours of all tasks related to this project and its child projects.", store = { - 'project.project': (lambda self, cr, uid, ids, c={}: ids, ['tasks'], 10), - 'project.task': (_get_project_task, ['planned_hours', 'effective_hours', 'remaining_hours', 'total_hours', 'progress', 'delay_hours','state'], 10), + 'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10), + 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20), + }), + 'effective_hours': fields.function(_progress_rate, multi="progress", string='Time Spent', help="Sum of spent hours of all tasks related to this project and its child projects.", + store = { + 'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10), + 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20), }), - 'effective_hours': fields.function(_progress_rate, multi="progress", string='Time Spent', help="Sum of spent hours of all tasks related to this project and its child projects."), - 'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ), 'total_hours': fields.function(_progress_rate, multi="progress", string='Total Time', help="Sum of total hours of all tasks related to this project and its child projects.", store = { - 'project.project': (lambda self, cr, uid, ids, c={}: ids, ['tasks'], 10), - 'project.task': (_get_project_task, ['planned_hours', 'effective_hours', 'remaining_hours', 'total_hours', 'progress', 'delay_hours','state'], 10), + 'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10), + 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20), }), - 'progress_rate': fields.function(_progress_rate, multi="progress", string='Progress', type='float', group_operator="avg", help="Percent of tasks closed according to the total of tasks todo."), + 'progress_rate': fields.function(_progress_rate, multi="progress", string='Progress', type='float', group_operator="avg", help="Percent of tasks closed according to the total of tasks todo.", + store = { + 'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10), + 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20), + }), + 'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ), 'warn_customer': fields.boolean('Warn Partner', help="If you check this, the user will have a popup when closing a task that propose a message to send by email to the customer.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'warn_header': fields.text('Mail Header', help="Header added at the beginning of the email for the warning message sent to the customer when a task is closed.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'warn_footer': fields.text('Mail Footer', help="Footer added at the beginning of the email for the warning message sent to the customer when a task is closed.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),