diff options
| author | Harry (OpenERP) <hmo@tinyerp.com> | 2010-09-24 14:56:14 +0530 |
|---|---|---|
| committer | Harry (OpenERP) <hmo@tinyerp.com> | 2010-09-24 14:56:14 +0530 |
| commit | fb383d75e475900d105a853f00063438e9d5be02 (patch) | |
| tree | 754db348eeefb1adbeacb7b6425075d1f316db3f /addons/project_long_term/project_long_term.py | |
| parent | 6c98b87e5ad7e535a146b26bd01ab54a4596005e (diff) | |
[REF] project_long_term: Refactor code to compute tasks and phases
bzr revid: hmo@tinyerp.com-20100924092614-kxmx5r8cox7hg4ry
Diffstat (limited to 'addons/project_long_term/project_long_term.py')
| -rw-r--r-- | addons/project_long_term/project_long_term.py | 308 |
1 files changed, 293 insertions, 15 deletions
diff --git a/addons/project_long_term/project_long_term.py b/addons/project_long_term/project_long_term.py index b795dda557e..05eca01887c 100644 --- a/addons/project_long_term/project_long_term.py +++ b/addons/project_long_term/project_long_term.py @@ -21,9 +21,10 @@ import time from datetime import date, datetime, timedelta - from tools.translate import _ from osv import fields, osv +from resource.faces import task as Task +import operator class project_phase(osv.osv): _name = "project.phase" @@ -181,38 +182,48 @@ class project_phase(osv.osv): uom_obj = self.pool.get('product.uom') if context is None: context = {} + res = super(project_phase, self).write(cr, uid, ids, vals, context=context) if context.get('scheduler',False): - return super(project_phase, self).write(cr, uid, ids, vals, context=context) + return res # Consider calendar and efficiency if the phase is performed by a resource # otherwise consider the project's working calendar + + #TOCHECK : why need this ? if isinstance(ids, (int, long)): ids = [ids] - phase = self.browse(cr, uid, ids[0], context=context) - calendar_id = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False - resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)],context=context) - if resource_id: + default_uom_id = self._get_default_uom_id(cr, uid) + for phase in self.browse(cr, uid, ids, context=context): + calendar_id = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False + resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)],context=context) + if resource_id: cal_id = resource_obj.browse(cr, uid, resource_id[0], context=context).calendar_id.id if cal_id: calendar_id = cal_id - default_uom_id = self._get_default_uom_id(cr, uid) - avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id) - # Change the date_start and date_end - # for previous and next phases respectively based on valid condition - if vals.get('date_start', False) and vals['date_start'] < phase.date_start: + + avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id) + # Change the date_start and date_end + # for previous and next phases respectively based on valid condition + if vals.get('date_start', False) and vals['date_start'] < phase.date_start: dt_start = datetime.strptime(vals['date_start'], '%Y-%m-%d') work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id, dt_start, avg_hours or 0.0, resource_id and resource_id[0] or False) if work_times: vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d') for prv_phase in phase.previous_phase_ids: + if prv_phase.id == phase.id: + continue self._check_date_start(cr, uid, prv_phase, dt_start, context=context) - if vals.get('date_end', False) and vals['date_end'] > phase.date_end: + + if vals.get('date_end', False) and vals['date_end'] > phase.date_end: dt_end = datetime.strptime(vals['date_end'], '%Y-%m-%d') work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id, dt_end, avg_hours or 0.0, resource_id and resource_id[0] or False) if work_times: vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d') for next_phase in phase.next_phase_ids: + if next_phase.id == phase.id: + continue self._check_date_end(cr, uid, next_phase, dt_end, context=context) - return super(project_phase, self).write(cr, uid, ids, vals, context=context) + + return res def copy(self, cr, uid, id, default=None, context=None): if default is None: @@ -241,6 +252,118 @@ class project_phase(osv.osv): self.write(cr, uid, ids, {'state': 'done'}) return True + def generate_resources(self, cr, uid, ids, context=None): + """ + Return a list of Resource Class objects for the resources allocated to the phase. + """ + res = {} + if context is None: + context = {} + resource_pool = self.pool.get('resource.resource') + for phase in self.browse(cr, uid, ids, context=context): + user_ids = map(lambda x:x.resource_id.user_id.id, phase.resource_ids) + project = phase.project_id + calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False + resource_objs = resource_pool.generate_resources(cr, uid, user_ids, calendar_id, context=context) + res[phase.id] = resource_objs + return res + + def generate_schedule(self, cr, uid, ids, start_date, calendar_id=False, context=None): + """ + Schedule phase with the start date till all the next phases are completed. + @param: start_dsate : start date for the phase + @param: calendar_id : working calendar of the project + """ + if context is None: + context = {} + resource_pool = self.pool.get('resource.resource') + uom_pool = self.pool.get('product.uom') + phase_resource = False + if context is None: + context = {} + default_uom_id = self._get_default_uom_id(cr, uid) + for phase in self.browse(cr, uid, ids, context=context): + if not phase.responsible_id: + raise osv.except_osv(_('No responsible person assigned !'),_("You must assign a responsible person for phase '%s' !") % (phase.name,)) + + phase_resource_obj = resource_pool.generate_resources(cr, uid, [phase.responsible_id.id], calendar_id, context=context) + avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id) + duration = str(avg_hours) + 'H' + # Create a new project for each phase + def Project(): + start = start_date + minimum_time_unit = 1 + resource = phase_resource_obj + # If project has working calendar then that + # else the default one would be considered + if calendar_id: + working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context) + vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id)) + + def phase(): + effort = duration + + project = Task.BalancedProject(Project) + s_date = project.phase.start.to_datetime() + e_date = project.phase.end.to_datetime() + # Recalculate date_start and date_end + # according to constraints on date start and date end on phase + if phase.constraint_date_start and str(s_date) < phase.constraint_date_start: + start_date = datetime.strptime(phase.constraint_date_start, '%Y-%m-%d') + else: + start_date = s_date + if phase.constraint_date_end and str(e_date) > phase.constraint_date_end: + end_date= datetime.strptime(phase.constraint_date_end, '%Y-%m-%d') + date_start = phase.constraint_date_end + else: + end_date = e_date + date_start = end_date + # Write the calculated dates back + ctx = context.copy() + ctx.update({'scheduler': True}) + self.write(cr, uid, [phase.id], { + 'date_start': start_date.strftime('%Y-%m-%d'), + 'date_end': end_date.strftime('%Y-%m-%d') + }, context=ctx) + + # Recursive call till all the next phases scheduled + for phase in phase.next_phase_ids: + if phase.state in ['draft', 'open', 'pending']: + id_cal = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False + self.generate_schedule(cr, uid, [phase.id], date_start, id_cal, context=context) + else: + continue + return True + + def schedule_tasks(self, cr, uid, ids, context=None): + """ + Schedule the tasks according to resource available and priority. + """ + task_pool = self.pool.get('project.task') + resource_pool = self.pool.get('resource.resource') + if context is None: + context = {} + resources_list = self.generate_resources(cr, uid, ids, context=context) + return_msg = {} + for phase in self.browse(cr, uid, ids, context=context): + start_date = phase.date_start + if not start_date and phase.project_id.date_start: + start_date = phase.project_id.date_start + if not start_date: + start_date = datetime.now().strftime("%Y-%m-%d") + resources = resources_list.get(phase.id, []) + calendar_id = phase.project_id.resource_calendar_id.id + task_ids = map(lambda x : x.id, (filter(lambda x : x.state in ['open', 'draft', 'pending'] , phase.task_ids))) + if task_ids: + task_pool.generate_schedule(cr, uid, task_ids, resources, calendar_id, start_date, context=context) + + if not task_ids: + warning_msg = _("No tasks to compute for Phase '%s'.") % (phase.name) + if "warning" not in return_msg: + return_msg["warning"] = warning_msg + else: + return_msg["warning"] = return_msg["warning"] + "\n" + warning_msg + return return_msg project_phase() class project_resource_allocation(osv.osv): @@ -266,14 +389,169 @@ class project(osv.osv): 'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"), 'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ), } + def generate_members(self, cr, uid, ids, context=None): + """ + Return a list of Resource Class objects for the resources allocated to the phase. + """ + res = {} + resource_pool = self.pool.get('resource.resource') + if context is None: + context = {} + for project in self.browse(cr, uid, ids, context=context): + user_ids = map(lambda x:x.id, project.members) + calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False + resource_objs = resource_pool.generate_resources(cr, uid, user_ids, calendar_id, context=context) + res[project.id] = resource_objs + return res + + def schedule_phases(self, cr, uid, ids, context=None): + """ + Schedule the phases. + """ + if context is None: + context = {} + if type(ids) in (long, int,): + ids = [ids] + phase_pool = self.pool.get('project.phase') + for project in self.browse(cr, uid, ids, context=context): + phase_ids = phase_pool.search(cr, uid, [('project_id', '=', project.id), + ('state', 'in', ['draft', 'open', 'pending']), + ('previous_phase_ids', '=', False) + ]) + start_date = project.date_start + if not start_date: + start_date = datetime.now().strftime("%Y-%m-%d") + start_dt = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d %H:%M") + calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False + phase_pool.generate_schedule(cr, uid, phase_ids, start_dt, calendar_id, context=context) + return True + + def schedule_tasks(self, cr, uid, ids, context=None): + """ + Schedule the tasks according to resource available and priority. + """ + if type(ids) in (long, int,): + ids = [ids] + user_pool = self.pool.get('res.users') + task_pool = self.pool.get('project.task') + resource_pool = self.pool.get('resource.resource') + if context is None: + context = {} + + resources_list = self.generate_members(cr, uid, ids, context=context) + return_msg = {} + for project in self.browse(cr, uid, ids, context=context): + start_date = project.date_start + if not start_date: + start_date = datetime.now().strftime("%Y-%m-%d") + resources = resources_list.get(project.id, []) + calendar_id = project.resource_calendar_id.id + task_ids = task_pool.search(cr, uid, [('project_id', '=', project.id), + ('state', 'in', ['draft', 'open', 'pending']) + ]) + + + if task_ids: + task_pool.generate_schedule(cr, uid, task_ids, resources, calendar_id, start_date, context=context) + else: + warning_msg = _("No tasks to compute for Project '%s'.") % (project.name) + if "warning" not in return_msg: + return_msg["warning"] = warning_msg + else: + return_msg["warning"] = return_msg["warning"] + "\n" + warning_msg + + return return_msg project() -class task(osv.osv): +class resource_resource(osv.osv): + _inherit = "resource.resource" + def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): + if context is None: + context = {} + if context.get('project_id',False): + project_pool = self.pool.get('project.project') + project_rec = project_pool.browse(cr, uid, context['project_id']) + user_ids = [user_id.id for user_id in project_rec.members] + args.append(('user_id','in',user_ids)) + return super(resource_resource, self).search(cr, uid, args, offset, limit, order, context, count) + +resource_resource() + +class project_task(osv.osv): _inherit = "project.task" _columns = { 'phase_id': fields.many2one('project.phase', 'Project Phase'), } + + def generate_schedule(self, cr, uid, ids, resources, calendar_id, start_date, context=None): + """ + Schedule the tasks according to resource available and priority. + """ + if not ids: + return False + if context is None: + context = {} + user_pool = self.pool.get('res.users') + project_pool = self.pool.get('project.project') + priority_dict = {'0': 1000, '1': 800, '2': 500, '3': 300, '4': 100} + # Create dynamic no of tasks with the resource specified + def create_tasks(task_number, eff, priorty=500, obj=False): + def task(): + """ + task is a dynamic method! + """ + effort = eff + if obj: + resource = obj + priority = priorty + task.__doc__ = "TaskNO%d" %task_number + task.__name__ = "task%d" %task_number + return task + + # Create a 'Faces' project with all the tasks and resources + def Project(): + title = "Project" + start = datetime.strftime(datetime.strptime(start_date, "%Y-%m-%d"), "%Y-%m-%d %H:%M") + resource = reduce(operator.or_, resources) + minimum_time_unit = 1 + if calendar_id: # If project has working calendar + working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context) + vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context)) + # Dynamic creation of tasks + task_number = 0 + for openobect_task in self.browse(cr, uid, ids, context=context): + hours = str(openobect_task.planned_hours )+ 'H' + if openobect_task.priority in priority_dict.keys(): + priorty = priority_dict[openobect_task.priority] + real_resource = False + if openobect_task.user_id: + for task_resource in resources: + if task_resource.__name__ == task_resource: + real_resource = task_resource + break + + task = create_tasks(task_number, hours, priorty, real_resource) + task_number += 1 + -task() + face_projects = Task.BalancedProject(Project) + loop_no = 0 + # Write back the computed dates + for face_project in face_projects: + s_date = face_project.start.to_datetime() + e_date = face_project.end.to_datetime() + if loop_no > 0: + ctx = context.copy() + ctx.update({'scheduler': True}) + user_id = user_pool.search(cr, uid, [('name', '=', face_project.booked_resource[0].__name__)]) + self.write(cr, uid, [ids[loop_no-1]], { + 'date_start': s_date.strftime('%Y-%m-%d %H:%M:%S'), + 'date_end': e_date.strftime('%Y-%m-%d %H:%M:%S'), + 'user_id': user_id[0] + }, context=ctx) + + loop_no += 1 + return True +project_task() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
