diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 770b32d84fd..f0ea2623dac 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -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 = _("Partner set to %s." % (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 diff --git a/addons/crm/test/lead2opportunity_assign_salesmen.yml b/addons/crm/test/lead2opportunity_assign_salesmen.yml index 5a750c94b72..de88a96cb50 100644 --- a/addons/crm/test/lead2opportunity_assign_salesmen.yml +++ b/addons/crm/test/lead2opportunity_assign_salesmen.yml @@ -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. diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index 8ce2aa1c6c7..e2fb70bfa56 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -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: diff --git a/addons/crm/wizard/crm_lead_to_opportunity_view.xml b/addons/crm/wizard/crm_lead_to_opportunity_view.xml index 09506c3ed81..130aa4192d6 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity_view.xml +++ b/addons/crm/wizard/crm_lead_to_opportunity_view.xml @@ -52,21 +52,17 @@
- + + - - - - - + - - - +