[merge]
[odoo/odoo.git] / addons / project_long_term / project_long_term.py
index 46aedb0..2e86426 100644 (file)
@@ -29,11 +29,14 @@ 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
-         # it should nither be in prev_ids nor in next_ids
+         # it should neither be in prev_ids nor in next_ids
          if (data_phase in prev_ids) or (data_phase in next_ids):
              return False
          ids = [id for id in prev_ids if id in next_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,58 +68,63 @@ class project_phase(osv.osv):
              next_ids = next_phase_ids
          return True
 
-    def _check_dates(self, cr, uid, ids, context={}):
-         phase = self.read(cr, uid, ids[0], ['date_start', 'date_end'], context=context)
-         if phase['date_start'] and phase['date_end'] and phase['date_start'] > phase['date_end']:
-             return False
+    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'),
-        'date_end': fields.datetime('End Date'),
+        '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\'.')
      }
     _defaults = {
         'responsible_id': lambda obj,cr,uid,context: uid,
-        'date_start': lambda *a: time.strftime('%Y-%m-%d'),
-        'state': lambda *a: 'draft',
-        'sequence': lambda *a: 10,
+        '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 = [
-        (_check_recursion,'Error ! Loops In Phases Not Allowed',['next_phase_ids', 'previous_phase_ids']),
-        (_check_dates, 'Error! Phase start-date must be lower then Phase end-date.', ['date_start', 'date_end']),
-        (_check_constraint_start, 'Error! Phase must start-after Constraint Start Date.', ['date_start', 'constraint_date_start']),
-        (_check_constraint_end, 'Error! Phase must end-before Constraint End Date.', ['date_end', 'constraint_date_end']),
+        (_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 = {}
         project_obj = self.pool.get('project.project')
         if project:
@@ -126,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.
        """
@@ -141,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.
        """
@@ -162,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)
@@ -185,21 +197,23 @@ 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)
-                vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
+                if work_times:
+                    vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
                 for prv_phase in phase.previous_phase_ids:
                     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')
                 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)
-                vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S')
+                if work_times:
+                    vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S')
                 for next_phase in phase.next_phase_ids:
                     self._check_date_end(cr, uid, next_phase, dt_end, context=context)
         return super(project_phase, self).write(cr, uid, ids, vals, context=context)
@@ -212,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
 
@@ -233,17 +247,18 @@ class project_resource_allocation(osv.osv):
     _columns = {
         'resource_id': fields.many2one('resource.resource', 'Resource', required=True),
         'phase_id': fields.many2one('project.phase', 'Project Phase', required=True),
-        'useability': fields.float('Useability', help="Useability of this ressource for this project phase in percentage (=50%)"),
+        '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': lambda *a: 100,
+        'useability': 100,
     }
 
 project_resource_allocation()
 
 class project(osv.osv):
     _inherit = "project.project"
-    _description = "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"),
@@ -253,117 +268,9 @@ project()
 
 class task(osv.osv):
     _inherit = "project.task"
-    _description = "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':lambda *a: '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)
-            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)
-            vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
-            super(task, self).write(cr, uid, ids, vals, context=context)
-            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)
-            vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S')
-            super(task, self).write(cr, uid, ids, vals, context=context)
-            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:
\ No newline at end of file
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: