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