Skip to content

Commit

Permalink
[IMP] performance improvement on progress rate calculation in projects
Browse files Browse the repository at this point in the history
  • Loading branch information
mdietrichc2c authored and Stefan Rijnhart committed Jul 2, 2014
1 parent c598d22 commit 13fe51c
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 20 deletions.
1 change: 1 addition & 0 deletions addons/project/__openerp__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
'test/project_demo.yml',
'test/project_process.yml',
'test/task_process.yml',
'test/hours_process.yml',
],
'installable': True,
'auto_install': False,
Expand Down
88 changes: 68 additions & 20 deletions addons/project/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ def _get_project_and_parents(self, cr, uid, ids, context=None):
res.update(ids)
return list(res)

# Deprecated; the _progress_rate method does not use this anymore
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)
Expand All @@ -154,28 +155,75 @@ def _get_project_and_children(self, cr, uid, ids, context=None):
return res

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
# How this works: the WITH RECURSIVE statement will create an union line
# for each parent project with the hours of each child project; the final
# SUM(...) ensures we get a total of hours by 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()),))
WITH RECURSIVE recur_table(project_id,
parent_id,
planned_hours,
total_hours,
effective_hours) AS (
SELECT project.id,
parent.id,
COALESCE(task.planned, 0.0),
COALESCE(task.total, 0.0),
COALESCE(task.effective, 0.0)
FROM project_project project
LEFT JOIN account_analytic_account account
ON project.analytic_account_id = account.id
LEFT JOIN project_project parent
ON parent.analytic_account_id = account.parent_id
LEFT JOIN (SELECT project_id,
SUM(planned_hours) as planned,
SUM(total_hours) as total,
SUM(effective_hours) as effective
FROM project_task
WHERE state <> 'cancelled'
GROUP BY project_id) AS task
ON project.id = task.project_id
UNION ALL
SELECT project.id,
parent.id,
recur_table.planned_hours,
recur_table.total_hours,
recur_table.effective_hours
FROM project_project project
LEFT JOIN account_analytic_account account
ON project.analytic_account_id = account.id
LEFT JOIN project_project parent
ON parent.analytic_account_id = account.parent_id
LEFT JOIN (SELECT project_id,
SUM(planned_hours) as planned,
SUM(total_hours) as total,
SUM(effective_hours) as effective
FROM project_task
WHERE state <> 'cancelled'
GROUP BY project_id) AS task
ON project.id = task.project_id
JOIN recur_table ON project.id = recur_table.parent_id
)
SELECT project_id,
SUM(planned_hours),
SUM(total_hours),
SUM(effective_hours)
FROM recur_table
WHERE project_id IN %s
GROUP BY project_id
""", (tuple(ids),))
# 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]
res = dict([(result_line[0], {'planned_hours': result_line[1],
'total_hours': result_line[2],
'effective_hours': result_line[3]})
for result_line in cr.fetchall()])
# 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)
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
Expand All @@ -192,7 +240,7 @@ def unlink(self, cr, uid, ids, context=None):
res = super(project, self).unlink(cr, uid, ids, context=context)
mail_alias.unlink(cr, uid, alias_ids, context=context)
return res

def _get_attached_docs(self, cr, uid, ids, field_name, arg, context):
res = {}
attachment = self.pool.get('ir.attachment')
Expand All @@ -203,7 +251,7 @@ def _get_attached_docs(self, cr, uid, ids, field_name, arg, context):
task_attachments = attachment.search(cr, uid, [('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)], context=context, count=True)
res[id] = (project_attachments or 0) + (task_attachments or 0)
return res

def _task_count(self, cr, uid, ids, field_name, arg, context=None):
if context is None:
context = {}
Expand All @@ -228,7 +276,7 @@ def _get_visibility_selection(self, cr, uid, context=None):
def attachment_tree_view(self, cr, uid, ids, context):
task_ids = self.pool.get('project.task').search(cr, uid, [('project_id', 'in', ids)])
domain = [
'|',
'|',
'&', ('res_model', '=', 'project.project'), ('res_id', 'in', ids),
'&', ('res_model', '=', 'project.task'), ('res_id', 'in', task_ids)
]
Expand Down Expand Up @@ -733,7 +781,7 @@ def copy_data(self, cr, uid, id, default=None, context=None):
new_name = _("%s (copy)") % (default.get('name', ''))
default.update({'name':new_name})
return super(task, self).copy_data(cr, uid, id, default, context)

def copy(self, cr, uid, id, default=None, context=None):
if context is None:
context = {}
Expand Down
174 changes: 174 additions & 0 deletions addons/project/test/hours_process.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
-
First, create the analytic accounts
-
!record {model: account.analytic.account, id: parent_project_account}:
name: Parent Account
code: PAR
type: view
-
!record {model: account.analytic.account, id: child1_project_account}:
name: Child Account 1
code: C1
type: view
-
I create a main project
-
!record {model: project.project, id: project_project_parent}:
company_id: base.main_company
name: Parent Project
user_id: base.user_demo
parent_id: all_projects_account
analytic_account_id: parent_project_account
alias_model: project.task
-
A first child project
-
!record {model: project.project, id: project_project_child_1}:
company_id: base.main_company
name: Child Project 1
user_id: base.user_demo
parent_id: parent_project_account
analytic_account_id: child1_project_account
alias_model: project.task
-
A second child project
-
!record {model: project.project, id: project_project_child_2}:
company_id: base.main_company
name: Child Project 2
user_id: base.user_demo
parent_id: parent_project_account
alias_model: project.task
-
A child of the first child project
-
!record {model: project.project, id: project_project_child_1_1}:
company_id: base.main_company
name: Child Project 1 1
user_id: base.user_demo
parent_id: child1_project_account
alias_model: project.task
-
A task for the main project, with 20 hours planned and 20 hours remaining
-
!record {model: project.task, id: project_task_parent_project}:
date_start: !eval time.strftime('%Y-05-%d %H:%M:%S')
name: Task parent project
planned_hours: 20.0
remaining_hours: 20.0
project_id: project_project_parent
user_id: base.user_demo
-
I test the hours
-
!python {model: project.project}: |
project = self.browse(cr, uid, ref("project_project_parent"))
assert abs(project.planned_hours - 20.0) < 1e-4, "Planned hours are not correct! 20.0 != %s" % project.planned_hours
assert abs(project.total_hours - 20.0) < 1e-4, "Total hours are not correct! 20.0 != %s" % project.total_hours
assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
-
A task for the first child project, with 30 hours planned and 30 hours remaining
-
!record {model: project.task, id: project_task_child_project_1}:
date_start: !eval time.strftime('%Y-05-%d %H:%M:%S')
name: Task child project 1
planned_hours: 30.0
remaining_hours: 30.0
project_id: project_project_child_1
user_id: base.user_demo
-
I test the hours
-
!python {model: project.project}: |
project = self.browse(cr, uid, ref("project_project_parent"))
assert abs(project.planned_hours - 50.0) < 1e-4, "Planned hours are not correct! 50.0 != %s" % project.planned_hours
assert abs(project.total_hours - 50.0) < 1e-4, "Total hours are not correct! 50.0 != %s" % project.total_hours
assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
project = self.browse(cr, uid, ref("project_project_child_1"))
assert abs(project.planned_hours - 30.0) < 1e-4, "Planned hours are not correct! 30.0 != %s" % project.planned_hours
assert abs(project.total_hours - 30.0) < 1e-4, "Total hours are not correct! 30.0 != %s" % project.total_hours
assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
-
A task for the second child project, with 40 hours planned and 10 hours remaining
-
!record {model: project.task, id: project_task_child_project_2}:
date_start: !eval time.strftime('%Y-05-%d %H:%M:%S')
name: Task child project 2
planned_hours: 40.0
remaining_hours: 10.0
project_id: project_project_child_2
user_id: base.user_demo
-
I test the hours
-
!python {model: project.project}: |
project = self.browse(cr, uid, ref("project_project_parent"))
assert abs(project.planned_hours - 90.0) < 1e-4, "Planned hours are not correct! 90.0 != %s" % project.planned_hours
assert abs(project.total_hours - 60.0) < 1e-4, "Total hours are not correct! 60.0 != %s" % project.total_hours
assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
project = self.browse(cr, uid, ref("project_project_child_2"))
assert abs(project.planned_hours - 40.0) < 1e-4, "Planned hours are not correct! 40.0 != %s" % project.planned_hours
assert abs(project.total_hours - 10.0) < 1e-4, "Total hours are not correct! 10.0 != %s" % project.total_hours
assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rat
-
A task for the child child project, with 50 hours planned and 50 hours remaining
-
!record {model: project.task, id: project_task_child_project_1_1}:
date_start: !eval time.strftime('%Y-05-%d %H:%M:%S')
name: Task child project 1 1
planned_hours: 50.0
remaining_hours: 50.0
project_id: project_project_child_1_1
user_id: base.user_demo
-
I test the hours
-
!python {model: project.project}: |
project = self.browse(cr, uid, ref("project_project_parent"))
assert abs(project.planned_hours - 140.0) < 1e-4, "Planned hours are not correct! 140.0 != %s" % project.planned_hours
assert abs(project.total_hours - 110.0) < 1e-4, "Total hours are not correct! 110.0 != %s" % project.total_hours
assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
project = self.browse(cr, uid, ref("project_project_child_1"))
assert abs(project.planned_hours - 80.0) < 1e-4, "Planned hours are not correct! 80.0 != %s" % project.planned_hours
assert abs(project.total_hours - 80.0) < 1e-4, "Total hours are not correct! 80.0 != %s" % project.total_hours
assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
project = self.browse(cr, uid, ref("project_project_child_1_1"))
assert abs(project.planned_hours - 50.0) < 1e-4, "Planned hours are not correct! 50.0 != %s" % project.planned_hours
assert abs(project.total_hours - 50.0) < 1e-4, "Total hours are not correct! 50.0 != %s" % project.total_hours
assert abs(project.effective_hours - 0.0) < 1e-4, "Effective hours are not correct! 0.0 != %s" % project.effective_hours
assert abs(project.progress_rate - 0.0) < 1e-4, "Progress rate is not correct! 0.0 != %s" % project.progress_rate
-
I create a task work on child 1 task
-
!record {model: project.task.work, id: project_child_1_work}:
name: test work
hours: 10.0
task_id: project_task_child_project_1
user_id: base.user_demo
-
and refresh the task to account for it
-
!record {model: project.task, id: project_task_child_project_1}:
planned_hours: 20.0
-
I test the hours
-
!python {model: project.project}: |
project = self.browse(cr, uid, ref("project_project_parent"))
assert abs(project.planned_hours - 130.0) < 1e-4, "Planned hours are not correct! 130.0 != %s" % project.planned_hours
assert abs(project.total_hours - 110.0) < 1e-4, "Total hours are not correct! 110.0 != %s" % project.total_hours
assert abs(project.effective_hours - 10.0) < 1e-4, "Effective hours are not correct! 10.0 != %s" % project.effective_hours
assert abs(project.progress_rate - 9.09) < 1e-4, "Progress rate is not correct! 9.09 != %s" % project.progress_rate
project = self.browse(cr, uid, ref("project_project_child_1"))
assert abs(project.planned_hours - 70.0) < 1e-4, "Planned hours are not correct! 70.0 != %s" % project.planned_hours
assert abs(project.total_hours - 80.0) < 1e-4, "Total hours are not correct! 80.0 != %s" % project.total_hours
assert abs(project.effective_hours - 10.0) < 1e-4, "Effective hours are not correct! 10.0 != %s" % project.effective_hours
assert abs(project.progress_rate - 12.5) < 1e-4, "Progress rate is not correct! 12.5 != %s" % project.progress_rate

0 comments on commit 13fe51c

Please sign in to comment.