[IMP] project_long_term: refector scheduling of phase/task process to make compatable...
[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         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
227         for phase in self.browse(cr, uid, ids, context=context):
228             avg_days = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, day_uom_id)
229             duration = str(avg_days) + 'd'
230             # Create a new project for each phase
231             str_resource = ('%s,'*len(phase.resource_ids))[:-1]
232             str_vals = str_resource % tuple(map(lambda x: 'Resource_%s'%x.resource_id.id, phase.resource_ids))
233             # Phases Defination for the Project
234             s = '''
235     def Phase_%s():
236         effort = \'%s\'
237         resource = %s
238 '''%(phase.id, duration, str_vals or False)
239             if parent:
240                 start = 'up.Phase_%s.end' % (parent.id)
241             else:
242                 start = phase.project_id.date_start or phase.date_start
243                 #start = datetime.strftime((datetime.strptime(start, "%Y-%m-%d")), "%Y-%m-%d")
244             s += '''
245         start = %s
246 '''%(start)
247             f += s + '\n'
248             phase_ids.append(phase.id)
249             # Recursive call till all the next phases scheduled
250             for next_phase in phase.next_phase_ids:
251                 if next_phase.state in ['draft', 'open', 'pending']:
252                     rf, rphase_ids = self.generate_phase(cr, uid, [next_phase.id], f = '', parent=phase, context=context)
253                     f += rf +'\n'
254                     phase_ids += rphase_ids
255                 else:   
256                     continue
257         return f, phase_ids
258
259     
260
261     def schedule_tasks(self, cr, uid, ids, context=None):
262         """
263         Schedule the tasks according to resource available and priority.
264         """
265         task_pool = self.pool.get('project.task')
266         resource_pool = self.pool.get('resource.resource')
267         resources_list = self.generate_resources(cr, uid, ids, context=context)
268         return_msg = {}
269         for phase in self.browse(cr, uid, ids, context=context):
270             start_date = phase.date_start
271             if not start_date and phase.project_id.date_start:
272                 start_date = phase.project_id.date_start
273             if not start_date:
274                 start_date = datetime.now().strftime("%Y-%m-%d")
275             resources = resources_list.get(phase.id, [])
276             calendar_id = phase.project_id.resource_calendar_id.id
277             task_ids = map(lambda x : x.id, (filter(lambda x : x.state in ['draft'] , phase.task_ids))) #reassign only task not yet started
278             if task_ids:
279                 task_pool.generate_schedule(cr, uid, task_ids, resources, calendar_id, start_date, context=context)
280
281             if not task_ids:
282                 warning_msg = _("No tasks to compute for Phase '%s'.") % (phase.name)
283                 if "warning" not in return_msg:
284                     return_msg["warning"] =  warning_msg
285                 else:
286                     return_msg["warning"] = return_msg["warning"] + "\n" + warning_msg
287         return return_msg
288
289     def schedule_tasks(self, cr, uid, ids, context=None):
290         """
291         Schedule tasks base on faces lib
292         """
293         if context is None:
294             context = {}
295         if type(ids) in (long, int,):
296             ids = [ids]
297         task_pool = self.pool.get('project.task')
298         resource_pool = self.pool.get('resource.resource')
299         data_pool = self.pool.get('ir.model.data')
300         resource_allocation_pool = self.pool.get('project.resource.allocation')
301
302         for phase in self.browse(cr, uid, ids, context=context):
303             task_ids = task_pool.search(cr, uid, [('id', 'in', map(lambda x : x.id, phase.task_ids)),
304                                               ('state', 'in', ['draft', 'open', 'pending'])
305                                               ], order='sequence')
306             project = phase.project_id
307             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
308             start_date = project.date_start
309             #Creating resources using the member of the Project
310             u_ids = [i.id for i in project.members]
311             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
312             start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
313             func_str = ''
314             start = start_date
315             minimum_time_unit = 1
316             # default values
317             working_hours_per_day = 24
318             working_days_per_week = 7
319             working_days_per_month = 30
320             working_days_per_year = 365
321             
322             vacation = []
323             if calendar_id:
324                 working_hours_per_day = 8 #TODO: it should be come from calendars
325                 working_days_per_week = 5
326                 working_days_per_month = 20
327                 working_days_per_year = 200
328                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
329
330             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
331             
332             cls_str = ''
333             # Creating Resources for the Project
334             for key, vals in resource_objs.items():
335                 cls_str +='''
336     class Resource_%s(Resource):
337         vacation = %s
338         efficiency = %s
339 '''%(key,  vals.get('vacation', False), vals.get('efficiency', False))
340     
341             # Create a new project for each phase
342             func_str += '''
343 def Phase_%d():
344     from resource.faces import Resource
345     start = \'%s\'
346     minimum_time_unit = %s
347     working_hours_per_day = %s
348     working_days_per_week = %s
349     working_days_per_month = %s
350     working_days_per_year = %s
351     vacation = %s
352     working_days =  %s
353 '''%(phase.id, start, minimum_time_unit, working_hours_per_day,  working_days_per_week, working_days_per_month, working_days_per_year, vacation, working_days )
354             func_str += cls_str
355             parent = False
356             task_ids = []
357             for task in task_pool.browse(cr, uid, task_ids, context=context):
358                 func_str += task_pool.generate_task(cr, uid, task.id, parent=parent, context=context)
359                 if not parent:
360                     parent = task
361                 task_ids.append(task.id)
362
363             #Temp File to test the Code for the Allocation
364             #fn = '/home/hmo/Desktop/plt.py'
365             #fp = open(fn, 'w')
366             #fp.writelines(func_str)
367             #fp.close()
368     
369             # Allocating Memory for the required Project and Pahses and Resources
370             exec(func_str)
371             Phase = eval('Phase_%d' % phase.id)
372             phase = Task.BalancedProject(Phase)
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         resource_pool = self.pool.get('resource.resource')
443         data_pool = self.pool.get('ir.model.data')
444         resource_allocation_pool = self.pool.get('project.resource.allocation')
445         uom_pool = self.pool.get('product.uom')
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             
480             cls_str = ''
481             # Creating Resources for the Project
482             for key, vals in resource_objs.items():
483                 cls_str +='''
484     class Resource_%s(Resource):
485         vacation = %s
486         efficiency = %s
487 '''%(key,  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     start = \'%s\'
494     minimum_time_unit = %s
495     working_hours_per_day = %s
496     working_days_per_week = %s
497     working_days_per_month = %s
498     working_days_per_year = %s
499     vacation = %s
500     working_days =  %s
501 '''%(project.id, start, minimum_time_unit, working_hours_per_day,  working_days_per_week, working_days_per_month, working_days_per_year, vacation, working_days )
502             func_str += cls_str
503             phase_ids = []
504             for root_phase in phase_pool.browse(cr, uid, root_phase_ids, context=context):
505                 phases, child_phase_ids = phase_pool.generate_phase(cr, uid, [root_phase.id], '', context=context)
506                 func_str += phases
507                 phase_ids += child_phase_ids
508             #Temp File to test the Code for the Allocation
509             #fn = '/home/hmo/Desktop/plt.py'
510             #fp = open(fn, 'w')
511             #fp.writelines(func_str)
512             #fp.close()
513         
514             # Allocating Memory for the required Project and Pahses and Resources
515             exec(func_str)
516             Project = eval('Project_%d' % project.id)
517             project = Task.BalancedProject(Project)
518         
519             for phase_id in phase_ids:
520                 phase = eval("project.Phase_%d" % phase_id)
521                 start_date = phase.start.to_datetime()
522                 end_date = phase.end.to_datetime()
523     
524                 # Recalculate date_start and date_end
525                 # according to constraints on date start and date end on phase
526
527                 #if phase.constraint_date_start and str(s_date) < phase.constraint_date_start:
528                 #    start_date = datetime.strptime(phase.constraint_date_start, '%Y-%m-%d')
529                 #else:
530                 #    start_date = s_date
531                 #if phase.constraint_date_end and str(e_date) > phase.constraint_date_end:
532                 #    end_date= datetime.strptime(phase.constraint_date_end, '%Y-%m-%d')
533                 #    date_start = phase.constraint_date_end
534                 #else:
535                 #    end_date = e_date
536                 #    date_start = end_date
537
538                 # Write the calculated dates back
539                 #ctx = context.copy()
540                 #ctx.update({'scheduler': True})
541                 phase_pool.write(cr, uid, [phase_id], {
542                                       'date_start': start_date.strftime('%Y-%m-%d'),
543                                       'date_end': end_date.strftime('%Y-%m-%d')
544                                     }, context=context)
545         return True            
546
547     def schedule_tasks(self, cr, uid, ids, context=None):
548         """
549         Schedule task base on faces lib
550         """
551         if context is None:
552             context = {}
553         if type(ids) in (long, int,):
554             ids = [ids]
555         task_pool = self.pool.get('project.task')
556         resource_pool = self.pool.get('resource.resource')
557         data_pool = self.pool.get('ir.model.data')
558         resource_allocation_pool = self.pool.get('project.resource.allocation')
559         uom_pool = self.pool.get('product.uom')
560         data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day')
561
562         for project in self.browse(cr, uid, ids, context=context):
563             task_ids = task_pool.search(cr, uid, [('project_id', '=', project.id),
564                                               ('state', 'in', ['draft', 'open', 'pending'])
565                                               ], order='sequence')
566             calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
567             start_date = project.date_start
568             #Creating resources using the member of the Project
569             u_ids = [i.id for i in project.members]
570             resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context)
571             start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d")
572             func_str = ''
573             start = start_date
574             minimum_time_unit = 1
575             # default values
576             working_hours_per_day = 24
577             working_days_per_week = 7
578             working_days_per_month = 30
579             working_days_per_year = 365
580             
581             vacation = []
582             if calendar_id:
583                 working_hours_per_day = 8 #TODO: it should be come from calendars
584                 working_days_per_week = 5
585                 working_days_per_month = 20
586                 working_days_per_year = 200
587                 vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
588
589             working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
590             
591             cls_str = ''
592             # Creating Resources for the Project
593             for key, vals in resource_objs.items():
594                 cls_str +='''
595     class Resource_%s(Resource):
596         vacation = %s
597         efficiency = %s
598 '''%(key,  vals.get('vacation', False), vals.get('efficiency', False))
599     
600             # Create a new project for each phase
601             func_str += '''
602 def Project_%d():
603     from resource.faces import Resource
604     start = \'%s\'
605     minimum_time_unit = %s
606     working_hours_per_day = %s
607     working_days_per_week = %s
608     working_days_per_month = %s
609     working_days_per_year = %s
610     vacation = %s
611     working_days =  %s
612 '''%(project.id, start, minimum_time_unit, working_hours_per_day,  working_days_per_week, working_days_per_month, working_days_per_year, vacation, working_days )
613             func_str += cls_str
614             parent = False
615             task_ids = []
616             for task in task_pool.browse(cr, uid, task_ids, context=context):
617                 func_str += task_pool.generate_task(cr, uid, task.id, parent=parent, context=context)
618                 if not parent:
619                     parent = task
620                 task_ids.append(task.id)
621
622             #Temp File to test the Code for the Allocation
623             fn = '/home/hmo/Desktop/plt.py'
624             fp = open(fn, 'w')
625             fp.writelines(func_str)
626             fp.close()
627     
628             # Allocating Memory for the required Project and Pahses and Resources
629             exec(func_str)
630             Project = eval('Project_%d' % project.id)
631             project = Task.BalancedProject(Project)
632         
633             for task_id in task_ids:
634                 task = eval("project.Task_%d" % task_id)
635                 start_date = task.start.to_datetime()
636                 end_date = task.end.to_datetime()
637                 
638                 task_pool.write(cr, uid, [task_id], {
639                                       'date_start': start_date.strftime('%Y-%m-%d'),
640                                       'date_end': end_date.strftime('%Y-%m-%d')
641                                     }, context=context)
642         return True
643
644 project()
645
646 class resource_resource(osv.osv):
647     _inherit = "resource.resource"
648     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
649         if context is None:
650             context = {}
651         if context.get('project_id',False):
652             project_pool = self.pool.get('project.project')
653             project_rec = project_pool.browse(cr, uid, context['project_id'], context=context)
654             user_ids = [user_id.id for user_id in project_rec.members]
655             args.append(('user_id','in',user_ids))
656         return super(resource_resource, self).search(cr, uid, args, offset, limit, order, context, count)
657
658 resource_resource()
659
660 class project_task(osv.osv):
661     _inherit = "project.task"
662     _columns = {
663         'phase_id': fields.many2one('project.phase', 'Project Phase'),
664     }
665
666     def generate_task(self, cr, uid, task_id, parent=False, context=None):
667         if context is None:
668             context = {}
669         resource_pool = self.pool.get('resource.resource')
670         resource_allocation_pool = self.pool.get('project.resource.allocation')
671         task = self.browse(cr, uid, task_id, context=context)
672         duration = str(task.planned_hours )+ 'H'
673         resource_ids = self.search(cr, uid, [('user_id', '=', task.user_id.id)], context=context)
674         resource = False
675         if len(resource_ids):
676             resource = 'Resource_%s'%resource_ids[0]
677         # Phases Defination for the Project
678         s = '''
679     def Task_%s():
680         effort = \'%s\'
681         resource = %s
682 '''%(task.id, duration, resource)
683         if parent:
684             start = 'up.Task_%s.end' % (parent.id)
685         else:
686             start = task.project_id.date_start or task.date_start
687             #start = datetime.strftime((datetime.strptime(start, "%Y-%m-%d")), "%Y-%m-%d")
688         s += '''
689         start = %s
690 '''%(start)
691         s += '\n'
692         return s
693 project_task()
694 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: