[FIX] ActiveId Ref on New Task When from Project>Tasks act_window and Query Code...
[odoo/odoo.git] / addons / project / project.py
index 96bf2f3..466c937 100644 (file)
@@ -25,6 +25,7 @@ from datetime import datetime, date
 
 from tools.translate import _
 from osv import fields, osv
+from resource.faces import task as Task
 
 # I think we can remove this in v6.1 since VMT's improvements in the framework ?
 #class project_project(osv.osv):
@@ -45,6 +46,7 @@ class project_task_type(osv.osv):
     _defaults = {
         'sequence': 1
     }
+    _order = 'sequence'
 project_task_type()
 
 class project(osv.osv):
@@ -73,27 +75,56 @@ class project(osv.osv):
     def onchange_partner_id(self, cr, uid, ids, part=False, context=None):
         partner_obj = self.pool.get('res.partner')
         if not part:
-            return {'value':{'contact_id': False, 'pricelist_id': False}}
+            return {'value':{'contact_id': False}}
         addr = partner_obj.address_get(cr, uid, [part], ['contact'])
-        pricelist = partner_obj.read(cr, uid, part, ['property_product_pricelist'], context=context)
-        pricelist_id = pricelist.get('property_product_pricelist', False) and pricelist.get('property_product_pricelist')[0] or False
-        return {'value':{'contact_id': addr['contact'], 'pricelist_id': pricelist_id}}
+        val = {'contact_id': addr['contact']}
+        if 'pricelist_id' in self.fields_get(cr, uid, context=context):
+            pricelist = partner_obj.read(cr, uid, part, ['property_product_pricelist'], context=context)
+            pricelist_id = pricelist.get('property_product_pricelist', False) and pricelist.get('property_product_pricelist')[0] or False
+            val['pricelist_id'] = pricelist_id
+        return {'value': val}
+
+    def _get_childs(self, cr, uid, ids, childs,context=None):
+        cr.execute("""SELECT id FROM project_project WHERE analytic_account_id IN (
+                        SELECT id FROM account_analytic_account WHERE parent_id = (
+                               SELECT  analytic_account_id FROM project_project project
+                                       LEFT JOIN account_analytic_account account ON account.id = project.analytic_account_id
+                                       WHERE project.id = %s
+                            )
+                        )"""%(ids)) 
+        for child in cr.fetchall():
+            if child[0] not in childs: childs.append(child[0])
+            self._get_childs( cr, uid, child[0], childs,context)
+        return childs
+
+
+    def _get_parents(self, cr, uid, ids, parents,context=None):
+        for project in self.read(cr, uid, ids, ['id', 'parent_id'],context):
+            if project.get('parent_id'):
+                cr.execute('''SELECT id FROM project_project WHERE analytic_account_id = '%s' '''%project.get('parent_id')[0])
+                for child in cr.fetchall():
+                    if child[0] not in parents: parents.append(child[0])
+                    child_rec= self.read(cr, uid, child[0], ['id', 'parent_id'],context)
+                    if child_rec.get('parent_id'):
+                        parents = self._get_parents(cr, uid, [child[0]], parents,context)
+        return parents
+
+    def _get_project(self,cr, ids):
+        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),))
+        return cr
 
     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):
+        parents = self._get_parents(cr, uid, ids, ids,context)
+        cr = self._get_project(cr,ids)
+        progress = dict(map(lambda x: (x[0], (x[1] or 0.0 ,x[2] or 0.0 ,x[3] or 0.0 ,x[4] or 0.0)), cr.fetchall()))
+        for project in self.browse(cr, uid, parents, context=context):
+            childs = []
+            childs = self._get_childs(cr, uid, project.id, childs,context)
             s = progress.get(project.id, (0.0,0.0,0.0,0.0))
             res[project.id] = {
                 'planned_hours': s[0],
@@ -101,12 +132,33 @@ class project(osv.osv):
                 'total_hours': s[1],
                 'progress_rate': s[1] and round(100.0*s[2]/s[1],2) or 0.0
             }
