diff --git a/addons/project/__openerp__.py b/addons/project/__openerp__.py index 9132636b56f..1913cc62c04 100644 --- a/addons/project/__openerp__.py +++ b/addons/project/__openerp__.py @@ -55,7 +55,8 @@ Dashboard for project members that includes: "res_partner_view.xml", "report/project_report_view.xml", "board_project_view.xml", - 'board_project_manager_view.xml' + 'board_project_manager_view.xml', + 'report/project_cumulative.xml' ], 'demo_xml': [ 'project_demo.xml', diff --git a/addons/project/project.py b/addons/project/project.py index abebd2d35ff..5c234cf5e26 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -974,6 +974,24 @@ class task(osv.osv): def prev_type(self, cr, uid, ids, *args): return self._change_type(cr, uid, ids, False, *args) + def _store_history(self, cr, uid, ids, context=None): + for task in self.browse(cr, uid, ids, context=context): + self.pool.get('project.task.history').create(cr, uid, { + 'task_id': task.id, + 'remaining_hours': task.remaining_hours, + 'kanban_state': task.kanban_state, + 'type_id': task.type_id.id, + 'state': task.state, + 'user_id': task.user_id.id + + }, context=context) + return True + + def create(self, cr, uid, vals, context=None): + result = super(task, self).create(cr, uid, vals, context=context) + self._store_history(cr, uid, [result], context=context) + return result + # Overridden to reset the kanban_state to normal whenever # the stage (type_id) of the task changes. def write(self, cr, uid, ids, vals, context=None): @@ -985,8 +1003,12 @@ class task(osv.osv): for t in self.browse(cr, uid, ids, context=context): write_vals = vals_reset_kstate if t.type_id != new_stage else vals super(task,self).write(cr, uid, [t.id], write_vals, context=context) - return True - return super(task,self).write(cr, uid, ids, vals, context=context) + result = True + else: + result = super(task,self).write(cr, uid, ids, vals, context=context) + if ('type_id' in vals) or ('remaining_hours' in vals) or ('user_id' in vals) or ('state' in vals) or ('kanban_state' in vals): + self._store_history(cr, uid, ids, context=context) + return result def unlink(self, cr, uid, ids, context=None): if context == None: @@ -1084,4 +1106,91 @@ class account_analytic_account(osv.osv): account_analytic_account() -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: +# +# Tasks History, used for cumulative flow charts (Lean/Agile) +# + +class project_task_history(osv.osv): + _name = 'project.task.history' + _description = 'History of Tasks' + _rec_name = 'task_id' + _log_access = False + def _get_date(self, cr, uid, ids, name, arg, context=None): + result = {} + for history in self.browse(cr, uid, ids, context=context): + if history.state in ('done','cancelled'): + result[history.id] = history.date + continue + cr.execute('''select + date + from + project_task_history + where + task_id=%s and + id>%s + order by id limit 1''', (history.task_id.id, history.id)) + res = cr.fetchone() + result[history.id] = res and res[0] or False + return result + + def _get_related_date(self, cr, uid, ids, context=None): + result = [] + for history in self.browse(cr, uid, ids, context=context): + cr.execute('''select + id + from + project_task_history + where + task_id=%s and + id<%s + order by id desc limit 1''', (history.task_id.id, history.id)) + res = cr.fetchone() + if res: + result.append(res[0]) + return result + + _columns = { + 'task_id': fields.many2one('project.task', 'Task', ondelete='cascade', required=True, select=True), + 'type_id': fields.many2one('project.task.type', 'Stage'), + 'state': fields.selection([('draft', 'New'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done'), ('cancelled', 'Cancelled')], 'State'), + 'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready To Pull')], 'Kanban State', required=False), + 'date': fields.date('Date', select=True), + 'end_date': fields.function(_get_date, string='End Date', type="date", store={ + 'project.task.history': (_get_related_date, None, 20) + }), + 'remaining_hours': fields.float('Remaining Hours', digits=(16,2)), + 'user_id': fields.many2one('res.users', 'Responsible'), + } + _defaults = { + 'date': lambda s,c,u,ctx: time.strftime('%Y-%m-%d') + } +project_task_history() + +class project_task_history_cumulative(osv.osv): + _name = 'project.task.history.cumulative' + _table = 'project_task_history_cumulative' + _inherit = 'project.task.history' + _auto = False + _columns = { + 'end_date': fields.date('End Date'), + 'project_id': fields.related('task_id', 'project_id', string='Project', type='many2one', relation='project.project') + } + def init(self, cr): + cr.execute(""" CREATE OR REPLACE VIEW project_task_history_cumulative AS ( + SELECT + history.date::varchar||'-'||history.history_id::varchar as id, + history.date as end_date, + * + FROM ( + SELECT + id as history_id, + date+generate_series(0, CAST((coalesce(end_date,DATE 'tomorrow')::date - date)AS integer)-1) as date, + task_id, type_id, user_id, kanban_state, state, + remaining_hours + FROM + project_task_history + ) as history + ) + """) +project_task_history_cumulative() + diff --git a/addons/project/report/project_cumulative.xml b/addons/project/report/project_cumulative.xml new file mode 100644 index 00000000000..aff90aea94e --- /dev/null +++ b/addons/project/report/project_cumulative.xml @@ -0,0 +1,81 @@ + + + + + + project.task.history.cumulative.tree + project.task.history.cumulative + tree + + + + + + + + + + + + + + + project.task.history.cumulative.graph + project.task.history.cumulative + graph + + + + + + + + + + + project.task.history.cumulative.search + project.task.history.cumulative + search + + + + + + + + + + + + + + + + + + + + + + + + + + + Cumulative Flow + project.task.history.cumulative + form + graph,tree + + {"search_default_open":1, "search_default_this_month": 1} + + + + + + diff --git a/addons/project/report/project_report_view.xml b/addons/project/report/project_report_view.xml index cfa667c7590..bf828d6b231 100644 --- a/addons/project/report/project_report_view.xml +++ b/addons/project/report/project_report_view.xml @@ -5,6 +5,8 @@ + report.project.task.user.tree @@ -152,7 +154,7 @@ This report allows you to analyse the performance of your projects and users. You can analyse the quantities of tasks, the hours spent compared to the planned hours, the average number of days to open or close a task, etc. - + diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv index 659948bc4d0..955a6ef0c97 100644 --- a/addons/project/security/ir.model.access.csv +++ b/addons/project/security/ir.model.access.csv @@ -22,3 +22,5 @@ "access_project_task_sale_user","project.task salesman","model_project_task","base.group_sale_salesman",1,0,0,0 "access_project_project_sale_user","project.project salesman","model_project_project","base.group_sale_salesman",1,0,0,0 "access_account_analytic_line_project","account.analytic.line project","analytic.model_account_analytic_line","project.group_project_manager",1,1,1,1 +"access_project_task_history","project.task.history project","project.model_project_task_history","project.group_project_user",1,1,1,0 +"access_project_task_history_cumulative","project.task.history project","project.model_project_task_history_cumulative","project.group_project_manager",1,0,0,0