[IMP] project_long_term : Resource allocation Scheduling Warning Message Improved
[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         for project in self.browse(cr, uid, ids, context=context):
449             root_phase_ids = phase_pool.search(cr, uid, [('project_id', '=', project.id),
450                                                   ('state', 'in', ['draft', 'open', 'pending']),
451                                                   ('previous_phase_ids', '=', False)
452                                                   ])
453             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
454             start_date = project.date_start
455             #if start_date:
456             #    start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
457             #Creating resources using the member of the Project
458             u_ids = [i.id for i in project.members]
459             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
460             func_str = ''
461             start = start_date
462             minimum_time_unit = 1
463             # default values
464             working_hours_per_day = 24
465             working_days_per_week = 7
466             working_days_per_month = 30
467             working_days_per_year = 365
468             
469             vacation = []
470             if calendar_id:
471                 working_hours_per_day = 8 #TODO: it should be come from calendars
472                 working_days_per_week = 5
473                 working_days_per_month = 20
474                 working_days_per_year = 200
475                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
476
477             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
478             
479             cls_str = ''
480             # Creating Resources for the Project
481             for key, vals in resource_objs.items():
482                 cls_str +='''
483     class Resource_%s(Resource):
484         title = \"%s\"
485         vacation = %s
486         efficiency = %s
487 '''%(key,  vals.get('name',False), vals.get('vacation', False), vals.get('efficiency', False))
488         
489             # Create a new project for each phase
490             func_str += '''
491 def Project_%d():
492     from resource.faces import Resource
493     title = \"%s\"
494     start = \'%s\'
495     minimum_time_unit = %s
496     working_hours_per_day = %s
497     working_days_per_week = %s
498     working_days_per_month = %s
499     working_days_per_year = %s
500     vacation = %s
501     working_days =  %s
502 '''%(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 )
503
504             func_str += cls_str
505             phase_ids = []
506             for root_phase in phase_pool.browse(cr, uid, root_phase_ids, context=context):
507                 phases, child_phase_ids = phase_pool.generate_phase(cr, uid, [root_phase.id], '', context=context)
508                 func_str += phases
509                 phase_ids += child_phase_ids
510         
511             # Allocating Memory for the required Project and Pahses and Resources
512             exec(func_str)
513             Project = eval('Project_%d' % project.id)
514             project = None
515             try:
516                 project = Task.BalancedProject(Project)
517             except :
518                 raise osv.except_osv(_('Error !'),_('Invalid Project Members.\nPhase Scheduling is not possible.'))
519             
520             for phase_id in phase_ids:
521                 act_phase = phase_pool.browse(cr, uid, phase_id, context=context)
522                 resources = act_phase.resource_ids
523                 phase = eval("project.Phase_%d" % phase_id)
524                 start_date = phase.start.to_datetime()
525                 end_date = phase.end.to_datetime()
526                 
527                 if resources:
528                     for res in resources:
529                         vals = {}
530                         vals.update({'date_start' :  start_date })
531                         vals.update({'date_end' :  end_date})
532                         resource_allocation_pool.write(cr, uid, res.id, vals, context=context)
533                 if act_phase.task_ids:
534                     for task in act_phase.task_ids:
535                         vals = {}
536                         #Getting values of the Tasks
537                         temp = eval("phase.Task_%s"%task.id)
538                         if temp.booked_resource:
539                             res_name = temp.booked_resource[0].title
540                             res_id = resource_pool.search(cr, uid,[('name','=',res_name)], context = context)
541                             if res_id:
542                                 res = resource_pool.browse(cr, uid, res_id[0], context = context)
543                                 vals.update({'user_id' : res.user_id.id})
544                                                
545                         vals.update({'date_start' : temp.start.strftime('%Y-%m-%d %H:%M:%S')})
546                         vals.update({'date_end' : temp.end.strftime('%Y-%m-%d %H:%M:%S')})
547                         task_pool.write(cr, uid, task.id, vals, context=context)
548
549
550                 phase_pool.write(cr, uid, [phase_id], {
551                                       'date_start': start_date.strftime('%Y-%m-%d'),
552                                       'date_end': end_date.strftime('%Y-%m-%d')
553                                     }, context=context)
554         return True            
555
556     #TODO: DO Resource allocation and compute availability
557     def compute_allocation(self, rc, uid, ids, start_date, end_date, context=None):
558         if context ==  None:
559             context = {}
560         allocation = {}
561         return allocation
562
563     def schedule_tasks(self, cr, uid, ids, context=None):
564         """
565         Schedule task base on faces lib
566         """
567         if context is None:
568             context = {}
569         if type(ids) in (long, int,):
570             ids = [ids]
571         task_pool = self.pool.get('project.task')
572         resource_pool = self.pool.get('resource.resource')
573         data_pool = self.pool.get('ir.model.data')
574         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
575
576         for project in self.browse(cr, uid, ids, context=context):
577             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
578             start_date = project.date_start
579             #Creating resources using the member of the Project
580             u_ids = [i.id for i in project.members]
581             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
582             try:
583                 start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
584             except:
585                 raise osv.except_osv(_('Error !'),_('Task Scheduling is not possible.\nProject should have the Start date for scheduling.'))
586             func_str = ''
587             start = start_date
588             minimum_time_unit = 1
589             # default values
590             working_hours_per_day = 24
591             working_days_per_week = 7
592             working_days_per_month = 30
593             working_days_per_year = 365
594             
595             vacation = []
596             if calendar_id:
597                 working_hours_per_day = 8 #TODO: it should be come from calendars
598                 working_days_per_week = 5
599                 working_days_per_month = 20
600                 working_days_per_year = 200
601                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
602
603             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
604             
605             cls_str = ''
606             # Creating Resources for the Project
607             for key, vals in resource_objs.items():
608                 cls_str +='''
609     class Resource_%s(Resource):
610         title = \"%s\"
611         vacation = %s
612         efficiency = %s
613 '''%(key,  vals.get('name',False), vals.get('vacation', False), vals.get('efficiency', False))
614     
615             # Create a new project for each phase
616             func_str += '''
617 def Project_%d():
618     from resource.faces import Resource
619     title = \"%s\"
620     start = \'%s\'
621     minimum_time_unit = %s
622     working_hours_per_day = %s
623     working_days_per_week = %s
624     working_days_per_month = %s
625     working_days_per_year = %s
626     vacation = %s
627     working_days =  %s
628 '''%(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 )
629             
630             parent = False
631             task_ids = []
632             todo_task_ids = task_pool.search(cr, uid, [('project_id', '=', project.id),
633                                               ('state', 'in', ['draft', 'open', 'pending'])
634                                               ], order='sequence')
635             if todo_task_ids:
636                 for task in task_pool.browse(cr, uid, todo_task_ids, context=context):
637                     func_str += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=True,context=context)
638                     if not parent:
639                         parent = task
640                     task_ids.append(task.id)
641             func_str += cls_str
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 = None
647             try:
648                 project = Task.BalancedProject(Project)
649             except :
650                 raise osv.except_osv(_('Error !'),_('Phase Scheduling is not possible.\nProject should have the Start date and member for scheduling.'))
651             
652             for task_id in task_ids:
653                 task = eval("project.Task_%d" % task_id)
654                 start_date = task.start.to_datetime()
655                 end_date = task.end.to_datetime()
656                 
657                 task_pool.write(cr, uid, [task_id], {
658                                       'date_start': start_date.strftime('%Y-%m-%d'),
659                                       'date_end': end_date.strftime('%Y-%m-%d')
660                                     }, context=context)
661         return True
662
663 project()
664
665 class resource_resource(osv.osv):
666     _inherit = "resource.resource"
667     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
668         if context is None:
669             context = {}
670         if context.get('project_id',False):
671             project_pool = self.pool.get('project.project')
672             project_rec = project_pool.browse(cr, uid, context['project_id'], context=context)
673             user_ids = [user_id.id for user_id in project_rec.members]
674             args.append(('user_id','in',user_ids))
675         return super(resource_resource, self).search(cr, uid, args, offset, limit, order, context, count)
676
677 resource_resource()
678
679 class project_task(osv.osv):
680     _inherit = "project.task"
681     _columns = {
682         'phase_id': fields.many2one('project.phase', 'Project Phase'),
683     }
684     _defaults = {
685         'user_id' : False
686     }
687
688     def generate_task(self, cr, uid, task_id, parent=False, flag=False, context=None):
689         if context is None:
690             context = {}
691         task = self.browse(cr, uid, task_id, context=context)
692         duration = str(task.planned_hours )+ 'H'
693         str_resource = False
694         if task.phase_id.resource_ids:
695             str_resource = ('%s | '*len(task.phase_id.resource_ids))[:-2]
696             str_resource = str_resource % tuple(map(lambda x: 'Resource_%s'%x.resource_id.id, task.phase_id.resource_ids))
697         # Task Defination for the Phase of the Project
698         if not flag:
699             s = '''
700         def Task_%s():
701             title = \"%s\"
702             effort = \'%s\'
703             resource = %s
704 '''%(task.id, task.name, duration, str_resource)
705             #start = datetime.strftime((datetime.strptime(start, "%Y-%m-%d")), "%Y-%m-%d")
706         else:
707             s = '''
708     def Task_%s():
709         title = \"%s\"
710         effort = \'%s\'
711         resource = %s
712 '''%(task.id, task.name, duration, str_resource)
713         s += '\n'
714         return s
715 project_task()
716 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: