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