+            
+            if childs:
+                cr = self._get_project(cr, childs)
+                child_progress = dict(map(lambda x: (x[0], (x[1] or 0.0 ,x[2] or 0.0 ,x[3] or 0.0 ,x[4] or 0.0)), cr.fetchall()))
+                planned_hours, effective_hours, total_hours, rnd= 0.0, 0.0,0.0, 0.0
+                for child in childs:
+                    ch_vals = child_progress.get(child, (0.0,0.0,0.0,0.0))
+                    planned_hours, effective_hours, total_hours = planned_hours+ch_vals[0], effective_hours+ch_vals[2] , total_hours+ch_vals[1]
+                if res.get(project.id).get('planned_hours')+ planned_hours > 0:
+                    rnd = round(( res.get(project.id).get('effective_hours')+effective_hours)/(res.get(project.id).get('planned_hours')+ planned_hours)*100,2) or 0.0
+                res[project.id] = {
+                    'planned_hours': res.get(project.id).get('planned_hours')+ planned_hours,
+                    'effective_hours': res.get(project.id).get('effective_hours')+ effective_hours,
+                    'total_hours': res.get(project.id).get('total_hours')+ total_hours,
+                    'progress_rate':  rnd
+                }
         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
+            if task.project_id: 
+                result[task.project_id.id] = True
+                if task.project_id.parent_id:
+                    cr.execute('''SELECT id FROM project_project WHERE analytic_account_id = '%s' '''%task.project_id.parent_id.id)
+                    for parent in cr.fetchall():
+                        result[parent[0]] = True
         return result.keys()
 
     def _get_project_work(self, cr, uid, ids, context=None):
@@ -134,13 +186,14 @@ 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.project': (lambda self, cr, uid, ids, c={}: ids, ['tasks', 'parent_id', 'child_ids'], 10),
                 'project.task': (_get_project_task, ['planned_hours', 'effective_hours', 'remaining_hours', 'total_hours', 'progress', 'delay_hours','state'], 10),
             }),
         '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.project': (lambda self, cr, uid, ids, c={}: ids, ['tasks','parent_id', 'child_ids'], 10),
                 'project.task': (_get_project_task, ['planned_hours', 'effective_hours', 'remaining_hours', 'total_hours', 'progress', 'delay_hours','state'], 10),
             }),
         '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."),
@@ -304,6 +357,105 @@ class project(osv.osv):
                 self.setActive(cr, uid, child_ids, value, context=None)
         return True
 
+    def _schedule_header(self, cr, uid, ids, force_members=True, context=None):
+        context = context or {}
+        if type(ids) in (long, int,):
+            ids = [ids]
+        projects = self.browse(cr, uid, ids, context=context)
+
+        for project in projects:
+            if (not project.members) and force_members:
+                raise osv.except_osv(_('Warning !'),_("You must assign members on the project '%s' !") % (project.name,))
+
+        resource_pool = self.pool.get('resource.resource')
+
+        result = "from resource.faces import *\n"
+        result += "import datetime\n"
+        for project in self.browse(cr, uid, ids, context=context):
+            u_ids = [i.id for i in project.members]
+            if project.user_id and (project.user_id.id not in u_ids):
+                u_ids.append(project.user_id.id)
+            for task in project.tasks:
+                if task.state in ('done','cancelled'):
+                    continue
+                if task.user_id and (task.user_id.id not in u_ids):
+                    u_ids.append(task.user_id.id)
+            calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
+            resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
+            for key, vals in resource_objs.items():
+                result +='''
+class User_%s(Resource):
+    efficiency = %s
+''' % (key,  vals.get('efficiency', False))
+
+        result += '''
+def Project():
+        '''
+        return result
+
+    def _schedule_project(self, cr, uid, project, context=None):
+        resource_pool = self.pool.get('resource.resource')
+        calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
+        working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
+        # TODO: check if we need working_..., default values are ok.
+        puids = [x.id for x in project.members]
+        if project.user_id:
+            puids.append(project.user_id.id)
+        result = """
+  def Project_%d():
+    start = \'%s\'
+    working_days = %s
+    resource = %s
+"""       % (
+            project.id, 
+            project.date_start, working_days,
+            '|'.join(['User_'+str(x) for x in puids])
+        )
+        vacation = calendar_id and tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context)) or False
+        if vacation:
+            result+= """
+    vacation = %s
+""" %   ( vacation, )
+        return result
+
+    #TODO: DO Resource allocation and compute availability
+    def compute_allocation(self, rc, uid, ids, start_date, end_date, context=None):
+        if context ==  None:
+            context = {}
+        allocation = {}
+        return allocation
+
+    def schedule_tasks(self, cr, uid, ids, context=None):
+        context = context or {}
+        if type(ids) in (long, int,):
+            ids = [ids]
+        projects = self.browse(cr, uid, ids, context=context)
+        result = self._schedule_header(cr, uid, ids, False, context=context)
+        for project in projects:
+            result += self._schedule_project(cr, uid, project, context=context)
+            result += self.pool.get('project.task')._generate_task(cr, uid, project.tasks, ident=4, context=context)
+
+        local_dict = {}
+        exec result in local_dict
+        projects_gantt = Task.BalancedProject(local_dict['Project'])
+
+        for project in projects:
+            project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,))
+            for task in project.tasks:
+                if task.state in ('done','cancelled'):
+                    continue
+
+                p = getattr(project_gantt, 'Task_%d' % (task.id,))
+
+                self.pool.get('project.task').write(cr, uid, [task.id], {
+                    'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'),
+                    'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S')
+                }, context=context)
+                if (not task.user_id) and (p.booked_resource):
+                    self.pool.get('project.task').write(cr, uid, [task.id], {
+                        'user_id': int(p.booked_resource[0].name[5:]),
+                    }, context=context)
+        return True
 project()
 
 class users(osv.osv):
@@ -319,6 +471,62 @@ class task(osv.osv):
     _log_create = True
     _date_name = "date_start"
 
+
+    def _resolve_project_id_from_context(self, cr, uid, context=None):
+        """Return ID of project based on the value of 'project_id'
+           context key, or None if it cannot be resolved to a single project.
+        """
+        if context is None: context = {}
+        if type(context.get('project_id')) in (int, long):
+            project_id = context['project_id']
+            return project_id
+        if isinstance(context.get('project_id'), basestring):
+            project_name = context['project_id']
+            project_ids = self.pool.get('project.project').name_search(cr, uid, name=project_name)
+            if len(project_ids) == 1:
+                return project_ids[0][0]
+
+    def _read_group_type_id(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
+        stage_obj = self.pool.get('project.task.type')
+        project_id = self._resolve_project_id_from_context(cr, uid, context=context)
+        order = stage_obj._order
+        access_rights_uid = access_rights_uid or uid
+        if read_group_order == 'type_id desc':
+            # lame way to allow reverting search, should just work in the trivial case
+            order = '%s desc' % order
+        if project_id:
+            domain = ['|', ('id','in',ids), ('project_ids','in',project_id)]
+        else:
+            domain = ['|', ('id','in',ids), ('project_default','=',1)]
+        stage_ids = stage_obj._search(cr, uid, domain, order=order, access_rights_uid=access_rights_uid, context=context)
+        result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context)
+        # restore order of the search
+        result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))
+        return result
+
+    def _read_group_user_id(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
+        res_users = self.pool.get('res.users')
+        project_id = self._resolve_project_id_from_context(cr, uid, context=context)
+        access_rights_uid = access_rights_uid or uid
+        if project_id:
+            ids += self.pool.get('project.project').read(cr, access_rights_uid, project_id, ['members'], context=context)['members']
+            order = res_users._order
+            # lame way to allow reverting search, should just work in the trivial case
+            if read_group_order == 'user_id desc':
+                order = '%s desc' % order
+            # de-duplicate and apply search order
+            ids = res_users._search(cr, uid, [('id','in',ids)], order=order, access_rights_uid=access_rights_uid, context=context)
+        result = res_users.name_get(cr, access_rights_uid, ids, context=context)
+        # restore order of the search
+        result.sort(lambda x,y: cmp(ids.index(x[0]), ids.index(y[0])))
+        return result
+
+    _group_by_full = {
+        'type_id': _read_group_type_id,
+        'user_id': _read_group_user_id
+    }
+
+
     def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
         obj_project = self.pool.get('project.project')
         for domain in args:
@@ -370,8 +578,8 @@ class task(osv.osv):
     def _default_project(self, cr, uid, context=None):
         if context is None:
             context = {}
-        if 'project_id' in context and context['project_id']:
-            return int(context['project_id'])
+        if 'default_project_id' in context and context['default_project_id']:
+            return int(context['default_project_id'])
         return False
 
     def duplicate_task(self, cr, uid, map_ids, context=None):
@@ -430,9 +638,15 @@ class task(osv.osv):
         'priority': fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Important'), ('0','Very important')], 'Priority'),
         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of tasks."),
         'type_id': fields.many2one('project.task.type', 'Stage'),
-        'state': fields.selection([('draft', 'Draft'),('open', 'In Progress'),('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True,
+        'state': fields.selection([('draft', 'New'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done'), ('cancelled', 'Cancelled')], 'State', readonly=True, required=True,
                                   help='If the task is created the state is \'Draft\'.\n If the task is started, the state becomes \'In Progress\'.\n If review is needed the task is in \'Pending\' state.\
                                   \n If the task is over, the states is set to \'Done\'.'),
+        'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready To Pull')], 'Kanban State',
+                                         help="A task's kanban state indicates special situations affecting it:\n"
+                                              " * Normal is the default situation\n"
+                                              " * Blocked indicates something is preventing the progress of this task\n"
+                                              " * Ready To Pull indicates the task is ready to be pulled to the next stage",
+                                         readonly=True, required=False),
         'create_date': fields.datetime('Create Date', readonly=True,select=True),
         'date_start': fields.datetime('Starting Date',select=True),
         'date_end': fields.datetime('Ending Date',select=True),
@@ -469,11 +683,14 @@ class task(osv.osv):
         'work_ids': fields.one2many('project.task.work', 'task_id', 'Work done'),
         'manager_id': fields.related('project_id', 'analytic_account_id', 'user_id', type='many2one', relation='res.users', string='Project Manager'),
         'company_id': fields.many2one('res.company', 'Company'),
-        'id': fields.integer('ID'),
+        'id': fields.integer('ID', readonly=True),
+        'color': fields.integer('Color Index'),
+        'user_email': fields.related('user_id', 'user_email', type='char', string='User Email', readonly=True),
     }
 
     _defaults = {
         'state': 'draft',
+        'kanban_state': 'normal',
         'priority': '2',
         'progress': 0,
         'sequence': 10,
@@ -483,7 +700,22 @@ class task(osv.osv):
         'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'project.task', context=c)
     }
 
-    _order = "sequence,priority, date_start, name, id"
+    _order = "priority, sequence, date_start, name, id"
+
+    def set_priority(self, cr, uid, ids, priority):
+        """Set task priority
+        """
+        return self.write(cr, uid, ids, {'priority' : priority})
+
+    def set_high_priority(self, cr, uid, ids, *args):
+        """Set task priority to high
+        """
+        return self.set_priority(cr, uid, ids, '1')
+
+    def set_normal_priority(self, cr, uid, ids, *args):
+        """Set task priority to normal
+        """
+        return self.set_priority(cr, uid, ids, '3')
 
     def _check_recursion(self, cr, uid, ids, context=None):
         for id in ids:
@@ -604,6 +836,7 @@ class task(osv.osv):
         Close Task
         """
         request = self.pool.get('res.request')
+        if not isinstance(ids,list): ids = [ids]
         for task in self.browse(cr, uid, ids, context=context):
             vals = {}
             project = task.project_id
@@ -678,6 +911,7 @@ class task(osv.osv):
         return True
 
     def do_open(self, cr, uid, ids, context={}):
+        if not isinstance(ids,list): ids = [ids]
         tasks= self.browse(cr, uid, ids, context=context)
         for t in tasks:
             data = {'state': 'open'}
@@ -692,37 +926,39 @@ class task(osv.osv):
         self.write(cr, uid, ids, {'state': 'draft'}, context=context)
         return True
 
-    def do_delegate(self, cr, uid, task_id, delegate_data={}, context=None):
+    def do_delegate(self, cr, uid, ids, delegate_data={}, context=None):
         """
         Delegate Task to another users.
         """
-        task = self.browse(cr, uid, task_id, context=context)
-        self.copy(cr, uid, task.id, {
-            'name': delegate_data['name'],
-            'user_id': delegate_data['user_id'],
-            'planned_hours': delegate_data['planned_hours'],
-            'remaining_hours': delegate_data['planned_hours'],
-            'parent_ids': [(6, 0, [task.id])],
-            'state': 'draft',
-            'description': delegate_data['new_task_description'] or '',
-            'child_ids': [],
-            'work_ids': []
-        }, context=context)
-        newname = delegate_data['prefix'] or ''
-        self.write(cr, uid, [task.id], {
-            'remaining_hours': delegate_data['planned_hours_me'],
-            'planned_hours': delegate_data['planned_hours_me'] + (task.effective_hours or 0.0),
-            'name': newname,
-        }, context=context)
-        if delegate_data['state'] == 'pending':
-            self.do_pending(cr, uid, [task.id], context)
-        else:
-            self.do_close(cr, uid, [task.id], context=context)
-        user_pool = self.pool.get('res.users')
-        delegate_user = user_pool.browse(cr, uid, delegate_data['user_id'], context=context)
-        message = _("The task '%s' has been delegated to %s.") % (delegate_data['name'], delegate_user.name)
-        self.log(cr, uid, task.id, message)
-        return True
+        assert delegate_data['user_id'], _("Delegated User should be specified")
+        delegrated_tasks = {}
+        for task in self.browse(cr, uid, ids, context=context):
+            delegrated_task_id = self.copy(cr, uid, task.id, {
+                'name': delegate_data['name'],
+                'project_id': delegate_data['project_id'] and delegate_data['project_id'][0] or False,
+                'user_id': delegate_data['user_id'] and delegate_data['user_id'][0] or False,
+                'planned_hours': delegate_data['planned_hours'] or 0.0,
+                'parent_ids': [(6, 0, [task.id])],
+                'state': 'draft',
+                'description': delegate_data['new_task_description'] or '',
+                'child_ids': [],
+                'work_ids': []
+            }, context=context)
+            newname = delegate_data['prefix'] or ''
+            task.write({
+                'remaining_hours': delegate_data['planned_hours_me'],
+                'planned_hours': delegate_data['planned_hours_me'] + (task.effective_hours or 0.0),
+                'name': newname,
+            }, context=context)
+            if delegate_data['state'] == 'pending':
+                self.do_pending(cr, uid, task.id, context=context)
+            elif delegate_data['state'] == 'done':
+                self.do_close(cr, uid, task.id, context=context)
+            
+            message = _("The task '%s' has been delegated to %s.") % (delegate_data['name'], delegate_data['user_id'][1])
+            self.log(cr, uid, task.id, message)
+            delegrated_tasks[task.id] = delegrated_task_id
+        return delegrated_tasks
 
     def do_pending(self, cr, uid, ids, context={}):
         self.write(cr, uid, ids, {'state': 'pending'}, context=context)
@@ -731,6 +967,31 @@ class task(osv.osv):
             self.log(cr, uid, id, message)
         return True
 
+    def set_remaining_time_1(self, cr, uid, ids, context=None):
+        self.write(cr, uid, ids, {'remaining_hours': 1.0}, context=context)
+        return True
+
+    def set_remaining_time_2(self, cr, uid, ids, context=None):
+        self.write(cr, uid, ids, {'remaining_hours': 2.0}, context=context)
+        return True
+
+    def set_remaining_time_5(self, cr, uid, ids, context=None):
+        self.write(cr, uid, ids, {'remaining_hours': 5.0}, context=context)
+        return True
+
+    def set_remaining_time_10(self, cr, uid, ids, context=None):
+        self.write(cr, uid, ids, {'remaining_hours': 10.0}, context=context)
+        return True
+
+    def set_kanban_state_blocked(self, cr, uid, ids, context=None):
+        self.write(cr, uid, ids, {'kanban_state': 'blocked'}, context=context)
+
+    def set_kanban_state_normal(self, cr, uid, ids, context=None):
+        self.write(cr, uid, ids, {'kanban_state': 'normal'}, context=context)
+
+    def set_kanban_state_done(self, cr, uid, ids, context=None):
+        self.write(cr, uid, ids, {'kanban_state': 'done'}, context=context)
+
     def _change_type(self, cr, uid, ids, next, *args):
         """
             go to the next stage
@@ -760,6 +1021,20 @@ class task(osv.osv):
     def prev_type(self, cr, uid, ids, *args):
         return self._change_type(cr, uid, ids, False, *args)
 
+    # 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):
+        if isinstance(ids, (int, long)):
+            ids = [ids]
+        if vals and not 'kanban_state' in vals and 'type_id' in vals:
+            new_stage = vals.get('type_id')
+            vals_reset_kstate = dict(vals, kanban_state='normal')
+            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)
+
     def unlink(self, cr, uid, ids, context=None):
         if context == None:
             context = {}
@@ -767,6 +1042,33 @@ class task(osv.osv):
         res = super(task, self).unlink(cr, uid, ids, context)
         return res
 
+    def _generate_task(self, cr, uid, tasks, ident=4, context=None):
+        context = context or {}
+        result = ""
+        ident = ' '*ident
+        for task in tasks:
+            if task.state in ('done','cancelled'):
+                continue
+            result += '''
+%sdef Task_%s():
+%s  todo = \"%.2fH\"
+%s  effort = \"%.2fH\"''' % (ident,task.id, ident,task.remaining_hours, ident,task.total_hours)
+            start = []
+            for t2 in task.parent_ids:
+                start.append("up.Task_%s.end" % (t2.id,))
+            if start:
+                result += '''
+%s  start = max(%s)
+''' % (ident,','.join(start))
+
+            if task.user_id:
+                result += '''
+%s  resource = %s
+''' % (ident, 'User_'+str(task.user_id.id))
+
+        result += "\n"
+        return result
+
 task()
 
 class project_work(osv.osv):