[MERGE]: Merge with lp:~openerp-dev/openobject-addons/trunk-dev-addons1
[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
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 = resource_pool.generate_resources(cr, uid, [phase.responsible_id.id], calendar_id, context=context)
250             avg_days = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, day_uom_id)
251             duration = str(avg_days) + 'd'
252             # Create a new project for each phase
253             def Project():
254                 # If project has working calendar then that
255                 # else the default one would be considered
256                 start = start_date
257                 minimum_time_unit = 1
258                 resource = phase_resource_obj
259                 working_hours_per_day = 24
260                 vacation = []
261                 if calendar_id:
262                     working_hours_per_day = 8 #TODO: it should be come from calendars
263                     vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id))
264                 working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
265                 def phase():
266                     effort = duration
267
268             project = Task.BalancedProject(Project)
269
270             s_date = project.phase.start.to_datetime()
271             e_date = project.phase.end.to_datetime()
272             # Recalculate date_start and date_end
273             # according to constraints on date start and date end on phase
274             if phase.constraint_date_start and str(s_date) < phase.constraint_date_start:
275                 start_date = datetime.strptime(phase.constraint_date_start, '%Y-%m-%d')
276             else:
277                 start_date = s_date
278             if phase.constraint_date_end and str(e_date) > phase.constraint_date_end:
279                 end_date= datetime.strptime(phase.constraint_date_end, '%Y-%m-%d')
280                 date_start = phase.constraint_date_end
281             else:
282                 end_date = e_date
283                 date_start = end_date
284             # Write the calculated dates back
285             ctx = context.copy()
286             ctx.update({'scheduler': True})
287             self.write(cr, uid, [phase.id], {
288                                           'date_start': start_date.strftime('%Y-%m-%d'),
289                                           'date_end': end_date.strftime('%Y-%m-%d')
290                                         }, context=ctx)
291             # write dates into Resources Allocation
292             for resource in phase.resource_ids:
293                 resource_allocation_pool.write(cr, uid, [resource.id], {
294                                         'date_start': start_date.strftime('%Y-%m-%d'),
295                                         'date_end': end_date.strftime('%Y-%m-%d')
296                                     }, context=ctx)
297             # Recursive call till all the next phases scheduled
298             for phase in phase.next_phase_ids:
299                if phase.state in ['draft', 'open', 'pending']:
300                    id_cal = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False
301                    self.generate_schedule(cr, uid, [phase.id], date_start, id_cal, context=context)
302                else:
303                    continue
304         return True
305
306     def schedule_tasks(self, cr, uid, ids, context=None):
307         """
308         Schedule the tasks according to resource available and priority.
309         """
310         task_pool = self.pool.get('project.task')
311         resource_pool = self.pool.get('resource.resource')
312         resources_list = self.generate_resources(cr, uid, ids, context=context)
313         return_msg = {}
314         for phase in self.browse(cr, uid, ids, context=context):
315             start_date = phase.date_start
316             if not start_date and phase.project_id.date_start:
317                 start_date = phase.project_id.date_start
318             if not start_date:
319                 start_date = datetime.now().strftime("%Y-%m-%d")
320             resources = resources_list.get(phase.id, [])
321             calendar_id = phase.project_id.resource_calendar_id.id
322             task_ids = map(lambda x : x.id, (filter(lambda x : x.state in ['open', 'draft', 'pending'] , phase.task_ids)))
323             if task_ids:
324                 task_pool.generate_schedule(cr, uid, task_ids, resources, calendar_id, start_date, context=context)
325
326             if not task_ids:
327                 warning_msg = _("No tasks to compute for Phase '%s'.") % (phase.name)
328                 if "warning" not in return_msg:
329                     return_msg["warning"] =  warning_msg
330                 else:
331                     return_msg["warning"] = return_msg["warning"] + "\n" + warning_msg
332         return return_msg
333 project_phase()
334
335 class project_resource_allocation(osv.osv):
336     _name = 'project.resource.allocation'
337     _description = 'Project Resource Allocation'
338     _rec_name = 'resource_id'
339
340     def get_name(self, cr, uid, ids, field_name, arg, context=None):
341         res = {}
342         for allocation in self.browse(cr, uid, ids, context=context):
343             name = allocation.phase_id.name
344             name += ' (%s%%)' %(allocation.useability)
345             res[allocation.id] = name
346         return res
347     _columns = {
348         'name': fields.function(get_name, method=True, type='char', size=256),
349         'resource_id': fields.many2one('resource.resource', 'Resource', required=True),
350         'phase_id': fields.many2one('project.phase', 'Project Phase', ondelete='cascade', required=True),
351         'project_id': fields.related('phase_id', 'project_id', type='many2one', relation="project.project", string='Project', store=True),
352         'user_id': fields.related('resource_id', 'user_id', type='many2one', relation="res.users", string='User'),
353         'date_start': fields.date('Start Date', help="Starting Date"),
354         'date_end': fields.date('End Date', help="Ending Date"),
355         'useability': fields.float('Availability', help="Availability of this resource for this project phase in percentage (=50%)"),
356     }
357     _defaults = {
358         'useability': 100,
359     }
360
361 project_resource_allocation()
362
363 class project(osv.osv):
364     _inherit = "project.project"
365     _columns = {
366         'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"),
367         'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ),
368     }
369     def generate_members(self, cr, uid, ids, context=None):
370         """
371         Return a list of  Resource Class objects for the resources allocated to the phase.
372         """
373         res = {}
374         resource_pool = self.pool.get('resource.resource')
375         for project in self.browse(cr, uid, ids, context=context):
376             user_ids = map(lambda x:x.id, project.members)
377             calendar_id  = project.resource_calendar_id and project.resource_calendar_id.id or False
378             resource_objs = resource_pool.generate_resources(cr, uid, user_ids, calendar_id, context=context)
379             res[project.id] = resource_objs
380         return res
381
382     def schedule_phases(self, cr, uid, ids, context=None):
383         """
384         Schedule the phases.
385         """
386         if type(ids) in (long, int,):
387             ids = [ids]
388         phase_pool = self.pool.get('project.phase')
389         for project in self.browse(cr, uid, ids, context=context):
390             phase_ids = phase_pool.search(cr, uid, [('project_id', '=', project.id),
391                                                   ('state', 'in', ['draft', 'open', 'pending']),
392                                                   ('previous_phase_ids', '=', False)
393                                                   ])
394             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
395             start_date = False
396             phase_pool.generate_schedule(cr, uid, phase_ids, start_date, calendar_id, context=context)
397         return True
398
399     def schedule_tasks(self, cr, uid, ids, context=None):
400         """
401         Schedule the tasks according to resource available and priority.
402         """
403         if type(ids) in (long, int,):
404             ids = [ids]
405         user_pool = self.pool.get('res.users')
406         task_pool = self.pool.get('project.task')
407         resource_pool = self.pool.get('resource.resource')
408         resources_list = self.generate_members(cr, uid, ids, context=context)
409         return_msg = {}
410         for project in self.browse(cr, uid, ids, context=context):
411             start_date = project.date_start
412             if not start_date:
413                 start_date = datetime.now().strftime("%Y-%m-%d")
414             resources = resources_list.get(project.id, [])
415             calendar_id = project.resource_calendar_id.id
416             task_ids = task_pool.search(cr, uid, [('project_id', '=', project.id),
417                                               ('state', 'in', ['draft', 'open', 'pending'])
418                                               ])
419
420
421             if task_ids:
422                 task_pool.generate_schedule(cr, uid, task_ids, resources, calendar_id, start_date, context=context)
423             else:
424                 warning_msg = _("No tasks to compute for Project '%s'.") % (project.name)
425                 if "warning" not in return_msg:
426                     return_msg["warning"] =  warning_msg
427                 else:
428                     return_msg["warning"] = return_msg["warning"] + "\n" + warning_msg
429
430         return return_msg
431
432 project()
433
434 class resource_resource(osv.osv):
435     _inherit = "resource.resource"
436     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
437         if context is None:
438             context = {}
439         if context.get('project_id',False):
440             project_pool = self.pool.get('project.project')
441             project_rec = project_pool.browse(cr, uid, context['project_id'], context=context)
442             user_ids = [user_id.id for user_id in project_rec.members]
443             args.append(('user_id','in',user_ids))
444         return super(resource_resource, self).search(cr, uid, args, offset, limit, order, context, count)
445
446 resource_resource()
447
448 class project_task(osv.osv):
449     _inherit = "project.task"
450     _columns = {
451         'phase_id': fields.many2one('project.phase', 'Project Phase'),
452     }
453
454     def generate_schedule(self, cr, uid, ids, resources, calendar_id, start_date, context=None):
455         """
456         Schedule the tasks according to resource available and priority.
457         """
458         resource_pool = self.pool.get('resource.resource')
459         if not ids:
460             return False
461         if context is None:
462             context = {}
463         user_pool = self.pool.get('res.users')
464         project_pool = self.pool.get('project.project')
465         priority_dict = {'0': 1000, '1': 800, '2': 500, '3': 300, '4': 100}
466         # Create dynamic no of tasks with the resource specified
467         def create_tasks(task_number, eff, priorty=500, obj=False):
468             def task():
469                 """
470                 task is a dynamic method!
471                 """
472                 effort = eff
473                 if obj:
474                     resource = obj
475                 priority = priorty
476             task.__doc__ = "TaskNO%d" %task_number
477             task.__name__ = "task%d" %task_number
478             return task
479
480         # Create a 'Faces' project with all the tasks and resources
481         def Project():
482             title = "Project"
483             start = datetime.strftime(datetime.strptime(start_date, "%Y-%m-%d"), "%Y-%m-%d %H:%M")
484             try:
485                 resource = reduce(operator.or_, resources)
486             except:
487                 raise osv.except_osv(_('Error'), _('Resources should be allocated to your phases and Members should be assigned to your Project!'))
488             minimum_time_unit = 1
489             working_hours_per_day = 24
490             vacation = []
491             if calendar_id:
492                 working_hours_per_day = 8 #TODO: it should be come from calendars
493                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
494             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
495             # Dynamic creation of tasks
496             task_number = 0
497             for openobect_task in self.browse(cr, uid, ids, context=context):
498                 hours = str(openobect_task.planned_hours )+ 'H'
499                 if openobect_task.priority in priority_dict.keys():
500                     priorty = priority_dict[openobect_task.priority]
501                 real_resource = False
502                 if openobect_task.user_id:
503                     for task_resource in resources:
504                         if task_resource.__name__ == task_resource:
505                             real_resource = task_resource
506                             break
507
508                 task = create_tasks(task_number, hours, priorty, real_resource)
509                 task_number += 1
510
511
512         face_projects = Task.BalancedProject(Project)
513         loop_no = 0
514         # Write back the computed dates
515         for face_project in face_projects:
516             s_date = face_project.start.to_datetime()
517             e_date = face_project.end.to_datetime()
518             if loop_no > 0:
519                 ctx = context.copy()
520                 ctx.update({'scheduler': True})
521                 user_id = user_pool.search(cr, uid, [('name', '=', face_project.booked_resource[0].__name__)])
522                 self.write(cr, uid, [ids[loop_no-1]], {
523                                                     'date_start': s_date.strftime('%Y-%m-%d %H:%M:%S'),
524                                                     'date_end': e_date.strftime('%Y-%m-%d %H:%M:%S'),
525                                                     'user_id': user_id[0]
526                                                 }, context=ctx)
527
528             loop_no += 1
529         return True
530 project_task()
531 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: