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