[FIX/BUG] project_lon_term : Task Scheduling shoud repect the parent task end date...
[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             phase_id = phase.id
364             #check known constraints before running Face algorithm in order to have the error translated
365             if not phase.project_id.date_start:
366                 raise osv.except_osv(_('Error !'),_('Task Scheduling is not possible.\nProject should have the Start date for scheduling.'))
367             # Allocating Memory for the required Project and Pahses and Resources
368             exec(func_str)
369             Phase = eval('Phase_%d' % phase.id)
370             phase = None
371             try:
372                 phase = Task.BalancedProject(Phase)
373             except Exception, e:
374                 raise osv.except_osv(_('Error !'),e)
375
376             for task_id in task_ids:
377                 task = eval("phase.Task_%d" % task_id)
378                 start_date = task.start.to_datetime()
379                 end_date = task.end.to_datetime()
380                 
381                 task_pool.write(cr, uid, [task_id], {
382                                       'date_start': start_date.strftime('%Y-%m-%d'),
383                                       'date_end': end_date.strftime('%Y-%m-%d')
384                                     }, context=context)
385         return True
386 project_phase()
387
388 class project_resource_allocation(osv.osv):
389     _name = 'project.resource.allocation'
390     _description = 'Project Resource Allocation'
391     _rec_name = 'resource_id'
392
393     def get_name(self, cr, uid, ids, field_name, arg, context=None):
394         res = {}
395         for allocation in self.browse(cr, uid, ids, context=context):
396             name = allocation.phase_id.name
397             name += ' (%s%%)' %(allocation.useability)
398             res[allocation.id] = name
399         return res
400     _columns = {
401         'name': fields.function(get_name, method=True, type='char', size=256),
402         'resource_id': fields.many2one('resource.resource', 'Resource', required=True),
403         'phase_id': fields.many2one('project.phase', 'Project Phase', ondelete='cascade', required=True),
404         'project_id': fields.related('phase_id', 'project_id', type='many2one', relation="project.project", string='Project', store=True),
405         'user_id': fields.related('resource_id', 'user_id', type='many2one', relation="res.users", string='User'),
406         'date_start': fields.date('Start Date', help="Starting Date"),
407         'date_end': fields.date('End Date', help="Ending Date"),
408         'useability': fields.float('Availability', help="Availability of this resource for this project phase in percentage (=50%)"),
409     }
410     _defaults = {
411         'useability': 100,
412     }
413
414 project_resource_allocation()
415
416 class project(osv.osv):
417     _inherit = "project.project"
418     _columns = {
419         'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"),
420         'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ),
421     }
422     def generate_members(self, cr, uid, ids, context=None):
423         """
424         Return a list of  Resource Class objects for the resources allocated to the phase.
425         """
426         res = {}
427         resource_pool = self.pool.get('resource.resource')
428         for project in self.browse(cr, uid, ids, context=context):
429             user_ids = map(lambda x:x.id, project.members)
430             calendar_id  = project.resource_calendar_id and project.resource_calendar_id.id or False
431             resource_objs = resource_pool.generate_resources(cr, uid, user_ids, calendar_id, context=context)
432             res[project.id] = resource_objs
433         return res
434
435     def schedule_phases(self, cr, uid, ids, context=None):
436         """
437         Schedule phase base on faces lib
438         """
439         if context is None:
440             context = {}
441         if type(ids) in (long, int,):
442             ids = [ids]
443         phase_pool = self.pool.get('project.phase')
444         task_pool = self.pool.get('project.task')        
445         resource_pool = self.pool.get('resource.resource')
446         data_pool = self.pool.get('ir.model.data')
447         resource_allocation_pool = self.pool.get('project.resource.allocation')
448         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
449         
450         #Checking the Valid Phase resource allocation from project member
451         for project in self.browse(cr, uid, ids, context=context):
452             flag = False
453             res_missing = []
454             members_ids = []
455             if project.members:
456                 members_ids = [user.id for user in project.members]
457             for phase in project.phase_ids:
458                 if phase.resource_ids:
459                     res_ids = [ re.id for re in  phase.resource_ids] 
460                     for res in resource_allocation_pool.browse(cr, uid, res_ids, context=context):
461                         if res.resource_id.user_id.id not in members_ids:
462                             res_missing += [res.resource_id.name]
463                             flag = True
464             if flag:
465                 raise osv.except_osv(_('Warning !'),_("Resource(s) %s is(are) not member(s) of the project '%s' .") % (",".join(res_missing), project.name))
466
467         for project in self.browse(cr, uid, ids, context=context):
468             root_phase_ids = phase_pool.search(cr, uid, [('project_id', '=', project.id),
469                                                   ('state', 'in', ['draft', 'open', 'pending']),
470                                                   ('previous_phase_ids', '=', False)
471                                                   ])
472             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
473             start_date = project.date_start
474             #if start_date:
475             #    start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
476             #Creating resources using the member of the Project
477             u_ids = [i.id for i in project.members]
478             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
479             func_str = ''
480             start = start_date
481             minimum_time_unit = 1
482             # default values
483             working_hours_per_day = 24
484             working_days_per_week = 7
485             working_days_per_month = 30
486             working_days_per_year = 365
487             
488             vacation = []
489             if calendar_id:
490                 working_hours_per_day = 8 #TODO: it should be come from calendars
491                 working_days_per_week = 5
492                 working_days_per_month = 20
493                 working_days_per_year = 200
494                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
495
496             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
497             
498             cls_str = ''
499             # Creating Resources for the Project
500             for key, vals in resource_objs.items():
501                 cls_str +='''
502     class Resource_%s(Resource):
503         title = \"%s\"
504         vacation = %s
505         efficiency = %s
506 '''%(key,  vals.get('name',False), vals.get('vacation', False), vals.get('efficiency', False))
507         
508             # Create a new project for each phase
509             func_str += '''
510 def Project_%d():
511     from resource.faces import Resource
512     title = \"%s\"
513     start = \'%s\'
514     minimum_time_unit = %s
515     working_hours_per_day = %s
516     working_days_per_week = %s
517     working_days_per_month = %s
518     working_days_per_year = %s
519     vacation = %s
520     working_days =  %s
521 '''%(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 )
522
523             func_str += cls_str
524             phase_ids = []
525             for root_phase in phase_pool.browse(cr, uid, root_phase_ids, context=context):
526                 phases, child_phase_ids = phase_pool.generate_phase(cr, uid, [root_phase.id], '', context=context)
527                 func_str += phases
528                 phase_ids += child_phase_ids
529         
530             project_id = project.id
531             if not project.date_start:
532                 raise osv.except_osv(_('Error !'),_('Task Scheduling is not possible.\nProject should have the Start date for scheduling.'))
533             # Allocating Memory for the required Project and Phases and Resources
534             exec(func_str)
535             Project = eval('Project_%d' % project.id)
536             project = None
537             try:
538                 project = Task.BalancedProject(Project)
539             except Exception, e:
540                 raise osv.except_osv(_('Error !'), e)
541             
542             for phase_id in phase_ids:
543                 act_phase = phase_pool.browse(cr, uid, phase_id, context=context)
544                 resources = act_phase.resource_ids
545                 phase = eval("project.Phase_%d" % phase_id)
546                 start_date = phase.start.to_datetime()
547                 end_date = phase.end.to_datetime()
548                 
549                 if resources:
550                     for res in resources:
551                         vals = {}
552                         vals.update({'date_start' :  start_date })
553                         vals.update({'date_end' :  end_date})
554                         resource_allocation_pool.write(cr, uid, res.id, vals, context=context)
555                 if act_phase.task_ids:
556                     for task in act_phase.task_ids:
557                         vals = {}
558                         #Getting values of the Tasks
559                         temp = eval("phase.Task_%s"%task.id)
560                         if temp.booked_resource:
561                             res_name = temp.booked_resource[0].title
562                             res_id = resource_pool.search(cr, uid,[('name','=',res_name)], context = context)
563                             if res_id:
564                                 res = resource_pool.browse(cr, uid, res_id[0], context = context)
565                                 vals.update({'user_id' : res.user_id.id})
566                                                
567                         vals.update({'date_start' : temp.start.strftime('%Y-%m-%d %H:%M:%S')})
568                         vals.update({'date_end' : temp.end.strftime('%Y-%m-%d %H:%M:%S')})
569                         task_pool.write(cr, uid, task.id, vals, context=context)
570
571
572                 phase_pool.write(cr, uid, [phase_id], {
573                                       'date_start': start_date.strftime('%Y-%m-%d'),
574                                       'date_end': end_date.strftime('%Y-%m-%d')
575                                     }, context=context)
576         return True            
577
578     #TODO: DO Resource allocation and compute availability
579     def compute_allocation(self, rc, uid, ids, start_date, end_date, context=None):
580         if context ==  None:
581             context = {}
582         allocation = {}
583         return allocation
584
585     def schedule_tasks(self, cr, uid, ids, context=None):
586         """
587         Schedule task base on faces lib
588         """
589         if context is None:
590             context = {}
591         if type(ids) in (long, int,):
592             ids = [ids]
593         task_pool = self.pool.get('project.task')
594         resource_pool = self.pool.get('resource.resource')
595         data_pool = self.pool.get('ir.model.data')
596         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
597
598         for project in self.browse(cr, uid, ids, context=context):
599             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
600             start_date = project.date_start
601         
602             #Checking the Valid Phase resource allocation from project member
603             flag = False
604             res_missing = []
605             members_ids = []
606             if project.members:
607                 members_ids = [user.id for user in project.members]
608             for phase in project.phase_ids:
609                 if phase.resource_ids:
610                     res_ids = [ re.id for re in  phase.resource_ids] 
611                     for res in self.pool.get('project.resource.allocation').browse(cr, uid, res_ids, context=context):
612                         if res.resource_id.user_id.id not in members_ids:
613                             res_missing += [res.resource_id.name]
614                             flag = True
615             if flag:
616                 raise osv.except_osv(_('Warning !'),_("Resource(s) %s is(are) not member(s) of the project '%s' .") % (",".join(res_missing), project.name))
617             #Creating resources using the member of the Project
618             u_ids = [i.id for i in project.members]
619             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
620             try:
621                 start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
622             except:
623                 raise osv.except_osv(_('Error !'),_('Task Scheduling is not possible.\nProject should have the Start date for scheduling.'))
624             func_str = ''
625             start = start_date
626             minimum_time_unit = 1
627             # default values
628             working_hours_per_day = 24
629             working_days_per_week = 7
630             working_days_per_month = 30
631             working_days_per_year = 365
632             
633             vacation = []
634             if calendar_id:
635                 working_hours_per_day = 8 #TODO: it should be come from calendars
636                 working_days_per_week = 5
637                 working_days_per_month = 20
638                 working_days_per_year = 200
639                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
640
641             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
642             
643             cls_str = ''
644             # Creating Resources for the Project
645             for key, vals in resource_objs.items():
646                 cls_str +='''
647     class Resource_%s(Resource):
648         title = \"%s\"
649         vacation = %s
650         efficiency = %s
651 '''%(key,  vals.get('name',False), vals.get('vacation', False), vals.get('efficiency', False))
652     
653             # Create a new project for each phase
654             func_str += '''
655 def Project_%d():
656     from resource.faces import Resource
657     title = \"%s\"
658     start = \'%s\'
659     minimum_time_unit = %s
660     working_hours_per_day = %s
661     working_days_per_week = %s
662     working_days_per_month = %s
663     working_days_per_year = %s
664     vacation = %s
665     working_days =  %s
666 '''%(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 )
667             
668             parent = False
669             task_ids = []
670             todo_task_ids = task_pool.search(cr, uid, [('project_id', '=', project.id),
671                                               ('state', 'in', ['draft', 'open', 'pending'])
672                                               ], order='sequence')
673             if todo_task_ids:
674                 for task in task_pool.browse(cr, uid, todo_task_ids, context=context):
675                     func_str += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=True,context=context)
676                     if not parent:
677                         parent = task
678                     task_ids.append(task.id)
679             func_str += cls_str
680
681             if not project.date_start:# or not project.members:
682                 raise osv.except_osv(_('Error !'),_('Task Scheduling is not possible.\nProject should have the Start date for scheduling.'))
683             # Allocating Memory for the required Project and Phases and Resources
684             exec(func_str)
685             Project = eval('Project_%d' % project.id)
686             project = None
687             try:
688                 project = Task.BalancedProject(Project)
689             except Exception, e:
690                 raise osv.except_osv(_('Error !'), e)
691             
692             for task_id in task_ids:
693                 task = eval("project.Task_%d" % task_id)
694                 start_date = task.start.to_datetime()
695                 end_date = task.end.to_datetime()
696                 
697                 task_pool.write(cr, uid, [task_id], {
698                                       'date_start': start_date.strftime('%Y-%m-%d'),
699                                       'date_end': end_date.strftime('%Y-%m-%d')
700                                     }, context=context)
701         return True
702
703 project()
704
705 class resource_resource(osv.osv):
706     _inherit = "resource.resource"
707     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
708         if context is None:
709             context = {}
710         if context.get('project_id',False):
711             project_pool = self.pool.get('project.project')
712             project_rec = project_pool.browse(cr, uid, context['project_id'], context=context)
713             user_ids = [user_id.id for user_id in project_rec.members]
714             args.append(('user_id','in',user_ids))
715         return super(resource_resource, self).search(cr, uid, args, offset, limit, order, context, count)
716
717 resource_resource()
718
719 class project_task(osv.osv):
720     _inherit = "project.task"
721     _columns = {
722         'phase_id': fields.many2one('project.phase', 'Project Phase'),
723     }
724     _defaults = {
725         'user_id' : False
726     }
727
728     def generate_task(self, cr, uid, task_id, parent=False, flag=False, context=None):
729         if context is None:
730             context = {}
731         task = self.browse(cr, uid, task_id, context=context)
732         duration = str(task.planned_hours )+ 'H'
733         str_resource = False
734         parent = task.parent_ids
735         if task.phase_id.resource_ids:
736             str_resource = ('%s | '*len(task.phase_id.resource_ids))[:-2]
737             str_resource = str_resource % tuple(map(lambda x: 'Resource_%s'%x.resource_id.id, task.phase_id.resource_ids))
738         # Task Defination for the Phase of the Project
739         if not flag:
740             s = '''
741         def Task_%s():
742             title = \"%s\"
743             effort = \'%s\'
744             resource = %s
745 '''%(task.id, task.name, duration, str_resource)
746             if parent:
747                 s +='''
748             start = up.Task_%s.end
749 '''%(parent[0].id)
750         else:
751             s = '''
752     def Task_%s():
753         title = \"%s\"
754         effort = \'%s\'
755         resource = %s
756 '''%(task.id, task.name, duration, str_resource)
757             if parent:
758                 s +='''
759         start = up.Task_%s.end
760 '''%(parent[0].id)
761         s += '\n'
762         return s
763 project_task()
764 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: