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