[IMP] Project_long_term : Refactoring Phase Scheduling
[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/tiny/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                 resources = act_phase.resource_ids
513                 phase = eval("project.Phase_%d" % phase_id)
514                 start_date = phase.start.to_datetime()
515                 end_date = phase.end.to_datetime()
516                 if resources:
517                     for res in resources:
518                         vals = {}
519                         vals.update({'date_start' :  start_date })
520                         vals.update({'date_end' :  end_date})
521                         resource_allocation_pool.write(cr, uid, res.id, vals)
522                 if act_phase.task_ids:
523                     for task in act_phase.task_ids:
524                         vals = {}
525                         #Getting values of the Tasks
526                         temp = eval("phase.Task_%s"%task.id)
527                         vals.update({'date_start' : temp.start.strftime('%Y-%m-%d %H:%M:%S')})
528                         vals.update({'date_end' : temp.end.strftime('%Y-%m-%d %H:%M:%S')})
529                         task_pool.write(cr, uid, task.id, vals, context=context)
530                 # Recalculate date_start and date_end
531                 # according to constraints on date start and date end on phase
532
533                 #if phase.constraint_date_start and str(s_date) < phase.constraint_date_start:
534                 #    start_date = datetime.strptime(phase.constraint_date_start, '%Y-%m-%d')
535                 #else:
536                 #    start_date = s_date
537                 #if phase.constraint_date_end and str(e_date) > phase.constraint_date_end:
538                 #    end_date= datetime.strptime(phase.constraint_date_end, '%Y-%m-%d')
539                 #    date_start = phase.constraint_date_end
540                 #else:
541                 #    end_date = e_date
542                 #    date_start = end_date
543
544                 # Write the calculated dates back
545                 #ctx = context.copy()
546                 #ctx.update({'scheduler': True})
547                 phase_pool.write(cr, uid, [phase_id], {
548                                       'date_start': start_date.strftime('%Y-%m-%d'),
549                                       'date_end': end_date.strftime('%Y-%m-%d')
550                                     }, context=context)
551         return True            
552
553     #TODO: DO Resource allocation and compute availability
554     def compute_allocation(self, rc, uid, ids, start_date, end_date, context=None):
555         if context ==  None:
556             contex = {}
557         allocation = {}
558         return allocation
559
560     def schedule_tasks(self, cr, uid, ids, context=None):
561         """
562         Schedule task base on faces lib
563         """
564         if context is None:
565             context = {}
566         if type(ids) in (long, int,):
567             ids = [ids]
568         task_pool = self.pool.get('project.task')
569         resource_pool = self.pool.get('resource.resource')
570         data_pool = self.pool.get('ir.model.data')
571         resource_allocation_pool = self.pool.get('project.resource.allocation')
572         uom_pool = self.pool.get('product.uom')
573         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
574
575         for project in self.browse(cr, uid, ids, context=context):
576             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
577             start_date = project.date_start
578             #Creating resources using the member of the Project
579             u_ids = [i.id for i in project.members]
580             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
581             start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
582             func_str = ''
583             start = start_date
584             minimum_time_unit = 1
585             # default values
586             working_hours_per_day = 24
587             working_days_per_week = 7
588             working_days_per_month = 30
589             working_days_per_year = 365
590             
591             vacation = []
592             if calendar_id:
593                 working_hours_per_day = 8 #TODO: it should be come from calendars
594                 working_days_per_week = 5
595                 working_days_per_month = 20
596                 working_days_per_year = 200
597                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
598
599             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
600             
601             cls_str = ''
602             # Creating Resources for the Project
603             for key, vals in resource_objs.items():
604                 cls_str +='''
605     class Resource_%s(Resource):
606         title = \"%s\"
607         vacation = %s
608         efficiency = %s
609 '''%(key,  vals.get('name',False), vals.get('vacation', False), vals.get('efficiency', False))
610     
611             # Create a new project for each phase
612             func_str += '''
613 def Project_%d():
614     from resource.faces import Resource
615     title = \"%s\"
616     start = \'%s\'
617     minimum_time_unit = %s
618     working_hours_per_day = %s
619     working_days_per_week = %s
620     working_days_per_month = %s
621     working_days_per_year = %s
622     vacation = %s
623     working_days =  %s
624 '''%(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 )
625             func_str += cls_str
626             parent = False
627             task_ids = []
628             todo_task_ids = task_pool.search(cr, uid, [('project_id', '=', project.id),
629                                               ('state', 'in', ['draft', 'open', 'pending'])
630                                               ], order='sequence')
631             for task in task_pool.browse(cr, uid, todo_task_ids, context=context):
632                 func_str += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=True,context=context)
633                 if not parent:
634                     parent = task
635                 task_ids.append(task.id)
636
637             #Temp File to test the Code for the Allocation
638 #            fn = '/home/tiny/Desktop/plt.py'
639 #            fp = open(fn, 'w')
640 #            fp.writelines(func_str)
641 #            fp.close()
642
643             # Allocating Memory for the required Project and Pahses and Resources
644             exec(func_str)
645             Project = eval('Project_%d' % project.id)
646             project = Task.BalancedProject(Project)
647             for task_id in task_ids:
648                 task = eval("project.Task_%d" % task_id)
649                 start_date = task.start.to_datetime()
650                 end_date = task.end.to_datetime()
651                 
652                 task_pool.write(cr, uid, [task_id], {
653                                       'date_start': start_date.strftime('%Y-%m-%d'),
654                                       'date_end': end_date.strftime('%Y-%m-%d')
655                                     }, context=context)
656         return True
657
658 project()
659
660 class resource_resource(osv.osv):
661     _inherit = "resource.resource"
662     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
663         if context is None:
664             context = {}
665         if context.get('project_id',False):
666             project_pool = self.pool.get('project.project')
667             project_rec = project_pool.browse(cr, uid, context['project_id'], context=context)
668             user_ids = [user_id.id for user_id in project_rec.members]
669             args.append(('user_id','in',user_ids))
670         return super(resource_resource, self).search(cr, uid, args, offset, limit, order, context, count)
671
672 resource_resource()
673
674 class project_task(osv.osv):
675     _inherit = "project.task"
676     _columns = {
677         'phase_id': fields.many2one('project.phase', 'Project Phase'),
678     }
679     _defaults = {
680         'user_id' : False
681     }
682
683     def generate_task(self, cr, uid, task_id, parent=False, flag=False, context=None):
684         if context is None:
685             context = {}
686         resource_pool = self.pool.get('resource.resource')
687         resource_allocation_pool = self.pool.get('project.resource.allocation')
688         task = self.browse(cr, uid, task_id, context=context)
689         duration = str(task.planned_hours )+ 'H'
690         resource = False
691         if task.user_id:
692             resource_ids = self.search(cr, uid, [('user_id', '=', task.user_id.id)], context=context)
693             if len(resource_ids):
694                 resource = 'Resource_%s'%resource_ids[0]
695         # Phases Defination for the Project 
696         if not flag:
697             s = '''
698         def Task_%s():
699             title = \"%s\"
700             effort = \'%s\'
701             resource = %s
702 '''%(task.id, task.name, duration, resource)
703             if parent:
704                 start = 'up.Task_%s.end' % (parent.id)
705                 s += '''
706             start = %s
707 '''%(start)
708             #start = datetime.strftime((datetime.strptime(start, "%Y-%m-%d")), "%Y-%m-%d")
709         else:
710             s = '''
711     def Task_%s():
712         title = \"%s\"
713         effort = \'%s\'
714         resource = %s
715 '''%(task.id, task.name, duration, resource)
716             if parent:
717                 start = 'up.Task_%s.end' % (parent.id)
718                 s += '''
719         start = %s
720 '''%(start)
721         s += '\n'
722         return s
723 project_task()
724 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: