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