[merge]
[odoo/odoo.git] / addons / project_long_term / project_long_term.py
index b924650..2e86426 100644 (file)
@@ -29,7 +29,10 @@ 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 +48,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 +58,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,39 +68,44 @@ 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']
+
     _columns = {
-        'name': fields.char("Phase Name", size=64, required=True),
-        'date_start': fields.datetime('Starting Date', help="Start date of the phase"),
-        'date_end': fields.datetime('End Date', help="End date of the phase"),
+        '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'),
         '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'),
         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of phases."),
-        'duration': fields.float('Duration', required=True),
+        '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'),
+        'responsible_id': fields.many2one('res.users', 'Responsible'),
         '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\'.')
@@ -106,6 +114,7 @@ class project_phase(osv.osv):
         '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]
     }
     _order = "name"
     _constraints = [
@@ -115,7 +124,7 @@ class project_phase(osv.osv):
         #(_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 = {}
         project_obj = self.pool.get('project.project')
         if project:
@@ -125,7 +134,9 @@ class project_phase(osv.osv):
                 return {'value': result}
         return {'value': {'date_start': []}}
 
-    def _check_date_start(self, cr, uid, phase, date_end, context={}):
+    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.
        """
@@ -140,13 +151,15 @@ class project_phase(osv.osv):
             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)
 
-    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.
        """
@@ -161,17 +174,17 @@ class project_phase(osv.osv):
             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)
 
-    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 = {}
         if context.get('scheduler',False):
             return super(project_phase, self).write(cr, uid, ids, vals, context=context)
@@ -184,13 +197,13 @@ class project_phase(osv.osv):
                 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]
+        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:
-                dt_start = mx.DateTime.strptime(vals['date_start'],'%Y-%m-%d %H:%M:%S')
+                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, 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')
@@ -213,7 +226,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
 
@@ -235,6 +248,8 @@ class project_resource_allocation(osv.osv):
         '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%)"),
+        'date_start': fields.related('phase_id', 'date_start', type='datetime', string='Start Date'),
+        'date_end': fields.related('phase_id', 'date_end', type='datetime', string='End Date'),
     }
     _defaults = {
         'useability': 100,
@@ -255,115 +270,7 @@ class 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:
-            context = {}
-        if context.get('scheduler',False):
-            return super(task, self).write(cr, uid, ids, vals, context=context)
-
-        # 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)
-
-        return super(task, self).write(cr, uid, ids, vals, context=context)
-
 task()
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: