[ADD] crm: mass assign apply deduplication
authorDenis Ledoux <dle@openerp.com>
Thu, 23 Jan 2014 18:02:11 +0000 (19:02 +0100)
committerDenis Ledoux <dle@openerp.com>
Thu, 23 Jan 2014 18:02:11 +0000 (19:02 +0100)
bzr revid: dle@openerp.com-20140123180211-o1ebik36wegmi8mc

addons/crm/crm_lead.py
addons/crm/test/lead2opportunity_assign_salesmen.yml
addons/crm/wizard/crm_lead_to_opportunity.py
addons/crm/wizard/crm_lead_to_opportunity_view.xml

index 770b32d..f0ea262 100644 (file)
@@ -761,24 +761,6 @@ class crm_lead(format_address, osv.osv):
             )
         return partner_id
 
-    def _lead_set_partner(self, cr, uid, lead, partner_id, context=None):
-        """
-        Assign a partner to a lead.
-
-        :param object lead: browse record of the lead to process
-        :param int partner_id: identifier of the partner to assign
-        :return bool: True if the partner has properly been assigned
-        """
-        res = False
-        res_partner = self.pool.get('res.partner')
-        if partner_id:
-            res_partner.write(cr, uid, partner_id, {'section_id': lead.section_id and lead.section_id.id or False})
-            contact_id = res_partner.address_get(cr, uid, [partner_id])['default']
-            res = lead.write({'partner_id': partner_id}, context=context)
-            message = _("<b>Partner</b> set to <em>%s</em>." % (lead.partner_id.name))
-            self.message_post(cr, uid, [lead.id], body=message, context=context)
-        return res
-
     def handle_partner_assignation(self, cr, uid, ids, action='create', partner_id=False, context=None):
         """
         Handle partner assignation during a lead conversion.
@@ -792,13 +774,16 @@ class crm_lead(format_address, osv.osv):
         """
         #TODO this is a duplication of the handle_partner_assignation method of crm_phonecall
         partner_ids = {}
-        # If a partner_id is given, force this partner for all elements
-        force_partner_id = partner_id
         for lead in self.browse(cr, uid, ids, context=context):
             # If the action is set to 'create' and no partner_id is set, create a new one
-            if action == 'create':
-                partner_id = force_partner_id or self._create_lead_partner(cr, uid, lead, context)
-            self._lead_set_partner(cr, uid, lead, partner_id, context=context)
+            if lead.partner_id:
+                partner_ids[lead.id] = lead.partner_id.id
+                continue
+            if not partner_id and action == 'create':
+                partner_id = self._create_lead_partner(cr, uid, lead, context)
+                self.pool['res.partner'].write(cr, uid, partner_id, {'section_id': lead.section_id and lead.section_id.id or False})
+            if partner_id:
+                lead.write({'partner_id': partner_id}, context=context)
             partner_ids[lead.id] = partner_id
         return partner_ids
 
index 5a750c9..de88a96 100644 (file)
@@ -66,7 +66,7 @@
 -
   !python {model: crm.lead2opportunity.partner.mass}: |
     context.update({'active_model': 'crm.lead', 'active_ids': [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], 'active_id': ref("test_crm_lead_01")})
-    id = self.create(cr, uid, {'user_ids': [(6, 0, [ref('test_res_user_01'), ref('test_res_user_02'), ref('test_res_user_03'), ref('test_res_user_04')])], 'section_id': ref('crm.section_sales_department')}, context=context)
+    id = self.create(cr, uid, {'user_ids': [(6, 0, [ref('test_res_user_01'), ref('test_res_user_02'), ref('test_res_user_03'), ref('test_res_user_04')])], 'section_id': ref('crm.section_sales_department'), 'deduplicate': False}, context=context)
     self.mass_convert(cr, uid, [id], context=context)
 -
   The leads should now be opps with a salesman and a salesteam.  Also, salesmen should have been assigned following a round-robin method.
index 8ce2aa1..e2fb70b 100644 (file)
@@ -41,6 +41,22 @@ class crm_lead2opportunity_partner(osv.osv_memory):
     def onchange_action(self, cr, uid, ids, action, context=None):
         return {'value': {'partner_id': False if action != 'exist' else self._find_matching_partner(cr, uid, context=context)}}
 
+    def _get_duplicated_leads(self, cr, uid, partner_id, email, context=None):
+        lead_obj = self.pool.get('crm.lead')
+        results = []
+        if partner_id:
+            # Search for opportunities that have the same partner and that arent done or cancelled
+            ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('probability', '=', False), ('probability', '<', '100')])
+            for id in ids:
+                results.append(id)
+        email = re.findall(r'([^ ,<@]+@[^> ,]+)', email or '')
+        if email:
+            ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('probability', '=', False), ('probability', '<', '100')])
+            for id in ids:
+                results.append(id)
+        return list(set(results))
+
+
     def default_get(self, cr, uid, fields, context=None):
         """
         Default get for name, opportunity_ids.
@@ -51,24 +67,15 @@ class crm_lead2opportunity_partner(osv.osv_memory):
 
         res = super(crm_lead2opportunity_partner, self).default_get(cr, uid, fields, context=context)
         if context.get('active_id'):
-            tomerge = set([int(context['active_id'])])
+            tomerge = [int(context['active_id'])]
 
             email = False
             partner_id = res.get('partner_id')
             lead = lead_obj.browse(cr, uid, int(context['active_id']), context=context)
 
             #TOFIX: use mail.mail_message.to_mail
-            email = re.findall(r'([^ ,<@]+@[^> ,]+)', lead.email_from or '')
-
-            if partner_id:
-                # Search for opportunities that have the same partner and that arent done or cancelled
-                ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('probability', '=', False), ('probability', '<', '100')])
-                for id in ids:
-                    tomerge.add(id)
-            if email:
-                ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('probability', '=', False), ('probability', '<', '100')])
-                for id in ids:
-                    tomerge.add(id)
+            tomerge.extend(self._get_duplicated_leads(cr, uid, partner_id, email))
+            tomerge = list(set(tomerge))
 
             if 'action' in fields:
                 res.update({'action' : partner_id and 'exist' or 'create'})
@@ -77,7 +84,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
             if 'name' in fields:
                 res.update({'name' : len(tomerge) >= 2 and 'merge' or 'convert'})
             if 'opportunity_ids' in fields and len(tomerge) >= 2:
-                res.update({'opportunity_ids': list(tomerge)})
+                res.update({'opportunity_ids': tomerge})
             if lead.user_id:
                 res.update({'user_id': lead.user_id.id})
             if lead.section_id:
@@ -116,11 +123,11 @@ class crm_lead2opportunity_partner(osv.osv_memory):
             context = {}
         lead = self.pool.get('crm.lead')
         res = False
-        partner_ids_map = self._create_partner(cr, uid, ids, context=context)
         lead_ids = vals.get('lead_ids', [])
         team_id = vals.get('section_id', False)
+        data = self.browse(cr, uid, ids, context=context)[0]
         for lead_id in lead_ids:
-            partner_id = partner_ids_map.get(lead_id, False)
+            partner_id = self._create_partner(cr, uid, lead_id, data.action, data.partner_id, context=context)
             # FIXME: cannot pass user_ids as the salesman allocation only works in batch
             res = lead.convert_opportunity(cr, uid, [lead_id], partner_id, [], team_id, context=context)
         # FIXME: must perform salesman allocation in batch separately here
@@ -152,7 +159,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
 
         return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
 
-    def _create_partner(self, cr, uid, ids, context=None):
+    def _create_partner(self, cr, uid, lead_id, action, partner_id, context=None):
         """
         Create partner based on action.
         :return dict: dictionary organized as followed: {lead_id: partner_assigned_id}
@@ -163,10 +170,14 @@ class crm_lead2opportunity_partner(osv.osv_memory):
         if context is None:
             context = {}
         lead = self.pool.get('crm.lead')
-        lead_ids = context.get('active_ids', [])
-        data = self.browse(cr, uid, ids, context=context)[0]
-        partner_id = data.partner_id and data.partner_id.id or False
-        return lead.handle_partner_assignation(cr, uid, lead_ids, data.action, partner_id, context=context)
+        if action == 'each_exist_or_create':
+            ctx = dict(context)
+            ctx['active_id'] = lead_id
+            partner_id = self._find_matching_partner(cr, uid, context=ctx)
+            action = 'create'
+            print partner_id
+        res = lead.handle_partner_assignation(cr, uid, [lead_id], action, partner_id, context=context)
+        return res.get(lead_id)
 
 class crm_lead2opportunity_mass_convert(osv.osv_memory):
     _name = 'crm.lead2opportunity.partner.mass'
@@ -176,6 +187,15 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
     _columns = {
         'user_ids':  fields.many2many('res.users', string='Salesmen'),
         'section_id': fields.many2one('crm.case.section', 'Sales Team'),
+        'deduplicate': fields.boolean('Apply deduplication', help='Merge with existing leads/opportunities of each partner'),
+        'action': fields.selection([
+                ('each_exist_or_create', 'Use existing partner or create'),
+                ('nothing', 'Do not link to a customer')
+            ], 'Related Customer', required=True),
+    }
+
+    _defaults = {
+        'deduplicate': True,
     }
 
     def default_get(self, cr, uid, fields, context=None):
@@ -184,13 +204,37 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
             # avoid forcing the partner of the first lead as default
             res['partner_id'] = False
         if 'action' in fields:
-            res['action'] = 'create'
+            res['action'] = 'each_exist_or_create'
         if 'name' in fields:
             res['name'] = 'convert'
         if 'opportunity_ids' in fields:
             res['opportunity_ids'] = False
         return res
 
+    def on_change_action(self, cr, uid, ids, action, context=None):
+        vals = {}
+        if action != 'exist':
+            vals = {'value': {'partner_id': False}} 
+        return vals
+
+    def on_change_deduplicate(self, cr, uid, ids, deduplicate, context=None):
+        if context is None:
+            context = {}
+        active_leads = self.pool['crm.lead'].browse(cr, uid, context['active_ids'], context=context)
+        partner_ids = [(lead.partner_id.id, lead.partner_id and lead.partner_id.email or lead.email_from) for lead in active_leads]
+        partners_duplicated_leads = {}
+        for partner_id, email in partner_ids:
+            duplicated_leads = self._get_duplicated_leads(cr, uid, partner_id, email)
+            if len(duplicated_leads) > 1:
+                partners_duplicated_leads.setdefault((partner_id, email), []).extend(duplicated_leads)
+        leads_with_duplicates = []
+        for lead in active_leads:
+            lead_tuple = (lead.partner_id.id, lead.partner_id.email if lead.partner_id else lead.email_from)
+            if len(partners_duplicated_leads.get(lead_tuple, [])) > 1:
+                leads_with_duplicates.append(lead.id)
+        return {'value': {'opportunity_ids': leads_with_duplicates}}
+
+
     def _convert_opportunity(self, cr, uid, ids, vals, context=None):
         """
         When "massively" (more than one at a time) converting leads to
@@ -208,6 +252,21 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
         return super(crm_lead2opportunity_mass_convert, self)._convert_opportunity(cr, uid, ids, vals, context=context)
 
     def mass_convert(self, cr, uid, ids, context=None):
-        return self.action_apply(cr, uid, ids, context=context)
+        data = self.browse(cr, uid, ids, context=context)[0]
+        ctx = dict(context)
+        if data.name == 'convert' and data.deduplicate:
+            merged_lead_ids = []
+            remaining_lead_ids = []
+            for lead in data.opportunity_ids:
+                duplicated_lead_ids = self._get_duplicated_leads(cr, uid, lead.partner_id.id, lead.partner_id and lead.partner_id.email or lead.email_from)
+                if len(duplicated_lead_ids) > 1:
+                    lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, duplicated_lead_ids, False, False, context=context)
+                    merged_lead_ids.extend(duplicated_lead_ids)
+                    remaining_lead_ids.append(lead_id)
+            active_ids = set(context.get('active_ids', []))
+            active_ids = active_ids.difference(merged_lead_ids)
+            active_ids = active_ids.union(remaining_lead_ids)
+            ctx['active_ids'] = list(active_ids)
+        return self.action_apply(cr, uid, ids, context=ctx)
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index 09506c3..130aa41 100644 (file)
                 <form string="Convert to Opportunity" version="7.0">
                     <separator string="Conversion Options"/>
                     <group>
-                        <field name="name" class="oe_inline"/>
+                        <field name="action" class="oe_inline" on_change="on_change_action(action)"/>
+                        <field name="deduplicate" class="oe_inline" on_change="on_change_deduplicate(deduplicate, context)"/>
                     </group>
-                    <group attrs="{'invisible': [('name', '!=', 'convert')]}">
-                        <field name="action" class="oe_inline"/>
-                        <field name="partner_id"
-                            attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"
-                            class="oe_inline"/>
-                    </group>
-                    <group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}">
+                    <group string="Assign opportunities to">
                         <field name="section_id" groups="base.group_multi_salesteams"/>
                         <field name="user_ids" widget="many2many_tags"/>
                     </group>
-                    <group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">
-                        <field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}">
-                            <tree>
+                    <label for="opportunity_ids" string="Leads with existing duplicates (for information)" help="Leads that you selected that have duplicates. If the list is empty, it means that no duplicates were found"/>
+                    <group attrs="{'invisible': [('deduplicate', '=', False)]}">
+                        <field name="opportunity_ids" colspan="4" nolabel="1">
+                            <tree create="false" delete="false">
                                 <field name="create_date"/>
                                 <field name="name"/>
                                 <field name="type"/>