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