[IMP] project_long_term : Added new warning for the Phase Scheduling when invalid...
[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
23 from tools.translate import _
24 from osv import fields, osv
25 from resource.faces import task as Task 
26
27 class project_phase(osv.osv):
28     _name = "project.phase"
29     _description = "Project Phase"
30
31     def _check_recursion(self, cr, uid, ids, context=None):
32          if context is None:
33             context = {}
34
35          data_phase = self.browse(cr, uid, ids[0], context=context)
36          prev_ids = data_phase.previous_phase_ids
37          next_ids = data_phase.next_phase_ids
38          # it should neither be in prev_ids nor in next_ids
39          if (data_phase in prev_ids) or (data_phase in next_ids):
40              return False
41          ids = [id for id in prev_ids if id in next_ids]
42          # both prev_ids and next_ids must be unique
43          if ids:
44              return False
45          # unrelated project
46          prev_ids = [rec.id for rec in prev_ids]
47          next_ids = [rec.id for rec in next_ids]
48          # iter prev_ids
49          while prev_ids:
50              cr.execute('SELECT distinct prv_phase_id FROM project_phase_rel WHERE next_phase_id IN %s', (tuple(prev_ids),))
51              prv_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
52              if data_phase.id in prv_phase_ids:
53                  return False
54              ids = [id for id in prv_phase_ids if id in next_ids]
55              if ids:
56                  return False
57              prev_ids = prv_phase_ids
58          # iter next_ids
59          while next_ids:
60              cr.execute('SELECT distinct next_phase_id FROM project_phase_rel WHERE prv_phase_id IN %s', (tuple(next_ids),))
61              next_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
62              if data_phase.id in next_phase_ids:
63                  return False
64              ids = [id for id in next_phase_ids if id in prev_ids]
65              if ids:
66                  return False
67              next_ids = next_phase_ids
68          return True
69
70     def _check_dates(self, cr, uid, ids, context=None):
71          for phase in self.read(cr, uid, ids, ['date_start', 'date_end'], context=context):
72              if phase['date_start'] and phase['date_end'] and phase['date_start'] > phase['date_end']:
73                  return False
74          return True
75
76     def _check_constraint_start(self, cr, uid, ids, context=None):
77          phase = self.read(cr, uid, ids[0], ['date_start', 'constraint_date_start'], context=context)
78          if phase['date_start'] and phase['constraint_date_start'] and phase['date_start'] < phase['constraint_date_start']:
79              return False
80          return True
81
82     def _check_constraint_end(self, cr, uid, ids, context=None):
83          phase = self.read(cr, uid, ids[0], ['date_end', 'constraint_date_end'], context=context)
84          if phase['date_end'] and phase['constraint_date_end'] and phase['date_end'] > phase['constraint_date_end']:
85              return False
86          return True
87
88     def _get_default_uom_id(self, cr, uid):
89        model_data_obj = self.pool.get('ir.model.data')
90        model_data_id = model_data_obj._get_id(cr, uid, 'product', 'uom_hour')
91        return model_data_obj.read(cr, uid, [model_data_id], ['res_id'])[0]['res_id']
92
93     def _compute(self, cr, uid, ids, field_name, arg, context=None):
94         res = {}
95         if not ids:
96             return res
97         for phase in self.browse(cr, uid, ids, context=context):
98             tot = 0.0
99             for task in phase.task_ids:
100                 tot += task.planned_hours
101             res[phase.id] = tot
102         return res
103
104     _columns = {
105         'name': fields.char("Name", size=64, required=True),
106         '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)]}),
107         '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)]}),
108         'constraint_date_start': fields.date('Minimum Start Date', help='force the phase to start after this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
109         'constraint_date_end': fields.date('Deadline', help='force the phase to finish before this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
110         'project_id': fields.many2one('project.project', 'Project', required=True),
111         'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases', states={'cancelled':[('readonly',True)]}),
112         'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases', states={'cancelled':[('readonly',True)]}),
113         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of phases."),
114         'duration': fields.float('Duration', required=True, help="By default in days", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
115         '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)]}),
116         'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
117         'resource_ids': fields.one2many('project.resource.allocation', 'phase_id', "Project Resources",states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
118         'responsible_id': fields.many2one('res.users', 'Responsible', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
119         'state': fields.selection([('draft', 'Draft'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True,
120                                   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.\
121                                   \n If the phase is over, the states is set to \'Done\'.'),
122         'total_hours': fields.function(_compute, method=True, string='Total Hours'),
123      }
124     _defaults = {
125         'responsible_id': lambda obj,cr,uid,context: uid,
126         'state': 'draft',
127         'sequence': 10,
128         'product_uom': lambda self,cr,uid,c: self.pool.get('product.uom').search(cr, uid, [('name', '=', _('Day'))], context=c)[0]
129     }
130     _order = "project_id, date_start, sequence, name"
131     _constraints = [
132         (_check_recursion,'Loops in phases not allowed',['next_phase_ids', 'previous_phase_ids']),
133         (_check_dates, 'Phase start-date must be lower than phase end-date.', ['date_start', 'date_end']),
134     ]
135
136     def onchange_project(self, cr, uid, ids, project, context=None):
137         result = {}
138         result['date_start'] = False
139         project_obj = self.pool.get('project.project')
140         if project:
141             project_id = project_obj.browse(cr, uid, project, context=context)
142             result['date_start'] = project_id.date_start
143         return {'value': result}
144
145
146     def _check_date_start(self, cr, uid, phase, date_end, context=None):
147        """
148        Check And Compute date_end of phase if change in date_start < older time.
149        """
150        uom_obj = self.pool.get('product.uom')
151        resource_obj = self.pool.get('resource.resource')
152        cal_obj = self.pool.get('resource.calendar')
153        calendar_id = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False
154        resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)])
155        if resource_id:
156             res = resource_obj.read(cr, uid, resource_id, ['calendar_id'], context=context)[0]
157             cal_id = res.get('calendar_id', False) and res.get('calendar_id')[0] or False
158             if cal_id:
159                 calendar_id = cal_id
160        default_uom_id = self._get_default_uom_id(cr, uid)
161        avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
162        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)
163        dt_start = work_times[0][0].strftime('%Y-%m-%d')
164        self.write(cr, uid, [phase.id], {'date_start': dt_start, 'date_end': date_end.strftime('%Y-%m-%d')}, context=context)
165
166     def _check_date_end(self, cr, uid, phase, date_start, context=None):
167        """
168        Check And Compute date_end of phase if change in date_end > older time.
169        """
170        uom_obj = self.pool.get('product.uom')
171        resource_obj = self.pool.get('resource.resource')
172        cal_obj = self.pool.get('resource.calendar')
173        calendar_id = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False
174        resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)], context=context)
175        if resource_id:
176             res = resource_obj.read(cr, uid, resource_id, ['calendar_id'], context=context)[0]
177             cal_id = res.get('calendar_id', False) and res.get('calendar_id')[0] or False
178             if cal_id:
179                 calendar_id = cal_id
180        default_uom_id = self._get_default_uom_id(cr, uid)
181        avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
182        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)
183        dt_end = work_times[-1][1].strftime('%Y-%m-%d')
184        self.write(cr, uid, [phase.id], {'date_start': date_start.strftime('%Y-%m-%d'), 'date_end': dt_end}, context=context)
185
186     def copy(self, cr, uid, id, default=None, context=None):
187         if default is None:
188             default = {}
189         if not default.get('name', False):
190             default['name'] = self.browse(cr, uid, id, context=context).name + _(' (copy)')
191         return super(project_phase, self).copy(cr, uid, id, default, context)
192
193     def set_draft(self, cr, uid, ids, *args):
194         self.write(cr, uid, ids, {'state': 'draft'})
195         return True
196
197     def set_open(self, cr, uid, ids, *args):
198         self.write(cr, uid, ids, {'state': 'open'})
199         return True
200
201     def set_pending(self, cr, uid, ids, *args):
202         self.write(cr, uid, ids, {'state': 'pending'})
203         return True
204
205     def set_cancel(self, cr, uid, ids, *args):
206         self.write(cr, uid, ids, {'state': 'cancelled'})
207         return True
208
209     def set_done(self, cr, uid, ids, *args):
210         self.write(cr, uid, ids, {'state': 'done'})
211         return True
212
213     def generate_phase(self, cr, uid, ids, f, parent=False, context=None):
214         if context is None:
215             context = {}
216         phase_ids = []
217         data_pool = self.pool.get('ir.model.data')
218         uom_pool = self.pool.get('product.uom')
219         task_pool = self.pool.get('project.task')
220         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
221         for phase in self.browse(cr, uid, ids, context=context):
222             avg_days = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, day_uom_id)
223             duration = str(avg_days) + 'd'
224             # Create a new project for each phase
225             str_resource = ('%s & '*len(phase.resource_ids))[:-2]
226             str_vals = str_resource % tuple(map(lambda x: 'Resource_%s'%x.resource_id.id, phase.resource_ids))
227
228             # Phases Defination for the Project
229             s = '''
230     def Phase_%s():
231         title = \"%s\"
232         effort = \'%s\'
233         resource = %s
234 '''%(phase.id, phase.name, duration, str_vals or False)
235
236             # Recalculate date_start and date_end
237             # according to constraints on date start and date end on phase
238             start_date = ''
239             end_date = ''
240             if phase.constraint_date_start:
241                 start_date = datetime.strptime(phase.constraint_date_start, '%Y-%m-%d')
242                 s += '''
243         start = \"%s\"
244 '''%(datetime.strftime(start_date, "%Y-%m-%d"))
245             else:
246                 if parent:
247                     start = 'up.Phase_%s.end' % (parent.id)
248                     s += '''
249         start = %s
250 '''%(start)
251                 else:
252                     start = phase.project_id.date_start or phase.date_start
253                     s += '''
254         start = \"%s\"
255 '''%(start)                
256                 
257             if phase.constraint_date_end :
258                 end_date= datetime.strptime(phase.constraint_date_end, '%Y-%m-%d')
259                 s += '''
260         end = \"%s\"
261 '''%(datetime.strftime(end_date, "%Y-%m-%d"))               
262
263
264                 #start = datetime.strftime((datetime.strptime(start, "%Y-%m-%d")), "%Y-%m-%d")
265
266             phase_ids.append(phase.id)
267             parent = False
268             task_ids = []
269             todo_task_ids = task_pool.search(cr, uid, [('id', 'in', map(lambda x : x.id, phase.task_ids)),
270                                               ('state', 'in', ['draft', 'open', 'pending'])
271                                               ], order='sequence')
272             if todo_task_ids :
273                 for task in task_pool.browse(cr, uid, todo_task_ids, context=context):
274                     s += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=False, context=context)
275                     if not parent:
276                         parent = task
277                     task_ids.append(task.id)
278             
279             f += s + '\n'
280             # Recursive call till all the next phases scheduled
281             for next_phase in phase.next_phase_ids:
282                 if next_phase.state in ['draft', 'open', 'pending']:
283                     rf, rphase_ids = self.generate_phase(cr, uid, [next_phase.id], f = '', parent=phase, context=context)
284                     f += rf +'\n'
285                     phase_ids += rphase_ids
286                 else:   
287                     continue
288         return f, phase_ids
289
290     def schedule_tasks(self, cr, uid, ids, context=None):
291         """
292         Schedule tasks base on faces lib
293         """
294         if context is None:
295             context = {}
296         if type(ids) in (long, int,):
297             ids = [ids]
298         task_pool = self.pool.get('project.task')
299         resource_pool = self.pool.get('resource.resource')
300         for phase in self.browse(cr, uid, ids, context=context):
301             project = phase.project_id
302             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
303             start_date = project.date_start
304             #Creating resources using the member of the Project
305             u_ids = [i.id for i in project.members]
306             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
307             start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
308             func_str = ''
309             start = start_date
310             minimum_time_unit = 1
311             # default values
312             working_hours_per_day = 24
313             working_days_per_week = 7
314             working_days_per_month = 30
315             working_days_per_year = 365
316             
317             vacation = []
318             if calendar_id:
319                 working_hours_per_day = 8 #TODO: it should be come from calendars
320                 working_days_per_week = 5
321                 working_days_per_month = 20
322                 working_days_per_year = 200
323                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
324
325             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
326             
327             cls_str = ''
328             # Creating Resources for the Project
329             for key, vals in resource_objs.items():
330                 cls_str +='''
331     class Resource_%s(Resource):
332         title = \"%s\"
333         vacation = %s
334         efficiency = %s
335 '''%(key,  vals.get('name',False), vals.get('vacation', False), vals.get('efficiency', False))
336     
337             # Create a new project for each phase
338             func_str += '''
339 def Phase_%d():
340     from resource.faces import Resource
341     title = \"%s\"
342     start = \'%s\'
343     minimum_time_unit = %s
344     working_hours_per_day = %s
345     working_days_per_week = %s
346     working_days_per_month = %s
347     working_days_per_year = %s
348     vacation = %s
349     working_days =  %s
350 '''%(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 )
351             
352             parent = False
353             task_ids = []
354             todo_task_ids = task_pool.search(cr, uid, [('id', 'in', map(lambda x : x.id, phase.task_ids)),
355                                               ('state', 'in', ['draft', 'open', 'pending'])
356                                               ], order='sequence')
357             for task in task_pool.browse(cr, uid, todo_task_ids, context=context):
358                 func_str += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=True, context=context)
359                 if not parent:
360                     parent = task
361                 task_ids.append(task.id)
362             func_str += cls_str
363     
364             # Allocating Memory for the required Project and Pahses and Resources
365             exec(func_str)
366             Phase = eval('Phase_%d' % phase.id)
367             phase = None
368             try:
369                 phase = Task.BalancedProject(Phase)
370             except :
371                 raise osv.except_osv(_('Error !'),_('Invalid Project Members.\nPhase Scheduling is not possible.'))
372         
373         
374             for task_id in task_ids:
375                 task = eval("phase.Task_%d" % task_id)
376                 start_date = task.start.to_datetime()
377                 end_date = task.end.to_datetime()
378                 
379                 task_pool.write(cr, uid, [task_id], {
380                                       'date_start': start_date.strftime('%Y-%m-%d'),
381                                       'date_end': end_date.strftime('%Y-%m-%d')
382                                     }, context=context)
383         return True
384 project_phase()
385
386 class project_resource_allocation(osv.osv):
387     _name = 'project.resource.allocation'
388     _description = 'Project Resource Allocation'
389     _rec_name = 'resource_id'
390
391     def get_name(self, cr, uid, ids, field_name, arg, context=None):
392         res = {}
393         for allocation in self.browse(cr, uid, ids, context=context):
394             name = allocation.phase_id.name
395             name += ' (%s%%)' %(allocation.useability)
396             res[allocation.id] = name
397         return res
398     _columns = {
399         'name': fields.function(get_name, method=True, type='char', size=256),
400         'resource_id': fields.many2one('resource.resource', 'Resource', required=True),
401         'phase_id': fields.many2one('project.phase', 'Project Phase', ondelete='cascade', required=True),
402         'project_id': fields.related('phase_id', 'project_id', type='many2one', relation="project.project", string='Project', store=True),
403         'user_id': fields.related('resource_id', 'user_id', type='many2one', relation="res.users", string='User'),
404         'date_start': fields.date('Start Date', help="Starting Date"),
405         'date_end': fields.date('End Date', help="Ending Date"),
406         'useability': fields.float('Availability', help="Availability of this resource for this project phase in percentage (=50%)"),
407     }
408     _defaults = {
409         'useability': 100,
410     }
411
412 project_resource_allocation()
413
414 class project(osv.osv):
415     _inherit = "project.project"
416     _columns = {
417         'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"),
418         'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ),
419     }
420     def generate_members(self, cr, uid, ids, context=None):
421         """
422         Return a list of  Resource Class objects for the resources allocated to the phase.
423         """
424         res = {}
425         resource_pool = self.pool.get('resource.resource')
426         for project in self.browse(cr, uid, ids, context=context):
427             user_ids = map(lambda x:x.id, project.members)
428             calendar_id  = project.resource_calendar_id and project.resource_calendar_id.id or False
429             resource_objs = resource_pool.generate_resources(cr, uid, user_ids, calendar_id, context=context)
430             res[project.id] = resource_objs
431         return res
432
433     def schedule_phases(self, cr, uid, ids, context=None):
434         """
435         Schedule phase base on faces lib
436         """
437         if context is None:
438             context = {}
439         if type(ids) in (long, int,):
440             ids = [ids]
441         phase_pool = self.pool.get('project.phase')
442         task_pool = self.pool.get('project.task')        
443         resource_pool = self.pool.get('resource.resource')
444         data_pool = self.pool.get('ir.model.data')
445         resource_allocation_pool = self.pool.get('project.resource.allocation')
446         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
447         
448         #Checking the Valid Phase resource allocation from project member
449         for project in self.browse(cr, uid, ids, context=context):
450             flag = False
451             res_msg = ''
452             memebrs_ids = []
453             resource_user_ids = []
454             if project.members:
455                 memebrs_ids = [use.id for use in project.members]
456             phase_ids = phase_pool.search(cr, uid, [('project_id', '=', project.id)], context=context)
457             if phase_ids:
458                 for phase in phase_pool.browse(cr, uid, phase_ids, context=context):
459                     if phase.resource_ids:
460                         res_ids = [ re.id for re in  phase.resource_ids] 
461                         for res in resource_allocation_pool.browse(cr, uid,res_ids, context=context):
462                             if res.resource_id.user_id.id not in memebrs_ids:
463                                 res_msg += " '%s' %s , "%(res.resource_id.name,res.resource_id.user_id.name)
464                                 flag = True
465             if flag:
466                 raise osv.except_osv(_('Warning !'),_("Resource %s is/are not Members of the Project '%s' .")%(res_msg[:-3], project.name))
467
468         for project in self.browse(cr, uid, ids, context=context):
469             root_phase_ids = phase_pool.search(cr, uid, [('project_id', '=', project.id),
470                                                   ('state', 'in', ['draft', 'open', 'pending']),
471                                                   ('previous_phase_ids', '=', False)
472                                                   ])
473             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
474             start_date = project.date_start
475             #if start_date:
476             #    start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
477             #Creating resources using the member of the Project
478             u_ids = [i.id for i in project.members]
479             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
480             func_str = ''
481             start = start_date
482             minimum_time_unit = 1
483             # default values
484             working_hours_per_day = 24
485             working_days_per_week = 7
486             working_days_per_month = 30
487             working_days_per_year = 365
488             
489             vacation = []
490             if calendar_id:
491                 working_hours_per_day = 8 #TODO: it should be come from calendars
492                 working_days_per_week = 5
493                 working_days_per_month = 20
494                 working_days_per_year = 200
495                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
496
497             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
498             
499             cls_str = ''
500             # Creating Resources for the Project
501             for key, vals in resource_objs.items():
502                 cls_str +='''
503     class Resource_%s(Resource):
504         title = \"%s\"
505         vacation = %s
506         efficiency = %s
507 '''%(key,  vals.get('name',False), vals.get('vacation', False), vals.get('efficiency', False))
508         
509             # Create a new project for each phase
510             func_str += '''
511 def Project_%d():
512     from resource.faces import Resource
513     title = \"%s\"
514     start = \'%s\'
515     minimum_time_unit = %s
516     working_hours_per_day = %s
517     working_days_per_week = %s
518     working_days_per_month = %s
519     working_days_per_year = %s
520     vacation = %s
521     working_days =  %s
522 '''%(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 )
523
524             func_str += cls_str
525             phase_ids = []
526             for root_phase in phase_pool.browse(cr, uid, root_phase_ids, context=context):
527                 phases, child_phase_ids = phase_pool.generate_phase(cr, uid, [root_phase.id], '', context=context)
528                 func_str += phases
529                 phase_ids += child_phase_ids
530         
531             # Allocating Memory for the required Project and Pahses and Resources
532             exec(func_str)
533             Project = eval('Project_%d' % project.id)
534             project = None
535             try:
536                 project = Task.BalancedProject(Project)
537             except :
538                 raise osv.except_osv(_('Error !'),_('Invalid Project Members.\nPhase Scheduling is not possible.'))
539             
540             for phase_id in phase_ids:
541                 act_phase = phase_pool.browse(cr, uid, phase_id, context=context)
542                 resources = act_phase.resource_ids
543                 phase = eval("project.Phase_%d" % phase_id)
544                 start_date = phase.start.to_datetime()
545                 end_date = phase.end.to_datetime()
546                 
547                 if resources:
548                     for res in resources:
549                         vals = {}
550                         vals.update({'date_start' :  start_date })
551                         vals.update({'date_end' :  end_date})
552                         resource_allocation_pool.write(cr, uid, res.id, vals, context=context)
553                 if act_phase.task_ids:
554                     for task in act_phase.task_ids:
555                         vals = {}
556                         #Getting values of the Tasks
557                         temp = eval("phase.Task_%s"%task.id)
558                         if temp.booked_resource:
559                             res_name = temp.booked_resource[0].title
560                             res_id = resource_pool.search(cr, uid,[('name','=',res_name)], context = context)
561                             if res_id:
562                                 res = resource_pool.browse(cr, uid, res_id[0], context = context)
563                                 vals.update({'user_id' : res.user_id.id})
564                                                
565                         vals.update({'date_start' : temp.start.strftime('%Y-%m-%d %H:%M:%S')})
566                         vals.update({'date_end' : temp.end.strftime('%Y-%m-%d %H:%M:%S')})
567                         task_pool.write(cr, uid, task.id, vals, context=context)
568
569
570                 phase_pool.write(cr, uid, [phase_id], {
571                                       'date_start': start_date.strftime('%Y-%m-%d'),
572                                       'date_end': end_date.strftime('%Y-%m-%d')
573                                     }, context=context)
574         return True            
575
576     #TODO: DO Resource allocation and compute availability
577     def compute_allocation(self, rc, uid, ids, start_date, end_date, context=None):
578         if context ==  None:
579             context = {}
580         allocation = {}
581         return allocation
582
583     def schedule_tasks(self, cr, uid, ids, context=None):
584         """
585         Schedule task base on faces lib
586         """
587         if context is None:
588             context = {}
589         if type(ids) in (long, int,):
590             ids = [ids]
591         task_pool = self.pool.get('project.task')
592         resource_pool = self.pool.get('resource.resource')
593         data_pool = self.pool.get('ir.model.data')
594         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
595
596         for project in self.browse(cr, uid, ids, context=context):
597             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
598             start_date = project.date_start
599             #Creating resources using the member of the Project
600             u_ids = [i.id for i in project.members]
601             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
602             try:
603                 start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
604             except:
605                 raise osv.except_osv(_('Error !'),_('Task Scheduling is not possible.\nProject should have the Start date for scheduling.'))
606             func_str = ''
607             start = start_date
608             minimum_time_unit = 1
609             # default values
610             working_hours_per_day = 24
611             working_days_per_week = 7
612             working_days_per_month = 30
613             working_days_per_year = 365
614             
615             vacation = []
616             if calendar_id:
617                 working_hours_per_day = 8 #TODO: it should be come from calendars
618                 working_days_per_week = 5
619                 working_days_per_month = 20
620                 working_days_per_year = 200
621                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
622
623             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
624             
625             cls_str = ''
626             # Creating Resources for the Project
627             for key, vals in resource_objs.items():
628                 cls_str +='''
629     class Resource_%s(Resource):
630         title = \"%s\"
631         vacation = %s
632         efficiency = %s
633 '''%(key,  vals.get('name',False), vals.get('vacation', False), vals.get('efficiency', False))
634     
635             # Create a new project for each phase
636             func_str += '''
637 def Project_%d():
638     from resource.faces import Resource
639     title = \"%s\"
640     start = \'%s\'
641     minimum_time_unit = %s
642     working_hours_per_day = %s
643     working_days_per_week = %s
644     working_days_per_month = %s
645     working_days_per_year = %s
646     vacation = %s
647     working_days =  %s
648 '''%(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 )
649             
650             parent = False
651             task_ids = []
652             todo_task_ids = task_pool.search(cr, uid, [('project_id', '=', project.id),
653                                               ('state', 'in', ['draft', 'open', 'pending'])
654                                               ], order='sequence')
655             if todo_task_ids:
656                 for task in task_pool.browse(cr, uid, todo_task_ids, context=context):
657                     func_str += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=True,context=context)
658                     if not parent:
659                         parent = task
660                     task_ids.append(task.id)
661             func_str += cls_str
662
663             # Allocating Memory for the required Project and Pahses and Resources
664             exec(func_str)
665             Project = eval('Project_%d' % project.id)
666             project = None
667             try:
668                 project = Task.BalancedProject(Project)
669             except :
670                 raise osv.except_osv(_('Error !'),_('Phase Scheduling is not possible.\nProject should have the Start date and member for scheduling.'))
671             
672             for task_id in task_ids:
673                 task = eval("project.Task_%d" % task_id)
674                 start_date = task.start.to_datetime()
675                 end_date = task.end.to_datetime()
676                 
677                 task_pool.write(cr, uid, [task_id], {
678                                       'date_start': start_date.strftime('%Y-%m-%d'),
679                                       'date_end': end_date.strftime('%Y-%m-%d')
680                                     }, context=context)
681         return True
682
683 project()
684
685 class resource_resource(osv.osv):
686     _inherit = "resource.resource"
687     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
688         if context is None:
689             context = {}
690         if context.get('project_id',False):
691             project_pool = self.pool.get('project.project')
692             project_rec = project_pool.browse(cr, uid, context['project_id'], context=context)
693             user_ids = [user_id.id for user_id in project_rec.members]
694             args.append(('user_id','in',user_ids))
695         return super(resource_resource, self).search(cr, uid, args, offset, limit, order, context, count)
696
697 resource_resource()
698
699 class project_task(osv.osv):
700     _inherit = "project.task"
701     _columns = {
702         'phase_id': fields.many2one('project.phase', 'Project Phase'),
703     }
704     _defaults = {
705         'user_id' : False
706     }
707
708     def generate_task(self, cr, uid, task_id, parent=False, flag=False, context=None):
709         if context is None:
710             context = {}
711         task = self.browse(cr, uid, task_id, context=context)
712         duration = str(task.planned_hours )+ 'H'
713         str_resource = False
714         if task.phase_id.resource_ids:
715             str_resource = ('%s | '*len(task.phase_id.resource_ids))[:-2]
716             str_resource = str_resource % tuple(map(lambda x: 'Resource_%s'%x.resource_id.id, task.phase_id.resource_ids))
717         # Task Defination for the Phase of the Project
718         if not flag:
719             s = '''
720         def Task_%s():
721             title = \"%s\"
722             effort = \'%s\'
723             resource = %s
724 '''%(task.id, task.name, duration, str_resource)
725             #start = datetime.strftime((datetime.strptime(start, "%Y-%m-%d")), "%Y-%m-%d")
726         else:
727             s = '''
728     def Task_%s():
729         title = \"%s\"
730         effort = \'%s\'
731         resource = %s
732 '''%(task.id, task.name, duration, str_resource)
733         s += '\n'
734         return s
735 project_task()
736 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: