Launchpad automatic translations update.
[odoo/odoo.git] / addons / project_issue / project_issue.py
index c763306..aaaff37 100644 (file)
 #
 ##############################################################################
 
-import base64
-import os
-import re
-import time
-import mx.DateTime
-from datetime import datetime, timedelta
-
-import tools
 from crm import crm
-from osv import fields,osv,orm
-from osv.orm import except_orm
+from datetime import datetime
+from osv import fields,osv
 from tools.translate import _
+import binascii
+import time
+import tools
 
-class project_issue(osv.osv, crm.crm_case):
+
+class project_issue_version(osv.osv):
+    _name = "project.issue.version"
+    _order = "name desc"
+    _columns = {
+        'name': fields.char('Version Number', size=32, required=True),
+        'active': fields.boolean('Active', required=False),
+    }
+    _defaults = {
+        'active': 1,
+    }
+project_issue_version()
+
+class project_issue(crm.crm_case, osv.osv):
     _name = "project.issue"
     _description = "Project Issue"
     _order = "priority, id desc"
-    _inherits = {'mailgate.thread': 'thread_id'}
+    _inherit = ['mailgate.thread']
 
     def case_open(self, cr, uid, ids, *args):
         """
@@ -49,9 +57,27 @@ class project_issue(osv.osv, crm.crm_case):
 
         res = super(project_issue, self).case_open(cr, uid, ids, *args)
         self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
+        for (id, name) in self.name_get(cr, uid, ids):
+            message = _("Issue '%s' has been opened.") % name
+            self.log(cr, uid, id, message)
+        return res
+
+    def case_close(self, cr, uid, ids, *args):
+        """
+        @param self: The object pointer
+        @param cr: the current row, from the database cursor,
+        @param uid: the current user’s ID for security checks,
+        @param ids: List of case's Ids
+        @param *args: Give Tuple Value
+        """
+
+        res = super(project_issue, self).case_close(cr, uid, ids, *args)
+        for (id, name) in self.name_get(cr, uid, ids):
+            message = _("Issue '%s' has been closed.") % name
+            self.log(cr, uid, id, message)
         return res
 
-    def _compute_day(self, cr, uid, ids, fields, args, context={}):
+    def _compute_day(self, cr, uid, ids, fields, args, context=None):
         """
         @param cr: the current row, from the database cursor,
         @param uid: the current user’s ID for security checks,
@@ -63,7 +89,7 @@ class project_issue(osv.osv, crm.crm_case):
         res_obj = self.pool.get('resource.resource')
 
         res = {}
-        for issue in self.browse(cr, uid, ids, context):
+        for issue in self.browse(cr, uid, ids, context=context):
             for field in fields:
                 res[issue.id] = {}
                 duration = 0
@@ -78,8 +104,8 @@ class project_issue(osv.osv, crm.crm_case):
                         date_until = issue.date_open
                         #Calculating no. of working hours to open the issue
                         hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
-                                mx.DateTime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'),
-                                mx.DateTime.strptime(issue.date_open, '%Y-%m-%d %H:%M:%S'))
+                                 datetime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'),
+                                 datetime.strptime(issue.date_open, '%Y-%m-%d %H:%M:%S'))
                 elif field in ['working_hours_close','day_close']:
                     if issue.date_closed:
                         date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
@@ -88,8 +114,8 @@ class project_issue(osv.osv, crm.crm_case):
                         ans = date_close - date_create
                         #Calculating no. of working hours to close the issue
                         hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
-                                mx.DateTime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'),
-                                mx.DateTime.strptime(issue.date_closed, '%Y-%m-%d %H:%M:%S'))
+                                datetime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'),
+                                datetime.strptime(issue.date_closed, '%Y-%m-%d %H:%M:%S'))
                 if ans:
                     resource_id = False
                     if issue.user_id:
@@ -99,15 +125,9 @@ class project_issue(osv.osv, crm.crm_case):
                     duration = float(ans.days)
                     if issue.project_id and issue.project_id.resource_calendar_id:
                         duration = float(ans.days) * 24
-                        new_dates = cal_obj.interval_min_get(cr,
-                            uid,
-                            issue.project_id.resource_calendar_id.id,
-                            mx.DateTime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'),
-                            duration,
-                            resource=resource_id
-                        )
+                        new_dates = cal_obj.interval_min_get(cr, uid, issue.project_id.resource_calendar_id.id, datetime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'), duration, resource=resource_id)
                         no_days = []
-                        date_until = mx.DateTime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
+                        date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
                         for in_time, out_time in new_dates:
                             if in_time.date not in no_days:
                                 no_days.append(in_time.date)
@@ -120,15 +140,38 @@ class project_issue(osv.osv, crm.crm_case):
                     res[issue.id][field] = abs(float(duration))
         return res
 
+    def _get_issue_task(self, cr, uid, ids, context=None):
+        issues = []
+        issue_pool = self.pool.get('project.issue')
+        for task in self.pool.get('project.task').browse(cr, uid, ids, context=context):
+            issues += issue_pool.search(cr, uid, [('task_id','=',task.id)])            
+        return issues
+
+    def _get_issue_work(self, cr, uid, ids, context=None):
+        issues = []
+        issue_pool = self.pool.get('project.issue')
+        for work in self.pool.get('project.task.work').browse(cr, uid, ids, context=context):
+            if work.task_id:
+                issues += issue_pool.search(cr, uid, [('task_id','=',work.task_id.id)])
+        return issues
+
+    def _hours_get(self, cr, uid, ids, field_names, args, context=None):
+        task_pool = self.pool.get('project.task')
+        res = {}
+        for issue in self.browse(cr, uid, ids, context=context):
+            progress = 0.0
+            if issue.task_id:
+                progress = task_pool._hours_get(cr, uid, [issue.task_id.id], field_names, args, context=context)[issue.task_id.id]['progress']
+            res[issue.id] = {'progress' : progress}     
+        return res        
+
     _columns = {
-        'thread_id': fields.many2one('mailgate.thread', 'Thread', required=False),
         'id': fields.integer('ID'),
-        'name': fields.char('Name', size=128, required=True),
+        'name': fields.char('Issue', size=128, required=True),
         'active': fields.boolean('Active', required=False),
-        'create_date': fields.datetime('Creation Date' , readonly=True),
-        'write_date': fields.datetime('Update Date' , readonly=True),
+        'create_date': fields.datetime('Creation Date', readonly=True,select=True),
+        'write_date': fields.datetime('Update Date', readonly=True),
         'date_deadline': fields.date('Deadline'),
-        'date_closed': fields.datetime('Closed', readonly=True),
         'section_id': fields.many2one('crm.case.section', 'Sales Team', \
                         select=True, help='Sales team to which Case belongs to.\
                              Define Responsible user and Email account for mail gateway.'),
@@ -138,65 +181,65 @@ class project_issue(osv.osv, crm.crm_case):
                                  domain="[('partner_id','=',partner_id)]"),
         'company_id': fields.many2one('res.company', 'Company'),
         'description': fields.text('Description'),
-        'state': fields.selection([
-                                    ('draft', 'Draft'),
-                                    ('open', 'Todo'),
-                                    ('cancel', 'Cancelled'),
-                                    ('done', 'Closed'),
-                                    ('pending', 'Pending'),
-                                ], 'State', size=16, readonly=True,
+        'state': fields.selection([('draft', 'Draft'), ('open', 'To Do'), ('cancel', 'Cancelled'), ('done', 'Closed'),('pending', 'Pending'), ], 'State', size=16, readonly=True,
                                   help='The state is set to \'Draft\', when a case is created.\
                                   \nIf the case is in progress the state is set to \'Open\'.\
                                   \nWhen the case is over, the state is set to \'Done\'.\
                                   \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
         'email_from': fields.char('Email', size=128, help="These people will receive email."),
-        'email_cc': fields.text('Watchers Emails', size=252 , help="These people\
- will receive a copy of the future" \
-" communication between partner and users by email"),
-        'date_open': fields.datetime('Opened', readonly=True),
+        'email_cc': fields.char('Watchers Emails', size=256, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
+        'date_open': fields.datetime('Opened', readonly=True,select=True),
         # Project Issue fields
-        'date_closed': fields.datetime('Closed', readonly=True),
+        'date_closed': fields.datetime('Closed', readonly=True,select=True),
         'date': fields.datetime('Date'),
-        'canal_id': fields.many2one('res.partner.canal', 'Channel',help="The channels represent the different communication modes available with the customer." \
+        'canal_id': fields.many2one('res.partner.canal', 'Channel', help="The channels represent the different communication modes available with the customer." \
                                                                         " With each commercial opportunity, you can indicate the canall which is this opportunity source."),
-        'categ_id': fields.many2one('crm.case.categ','Category', domain="[('object_id.model', '=', 'crm.project.bug')]"),
-        'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Severity'),
-        'type_id': fields.many2one('crm.case.resource.type', 'Version', domain="[('object_id.model', '=', 'project.issue')]"),
+        'categ_id': fields.many2one('crm.case.categ', 'Category', domain="[('object_id.model', '=', 'crm.project.bug')]"),
+        'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
+        'version_id': fields.many2one('project.issue.version', 'Version'),
         'partner_name': fields.char("Employee's Name", size=64),
         'partner_mobile': fields.char('Mobile', size=32),
         'partner_phone': fields.char('Phone', size=32),
-        'stage_id': fields.many2one ('crm.case.stage', 'Stage', domain="[('object_id.model', '=', 'project.issue')]"),
+        'type_id': fields.many2one ('project.task.type', 'Resolution'),
         'project_id':fields.many2one('project.project', 'Project'),
         'duration': fields.float('Duration'),
         'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]"),
-        'date_open': fields.datetime('Opened', readonly=True),
         'day_open': fields.function(_compute_day, string='Days to Open', \
                                 method=True, multi='day_open', type="float", store=True),
         'day_close': fields.function(_compute_day, string='Days to Close', \
                                 method=True, multi='day_close', type="float", store=True),
-        'assigned_to' : fields.many2one('res.users', 'Assigned to'),
+        'assigned_to': fields.related('task_id', 'user_id', string = 'Assigned to', type="many2one", relation="res.users", store=True, help='This is the current user to whom the related task have been assigned'),
         'working_hours_open': fields.function(_compute_day, string='Working Hours to Open the Issue', \
                                 method=True, multi='working_days_open', type="float", store=True),
         'working_hours_close': fields.function(_compute_day, string='Working Hours to Close the Issue', \
                                 method=True, multi='working_days_close', type="float", store=True),
+        'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
+        'date_action_last': fields.datetime('Last Action', readonly=1),
+        'date_action_next': fields.datetime('Next Action', readonly=1),
+        'progress': fields.function(_hours_get, method=True, string='Progress (%)', multi='hours', group_operator="avg", help="Computed as: Time Spent / Total Time.",
+            store = {
+                'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['task_id'], 10),
+                'project.task': (_get_issue_task, ['progress'], 10),
+                'project.task.work': (_get_issue_work, ['hours'], 10),
+            }),
     }
 
-    def _get_project(self, cr, uid, context):
-       user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
-       if user.context_project_id:
-           return user.context_project_id.id
-       return False
+    def _get_project(self, cr, uid, context=None):
+        user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+        if user.context_project_id:
+            return user.context_project_id.id
+        return False
 
     _defaults = {
-        'active': lambda *a: 1,
+        'active': 1,
         'user_id': crm.crm_case._get_default_user,
         'partner_id': crm.crm_case._get_default_partner,
         'partner_address_id': crm.crm_case._get_default_partner_address,
         'email_from': crm.crm_case. _get_default_email,
-        'state': lambda *a: 'draft',
+        'state': 'draft',
         'section_id': crm.crm_case. _get_section,
         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
-        'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
+        'priority': crm.AVAILABLE_PRIORITIES[2][0],
         'project_id':_get_project,
     }
 
@@ -205,14 +248,10 @@ class project_issue(osv.osv, crm.crm_case):
         data_obj = self.pool.get('ir.model.data')
         task_obj = self.pool.get('project.task')
 
+
         if context is None:
             context = {}
 
-#        for case in case_obj.browse(cr, uid, ids, context=context):
-#            if case.state != 'open':
-#                raise osv.except_osv(_('Warning !'),
-#                    _('Issues or Feature Requests should be in \'Open\' state before converting into Task.'))
-
         result = data_obj._get_id(cr, uid, 'project', 'view_task_search_form')
         res = data_obj.read(cr, uid, result, ['res_id'])
         id2 = data_obj._get_id(cr, uid, 'project', 'view_task_form2')
@@ -228,17 +267,16 @@ class project_issue(osv.osv, crm.crm_case):
                 'partner_id': bug.partner_id.id,
                 'description':bug.description,
                 'date': bug.date,
-                'project_id':bug.project_id.id,
-                'priority':bug.priority,
-                'user_id':bug.assigned_to.id,
+                'project_id': bug.project_id.id,
+                'priority': bug.priority,
+                'user_id': bug.assigned_to.id,
                 'planned_hours': 0.0,
             })
 
-            new_task = task_obj.browse(cr, uid, new_task_id)
-
             vals = {
                 'task_id': new_task_id,
-                }
+                'state':'pending'
+            }
             case_obj.write(cr, uid, [bug.id], vals)
 
         return  {
@@ -254,6 +292,7 @@ class project_issue(osv.osv, crm.crm_case):
             'nodestroy': True
         }
 
+
     def _convert(self, cr, uid, ids, xml_id, context=None):
         data_obj = self.pool.get('ir.model.data')
         id2 = data_obj._get_id(cr, uid, 'project_issue', xml_id)
@@ -270,13 +309,35 @@ class project_issue(osv.osv, crm.crm_case):
     def convert_to_bug(self, cr, uid, ids, context=None):
         return self._convert(cr, uid, ids, 'bug_categ', context=context)
 
-    def onchange_stage_id(self, cr, uid, ids, stage_id, context={}):
-        if not stage_id:
-            return {'value':{}}
-        stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
-        if not stage.on_change:
+    def next_type(self, cr, uid, ids, *args):
+        for task in self.browse(cr, uid, ids):
+            typeid = task.type_id.id
+            types = map(lambda x:x.id, task.project_id.type_ids or [])
+            if types:
+                if not typeid:
+                    self.write(cr, uid, task.id, {'type_id': types[0]})
+                elif typeid and typeid in types and types.index(typeid) != len(types)-1 :
+                    index = types.index(typeid)
+                    self.write(cr, uid, task.id, {'type_id': types[index+1]})
+        return True
+
+    def prev_type(self, cr, uid, ids, *args):
+        for task in self.browse(cr, uid, ids):
+            typeid = task.type_id.id
+            types = map(lambda x:x.id, task.project_id and task.project_id.type_ids or [])
+            if types:
+                if typeid and typeid in types:
+                    index = types.index(typeid)
+                    self.write(cr, uid, task.id, {'type_id': index and types[index-1] or False})
+        return True
+
+
+    def onchange_task_id(self, cr, uid, ids, task_id, context=None):
+        result = {}
+        if not task_id:
             return {'value':{}}
-        return {'value':{}}
+        task = self.pool.get('project.task').browse(cr, uid, task_id, context=context)
+        return {'value':{'assigned_to': task.user_id.id,}}
 
     def case_escalate(self, cr, uid, ids, *args):
         """Escalates case to top level
@@ -293,10 +354,146 @@ class project_issue(osv.osv, crm.crm_case):
                 data['project_id'] = case.project_id.project_escalation_id.id
                 if case.project_id.project_escalation_id.user_id:
                     data['user_id'] = case.project_id.project_escalation_id.user_id.id
+                if case.task_id:
+                    self.pool.get('project.task').write(cr, uid, [case.task_id.id], {'project_id': data['project_id'], 'user_id': False})
             else:
                 raise osv.except_osv(_('Warning !'), _('You cannot escalate this issue.\nThe relevant Project has not configured the Escalation Project!'))
             self.write(cr, uid, [case.id], data)
+        self._history(cr, uid, cases, _('Escalate'))
         return True
 
+    def message_new(self, cr, uid, msg, context=None):
+        """
+        Automatically calls when new email message arrives
+
+        @param self: The object pointer
+        @param cr: the current row, from the database cursor,
+        @param uid: the current user’s ID for security checks
+        """
+        if context is None: 
+            context = {}
+        mailgate_pool = self.pool.get('email.server.tools')
+
+        subject = msg.get('subject') or _('No Title')
+        body = msg.get('body')
+        msg_from = msg.get('from')
+        priority = msg.get('priority')
+
+        vals = {
+            'name': subject,
+            'email_from': msg_from,
+            'email_cc': msg.get('cc'),
+            'description': body,
+            'user_id': False,
+        }
+        if msg.get('priority', False):
+            vals['priority'] = priority
+
+        res = mailgate_pool.get_partner(cr, uid, msg.get('from'))
+        if res:
+            vals.update(res)
+        context.update({'state_to' : 'draft'})
+        res = self.create(cr, uid, vals, context=context)
+        self.convert_to_bug(cr, uid, [res], context=context)
+
+        attachents = msg.get('attachments', [])
+        for attactment in attachents or []:
+            data_attach = {
+                'name': attactment,
+                'datas': binascii.b2a_base64(str(attachents.get(attactment))),
+                'datas_fname': attactment,
+                'description': 'Mail attachment',
+                'res_model': self._name,
+                'res_id': res,
+            }
+            self.pool.get('ir.attachment').create(cr, uid, data_attach)
+
+        return res
+
+    def message_update(self, cr, uid, ids, vals=None, msg="", default_act='pending', context=None):
+        """
+        @param self: The object pointer
+        @param cr: the current row, from the database cursor,
+        @param uid: the current user’s ID for security checks,
+        @param ids: List of update mail’s IDs
+        """
+
+        if vals is None:
+            vals = {}
+
+        if isinstance(ids, (str, int, long)):
+            ids = [ids]
+
+        vals.update({
+            'description': msg['body']
+        })
+        if msg.get('priority', False):
+            vals['priority'] = msg.get('priority')
+
+        maps = {
+            'cost': 'planned_cost',
+            'revenue': 'planned_revenue',
+            'probability': 'probability'
+        }
+
+        # Reassign the 'open' state to the case if this one is in pending or done
+        for record in self.browse(cr, uid, ids, context=context):
+            if record.state in ('pending', 'done'):
+                record.write({'state' : 'open'})
+
+        vls = { }
+        for line in msg['body'].split('\n'):
+            line = line.strip()
+            res = tools.misc.command_re.match(line)
+            if res and maps.get(res.group(1).lower(), False):
+                key = maps.get(res.group(1).lower())
+                vls[key] = res.group(2).lower()
+
+        vals.update(vls)
+        res = self.write(cr, uid, ids, vals)
+        return res
+
+    def msg_send(self, cr, uid, id, *args, **argv):
+
+        """ Send The Message
+            @param self: The object pointer
+            @param cr: the current row, from the database cursor,
+            @param uid: the current user’s ID for security checks,
+            @param ids: List of email’s IDs
+            @param *args: Return Tuple Value
+            @param **args: Return Dictionary of Keyword Value
+        """
+        return True
+
+    def copy(self, cr, uid, id, default=None, context=None):
+        issue = self.read(cr, uid, id, ['name'], context=context)
+        if not default:
+            default = {}
+        default = default.copy()
+        default['name'] = issue['name'] + _(' (copy)')
+        return super(project_issue, self).copy(cr, uid, id, default=default,
+                context=context)
+
 project_issue()
 
+class project(osv.osv):
+    _inherit = "project.project"
+    _columns = {
+        'resource_calendar_id' : fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
+        'project_escalation_id' : fields.many2one('project.project','Project Escalation', help='If any issue is escalated from the current Project, it will be listed under the project selected here.', states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
+        'reply_to' : fields.char('Reply-To Email Address', size=256)
+    }
+
+    def _check_escalation(self, cr, uid, ids, context=None):
+         project_obj = self.browse(cr, uid, ids[0], context=context)
+         if project_obj.project_escalation_id:
+             if project_obj.project_escalation_id.id == project_obj.id:
+                 return False
+         return True
+
+    _constraints = [
+        (_check_escalation, 'Error! You cannot assign escalation to the same project!', ['project_escalation_id'])
+    ]
+project()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: