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