diff options
| author | sbh (Open ERP) <sbh@tinyerp.com> | 2010-11-01 11:43:43 +0530 |
|---|---|---|
| committer | sbh (Open ERP) <sbh@tinyerp.com> | 2010-11-01 11:43:43 +0530 |
| commit | aaa0b610b1017ff4f9df9add2ee6cc5af5ade421 (patch) | |
| tree | b371309cd97bccb9d5195813189e3c42a244b789 /addons/project_long_term/project_long_term.py | |
| parent | 263091c0e454f4de86de49310a8b50d565969fc7 (diff) | |
| parent | 9d44b90544bc1636e493c9be7301dbda69e1921f (diff) | |
[MERGE] Merge with trunk-dev-addons1
bzr revid: sbh@tinyerp.com-20101101061343-oefsywzw2l441hub
Diffstat (limited to 'addons/project_long_term/project_long_term.py')
| -rw-r--r-- | addons/project_long_term/project_long_term.py | 566 |
1 files changed, 396 insertions, 170 deletions
diff --git a/addons/project_long_term/project_long_term.py b/addons/project_long_term/project_long_term.py index 212af8a30dc..faea0916906 100644 --- a/addons/project_long_term/project_long_term.py +++ b/addons/project_long_term/project_long_term.py @@ -18,18 +18,22 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # ############################################################################## -from lxml import etree -import mx.DateTime -import time +from datetime import datetime +from dateutil.relativedelta import relativedelta 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" _description = "Project Phase" - def _check_recursion(self, cr, uid, ids, context={}): + def _check_recursion(self, cr, uid, ids, context=None): + if context is None: + context = {} + data_phase = self.browse(cr, uid, ids[0], context=context) prev_ids = data_phase.previous_phase_ids next_ids = data_phase.next_phase_ids @@ -45,7 +49,7 @@ class project_phase(osv.osv): next_ids = [rec.id for rec in next_ids] # iter prev_ids while prev_ids: - cr.execute('select distinct prv_phase_id from project_phase_rel where next_phase_id in ('+','.join(map(str, prev_ids))+')') + cr.execute('SELECT distinct prv_phase_id FROM project_phase_rel WHERE next_phase_id IN %s', (tuple(prev_ids),)) prv_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall())) if data_phase.id in prv_phase_ids: return False @@ -55,7 +59,7 @@ class project_phase(osv.osv): prev_ids = prv_phase_ids # iter next_ids while next_ids: - cr.execute('select distinct next_phase_id from project_phase_rel where prv_phase_id in ('+','.join(map(str, next_ids))+')') + cr.execute('SELECT distinct next_phase_id FROM project_phase_rel WHERE prv_phase_id IN %s', (tuple(next_ids),)) next_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall())) if data_phase.id in next_phase_ids: return False @@ -65,68 +69,92 @@ class project_phase(osv.osv): next_ids = next_phase_ids return True - def _check_dates(self, cr, uid, ids, context={}): + def _check_dates(self, cr, uid, ids, context=None): for phase in self.read(cr, uid, ids, ['date_start', 'date_end'], context=context): if phase['date_start'] and phase['date_end'] and phase['date_start'] > phase['date_end']: return False return True - def _check_constraint_start(self, cr, uid, ids, context={}): + def _check_constraint_start(self, cr, uid, ids, context=None): phase = self.read(cr, uid, ids[0], ['date_start', 'constraint_date_start'], context=context) if phase['date_start'] and phase['constraint_date_start'] and phase['date_start'] < phase['constraint_date_start']: return False return True - def _check_constraint_end(self, cr, uid, ids, context={}): + def _check_constraint_end(self, cr, uid, ids, context=None): phase = self.read(cr, uid, ids[0], ['date_end', 'constraint_date_end'], context=context) if phase['date_end'] and phase['constraint_date_end'] and phase['date_end'] > phase['constraint_date_end']: return False return True + def _get_default_uom_id(self, cr, uid): + model_data_obj = self.pool.get('ir.model.data') + model_data_id = model_data_obj._get_id(cr, uid, 'product', 'uom_hour') + return model_data_obj.read(cr, uid, [model_data_id], ['res_id'])[0]['res_id'] + + def _compute(self, cr, uid, ids, field_name, arg, context=None): + res = {} + if not ids: + return res + for phase in self.browse(cr, uid, ids, context=context): + tot = 0.0 + for task in phase.task_ids: + tot += task.planned_hours + res[phase.id] = tot + return res + _columns = { 'name': fields.char("Name", size=64, required=True), - 'date_start': fields.datetime('Start Date', help="Starting Date of the phase"), - 'date_end': fields.datetime('End Date', help="Ending Date of the phase"), - 'constraint_date_start': fields.datetime('Start Date', help='force the phase to start after this date'), - 'constraint_date_end': fields.datetime('End Date', help='force the phase to finish before this date'), + 'date_start': fields.date('Start Date', help="Starting Date of the phase", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), + 'date_end': fields.date('End Date', help="Ending Date of the phase", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), + 'constraint_date_start': fields.date('Minimum Start Date', help='force the phase to start after this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), + 'constraint_date_end': fields.date('Deadline', help='force the phase to finish before this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'project_id': fields.many2one('project.project', 'Project', required=True), - 'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases'), - 'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases'), + 'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases', states={'cancelled':[('readonly',True)]}), + 'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases', states={'cancelled':[('readonly',True)]}), 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of phases."), - 'duration': fields.float('Duration', required=True, help="By default in days"), - 'product_uom': fields.many2one('product.uom', 'Duration UoM', required=True, help="UoM (Unit of Measure) is the unit of measurement for Duration"), - 'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks"), - 'resource_ids': fields.one2many('project.resource.allocation', 'phase_id', "Project Resources"), - 'responsible_id':fields.many2one('res.users', 'Responsible'), + 'duration': fields.float('Duration', required=True, help="By default in days", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), + '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)]}), + 'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), + 'resource_ids': fields.one2many('project.resource.allocation', 'phase_id', "Project Resources",states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), + 'responsible_id': fields.many2one('res.users', 'Responsible', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'state': fields.selection([('draft', 'Draft'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True, 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.\ - \n If the phase is over, the states is set to \'Done\'.') + \n If the phase is over, the states is set to \'Done\'.'), + 'total_hours': fields.function(_compute, method=True, string='Total Hours'), } _defaults = { 'responsible_id': lambda obj,cr,uid,context: uid, 'state': 'draft', 'sequence': 10, - 'product_uom': lambda self,cr,uid,c: self.pool.get('product.uom').search(cr, uid, [('name', '=', 'Day')], context=c)[0] + 'product_uom': lambda self,cr,uid,c: self.pool.get('product.uom').search(cr, uid, [('name', '=', _('Day'))], context=c)[0] } - _order = "name" + _order = "project_id, date_start, sequence, name" _constraints = [ (_check_recursion,'Loops in phases not allowed',['next_phase_ids', 'previous_phase_ids']), (_check_dates, 'Phase start-date must be lower than phase end-date.', ['date_start', 'date_end']), - #(_check_constraint_start, 'Phase must start-after constraint start Date.', ['date_start', 'constraint_date_start']), - #(_check_constraint_end, 'Phase must end-before constraint end Date.', ['date_end', 'constraint_date_end']), ] - def onchange_project(self, cr, uid, ids, project, context={}): + def onchange_project(self, cr, uid, ids, project, context=None): result = {} + result['date_start'] = False project_obj = self.pool.get('project.project') if project: project_id = project_obj.browse(cr, uid, project, context=context) - if project_id.date_start: - result['date_start'] = mx.DateTime.strptime(project_id.date_start, "%Y-%m-%d").strftime('%Y-%m-%d %H:%M:%S') - return {'value': result} - return {'value': {'date_start': []}} + result['date_start'] = project_id.date_start + return {'value': result} - def _check_date_start(self, cr, uid, phase, date_end, context={}): + def onchange_days(self, cr, uid, ids, project, context=None): + result = {} + for id in ids: + project_id = self.browse(cr, uid, id, context=context) + newdate = datetime.strptime(project_id.date_start, '%Y-%m-%d') + relativedelta(days=project_id.duration or 0.0) + result['date_end'] = newdate.strftime('%Y-%m-%d') + return {'value': result} + + def _check_date_start(self, cr, uid, phase, date_end, context=None): + if context is None: + context = {} """ Check And Compute date_end of phase if change in date_start < older time. """ @@ -136,18 +164,19 @@ class project_phase(osv.osv): 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)]) if resource_id: -# cal_id = resource_obj.browse(cr, uid, resource_id[0], context=context).calendar_id.id - res = resource_obj.read(cr, uid, resource_id[0], ['calendar_id'], context=context)[0] + res = resource_obj.read(cr, uid, resource_id, ['calendar_id'], context=context)[0] cal_id = res.get('calendar_id', False) and res.get('calendar_id')[0] or False if cal_id: calendar_id = cal_id - default_uom_id = uom_obj.search(cr, uid, [('name', '=', 'Hour')], context=context)[0] + 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) 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) - dt_start = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S') - self.write(cr, uid, [phase.id], {'date_start': dt_start, 'date_end': date_end.strftime('%Y-%m-%d %H:%M:%S')}, context=context) + dt_start = work_times[0][0].strftime('%Y-%m-%d') + self.write(cr, uid, [phase.id], {'date_start': dt_start, 'date_end': date_end.strftime('%Y-%m-%d')}, context=context) - def _check_date_end(self, cr, uid, phase, date_start, context={}): + def _check_date_end(self, cr, uid, phase, date_start, context=None): + if context is None: + context = {} """ Check And Compute date_end of phase if change in date_end > older time. """ @@ -157,54 +186,71 @@ class project_phase(osv.osv): 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 - res = resource_obj.read(cr, uid, resource_id[0], ['calendar_id'], context=context)[0] + res = resource_obj.read(cr, uid, resource_id, ['calendar_id'], context=context)[0] cal_id = res.get('calendar_id', False) and res.get('calendar_id')[0] or False if cal_id: calendar_id = cal_id - default_uom_id = uom_obj.search(cr, uid, [('name', '=', 'Hour')], context=context)[0] + 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) 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) - dt_end = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S') - self.write(cr, uid, [phase.id], {'date_start': date_start.strftime('%Y-%m-%d %H:%M:%S'), 'date_end': dt_end}, context=context) + dt_end = work_times[-1][1].strftime('%Y-%m-%d') + self.write(cr, uid, [phase.id], {'date_start': date_start.strftime('%Y-%m-%d'), 'date_end': dt_end}, context=context) - def write(self, cr, uid, ids, vals, context={}): + def write(self, cr, uid, ids, vals, context=None): resource_calendar_obj = self.pool.get('resource.calendar') resource_obj = self.pool.get('resource.resource') uom_obj = self.pool.get('product.uom') - if not context: + 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 - 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: + + #TOCHECK : why need this ? + if isinstance(ids, (int, long)): + ids = [ids] + 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 = uom_obj.search(cr, uid, [('name', '=', 'Hour')])[0] - 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 = mx.DateTime.strptime(vals['date_start'],'%Y-%m-%d %H:%M:%S') + 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 %H:%M:%S') + 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: - dt_end = mx.DateTime.strptime(vals['date_end'],'%Y-%m-%d %H:%M:%S') + + 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 %H:%M:%S') + 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: + default = {} + if not default.get('name', False): + default['name'] = self.browse(cr, uid, id, context=context).name + _(' (copy)') + return super(project_phase, self).copy(cr, uid, id, default, context) def set_draft(self, cr, uid, ids, *args): self.write(cr, uid, ids, {'state': 'draft'}) @@ -214,7 +260,7 @@ class project_phase(osv.osv): self.write(cr, uid, ids, {'state': 'open'}) return True - def set_pending(self, cr, uid, ids,*args): + def set_pending(self, cr, uid, ids, *args): self.write(cr, uid, ids, {'state': 'pending'}) return True @@ -226,16 +272,146 @@ 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') + resource_allocation_pool = self.pool.get('project.resource.allocation') + uom_pool = self.pool.get('product.uom') + 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(): + # If project has working calendar then that + # else the default one would be considered + start = start_date + minimum_time_unit = 1 + resource = phase_resource_obj + 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) + # write dates into Resources Allocation + for resource in phase.resource_ids: + resource_allocation_pool.write(cr, uid, [resource.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): _name = 'project.resource.allocation' _description = 'Project Resource Allocation' _rec_name = 'resource_id' + + def get_name(self, cr, uid, ids, field_name, arg, context=None): + res = {} + for allocation in self.browse(cr, uid, ids, context=context): + name = allocation.phase_id.name + name += ' (%s%%)' %(allocation.useability) + res[allocation.id] = name + return res _columns = { + 'name': fields.function(get_name, method=True, type='char', size=256), 'resource_id': fields.many2one('resource.resource', 'Resource', required=True), - 'phase_id': fields.many2one('project.phase', 'Project Phase', required=True), - 'useability': fields.float('Usability', help="Usability of this resource for this project phase in percentage (=50%)"), + 'phase_id': fields.many2one('project.phase', 'Project Phase', ondelete='cascade', required=True), + 'project_id': fields.related('phase_id', 'project_id', type='many2one', relation="project.project", string='Project', store=True), + 'user_id': fields.related('resource_id', 'user_id', type='many2one', relation="res.users", string='User'), + 'date_start': fields.date('Start Date', help="Starting Date"), + 'date_end': fields.date('End Date', help="Ending Date"), + 'useability': fields.float('Availability', help="Availability of this resource for this project phase in percentage (=50%)"), } _defaults = { 'useability': 100, @@ -247,124 +423,174 @@ class project(osv.osv): _inherit = "project.project" _columns = { '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"), + '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'), - 'occupation_rate': fields.float('Occupation Rate', help='The occupation rate fields indicates how much of his time a user is working on a task. A 100% occupation rate means the user works full time on the tasks. The ending date of a task is computed like this: Starting Date + Duration / Occupation Rate.'), - 'planned_hours': fields.float('Planned Hours', required=True, help='Estimated time to do the task, usually set by the project manager when the task is in draft state.'), - } - _defaults = { - 'occupation_rate': '1' } - def onchange_planned(self, cr, uid, ids, project, user_id=False, planned=0.0, effective=0.0, date_start=None, occupation_rate=0.0): - result = {} - resource = False - resource_obj = self.pool.get('resource.resource') - project_pool = self.pool.get('project.project') - resource_calendar = self.pool.get('resource.calendar') - if not project: - return {'value' : result} - if date_start: - hrs = float(planned / float(occupation_rate)) - calendar_id = project_pool.browse(cr, uid, project).resource_calendar_id.id - dt_start = mx.DateTime.strptime(date_start, '%Y-%m-%d %H:%M:%S') - resource_id = resource_obj.search(cr, uid, [('user_id','=',user_id)]) - if resource_id: - resource_data = resource_obj.browse(cr, uid, resource_id)[0] - resource = resource_data.id - hrs = planned / (float(occupation_rate) * resource_data.time_efficiency) - if resource_data.calendar_id.id: - calendar_id = resource_data.calendar_id.id - work_times = resource_calendar.interval_get(cr, uid, calendar_id, dt_start, hrs or 0.0, resource or False) - if work_times: - result['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S') - result['remaining_hours'] = planned - effective - return {'value' : result} - - def _check_date_start(self, cr, uid, task, date_end, context={}): - """ - Check And Compute date_end of task if change in date_start < older time. - """ - resource_calendar_obj = self.pool.get('resource.calendar') - resource_obj = self.pool.get('resource.resource') - calendar_id = task.project_id.resource_calendar_id and task.project_id.resource_calendar_id.id or False - hours = task.planned_hours / task.occupation_rate - resource_id = resource_obj.search(cr, uid, [('user_id', '=', task.user_id.id)], context=context) - if resource_id: - resource = resource_obj.browse(cr, uid, resource_id[0], context=context) - if resource.calendar_id.id: - calendar_id = resource.calendar_id and resource.calendar_id.id or False - hours = task.planned_hours / (float(task.occupation_rate) * resource.time_efficiency) - work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id, date_end, hours or 0.0, resource_id and resource_id[0] or False) - dt_start = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S') - self.write(cr, uid, [task.id], {'date_start' : dt_start,'date_end' : date_end.strftime('%Y-%m-%d %H:%M:%S')}) - - def _check_date_end(self, cr, uid, task, date_start, context={}): - """ - Check And Compute date_end of task if change in date_end > older time. - """ - resource_calendar_obj = self.pool.get('resource.calendar') - resource_obj = self.pool.get('resource.resource') - calendar_id = task.project_id.resource_calendar_id and task.project_id.resource_calendar_id.id or False - hours = task.planned_hours / task.occupation_rate - resource_id = resource_obj.search(cr,uid,[('user_id', '=', task.user_id.id)], context=context) - if resource_id: - resource = resource_obj.browse(cr, uid, resource_id[0], context=context) - if resource.calendar_id.id: - calendar_id = resource.calendar_id and resource.calendar_id.id or False - hours = task.planned_hours / (float(task.occupation_rate) * resource.time_efficiency) - work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id, date_start, hours or 0.0, resource_id and resource_id[0] or False) - dt_end = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S') - self.write(cr, uid, [task.id], {'date_start': date_start.strftime('%Y-%m-%d %H:%M:%S'),'date_end' : dt_end}, context=context) - - def write(self, cr, uid, ids, vals, context={}): - resource_calendar_obj = self.pool.get('resource.calendar') - resource_obj = self.pool.get('resource.resource') - if not context: + 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 = {} - if context.get('scheduler',False): - return super(task, self).write(cr, uid, ids, vals, context=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 - # Consider calendar and efficiency if the task is performed by a resource - # otherwise consider the project's working calendar - task_id = ids - if isinstance(ids, list): - task_id = ids[0] - task_rec = self.browse(cr, uid, task_id, context=context) - calendar_id = task_rec.project_id.resource_calendar_id and task_rec.project_id.resource_calendar_id.id or False - hrs = task_rec.planned_hours / task_rec.occupation_rate - resource_id = resource_obj.search(cr, uid, [('user_id', '=', task_rec.user_id.id)], context=context) - if resource_id: - resource = resource_obj.browse(cr, uid, resource_id[0], context=context) - if resource.calendar_id.id: - calendar_id = resource.calendar_id and resource.calendar_id.id or False - hrs = task_rec.planned_hours / (float(task_rec.occupation_rate) * resource.time_efficiency) - - # Change the date_start and date_end - # for previous and next tasks respectively based on valid condition - if vals.get('date_start', False) and vals['date_start'] < task_rec.date_start: - dt_start = mx.DateTime.strptime(vals['date_start'], '%Y-%m-%d %H:%M:%S') - work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id, dt_start, hrs or 0.0, resource.id or False) - if work_times: - vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S') - for prv_task in task_rec.parent_ids: - self._check_date_start(cr, uid, prv_task, dt_start) - if vals.get('date_end', False) and vals['date_end'] > task_rec.date_end: - dt_end = mx.DateTime.strptime(vals['date_end'], '%Y-%m-%d %H:%M:%S') - work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id, dt_end, hrs or 0.0, resource.id or False) - if work_times: - vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S') - for next_task in task_rec.child_ids: - self._check_date_end(cr, uid, next_task, dt_end) + # 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") + try: + resource = reduce(operator.or_, resources) + except: + raise osv.except_osv(_('Error'), _('Should have Resources Allocation or Project Members!')) + 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 - return super(task, self).write(cr, uid, ids, vals, context=context) + 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: |
