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