[MERGE] Forward-port of latest 7.0 bugfixes, up to rev. 9929 revid:dle@openerp.com...
[odoo/odoo.git] / addons / crm / crm_lead.py
index bd3c6f3..2dfd84f 100644 (file)
 #
 ##############################################################################
 
-from openerp.addons.base_status.base_stage import base_stage
 import crm
 from datetime import datetime
 from operator import itemgetter
-from openerp.osv import fields, osv, orm
-import time
+
 from openerp import SUPERUSER_ID
 from openerp import tools
-from openerp.tools.translate import _
+from openerp.addons.base.res.res_partner import format_address
+from openerp.osv import fields, osv, orm
 from openerp.tools import html2plaintext
-
-from base.res.res_partner import format_address
+from openerp.tools.translate import _
 
 CRM_LEAD_FIELDS_TO_MERGE = ['name',
     'partner_id',
@@ -62,13 +60,9 @@ CRM_LEAD_FIELDS_TO_MERGE = ['name',
     'email_from',
     'email_cc',
     'partner_name']
-CRM_LEAD_PENDING_STATES = (
-    crm.AVAILABLE_STATES[2][0], # Cancelled
-    crm.AVAILABLE_STATES[3][0], # Done
-    crm.AVAILABLE_STATES[4][0], # Pending
-)
 
-class crm_lead(base_stage, format_address, osv.osv):
+
+class crm_lead(format_address, osv.osv):
     """ CRM Lead Case """
     _name = "crm.lead"
     _description = "Lead/Opportunity"
@@ -76,27 +70,21 @@ class crm_lead(base_stage, format_address, osv.osv):
     _inherit = ['mail.thread', 'ir.needaction_mixin']
 
     _track = {
-        'state': {
-            'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj['state'] in ['new', 'draft'],
-            'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'done',
-            'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'cancel',
-        },
         'stage_id': {
-            'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: obj['state'] not in ['new', 'draft', 'cancel', 'done'],
+            # this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
+            'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.sequence <= 1,
+            'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: (obj.stage_id and obj.stage_id.sequence > 1) and obj.probability < 100,
+            'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj.probability == 100 and obj.stage_id and obj.stage_id.fold,
+            'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.fold and obj.stage_id.sequence > 1,
         },
     }
 
-    def create(self, cr, uid, vals, context=None):
-        if context is None:
-            context = {}
-        if vals.get('type') and not context.get('default_type'):
-            context['default_type'] = vals.get('type')
-        if vals.get('section_id') and not context.get('default_section_id'):
-            context['default_section_id'] = vals.get('section_id')
-
-        # context: no_log, because subtype already handle this
-        create_context = dict(context, mail_create_nolog=True)
-        return super(crm_lead, self).create(cr, uid, vals, context=create_context)
+    def get_empty_list_help(self, cr, uid, help, context=None):
+        if context.get('default_type') == 'lead':
+            context['empty_list_help_model'] = 'crm.case.section'
+            context['empty_list_help_id'] = context.get('default_section_id')
+        context['empty_list_help_document_name'] = _("leads")
+        return super(crm_lead, self).get_empty_list_help(cr, uid, help, context=context)
 
     def _get_default_section_id(self, cr, uid, context=None):
         """ Gives default section by checking if present in the context """
@@ -105,7 +93,7 @@ class crm_lead(base_stage, format_address, osv.osv):
     def _get_default_stage_id(self, cr, uid, context=None):
         """ Gives default stage_id """
         section_id = self._get_default_section_id(cr, uid, context=context)
-        return self.stage_find(cr, uid, [], section_id, [('state', '=', 'draft')], context=context)
+        return self.stage_find(cr, uid, [], section_id, [('fold', '=', False)], context=context)
 
     def _resolve_section_id_from_context(self, cr, uid, context=None):
         """ Returns ID of section based on the value of 'section_id'
@@ -117,8 +105,7 @@ class crm_lead(base_stage, format_address, osv.osv):
         if type(context.get('default_section_id')) in (int, long):
             return context.get('default_section_id')
         if isinstance(context.get('default_section_id'), basestring):
-            section_name = context['default_section_id']
-            section_ids = self.pool.get('crm.case.section').name_search(cr, uid, name=section_name, context=context)
+            section_ids = self.pool.get('crm.case.section').name_search(cr, uid, name=context['default_section_id'], context=context)
             if len(section_ids) == 1:
                 return int(section_ids[0][0])
         return None
@@ -157,7 +144,7 @@ class crm_lead(base_stage, format_address, osv.osv):
         stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context)
         result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context)
         # restore order of the search
-        result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))
+        result.sort(lambda x, y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))
 
         fold = {}
         for stage in stage_obj.browse(cr, access_rights_uid, stage_ids, context=context):
@@ -165,7 +152,7 @@ class crm_lead(base_stage, format_address, osv.osv):
         return result, fold
 
     def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
-        res = super(crm_lead,self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
+        res = super(crm_lead, self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
         if view_type == 'form':
             res['arch'] = self.fields_view_get_address(cr, user, res['arch'], context=context)
         return res
@@ -227,17 +214,6 @@ class crm_lead(base_stage, format_address, osv.osv):
                 res[lead.id][field] = abs(int(duration))
         return res
 
-    def _history_search(self, cr, uid, obj, name, args, context=None):
-        res = []
-        msg_obj = self.pool.get('mail.message')
-        message_ids = msg_obj.search(cr, uid, [('email_from','!=',False), ('subject', args[0][1], args[0][2])], context=context)
-        lead_ids = self.search(cr, uid, [('message_ids', 'in', message_ids)], context=context)
-
-        if lead_ids:
-            return [('id', 'in', lead_ids)]
-        else:
-            return [('id', '=', '0')]
-
     _columns = {
         'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null', track_visibility='onchange',
             select=True, help="Linked partner (optional). Usually created when converting the lead."),
@@ -250,10 +226,10 @@ class crm_lead(base_stage, format_address, osv.osv):
         'email_from': fields.char('Email', size=128, help="Email address of the contact", select=1),
         'section_id': fields.many2one('crm.case.section', 'Sales Team',
                         select=True, track_visibility='onchange', help='When sending mails, the default email address is taken from the sales team.'),
-        'create_date': fields.datetime('Creation Date' , readonly=True),
-        'email_cc': fields.text('Global CC', size=252 , 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"),
+        'create_date': fields.datetime('Creation Date', readonly=True),
+        'email_cc': fields.text('Global CC', 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"),
         'description': fields.text('Notes'),
-        'write_date': fields.datetime('Update Date' , readonly=True),
+        'write_date': fields.datetime('Update Date', readonly=True),
         'categ_ids': fields.many2many('crm.case.categ', 'crm_lead_category_rel', 'lead_id', 'category_id', 'Categories', \
             domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]"),
         'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
@@ -268,20 +244,20 @@ class crm_lead(base_stage, format_address, osv.osv):
         'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True),
         'date_closed': fields.datetime('Closed', readonly=True),
         'stage_id': fields.many2one('crm.case.stage', 'Stage', track_visibility='onchange',
-                        domain="['&', '&', ('fold', '=', False), ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"),
+                        domain="['&', ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"),
         'user_id': fields.many2one('res.users', 'Salesperson', select=True, track_visibility='onchange'),
         'referred': fields.char('Referred By', size=64),
-        'date_open': fields.datetime('Opened', readonly=True),
+        'date_open': fields.datetime('Assigned', readonly=True),
         'day_open': fields.function(_compute_day, string='Days to Open', \
                                 multi='day_open', type="float", store=True),
         'day_close': fields.function(_compute_day, string='Days to Close', \
                                 multi='day_close', type="float", store=True),
-        'state': fields.related('stage_id', 'state', type="selection", store=True,
-                selection=crm.AVAILABLE_STATES, string="Status", readonly=True,
-                help='The Status is set to \'Draft\', when a case is created. If the case is in progress the Status is set to \'Open\'. When the case is over, the Status is set to \'Done\'. If the case needs to be reviewed then the Status is  set to \'Pending\'.'),
+        'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
 
+        # Messaging and marketing
+        'message_bounce': fields.integer('Bounce'),
         # Only used for type opportunity
-        'probability': fields.float('Success Rate (%)',group_operator="avg"),
+        'probability': fields.float('Success Rate (%)', group_operator="avg"),
         'planned_revenue': fields.float('Expected Revenue', track_visibility='always'),
         'ref': fields.reference('Reference', selection=crm._links_get, size=128),
         'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128),
@@ -317,13 +293,13 @@ class crm_lead(base_stage, format_address, osv.osv):
     _defaults = {
         'active': 1,
         'type': 'lead',
-        'user_id': lambda s, cr, uid, c: s._get_default_user(cr, uid, c),
-        'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c),
+        'user_id': lambda s, cr, uid, c: uid,
         'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
         'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c),
         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
         'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
         'color': 0,
+        'date_last_stage_update': fields.datetime.now,
     }
 
     _sql_constraints = [
@@ -332,55 +308,43 @@ class crm_lead(base_stage, format_address, osv.osv):
 
     def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
         if not stage_id:
-            return {'value':{}}
-        stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
+            return {'value': {}}
+        stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context=context)
         if not stage.on_change:
-            return {'value':{}}
-        return {'value':{'probability': stage.probability}}
+            return {'value': {}}
+        vals = {'probability': stage.probability}
+        if stage.probability >= 100 or (stage.probability == 0 and stage.sequence > 1):
+                vals['date_closed'] = fields.datetime.now()
+        return {'value': vals}
 
-    def on_change_partner(self, cr, uid, ids, partner_id, context=None):
+    def on_change_partner_id(self, cr, uid, ids, partner_id, context=None):
         values = {}
         if partner_id:
             partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
             values = {
-                'partner_name' : partner.name,
-                'street' : partner.street,
-                'street2' : partner.street2,
-                'city' : partner.city,
-                'state_id' : partner.state_id and partner.state_id.id or False,
-                'country_id' : partner.country_id and partner.country_id.id or False,
-                'email_from' : partner.email,
-                'phone' : partner.phone,
-                'mobile' : partner.mobile,
-                'fax' : partner.fax,
+                'partner_name': partner.name,
+                'street': partner.street,
+                'street2': partner.street2,
+                'city': partner.city,
+                'state_id': partner.state_id and partner.state_id.id or False,
+                'country_id': partner.country_id and partner.country_id.id or False,
+                'email_from': partner.email,
+                'phone': partner.phone,
+                'mobile': partner.mobile,
+                'fax': partner.fax,
                 'zip': partner.zip,
             }
-        return {'value' : values}
+        return {'value': values}
 
     def on_change_user(self, cr, uid, ids, user_id, context=None):
         """ When changing the user, also set a section_id or restrict section id
             to the ones user_id is member of. """
-        if user_id:
+        section_id = self._get_default_section_id(cr, uid, context=context) or False
+        if user_id and not section_id:
             section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
             if section_ids:
-                return {'value': {'section_id': section_ids[0]}}
-        return {'value': {}}
-
-    def _check(self, cr, uid, ids=False, context=None):
-        """ Override of the base.stage method.
-            Function called by the scheduler to process cases for date actions
-            Only works on not done and cancelled cases
-        """
-        cr.execute('select * from crm_case \
-                where (date_action_last<%s or date_action_last is null) \
-                and (date_action_next<=%s or date_action_next is null) \
-                and state not in (\'cancel\',\'done\')',
-                (time.strftime("%Y-%m-%d %H:%M:%S"),
-                    time.strftime('%Y-%m-%d %H:%M:%S')))
-
-        ids2 = map(lambda x: x[0], cr.fetchall() or [])
-        cases = self.browse(cr, uid, ids2, context=context)
-        return self._action(cr, uid, cases, False, context=context)
+                section_id = section_ids[0]
+        return {'value': {'section_id': section_id}}
 
     def stage_find(self, cr, uid, cases, section_id, domain=None, order='sequence', context=None):
         """ Override of the base.stage method
@@ -392,17 +356,21 @@ class crm_lead(base_stage, format_address, osv.osv):
         """
         if isinstance(cases, (int, long)):
             cases = self.browse(cr, uid, cases, context=context)
+        if context is None:
+            context = {}
+        # check whether we should try to add a condition on type
+        avoid_add_type_term = any([term for term in domain if len(term) == 3 if term[0] == 'type'])
         # collect all section_ids
-        section_ids = []
+        section_ids = set()
         types = ['both']
-        if not cases :
-            type = context.get('default_type')
-            types += [type]
+        if not cases and context.get('default_type'):
+            ctx_type = context.get('default_type')
+            types += [ctx_type]
         if section_id:
-            section_ids.append(section_id)
+            section_ids.add(section_id)
         for lead in cases:
             if lead.section_id:
-                section_ids.append(lead.section_id.id)
+                section_ids.add(lead.section_id.id)
             if lead.type not in types:
                 types.append(lead.type)
         # OR all section_ids and OR with case_default
@@ -411,60 +379,88 @@ class crm_lead(base_stage, format_address, osv.osv):
             search_domain += [('|')] * len(section_ids)
             for section_id in section_ids:
                 search_domain.append(('section_ids', '=', section_id))
-        else:
-            search_domain.append(('case_default', '=', True))
+        search_domain.append(('case_default', '=', True))
         # AND with cases types
-        search_domain.append(('type', 'in', types))
+        if not avoid_add_type_term:
+            search_domain.append(('type', 'in', types))
         # AND with the domain in parameter
         search_domain += list(domain)
         # perform search, return the first found
-        stage_ids = self.pool.get('crm.case.stage').search(cr, uid, search_domain, order=order, context=context)
+        stage_ids = self.pool.get('crm.case.stage').search(cr, uid, search_domain, order=order, limit=1, context=context)
         if stage_ids:
             return stage_ids[0]
         return False
 
-    def case_cancel(self, cr, uid, ids, context=None):
-        """ Overrides case_cancel from base_stage to set probability """
-        res = super(crm_lead, self).case_cancel(cr, uid, ids, context=context)
-        self.write(cr, uid, ids, {'probability' : 0.0}, context=context)
-        return res
-
-    def case_reset(self, cr, uid, ids, context=None):
-        """ Overrides case_reset from base_stage to set probability """
-        res = super(crm_lead, self).case_reset(cr, uid, ids, context=context)
-        self.write(cr, uid, ids, {'probability': 0.0}, context=context)
-        return res
-
     def case_mark_lost(self, cr, uid, ids, context=None):
-        """ Mark the case as lost: state=cancel and probability=0 """
-        for lead in self.browse(cr, uid, ids):
-            stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0),('on_change','=',True)], context=context)
+        """ Mark the case as lost: state=cancel and probability=0
+            :deprecated: this method will be removed in OpenERP v8.
+        """
+        stages_leads = {}
+        for lead in self.browse(cr, uid, ids, context=context):
+            stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0), ('fold', '=', True), ('sequence', '>', 1)], context=context)
             if stage_id:
-                self.case_set(cr, uid, [lead.id], values_to_update={'probability': 0.0, 'date_closed': fields.datetime.now()}, new_stage_id=stage_id, context=context)
+                if stages_leads.get(stage_id):
+                    stages_leads[stage_id].append(lead.id)
+                else:
+                    stages_leads[stage_id] = [lead.id]
+            else:
+                raise osv.except_osv(_('Warning!'),
+                    _('To relieve your sales pipe and group all Lost opportunities, configure one of your sales stage as follow:\n'
+                        'probability = 0 %, select "Change Probability Automatically".\n'
+                        'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.'))
+        for stage_id, lead_ids in stages_leads.items():
+            self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context)
         return True
 
     def case_mark_won(self, cr, uid, ids, context=None):
-        """ Mark the case as won: state=done and probability=100 """
-        for lead in self.browse(cr, uid, ids):
-            stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0),('on_change','=',True)], context=context)
+        """ Mark the case as won: state=done and probability=100
+            :deprecated: this method will be removed in OpenERP v8.
+        """
+        stages_leads = {}
+        for lead in self.browse(cr, uid, ids, context=context):
+            stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0), ('fold', '=', True)], context=context)
             if stage_id:
-                self.case_set(cr, uid, [lead.id], values_to_update={'probability': 100.0, 'date_closed': fields.datetime.now()}, new_stage_id=stage_id, context=context)
+                if stages_leads.get(stage_id):
+                    stages_leads[stage_id].append(lead.id)
+                else:
+                    stages_leads[stage_id] = [lead.id]
+            else:
+                raise osv.except_osv(_('Warning!'),
+                    _('To relieve your sales pipe and group all Won opportunities, configure one of your sales stage as follow:\n'
+                        'probability = 100 % and select "Change Probability Automatically".\n'
+                        'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.'))
+        for stage_id, lead_ids in stages_leads.items():
+            self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context)
+        return True
+
+    def case_escalate(self, cr, uid, ids, context=None):
+        """ Escalates case to parent level """
+        for case in self.browse(cr, uid, ids, context=context):
+            data = {'active': True}
+            if case.section_id.parent_id:
+                data['section_id'] = case.section_id.parent_id.id
+                if case.section_id.parent_id.change_responsible:
+                    if case.section_id.parent_id.user_id:
+                        data['user_id'] = case.section_id.parent_id.user_id.id
+            else:
+                raise osv.except_osv(_('Error!'), _("You are already at the top level of your sales-team category.\nTherefore you cannot escalate furthermore."))
+            self.write(cr, uid, [case.id], data, context=context)
         return True
 
-    def set_priority(self, cr, uid, ids, priority):
+    def set_priority(self, cr, uid, ids, priority, context=None):
         """ Set lead priority
         """
-        return self.write(cr, uid, ids, {'priority' : priority})
+        return self.write(cr, uid, ids, {'priority': priority}, context=context)
 
     def set_high_priority(self, cr, uid, ids, context=None):
         """ Set lead priority to high
         """
-        return self.set_priority(cr, uid, ids, '1')
+        return self.set_priority(cr, uid, ids, '1', context=context)
 
     def set_normal_priority(self, cr, uid, ids, context=None):
         """ Set lead priority to normal
         """
-        return self.set_priority(cr, uid, ids, '3')
+        return self.set_priority(cr, uid, ids, '3', context=context)
 
     def _merge_get_result_type(self, cr, uid, opps, context=None):
         """
@@ -621,7 +617,7 @@ class crm_lead(base_stage, format_address, osv.osv):
                 attachment.write(values)
         return True
 
-    def merge_opportunity(self, cr, uid, ids, context=None):
+    def merge_opportunity(self, cr, uid, ids, user_id=False, section_id=False, context=None):
         """
         Different cases of merge:
         - merge leads together = 1 new lead
@@ -638,9 +634,12 @@ class crm_lead(base_stage, format_address, osv.osv):
 
         opportunities = self.browse(cr, uid, ids, context=context)
         sequenced_opps = []
+        # Sorting the leads/opps according to the confidence level of its stage, which relates to the probability of winning it
+        # The confidence level increases with the stage sequence, except when the stage probability is 0.0 (Lost cases)
+        # An Opportunity always has higher confidence level than a lead, unless its stage probability is 0.0
         for opportunity in opportunities:
             sequence = -1
-            if opportunity.stage_id and opportunity.stage_id.state != 'cancel':
+            if opportunity.stage_id and not opportunity.stage_id.fold:
                 sequence = opportunity.stage_id.sequence
             sequenced_opps.append(((int(sequence != -1 and opportunity.type == 'opportunity'), sequence, -opportunity.id), opportunity))
 
@@ -655,6 +654,11 @@ class crm_lead(base_stage, format_address, osv.osv):
         fields = list(CRM_LEAD_FIELDS_TO_MERGE)
         merged_data = self._merge_data(cr, uid, ids, highest, fields, context=context)
 
+        if user_id:
+            merged_data['user_id'] = user_id
+        if section_id:
+            merged_data['section_id'] = section_id
+
         # Merge messages and attachements into the first opportunity
         self._merge_opportunity_history(cr, uid, highest.id, tail_opportunities, context=context)
         self._merge_opportunity_attachments(cr, uid, highest.id, tail_opportunities, context=context)
@@ -696,7 +700,7 @@ class crm_lead(base_stage, format_address, osv.osv):
             'phone': customer and customer.phone or lead.phone,
         }
         if not lead.stage_id or lead.stage_id.type=='lead':
-            val['stage_id'] = self.stage_find(cr, uid, [lead], section_id, [('state', '=', 'draft'),('type', 'in', ('opportunity','both'))], context=context)
+            val['stage_id'] = self.stage_find(cr, uid, [lead], section_id, [('type', 'in', ('opportunity', 'both'))], context=context)
         return val
 
     def convert_opportunity(self, cr, uid, ids, partner_id, user_ids=False, section_id=False, context=None):
@@ -705,11 +709,11 @@ class crm_lead(base_stage, format_address, osv.osv):
             partner = self.pool.get('res.partner')
             customer = partner.browse(cr, uid, partner_id, context=context)
         for lead in self.browse(cr, uid, ids, context=context):
-            if lead.state in ('done', 'cancel'):
+            # TDE: was if lead.state in ('done', 'cancel'):
+            if lead.probability == 100 or (lead.probability == 0 and lead.stage_id.fold):
                 continue
             vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context)
             self.write(cr, uid, [lead.id], vals, context=context)
-        self.message_post(cr, uid, ids, body=_("Lead <b>converted into an Opportunity</b>"), subtype="crm.mt_lead_convert_to_opportunity", context=context)
 
         if user_ids or section_id:
             self.allocate_salesman(cr, uid, ids, user_ids, section_id, context=context)
@@ -860,9 +864,9 @@ class crm_lead(base_stage, format_address, osv.osv):
                 'priority': lead.priority,
             }
             new_id = phonecall.create(cr, uid, vals, context=context)
-            phonecall.case_open(cr, uid, [new_id], context=context)
+            phonecall.write(cr, uid, [new_id], {'state': 'open'}, context=context)
             if action == 'log':
-                phonecall.case_close(cr, uid, [new_id], context=context)
+                phonecall.write(cr, uid, [new_id], {'state': 'done'}, context=context)
             phonecall_dict[lead.id] = new_id
             self.schedule_phonecall_send_note(cr, uid, [lead.id], new_id, action, context=context)
         return phonecall_dict
@@ -925,15 +929,26 @@ class crm_lead(base_stage, format_address, osv.osv):
         }
         return res
 
+    def create(self, cr, uid, vals, context=None):
+        if context is None:
+            context = {}
+        if vals.get('type') and not context.get('default_type'):
+            context['default_type'] = vals.get('type')
+        if vals.get('section_id') and not context.get('default_section_id'):
+            context['default_section_id'] = vals.get('section_id')
+
+        # context: no_log, because subtype already handle this
+        create_context = dict(context, mail_create_nolog=True)
+        return super(crm_lead, self).create(cr, uid, vals, context=create_context)
+
     def write(self, cr, uid, ids, vals, context=None):
-        if vals.get('stage_id'):
-            stage = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context)
-            if not vals.get('probability') and stage.on_change:
-                # change probability of lead(s) if required by stage
-                vals['probability'] = stage.probability
-            # set closed date when won or lost
-            if not vals.get('date_closed') and (vals.get('probability', 0) >= 100 or stage.state == 'canceled'):
-                vals['date_closed'] = fields.datetime.now()
+        # stage change: update date_last_stage_update
+        if 'stage_id' in vals:
+            vals['date_last_stage_update'] = fields.datetime.now()
+        # stage change with new stage: update probability
+        if vals.get('stage_id') and not vals.get('probability'):
+            onchange_stage_values = self.onchange_stage_id(cr, uid, ids, vals.get('stage_id'), context=context)['value']
+            vals.update(onchange_stage_values)
         return super(crm_lead, self).write(cr, uid, ids, vals, context=context)
 
     def copy(self, cr, uid, id, default=None, context=None):
@@ -953,41 +968,6 @@ class crm_lead(base_stage, format_address, osv.osv):
         default['stage_id'] = self._get_default_stage_id(cr, uid, local_context)
         return super(crm_lead, self).copy(cr, uid, id, default, context=context)
 
-    def new_mail_send(self, cr, uid, ids, context=None):
-        '''
-        This function opens a window to compose an email, with the edi sale template message loaded by default
-        '''
-        assert len(ids) == 1, 'This option should only be used for a single id at a time.'
-        ir_model_data = self.pool.get('ir.model.data')
-        try:
-            template_id = ir_model_data.get_object_reference(cr, uid, 'crm', 'email_template_opportunity_mail')[1]
-        except ValueError:
-            template_id = False
-        try:
-            compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
-        except ValueError:
-            compose_form_id = False
-        if context is None:
-            context = {}
-        ctx = context.copy()
-        ctx.update({
-            'default_model': 'crm.lead',
-            'default_res_id': ids[0],
-            'default_use_template': bool(template_id),
-            'default_template_id': template_id,
-            'default_composition_mode': 'comment',
-        })
-        return {
-            'type': 'ir.actions.act_window',
-            'view_type': 'form',
-            'view_mode': 'form',
-            'res_model': 'mail.compose.message',
-            'views': [(compose_form_id, 'form')],
-            'view_id': compose_form_id,
-            'target': 'new',
-            'context': ctx,
-        }
-
     # ----------------------------------------
     # Mail Gateway
     # ----------------------------------------
@@ -997,6 +977,16 @@ class crm_lead(base_stage, format_address, osv.osv):
         return [lead.section_id.message_get_reply_to()[0] if lead.section_id else False
                     for lead in self.browse(cr, SUPERUSER_ID, ids, context=context)]
 
+    def _get_formview_action(self, cr, uid, id, context=None):
+        action = super(crm_lead, self)._get_formview_action(cr, uid, id, context=context)
+        obj = self.browse(cr, uid, id, context=context)
+        if obj.type == 'opportunity':
+            model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor')
+            action.update({
+                'views': [(view_id, 'form')],
+                })
+        return action
+
     def message_get_suggested_recipients(self, cr, uid, ids, context=None):
         recipients = super(crm_lead, self).message_get_suggested_recipients(cr, uid, ids, context=context)
         try:
@@ -1016,17 +1006,15 @@ class crm_lead(base_stage, format_address, osv.osv):
         """
         if custom_values is None:
             custom_values = {}
-        desc = html2plaintext(msg.get('body')) if msg.get('body') else ''
         defaults = {
             'name':  msg.get('subject') or _("No Subject"),
-            'description': desc,
             'email_from': msg.get('from'),
             'email_cc': msg.get('cc'),
             'partner_id': msg.get('author_id', False),
             'user_id': False,
         }
         if msg.get('author_id'):
-            defaults.update(self.on_change_partner(cr, uid, None, msg.get('author_id'), context=context)['value'])
+            defaults.update(self.on_change_partner_id(cr, uid, None, msg.get('author_id'), context=context)['value'])
         if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
             defaults['priority'] = msg.get('priority')
         defaults.update(custom_values)