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