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