[FIX] project_long_term: fix
[odoo/odoo.git] / addons / project_long_term / project_long_term.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from datetime import datetime, timedelta
23 from dateutil.relativedelta import relativedelta
24 from tools.translate import _
25 from osv import fields, osv
26 from resource.faces import task as Task
27 import operator
28
29 class project_phase(osv.osv):
30     _name = "project.phase"
31     _description = "Project Phase"
32
33     def _check_recursion(self, cr, uid, ids, context=None):
34          if context is None:
35             context = {}
36
37          data_phase = self.browse(cr, uid, ids[0], context=context)
38          prev_ids = data_phase.previous_phase_ids
39          next_ids = data_phase.next_phase_ids
40          # it should neither be in prev_ids nor in next_ids
41          if (data_phase in prev_ids) or (data_phase in next_ids):
42              return False
43          ids = [id for id in prev_ids if id in next_ids]
44          # both prev_ids and next_ids must be unique
45          if ids:
46              return False
47          # unrelated project
48          prev_ids = [rec.id for rec in prev_ids]
49          next_ids = [rec.id for rec in next_ids]
50          # iter prev_ids
51          while prev_ids:
52              cr.execute('SELECT distinct prv_phase_id FROM project_phase_rel WHERE next_phase_id IN %s', (tuple(prev_ids),))
53              prv_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
54              if data_phase.id in prv_phase_ids:
55                  return False
56              ids = [id for id in prv_phase_ids if id in next_ids]
57              if ids:
58                  return False
59              prev_ids = prv_phase_ids
60          # iter next_ids
61          while next_ids:
62              cr.execute('SELECT distinct next_phase_id FROM project_phase_rel WHERE prv_phase_id IN %s', (tuple(next_ids),))
63              next_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
64              if data_phase.id in next_phase_ids:
65                  return False
66              ids = [id for id in next_phase_ids if id in prev_ids]
67              if ids:
68                  return False
69              next_ids = next_phase_ids
70          return True
71
72     def _check_dates(self, cr, uid, ids, context=None):
73          for phase in self.read(cr, uid, ids, ['date_start', 'date_end'], context=context):
74              if phase['date_start'] and phase['date_end'] and phase['date_start'] > phase['date_end']:
75                  return False
76          return True
77
78     def _check_constraint_start(self, cr, uid, ids, context=None):
79          phase = self.read(cr, uid, ids[0], ['date_start', 'constraint_date_start'], context=context)
80          if phase['date_start'] and phase['constraint_date_start'] and phase['date_start'] < phase['constraint_date_start']:
81              return False
82          return True
83
84     def _check_constraint_end(self, cr, uid, ids, context=None):
85          phase = self.read(cr, uid, ids[0], ['date_end', 'constraint_date_end'], context=context)
86          if phase['date_end'] and phase['constraint_date_end'] and phase['date_end'] > phase['constraint_date_end']:
87              return False
88          return True
89
90     def _get_default_uom_id(self, cr, uid):
91        model_data_obj = self.pool.get('ir.model.data')
92        model_data_id = model_data_obj._get_id(cr, uid, 'product', 'uom_hour')
93        return model_data_obj.read(cr, uid, [model_data_id], ['res_id'])[0]['res_id']
94
95     def _compute(self, cr, uid, ids, field_name, arg, context=None):
96         res = {}
97         if not ids:
98             return res
99         for phase in self.browse(cr, uid, ids, context=context):
100             tot = 0.0
101             for task in phase.task_ids:
102                 tot += task.planned_hours
103             res[phase.id] = tot
104         return res
105
106     _columns = {
107         'name': fields.char("Name", size=64, required=True),
108         'date_start': fields.date('Start Date', help="It's computed according to the phases order : the start date of the 1st phase is set by you while the other start dates depend on the end date of their previous phases", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
109         'date_end': fields.date('End Date', help=" It's computed by the scheduler according to the start date and the duration.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
110         'constraint_date_start': fields.date('Minimum Start Date', help='force the phase to start after this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
111         'constraint_date_end': fields.date('Deadline', help='force the phase to finish before this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
112         'project_id': fields.many2one('project.project', 'Project', required=True),
113         'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases', states={'cancelled':[('readonly',True)]}),
114         'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases', states={'cancelled':[('readonly',True)]}),
115         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of phases."),
116         'duration': fields.float('Duration', required=True, help="By default in days", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
117         'product_uom': fields.many2one('product.uom', 'Duration UoM', required=True, help="UoM (Unit of Measure) is the unit of measurement for Duration", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
118         'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
119         'resource_ids': fields.one2many('project.resource.allocation', 'phase_id', "Project Resources",states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
120         'responsible_id': fields.many2one('res.users', 'Responsible', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
121         'state': fields.selection([('draft', 'Draft'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True,
122                                   help='If the phase is created the state \'Draft\'.\n If the phase is started, the state becomes \'In Progress\'.\n If review is needed the phase is in \'Pending\' state.\
123                                   \n If the phase is over, the states is set to \'Done\'.'),
124         'total_hours': fields.function(_compute, method=True, string='Total Hours'),
125      }
126     _defaults = {
127         'responsible_id': lambda obj,cr,uid,context: uid,
128         'state': 'draft',
129         'sequence': 10,
130         'product_uom': lambda self,cr,uid,c: self.pool.get('product.uom').search(cr, uid, [('name', '=', _('Day'))], context=c)[0]
131     }
132     _order = "project_id, date_start, sequence, name"
133     _constraints = [
134         (_check_recursion,'Loops in phases not allowed',['next_phase_ids', 'previous_phase_ids']),
135         (_check_dates, 'Phase start-date must be lower than phase end-date.', ['date_start', 'date_end']),
136     ]
137
138     def onchange_project(self, cr, uid, ids, project, context=None):
139         result = {}
140         result['date_start'] = False
141         project_obj = self.pool.get('project.project')
142         if project:
143             project_id = project_obj.browse(cr, uid, project, context=context)
144             result['date_start'] = project_id.date_start
145         return {'value': result}
146
147
148     def _check_date_start(self, cr, uid, phase, date_end, context=None):
149        """
150        Check And Compute date_end of phase if change in date_start < older time.
151        """
152        uom_obj = self.pool.get('product.uom')
153        resource_obj = self.pool.get('resource.resource')
154        cal_obj = self.pool.get('resource.calendar')
155        calendar_id = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False
156        resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)])
157        if resource_id:
158             res = resource_obj.read(cr, uid, resource_id, ['calendar_id'], context=context)[0]
159             cal_id = res.get('calendar_id', False) and res.get('calendar_id')[0] or False
160             if cal_id:
161                 calendar_id = cal_id
162        default_uom_id = self._get_default_uom_id(cr, uid)
163        avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
164        work_times = cal_obj.interval_min_get(cr, uid, calendar_id, date_end, avg_hours or 0.0, resource_id and resource_id[0] or False)
165        dt_start = work_times[0][0].strftime('%Y-%m-%d')
166        self.write(cr, uid, [phase.id], {'date_start': dt_start, 'date_end': date_end.strftime('%Y-%m-%d')}, context=context)
167
168     def _check_date_end(self, cr, uid, phase, date_start, context=None):
169        """
170        Check And Compute date_end of phase if change in date_end > older time.
171        """
172        uom_obj = self.pool.get('product.uom')
173        resource_obj = self.pool.get('resource.resource')
174        cal_obj = self.pool.get('resource.calendar')
175        calendar_id = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False
176        resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)], context=context)
177        if resource_id:
178             res = resource_obj.read(cr, uid, resource_id, ['calendar_id'], context=context)[0]
179             cal_id = res.get('calendar_id', False) and res.get('calendar_id')[0] or False
180             if cal_id:
181                 calendar_id = cal_id
182        default_uom_id = self._get_default_uom_id(cr, uid)
183        avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
184        work_times = cal_obj.interval_get(cr, uid, calendar_id, date_start, avg_hours or 0.0, resource_id and resource_id[0] or False)
185        dt_end = work_times[-1][1].strftime('%Y-%m-%d')
186        self.write(cr, uid, [phase.id], {'date_start': date_start.strftime('%Y-%m-%d'), 'date_end': dt_end}, context=context)
187
188     def copy(self, cr, uid, id, default=None, context=None):
189         if default is None:
190             default = {}
191         if not default.get('name', False):
192             default['name'] = self.browse(cr, uid, id, context=context).name + _(' (copy)')
193         return super(project_phase, self).copy(cr, uid, id, default, context)
194
195     def set_draft(self, cr, uid, ids, *args):
196         self.write(cr, uid, ids, {'state': 'draft'})
197         return True
198
199     def set_open(self, cr, uid, ids, *args):
200         self.write(cr, uid, ids, {'state': 'open'})
201         return True
202
203     def set_pending(self, cr, uid, ids, *args):
204         self.write(cr, uid, ids, {'state': 'pending'})
205         return True
206
207     def set_cancel(self, cr, uid, ids, *args):
208         self.write(cr, uid, ids, {'state': 'cancelled'})
209         return True
210
211     def set_done(self, cr, uid, ids, *args):
212         self.write(cr, uid, ids, {'state': 'done'})
213         return True
214
215     def generate_resources(self, cr, uid, ids, context=None):
216         """
217         Return a list of  Resource Class objects for the resources allocated to the phase.
218         """
219         res = {}
220         resource_pool = self.pool.get('resource.resource')
221         for phase in self.browse(cr, uid, ids, context=context):
222             user_ids = map(lambda x:x.resource_id.user_id.id, phase.resource_ids)
223             project = phase.project_id
224             calendar_id  = project.resource_calendar_id and project.resource_calendar_id.id or False
225             resource_objs = resource_pool.generate_resources(cr, uid, user_ids, calendar_id, context=context)
226             res[phase.id] = resource_objs
227         return res
228
229     def generate_schedule(self, cr, uid, ids, start_date=False, calendar_id=False, context=None):
230         """
231         Schedule phase with the start date till all the next phases are completed.
232         @param: start_date (datetime.datetime) : start date for the phase. It would be either Start date of phase or start date of project or system current date
233         @param: calendar_id : working calendar of the project
234         """
235         if context is None:
236             context = {}
237         resource_pool = self.pool.get('resource.resource')
238         data_pool = self.pool.get('ir.model.data')
239         resource_allocation_pool = self.pool.get('project.resource.allocation')
240         uom_pool = self.pool.get('product.uom')
241         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
242         for phase in self.browse(cr, uid, ids, context=context):
243             if not phase.responsible_id:
244                 raise osv.except_osv(_('No responsible person assigned !'),_("You must assign a responsible person for phase '%s' !") % (phase.name,))
245
246             if not start_date:
247                 start_date = phase.project_id.date_start or phase.date_start or datetime.now().strftime("%Y-%m-%d")
248                 start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d") 
249             phase_resource_obj = self.generate_resources(cr, uid, [phase.id], context=context)[phase.id]
250             avg_days = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, day_uom_id)
251             if not phase_resource_obj: #TOCHECK: why need this ?
252                 avg_days = avg_days - 1
253             duration = str(avg_days) + 'd'
254             # Create a new project for each phase
255             def Project():
256                 # If project has working calendar then that
257                 # else the default one would be considered
258                 start = start_date
259                 minimum_time_unit = 1
260                 resource = phase_resource_obj
261                 working_hours_per_day = 24
262                 vacation = []
263                 if calendar_id:
264                     working_hours_per_day = 8 #TODO: it should be come from calendars
265                     vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id))
266                 working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
267                 def phase():
268                     effort = duration
269
270             project = Task.BalancedProject(Project)
271
272             s_date = project.phase.start.to_datetime()
273             e_date = project.phase.end.to_datetime()
274             # Recalculate date_start and date_end
275             # according to constraints on date start and date end on phase
276             if phase.constraint_date_start and str(s_date) < phase.constraint_date_start:
277                 start_date = datetime.strptime(phase.constraint_date_start, '%Y-%m-%d')
278             else:
279                 start_date = s_date
280             if phase.constraint_date_end and str(e_date) > phase.constraint_date_end:
281                 end_date= datetime.strptime(phase.constraint_date_end, '%Y-%m-%d')
282                 date_start = phase.constraint_date_end
283             else:
284                 end_date = e_date
285                 date_start = end_date
286             # Write the calculated dates back
287             ctx = context.copy()
288             ctx.update({'scheduler': True})
289             self.write(cr, uid, [phase.id], {
290                                           'date_start': start_date.strftime('%Y-%m-%d'),
291                                           'date_end': end_date.strftime('%Y-%m-%d')
292                                         }, context=ctx)
293             # write dates into Resources Allocation
294             for resource in phase.resource_ids:
295                 resource_allocation_pool.write(cr, uid, [resource.id], {
296                                         'date_start': start_date.strftime('%Y-%m-%d'),
297                                         'date_end': end_date.strftime('%Y-%m-%d')
298                                     }, context=ctx)
299             # Recursive call till all the next phases scheduled
300             for next_phase in phase.next_phase_ids:
301                if next_phase.state in ['draft', 'open', 'pending']:
302                    id_cal = next_phase.project_id.resource_calendar_id and next_phase.project_id.resource_calendar_id.id or False
303                    self.generate_schedule(cr, uid, [next_phase.id], date_start+timedelta(days=1), id_cal, context=context)
304                else:
305                    continue
306         return True
307
308     def schedule_tasks(self, cr, uid, ids, context=None):
309         """
310         Schedule the tasks according to resource available and priority.
311         """
312         task_pool = self.pool.get('project.task')
313         resource_pool = self.pool.get('resource.resource')
314         resources_list = self.generate_resources(cr, uid, ids, context=context)
315         return_msg = {}
316         for phase in self.browse(cr, uid, ids, context=context):
317             start_date = phase.date_start
318             if not start_date and phase.project_id.date_start:
319                 start_date = phase.project_id.date_start
320             if not start_date:
321                 start_date = datetime.now().strftime("%Y-%m-%d")
322             resources = resources_list.get(phase.id, [])
323             calendar_id = phase.project_id.resource_calendar_id.id
324             task_ids = map(lambda x : x.id, (filter(lambda x : x.state in ['draft'] , phase.task_ids))) #reassign only task not yet started
325             if task_ids:
326                 task_pool.generate_schedule(cr, uid, task_ids, resources, calendar_id, start_date, context=context)
327
328             if not task_ids:
329                 warning_msg = _("No tasks to compute for Phase '%s'.") % (phase.name)
330                 if "warning" not in return_msg:
331                     return_msg["warning"] =  warning_msg
332                 else:
333                     return_msg["warning"] = return_msg["warning"] + "\n" + warning_msg
334         return return_msg
335 project_phase()
336
337 class project_resource_allocation(osv.osv):
338     _name = 'project.resource.allocation'
339     _description = 'Project Resource Allocation'
340     _rec_name = 'resource_id'
341
342     def get_name(self, cr, uid, ids, field_name, arg, context=None):
343         res = {}
344         for allocation in self.browse(cr, uid, ids, context=context):
345             name = allocation.phase_id.name
346             name += ' (%s%%)' %(allocation.useability)
347             res[allocation.id] = name
348         return res
349     _columns = {
350         'name': fields.function(get_name, method=True, type='char', size=256),
351         'resource_id': fields.many2one('resource.resource', 'Resource', required=True),
352         'phase_id': fields.many2one('project.phase', 'Project Phase', ondelete='cascade', required=True),
353         'project_id': fields.related('phase_id', 'project_id', type='many2one', relation="project.project", string='Project', store=True),
354         'user_id': fields.related('resource_id', 'user_id', type='many2one', relation="res.users", string='User'),
355         'date_start': fields.date('Start Date', help="Starting Date"),
356         'date_end': fields.date('End Date', help="Ending Date"),
357         'useability': fields.float('Availability', help="Availability of this resource for this project phase in percentage (=50%)"),
358     }
359     _defaults = {
360         'useability': 100,
361     }
362
363 project_resource_allocation()
364
365 class project(osv.osv):
366     _inherit = "project.project"
367     _columns = {
368         'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"),
369         'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ),
370     }
371     def generate_members(self, cr, uid, ids, context=None):
372         """
373         Return a list of  Resource Class objects for the resources allocated to the phase.
374         """
375         res = {}
376         resource_pool = self.pool.get('resource.resource')
377         for project in self.browse(cr, uid, ids, context=context):
378             user_ids = map(lambda x:x.id, project.members)
379             calendar_id  = project.resource_calendar_id and project.resource_calendar_id.id or False
380             resource_objs = resource_pool.generate_resources(cr, uid, user_ids, calendar_id, context=context)
381             res[project.id] = resource_objs
382         return res
383
384     def schedule_phases(self, cr, uid, ids, context=None):
385         """
386         Schedule the phases.
387         """
388         if type(ids) in (long, int,):
389             ids = [ids]
390         phase_pool = self.pool.get('project.phase')
391         for project in self.browse(cr, uid, ids, context=context):
392             phase_ids = phase_pool.search(cr, uid, [('project_id', '=', project.id),
393                                                   ('state', 'in', ['draft', 'open', 'pending']),
394                                                   ('previous_phase_ids', '=', False)
395                                                   ])
396             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
397             start_date = False
398             phase_pool.generate_schedule(cr, uid, phase_ids, start_date, calendar_id, context=context)
399         return True
400
401     def schedule_tasks(self, cr, uid, ids, context=None):
402         """
403         Schedule the tasks according to resource available and priority.
404         """
405         if type(ids) in (long, int,):
406             ids = [ids]
407         user_pool = self.pool.get('res.users')
408         task_pool = self.pool.get('project.task')
409         resource_pool = self.pool.get('resource.resource')
410         resources_list = self.generate_members(cr, uid, ids, context=context)
411         return_msg = {}
412         for project in self.browse(cr, uid, ids, context=context):
413             start_date = project.date_start
414             if not start_date:
415                 start_date = datetime.now().strftime("%Y-%m-%d")
416             resources = resources_list.get(project.id, [])
417             calendar_id = project.resource_calendar_id.id
418             task_ids = task_pool.search(cr, uid, [('project_id', '=', project.id),
419                                               ('state', 'in', ['draft', 'open', 'pending'])
420                                               ])
421
422
423             if task_ids:
424                 task_pool.generate_schedule(cr, uid, task_ids, resources, calendar_id, start_date, context=context)
425             else:
426                 warning_msg = _("No tasks to compute for Project '%s'.") % (project.name)
427                 if "warning" not in return_msg:
428                     return_msg["warning"] =  warning_msg
429                 else:
430                     return_msg["warning"] = return_msg["warning"] + "\n" + warning_msg
431
432         return return_msg
433
434 project()
435
436 class resource_resource(osv.osv):
437     _inherit = "resource.resource"
438     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
439         if context is None:
440             context = {}
441         if context.get('project_id',False):
442             project_pool = self.pool.get('project.project')
443             project_rec = project_pool.browse(cr, uid, context['project_id'], context=context)
444             user_ids = [user_id.id for user_id in project_rec.members]
445             args.append(('user_id','in',user_ids))
446         return super(resource_resource, self).search(cr, uid, args, offset, limit, order, context, count)
447
448 resource_resource()
449
450 class project_task(osv.osv):
451     _inherit = "project.task"
452     _columns = {
453         'phase_id': fields.many2one('project.phase', 'Project Phase'),
454     }
455
456     def generate_schedule(self, cr, uid, ids, resources, calendar_id, start_date, context=None):
457         """
458         Schedule the tasks according to resource available and priority.
459         """
460         resource_pool = self.pool.get('resource.resource')
461         if not ids:
462             return False
463         if context is None:
464             context = {}
465         user_pool = self.pool.get('res.users')
466         project_pool = self.pool.get('project.project')
467         priority_dict = {'0': 1000, '1': 800, '2': 500, '3': 300, '4': 100}
468         # Create dynamic no of tasks with the resource specified
469         def create_tasks(task_number, eff, priorty=500, obj=False):
470             def task():
471                 """
472                 task is a dynamic method!
473                 """
474                 effort = eff
475                 if obj:
476                     resource = obj
477                 priority = priorty
478             task.__doc__ = "TaskNO%d" %task_number
479             task.__name__ = "task%d" %task_number
480             return task
481
482         # Create a 'Faces' project with all the tasks and resources
483         def Project():
484             title = "Project"
485             start = datetime.strftime(datetime.strptime(start_date, "%Y-%m-%d"), "%Y-%m-%d %H:%M")
486             try:
487                 resource = reduce(operator.or_, resources)
488             except:
489                 raise osv.except_osv(_('Error'), _('Resources should be allocated to your phases and Members should be assigned to your Project!'))
490             minimum_time_unit = 1
491             working_hours_per_day = 24
492             vacation = []
493             if calendar_id:
494                 working_hours_per_day = 8 #TODO: it should be come from calendars
495                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
496             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
497             # Dynamic creation of tasks
498             task_number = 0
499             for openobect_task in self.browse(cr, uid, ids, context=context):
500                 hours = str(openobect_task.planned_hours )+ 'H'
501                 if openobect_task.priority in priority_dict.keys():
502                     priorty = priority_dict[openobect_task.priority]
503                 real_resource = False
504                 if openobect_task.user_id:
505                     for task_resource in resources:
506                         if task_resource.__name__ == task_resource:
507                             real_resource = task_resource
508                             break
509
510                 task = create_tasks(task_number, hours, priorty, real_resource)
511                 task_number += 1
512
513
514         face_projects = Task.BalancedProject(Project)
515         loop_no = 0
516         # Write back the computed dates
517         for face_project in face_projects:
518             s_date = face_project.start.to_datetime()
519             e_date = face_project.end.to_datetime()
520             if loop_no > 0:
521                 ctx = context.copy()
522                 ctx.update({'scheduler': True})
523                 user_id = user_pool.search(cr, uid, [('name', '=', face_project.booked_resource[0].__name__)])
524                 self.write(cr, uid, [ids[loop_no-1]], {
525                                                     'date_start': s_date.strftime('%Y-%m-%d %H:%M:%S'),
526                                                     'date_end': e_date.strftime('%Y-%m-%d %H:%M:%S'),
527                                                     'user_id': user_id[0]
528                                                 }, context=ctx)
529
530             loop_no += 1
531         return True
532 project_task()
533 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: