[IMP] Project_long_term: Refactoring Scheduling Process.
[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 by the scheduler according the project date or the end date of the previous phase.", 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_phase(self, cr, uid, ids, f, parent=False, context=None):
219         if context is None:
220             context = {}
221         phase_ids = []
222         resource_pool = self.pool.get('resource.resource')
223         data_pool = self.pool.get('ir.model.data')
224         resource_allocation_pool = self.pool.get('project.resource.allocation')
225         uom_pool = self.pool.get('product.uom')
226         task_pool = self.pool.get('project.task')
227         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
228         for phase in self.browse(cr, uid, ids, context=context):
229             avg_days = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, day_uom_id)
230             duration = str(avg_days) + 'd'
231             # Create a new project for each phase
232             str_resource = ('%s,'*len(phase.resource_ids))[:-1]
233             str_vals = str_resource % tuple(map(lambda x: 'Resource_%s'%x.resource_id.id, phase.resource_ids))
234             # Phases Defination for the Project
235             s = '''
236     def Phase_%s():
237         effort = \'%s\'
238         resource = %s
239 '''%(phase.id, duration, str_vals or False)
240             if parent:
241                 start = 'up.Phase_%s.end' % (parent.id)
242                 s += '''
243         start = %s
244 '''%(start)
245             else:
246                 start = phase.project_id.date_start or phase.date_start
247                 s += '''
248         start = \"%s\"
249 '''%(start)
250                 #start = datetime.strftime((datetime.strptime(start, "%Y-%m-%d")), "%Y-%m-%d")
251             
252             
253             phase_ids.append(phase.id)
254             parent = False
255             task_ids = []
256             todo_task_ids = task_pool.search(cr, uid, [('id', 'in', map(lambda x : x.id, phase.task_ids)),
257                                               ('state', 'in', ['draft', 'open', 'pending'])
258                                               ], order='sequence')
259             for task in task_pool.browse(cr, uid, todo_task_ids, context=context):
260                 s += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=False, context=context)
261                 if not parent:
262                     parent = task
263                 task_ids.append(task.id)
264             
265             f += s + '\n'
266             # Recursive call till all the next phases scheduled
267             for next_phase in phase.next_phase_ids:
268                 if next_phase.state in ['draft', 'open', 'pending']:
269                     rf, rphase_ids = self.generate_phase(cr, uid, [next_phase.id], f = '', parent=phase, context=context)
270                     f += rf +'\n'
271                     phase_ids += rphase_ids
272                 else:   
273                     continue
274         return f, phase_ids
275
276     def schedule_tasks(self, cr, uid, ids, context=None):
277         """
278         Schedule tasks base on faces lib
279         """
280         if context is None:
281             context = {}
282         if type(ids) in (long, int,):
283             ids = [ids]
284         task_pool = self.pool.get('project.task')
285         resource_pool = self.pool.get('resource.resource')
286         data_pool = self.pool.get('ir.model.data')
287         resource_allocation_pool = self.pool.get('project.resource.allocation')
288
289         for phase in self.browse(cr, uid, ids, context=context):
290             project = phase.project_id
291             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
292             start_date = project.date_start
293             #Creating resources using the member of the Project
294             u_ids = [i.id for i in project.members]
295             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
296             start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
297             func_str = ''
298             start = start_date
299             minimum_time_unit = 1
300             # default values
301             working_hours_per_day = 24
302             working_days_per_week = 7
303             working_days_per_month = 30
304             working_days_per_year = 365
305             
306             vacation = []
307             if calendar_id:
308                 working_hours_per_day = 8 #TODO: it should be come from calendars
309                 working_days_per_week = 5
310                 working_days_per_month = 20
311                 working_days_per_year = 200
312                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
313
314             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
315             
316             cls_str = ''
317             # Creating Resources for the Project
318             for key, vals in resource_objs.items():
319                 cls_str +='''
320     class Resource_%s(Resource):
321         vacation = %s
322         efficiency = %s
323 '''%(key,  vals.get('vacation', False), vals.get('efficiency', False))
324     
325             # Create a new project for each phase
326             func_str += '''
327 def Phase_%d():
328     from resource.faces import Resource
329     start = \'%s\'
330     minimum_time_unit = %s
331     working_hours_per_day = %s
332     working_days_per_week = %s
333     working_days_per_month = %s
334     working_days_per_year = %s
335     vacation = %s
336     working_days =  %s
337 '''%(phase.id, start, minimum_time_unit, working_hours_per_day,  working_days_per_week, working_days_per_month, working_days_per_year, vacation, working_days )
338             func_str += cls_str
339             parent = False
340             task_ids = []
341             todo_task_ids = task_pool.search(cr, uid, [('id', 'in', map(lambda x : x.id, phase.task_ids)),
342                                               ('state', 'in', ['draft', 'open', 'pending'])
343                                               ], order='sequence')
344             for task in task_pool.browse(cr, uid, todo_task_ids, context=context):
345                 func_str += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=True, context=context)
346                 if not parent:
347                     parent = task
348                 task_ids.append(task.id)
349
350             #Temp File to test the Code for the Allocation
351 #            fn = '/home/hmo/Desktop/plt.py'
352 #            fp = open(fn, 'w')
353 #            fp.writelines(func_str)
354 #            fp.close()
355     
356             # Allocating Memory for the required Project and Pahses and Resources
357             exec(func_str)
358             Phase = eval('Phase_%d' % phase.id)
359             phase = Task.BalancedProject(Phase)
360         
361             for task_id in task_ids:
362                 task = eval("phase.Task_%d" % task_id)
363                 start_date = task.start.to_datetime()
364                 end_date = task.end.to_datetime()
365                 
366                 task_pool.write(cr, uid, [task_id], {
367                                       'date_start': start_date.strftime('%Y-%m-%d'),
368                                       'date_end': end_date.strftime('%Y-%m-%d')
369                                     }, context=context)
370         return True
371 project_phase()
372
373 class project_resource_allocation(osv.osv):
374     _name = 'project.resource.allocation'
375     _description = 'Project Resource Allocation'
376     _rec_name = 'resource_id'
377
378     def get_name(self, cr, uid, ids, field_name, arg, context=None):
379         res = {}
380         for allocation in self.browse(cr, uid, ids, context=context):
381             name = allocation.phase_id.name
382             name += ' (%s%%)' %(allocation.useability)
383             res[allocation.id] = name
384         return res
385     _columns = {
386         'name': fields.function(get_name, method=True, type='char', size=256),
387         'resource_id': fields.many2one('resource.resource', 'Resource', required=True),
388         'phase_id': fields.many2one('project.phase', 'Project Phase', ondelete='cascade', required=True),
389         'project_id': fields.related('phase_id', 'project_id', type='many2one', relation="project.project", string='Project', store=True),
390         'user_id': fields.related('resource_id', 'user_id', type='many2one', relation="res.users", string='User'),
391         'date_start': fields.date('Start Date', help="Starting Date"),
392         'date_end': fields.date('End Date', help="Ending Date"),
393         'useability': fields.float('Availability', help="Availability of this resource for this project phase in percentage (=50%)"),
394     }
395     _defaults = {
396         'useability': 100,
397     }
398
399 project_resource_allocation()
400
401 class project(osv.osv):
402     _inherit = "project.project"
403     _columns = {
404         'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"),
405         'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ),
406     }
407     def generate_members(self, cr, uid, ids, context=None):
408         """
409         Return a list of  Resource Class objects for the resources allocated to the phase.
410         """
411         res = {}
412         resource_pool = self.pool.get('resource.resource')
413         for project in self.browse(cr, uid, ids, context=context):
414             user_ids = map(lambda x:x.id, project.members)
415             calendar_id  = project.resource_calendar_id and project.resource_calendar_id.id or False
416             resource_objs = resource_pool.generate_resources(cr, uid, user_ids, calendar_id, context=context)
417             res[project.id] = resource_objs
418         return res
419
420     def schedule_phases(self, cr, uid, ids, context=None):
421         """
422         Schedule phase base on faces lib
423         """
424         if context is None:
425             context = {}
426         if type(ids) in (long, int,):
427             ids = [ids]
428         phase_pool = self.pool.get('project.phase')
429         resource_pool = self.pool.get('resource.resource')
430         data_pool = self.pool.get('ir.model.data')
431         resource_allocation_pool = self.pool.get('project.resource.allocation')
432         uom_pool = self.pool.get('product.uom')
433         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
434
435         for project in self.browse(cr, uid, ids, context=context):
436             root_phase_ids = phase_pool.search(cr, uid, [('project_id', '=', project.id),
437                                                   ('state', 'in', ['draft', 'open', 'pending']),
438                                                   ('previous_phase_ids', '=', False)
439                                                   ])
440             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
441             start_date = project.date_start
442             #if start_date:
443             #    start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
444             #Creating resources using the member of the Project
445             u_ids = [i.id for i in project.members]
446             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
447             func_str = ''
448             start = start_date
449             minimum_time_unit = 1
450             # default values
451             working_hours_per_day = 24
452             working_days_per_week = 7
453             working_days_per_month = 30
454             working_days_per_year = 365
455             
456             vacation = []
457             if calendar_id:
458                 working_hours_per_day = 8 #TODO: it should be come from calendars
459                 working_days_per_week = 5
460                 working_days_per_month = 20
461                 working_days_per_year = 200
462                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
463
464             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
465             
466             cls_str = ''
467             # Creating Resources for the Project
468             for key, vals in resource_objs.items():
469                 cls_str +='''
470     class Resource_%s(Resource):
471         vacation = %s
472         efficiency = %s
473 '''%(key,  vals.get('vacation', False), vals.get('efficiency', False))
474         
475             # Create a new project for each phase
476             func_str += '''
477 def Project_%d():
478     from resource.faces import Resource
479     start = \'%s\'
480     minimum_time_unit = %s
481     working_hours_per_day = %s
482     working_days_per_week = %s
483     working_days_per_month = %s
484     working_days_per_year = %s
485     vacation = %s
486     working_days =  %s
487 '''%(project.id, start, minimum_time_unit, working_hours_per_day,  working_days_per_week, working_days_per_month, working_days_per_year, vacation, working_days )
488             func_str += cls_str
489             phase_ids = []
490             for root_phase in phase_pool.browse(cr, uid, root_phase_ids, context=context):
491                 phases, child_phase_ids = phase_pool.generate_phase(cr, uid, [root_phase.id], '', context=context)
492                 func_str += phases
493                 phase_ids += child_phase_ids
494             #Temp File to test the Code for the Allocation
495             fn = '/home/tiny/Desktop/plt.py'
496             fp = open(fn, 'w')
497             fp.writelines(func_str)
498             fp.close()
499         
500             # Allocating Memory for the required Project and Pahses and Resources
501             exec(func_str)
502             Project = eval('Project_%d' % project.id)
503             project = Task.BalancedProject(Project)
504         
505             for phase_id in phase_ids:
506                 phase = eval("project.Phase_%d" % phase_id)
507                 start_date = phase.start.to_datetime()
508                 end_date = phase.end.to_datetime()
509
510                 # Recalculate date_start and date_end
511                 # according to constraints on date start and date end on phase
512
513                 #if phase.constraint_date_start and str(s_date) < phase.constraint_date_start:
514                 #    start_date = datetime.strptime(phase.constraint_date_start, '%Y-%m-%d')
515                 #else:
516                 #    start_date = s_date
517                 #if phase.constraint_date_end and str(e_date) > phase.constraint_date_end:
518                 #    end_date= datetime.strptime(phase.constraint_date_end, '%Y-%m-%d')
519                 #    date_start = phase.constraint_date_end
520                 #else:
521                 #    end_date = e_date
522                 #    date_start = end_date
523
524                 # Write the calculated dates back
525                 #ctx = context.copy()
526                 #ctx.update({'scheduler': True})
527                 phase_pool.write(cr, uid, [phase_id], {
528                                       'date_start': start_date.strftime('%Y-%m-%d'),
529                                       'date_end': end_date.strftime('%Y-%m-%d')
530                                     }, context=context)
531         return True            
532
533     def schedule_tasks(self, cr, uid, ids, context=None):
534         """
535         Schedule task base on faces lib
536         """
537         if context is None:
538             context = {}
539         if type(ids) in (long, int,):
540             ids = [ids]
541         task_pool = self.pool.get('project.task')
542         resource_pool = self.pool.get('resource.resource')
543         data_pool = self.pool.get('ir.model.data')
544         resource_allocation_pool = self.pool.get('project.resource.allocation')
545         uom_pool = self.pool.get('product.uom')
546         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
547
548         for project in self.browse(cr, uid, ids, context=context):
549             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
550             start_date = project.date_start
551             #Creating resources using the member of the Project
552             u_ids = [i.id for i in project.members]
553             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
554             start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
555             func_str = ''
556             start = start_date
557             minimum_time_unit = 1
558             # default values
559             working_hours_per_day = 24
560             working_days_per_week = 7
561             working_days_per_month = 30
562             working_days_per_year = 365
563             
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
572             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
573             
574             cls_str = ''
575             # Creating Resources for the Project
576             for key, vals in resource_objs.items():
577                 cls_str +='''
578     class Resource_%s(Resource):
579         vacation = %s
580         efficiency = %s
581 '''%(key,  vals.get('vacation', False), vals.get('efficiency', False))
582     
583             # Create a new project for each phase
584             func_str += '''
585 def Project_%d():
586     from resource.faces import Resource
587     start = \'%s\'
588     minimum_time_unit = %s
589     working_hours_per_day = %s
590     working_days_per_week = %s
591     working_days_per_month = %s
592     working_days_per_year = %s
593     vacation = %s
594     working_days =  %s
595 '''%(project.id, start, minimum_time_unit, working_hours_per_day,  working_days_per_week, working_days_per_month, working_days_per_year, vacation, working_days )
596             func_str += cls_str
597             parent = False
598             task_ids = []
599             todo_task_ids = task_pool.search(cr, uid, [('project_id', '=', project.id),
600                                               ('state', 'in', ['draft', 'open', 'pending'])
601                                               ], order='sequence')
602             for task in task_pool.browse(cr, uid, todo_task_ids, context=context):
603                 func_str += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=True,context=context)
604                 if not parent:
605                     parent = task
606                 task_ids.append(task.id)
607
608             #Temp File to test the Code for the Allocation
609 #            fn = '/home/tiny/Desktop/plt_1.py'
610 #            fp = open(fn, 'w')
611 #            fp.writelines(func_str)
612 #            fp.close()
613     
614             # Allocating Memory for the required Project and Pahses and Resources
615             exec(func_str)
616             Project = eval('Project_%d' % project.id)
617             project = Task.BalancedProject(Project)
618         
619             for task_id in task_ids:
620                 task = eval("project.Task_%d" % task_id)
621                 start_date = task.start.to_datetime()
622                 end_date = task.end.to_datetime()
623                 
624                 task_pool.write(cr, uid, [task_id], {
625                                       'date_start': start_date.strftime('%Y-%m-%d'),
626                                       'date_end': end_date.strftime('%Y-%m-%d')
627                                     }, context=context)
628         return True
629
630 project()
631
632 class resource_resource(osv.osv):
633     _inherit = "resource.resource"
634     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
635         if context is None:
636             context = {}
637         if context.get('project_id',False):
638             project_pool = self.pool.get('project.project')
639             project_rec = project_pool.browse(cr, uid, context['project_id'], context=context)
640             user_ids = [user_id.id for user_id in project_rec.members]
641             args.append(('user_id','in',user_ids))
642         return super(resource_resource, self).search(cr, uid, args, offset, limit, order, context, count)
643
644 resource_resource()
645
646 class project_task(osv.osv):
647     _inherit = "project.task"
648     _columns = {
649         'phase_id': fields.many2one('project.phase', 'Project Phase'),
650     }
651
652     def generate_task(self, cr, uid, task_id, parent=False, flag=False, context=None):
653         if context is None:
654             context = {}
655         resource_pool = self.pool.get('resource.resource')
656         resource_allocation_pool = self.pool.get('project.resource.allocation')
657         task = self.browse(cr, uid, task_id, context=context)
658         duration = str(task.planned_hours )+ 'H'
659         resource_ids = self.search(cr, uid, [('user_id', '=', task.user_id.id)], context=context)
660         resource = False
661         if len(resource_ids):
662             resource = 'Resource_%s'%resource_ids[0]
663         # Phases Defination for the Project 
664         if not flag:
665             s = '''
666         def Task_%s():
667             effort = \'%s\'
668             resource = %s
669 '''%(task.id, duration, resource)
670             if parent:
671                 start = 'up.Task_%s.end' % (parent.id)
672                 s += '''
673             start = %s
674 '''%(start)
675             #start = datetime.strftime((datetime.strptime(start, "%Y-%m-%d")), "%Y-%m-%d")
676         else:
677             s = '''
678     def Task_%s():
679         effort = \'%s\'
680         resource = %s
681 '''%(task.id, duration, resource)
682             if parent:
683                 start = 'up.Task_%s.end' % (parent.id)
684                 s += '''
685         start = %s
686 '''%(start)
687         s += '\n'
688         return s
689 project_task()
690 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: