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