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