Files
odoo_source/addons/crm/tests/test_crm_lead_convert.py
T
Thibault Francois e26d3e1166 [FIX] crm: sale user should be able to convert and merge lead
Use case
--------

A sale user convert a lead to an opportunity, this lead has duplicates.

The wizard will convert and merge the lead. At the end of the process
the duplicated lead are delete.

Issue
-----

Before this commit the user get an access error because he cannot delete
leads

Since they can merge lead using the action "Merge" it's not consistent
and sales user should be able to merge lead during the conversion
as well

closes odoo/odoo#94699

X-original-commit: 3b79867460d9a407fba094fe1cd719d8d52011ef
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
Signed-off-by: Thibault Francois <tfr@odoo.com>
2022-06-27 17:35:22 +02:00

578 lines
26 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import SUPERUSER_ID
from odoo.addons.crm.tests import common as crm_common
from odoo.fields import Datetime
from odoo.tests.common import tagged, users
from odoo.tests.common import Form
@tagged('lead_manage')
class TestLeadConvertForm(crm_common.TestLeadConvertCommon):
@users('user_sales_manager')
def test_form_action_default(self):
""" Test Lead._find_matching_partner() """
lead = self.env['crm.lead'].browse(self.lead_1.ids)
customer = self.env['res.partner'].create({
"name": "Amy Wong",
"email": '"Amy, PhD Student, Wong" Tiny <AMY.WONG@test.example.com>'
})
wizard = Form(self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': lead.id,
'active_ids': lead.ids,
}))
self.assertEqual(wizard.name, 'convert')
self.assertEqual(wizard.action, 'exist')
self.assertEqual(wizard.partner_id, customer)
@users('user_sales_manager')
def test_form_name_onchange(self):
""" Test Lead._find_matching_partner() """
lead = self.env['crm.lead'].browse(self.lead_1.ids)
lead_dup = lead.copy({'name': 'Duplicate'})
customer = self.env['res.partner'].create({
"name": "Amy Wong",
"email": '"Amy, PhD Student, Wong" Tiny <AMY.WONG@test.example.com>'
})
wizard = Form(self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': lead.id,
'active_ids': lead.ids,
}))
self.assertEqual(wizard.name, 'merge')
self.assertEqual(wizard.action, 'exist')
self.assertEqual(wizard.partner_id, customer)
self.assertEqual(wizard.duplicated_lead_ids[:], lead + lead_dup)
wizard.name = 'convert'
wizard.action = 'create'
self.assertEqual(wizard.action, 'create', 'Should keep user input')
self.assertEqual(wizard.name, 'convert', 'Should keep user input')
@tagged('lead_manage')
class TestLeadConvert(crm_common.TestLeadConvertCommon):
"""
TODO: created partner (handle assignation) has team of lead
TODO: create partner has user_id coming from wizard
"""
@classmethod
def setUpClass(cls):
super(TestLeadConvert, cls).setUpClass()
date = Datetime.from_string('2020-01-20 16:00:00')
cls.crm_lead_dt_mock.now.return_value = date
@users('user_sales_manager')
def test_duplicates_computation(self):
""" Test Lead._get_lead_duplicates() and check won / probability usage """
test_lead = self.env['crm.lead'].browse(self.lead_1.ids)
customer, dup_leads = self._create_duplicates(test_lead)
dup_leads += self.env['crm.lead'].create([
{'name': 'Duplicate lead: same email_from, lost',
'type': 'lead',
'email_from': test_lead.email_from,
'probability': 0, 'active': False,
},
{'name': 'Duplicate lead: same email_from, proba 0 but not lost',
'type': 'lead',
'email_from': test_lead.email_from,
'probability': 0, 'active': True,
},
{'name': 'Duplicate opp: same email_from, won',
'type': 'opportunity',
'email_from': test_lead.email_from,
'probability': 100, 'stage_id': self.stage_team1_won.id,
},
{'name': 'Duplicate opp: same email_from, proba 100 but not won',
'type': 'opportunity',
'email_from': test_lead.email_from,
'probability': 100, 'stage_id': self.stage_team1_2.id,
}
])
lead_lost = dup_leads.filtered(lambda lead: lead.name == 'Duplicate lead: same email_from, lost')
_opp_proba100 = dup_leads.filtered(lambda lead: lead.name == 'Duplicate opp: same email_from, proba 100 but not won')
opp_won = dup_leads.filtered(lambda lead: lead.name == 'Duplicate opp: same email_from, won')
opp_lost = dup_leads.filtered(lambda lead: lead.name == 'Duplicate: lost opportunity')
test_lead.write({'partner_id': customer.id})
# not include_lost = remove archived leads as well as 'won' opportunities
result = test_lead._get_lead_duplicates(
partner=test_lead.partner_id,
email=test_lead.email_from,
include_lost=False
)
self.assertEqual(result, test_lead + dup_leads - (lead_lost + opp_won + opp_lost))
# include_lost = remove archived opp only
result = test_lead._get_lead_duplicates(
partner=test_lead.partner_id,
email=test_lead.email_from,
include_lost=True
)
self.assertEqual(result, test_lead + dup_leads - (lead_lost))
def test_initial_data(self):
""" Ensure initial data to avoid spaghetti test update afterwards """
self.assertFalse(self.lead_1.date_conversion)
self.assertEqual(self.lead_1.date_open, Datetime.from_string('2020-01-15 11:30:00'))
self.assertEqual(self.lead_1.lang_id, self.lang_fr)
self.assertFalse(self.lead_1.mobile)
self.assertEqual(self.lead_1.phone, '+1 202 555 9999')
self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
self.assertEqual(self.lead_1.team_id, self.sales_team_1)
self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
@users('user_sales_manager')
def test_lead_convert_base(self):
""" Test base method ``convert_opportunity`` or crm.lead model """
self.contact_2.phone = False # force Falsy to compare with mobile
self.assertFalse(self.contact_2.phone)
lead = self.lead_1.with_user(self.env.user)
lead.write({
'phone': '123456789',
})
self.assertEqual(lead.team_id, self.sales_team_1)
self.assertEqual(lead.stage_id, self.stage_team1_1)
self.assertEqual(lead.email_from, 'amy.wong@test.example.com')
self.assertEqual(lead.lang_id, self.lang_fr)
lead.convert_opportunity(self.contact_2)
self.assertEqual(lead.type, 'opportunity')
self.assertEqual(lead.partner_id, self.contact_2)
self.assertEqual(lead.email_from, self.contact_2.email)
self.assertEqual(lead.lang_id, self.lang_en)
self.assertEqual(lead.mobile, self.contact_2.mobile)
self.assertEqual(lead.phone, '123456789')
self.assertEqual(lead.team_id, self.sales_team_1)
self.assertEqual(lead.stage_id, self.stage_team1_1)
@users('user_sales_manager')
def test_lead_convert_base_corner_cases(self):
""" Test base method ``convert_opportunity`` or crm.lead model with corner
cases: inactive, won, stage update, ... """
# inactive leads are not converted
lead = self.lead_1.with_user(self.env.user)
lead.action_archive()
self.assertFalse(lead.active)
lead.convert_opportunity(self.contact_2)
self.assertEqual(lead.type, 'lead')
self.assertEqual(lead.partner_id, self.env['res.partner'])
lead.action_unarchive()
self.assertTrue(lead.active)
# won leads are not converted
lead.action_set_won()
# TDE FIXME: set won does not take into account sales team when fetching a won stage
# self.assertEqual(lead.stage_id, self.stage_team1_won)
self.assertEqual(lead.stage_id, self.stage_gen_won)
self.assertEqual(lead.probability, 100)
lead.convert_opportunity(self.contact_2)
self.assertEqual(lead.type, 'lead')
self.assertEqual(lead.partner_id, self.env['res.partner'])
@users('user_sales_manager')
def test_lead_convert_base_w_salesmen(self):
""" Test base method ``convert_opportunity`` while forcing salesmen, as it
should also force sales team """
lead = self.lead_1.with_user(self.env.user)
self.assertEqual(lead.team_id, self.sales_team_1)
lead.convert_opportunity(False, user_ids=self.user_sales_salesman.ids)
self.assertEqual(lead.user_id, self.user_sales_salesman)
self.assertEqual(lead.team_id, self.sales_team_convert)
# TDE FIXME: convert does not recompute stage based on updated team of assigned user
# self.assertEqual(lead.stage_id, self.stage_team_convert_1)
@users('user_sales_manager')
def test_lead_convert_base_w_team(self):
""" Test base method ``convert_opportunity`` while forcing team """
lead = self.lead_1.with_user(self.env.user)
lead.convert_opportunity(False, team_id=self.sales_team_convert.id)
self.assertEqual(lead.team_id, self.sales_team_convert)
self.assertEqual(lead.user_id, self.user_sales_leads)
# TDE FIXME: convert does not recompute stage based on team
# self.assertEqual(lead.stage_id, self.stage_team_convert_1)
@users('user_sales_manager')
def test_lead_convert_corner_cases_crud(self):
""" Test Lead._find_matching_partner() """
# email formatting
other_lead = self.lead_1.copy()
other_lead.write({'partner_id': self.contact_1.id})
convert = self.env['crm.lead2opportunity.partner'].with_context({
'default_lead_id': other_lead.id,
}).create({})
self.assertEqual(convert.lead_id, other_lead)
self.assertEqual(convert.partner_id, self.contact_1)
self.assertEqual(convert.action, 'exist')
convert = self.env['crm.lead2opportunity.partner'].with_context({
'default_lead_id': other_lead.id,
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
}).create({})
self.assertEqual(convert.lead_id, other_lead)
self.assertEqual(convert.partner_id, self.contact_1)
self.assertEqual(convert.action, 'exist')
@users('user_sales_manager')
def test_lead_convert_corner_cases_matching(self):
""" Test Lead._find_matching_partner() """
# email formatting
self.lead_1.write({
'email_from': 'Amy Wong <amy.wong@test.example.com>'
})
customer = self.env['res.partner'].create({
'name': 'Different Name',
'email': 'Wong AMY <AMY.WONG@test.example.com>'
})
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
'active_ids': self.lead_1.ids,
}).create({})
# TDE FIXME: should take into account normalized email version, not encoded one
# self.assertEqual(convert.partner_id, customer)
@users('user_sales_manager')
def test_lead_convert_internals(self):
""" Test internals of convert wizard """
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
'active_ids': self.lead_1.ids,
}).create({})
# test internals of convert wizard
self.assertEqual(convert.lead_id, self.lead_1)
self.assertEqual(convert.user_id, self.lead_1.user_id)
self.assertEqual(convert.team_id, self.lead_1.team_id)
self.assertFalse(convert.partner_id)
self.assertEqual(convert.name, 'convert')
self.assertEqual(convert.action, 'create')
convert.write({'user_id': self.user_sales_salesman.id})
self.assertEqual(convert.user_id, self.user_sales_salesman)
self.assertEqual(convert.team_id, self.sales_team_convert)
convert.action_apply()
# convert test
self.assertEqual(self.lead_1.type, 'opportunity')
self.assertEqual(self.lead_1.user_id, self.user_sales_salesman)
self.assertEqual(self.lead_1.team_id, self.sales_team_convert)
# TDE FIXME: stage is linked to the old sales team and is not updated when converting, could be improved
# self.assertEqual(self.lead_1.stage_id, self.stage_gen_1)
# partner creation test
new_partner = self.lead_1.partner_id
self.assertEqual(new_partner.email, 'amy.wong@test.example.com')
self.assertEqual(new_partner.lang, self.lang_fr.code)
self.assertEqual(new_partner.phone, '+1 202 555 9999')
self.assertEqual(new_partner.name, 'Amy Wong')
@users('user_sales_manager')
def test_lead_convert_action_exist(self):
""" Test specific use case of 'exist' action in conver wizard """
self.lead_1.write({'partner_id': self.contact_1.id})
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
'active_ids': self.lead_1.ids,
}).create({})
self.assertEqual(convert.action, 'exist')
convert.action_apply()
self.assertEqual(self.lead_1.type, 'opportunity')
self.assertEqual(self.lead_1.partner_id, self.contact_1)
@users('user_sales_manager')
def test_lead_convert_action_nothing(self):
""" Test specific use case of 'nothing' action in conver wizard """
self.lead_1.write({'contact_name': False})
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
'active_ids': self.lead_1.ids,
}).create({})
self.assertEqual(convert.action, 'nothing')
convert.action_apply()
self.assertEqual(self.lead_1.type, 'opportunity')
self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
self.assertEqual(self.lead_1.team_id, self.sales_team_1)
self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
self.assertEqual(self.lead_1.partner_id, self.env['res.partner'])
@users('user_sales_manager')
def test_lead_convert_contact_mutlicompany(self):
""" Check the wizard convert to opp don't find contact
You are not able to see because they belong to another company """
# Use superuser_id because creating a company with a user add directly
# the company in company_ids of the user.
company_2 = self.env['res.company'].with_user(SUPERUSER_ID).create({'name': 'Company 2'})
partner_company_2 = self.env['res.partner'].with_user(SUPERUSER_ID).create({
'name': 'Contact in other company',
'email': 'test@company2.com',
'company_id': company_2.id,
})
lead = self.env['crm.lead'].create({
'name': 'LEAD',
'type': 'lead',
'email_from': 'test@company2.com',
})
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': lead.id,
'active_ids': lead.ids,
}).create({'name': 'convert', 'action': 'exist'})
self.assertNotEqual(convert.partner_id, partner_company_2,
"Conversion wizard should not be able to find the partner from another company")
@users('user_sales_manager')
def test_lead_convert_same_partner(self):
""" Check that we don't erase lead information
with existing partner info if the partner is already set
"""
partner = self.env['res.partner'].create({
'name': 'Empty partner',
})
lead = self.env['crm.lead'].create({
'name': 'LEAD',
'partner_id': partner.id,
'type': 'lead',
'email_from': 'demo@test.com',
'lang_id': self.lang_fr.id,
'street': 'my street',
'city': 'my city',
})
lead.convert_opportunity(partner)
self.assertEqual(lead.email_from, 'demo@test.com', 'Email From should be preserved during conversion')
self.assertEqual(lead.lang_id, self.lang_fr, 'Lang should be preserved during conversion')
self.assertEqual(lead.street, 'my street', 'Street should be preserved during conversion')
self.assertEqual(lead.city, 'my city', 'City should be preserved during conversion')
self.assertEqual(partner.lang, 'en_US')
@users('user_sales_manager')
def test_lead_merge(self):
""" Test convert wizard working in merge mode """
date = Datetime.from_string('2020-01-20 16:00:00')
self.crm_lead_dt_mock.now.return_value = date
leads = self.env['crm.lead']
for x in range(2):
leads |= self.env['crm.lead'].create({
'name': 'Dup-%02d-%s' % (x+1, self.lead_1.name),
'type': 'lead', 'user_id': False, 'team_id': self.lead_1.team_id.id,
'contact_name': 'Duplicate %02d of %s' % (x+1, self.lead_1.contact_name),
'email_from': self.lead_1.email_from,
'probability': 10,
})
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
'active_ids': self.lead_1.ids,
}).create({})
# test internals of convert wizard
self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | leads)
self.assertEqual(convert.user_id, self.lead_1.user_id)
self.assertEqual(convert.team_id, self.lead_1.team_id)
self.assertFalse(convert.partner_id)
self.assertEqual(convert.name, 'merge')
self.assertEqual(convert.action, 'create')
convert.write({'user_id': self.user_sales_salesman.id})
self.assertEqual(convert.user_id, self.user_sales_salesman)
self.assertEqual(convert.team_id, self.sales_team_convert)
convert.action_apply()
self.assertEqual(self.lead_1.type, 'opportunity')
@users('user_sales_salesman')
def test_lead_merge_user(self):
""" Test convert wizard working in merge mode with sales user """
date = Datetime.from_string('2020-01-20 16:00:00')
self.crm_lead_dt_mock.now.return_value = date
leads = self.env['crm.lead']
for x in range(2):
leads |= self.env['crm.lead'].create({
'name': 'Dup-%02d-%s' % (x+1, self.lead_1.name),
'type': 'lead', 'user_id': False, 'team_id': self.lead_1.team_id.id,
'contact_name': 'Duplicate %02d of %s' % (x+1, self.lead_1.contact_name),
'email_from': self.lead_1.email_from,
'probability': 10,
})
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': leads[0].id,
'active_ids': leads[0].ids,
}).create({})
# test internals of convert wizard
self.assertEqual(convert.duplicated_lead_ids, leads)
self.assertEqual(convert.name, 'merge')
self.assertEqual(convert.action, 'create')
convert.write({'user_id': self.user_sales_salesman.id})
self.assertEqual(convert.user_id, self.user_sales_salesman)
self.assertEqual(convert.team_id, self.sales_team_convert)
convert.action_apply()
self.assertEqual(leads[0].type, 'opportunity')
@users('user_sales_manager')
def test_lead_merge_duplicates(self):
""" Test Lead._get_lead_duplicates() and check: partner / email fallbacks """
customer, dup_leads = self._create_duplicates(self.lead_1)
lead_partner = dup_leads.filtered(lambda lead: lead.name == 'Duplicate: customer ID')
self.assertTrue(bool(lead_partner))
self.lead_1.write({
'partner_id': customer.id,
})
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
'active_ids': self.lead_1.ids,
}).create({})
self.assertEqual(convert.partner_id, customer)
self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | dup_leads)
# Check: partner fallbacks
self.lead_1.write({
'email_from': False,
'partner_id': customer.id,
})
customer.write({'email': False})
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
'active_ids': self.lead_1.ids,
}).create({})
self.assertEqual(convert.partner_id, customer)
self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | lead_partner)
@users('user_sales_manager')
def test_lead_merge_duplicates_flow(self):
""" Test Lead._get_lead_duplicates() + merge with active_test """
# Check: email formatting
self.lead_1.write({
'email_from': 'Amy Wong <amy.wong@test.example.com>'
})
customer, dup_leads = self._create_duplicates(self.lead_1)
opp_lost = dup_leads.filtered(lambda lead: lead.name == 'Duplicate: lost opportunity')
self.assertTrue(bool(opp_lost))
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
'active_ids': self.lead_1.ids,
}).create({})
self.assertEqual(convert.partner_id, customer)
self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | dup_leads)
convert.action_apply()
self.assertEqual(
(self.lead_1 | dup_leads).exists(),
opp_lost)
@tagged('lead_manage')
class TestLeadConvertBatch(crm_common.TestLeadConvertMassCommon):
def test_initial_data(self):
""" Ensure initial data to avoid spaghetti test update afterwards """
self.assertFalse(self.lead_1.date_conversion)
self.assertEqual(self.lead_1.date_open, Datetime.from_string('2020-01-15 11:30:00'))
self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
self.assertEqual(self.lead_1.team_id, self.sales_team_1)
self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
self.assertEqual(self.lead_w_partner.stage_id, self.env['crm.stage'])
self.assertEqual(self.lead_w_partner.user_id, self.user_sales_manager)
self.assertEqual(self.lead_w_partner.team_id, self.sales_team_1)
self.assertEqual(self.lead_w_partner_company.stage_id, self.stage_team1_1)
self.assertEqual(self.lead_w_partner_company.user_id, self.user_sales_manager)
self.assertEqual(self.lead_w_partner_company.team_id, self.sales_team_1)
self.assertEqual(self.lead_w_contact.stage_id, self.stage_gen_1)
self.assertEqual(self.lead_w_contact.user_id, self.user_sales_salesman)
self.assertEqual(self.lead_w_contact.team_id, self.sales_team_convert)
self.assertEqual(self.lead_w_email.stage_id, self.stage_gen_1)
self.assertEqual(self.lead_w_email.user_id, self.user_sales_salesman)
self.assertEqual(self.lead_w_email.team_id, self.sales_team_convert)
self.assertEqual(self.lead_w_email_lost.stage_id, self.stage_team1_2)
self.assertEqual(self.lead_w_email_lost.user_id, self.user_sales_leads)
self.assertEqual(self.lead_w_email_lost.team_id, self.sales_team_1)
@users('user_sales_manager')
def test_lead_convert_batch_internals(self):
""" Test internals of convert wizard, working in batch mode """
date = self.env.cr.now()
lead_w_partner = self.lead_w_partner
lead_w_contact = self.lead_w_contact
lead_w_email_lost = self.lead_w_email_lost
lead_w_email_lost.action_set_lost()
self.assertEqual(lead_w_email_lost.active, False)
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
'active_ids': (self.lead_1 | lead_w_partner | lead_w_contact | lead_w_email_lost).ids,
}).create({})
# test internals of convert wizard
# self.assertEqual(convert.lead_id, self.lead_1)
self.assertEqual(convert.user_id, self.lead_1.user_id)
self.assertEqual(convert.team_id, self.lead_1.team_id)
self.assertFalse(convert.partner_id)
self.assertEqual(convert.name, 'convert')
self.assertEqual(convert.action, 'create')
convert.action_apply()
self.assertEqual(convert.user_id, self.user_sales_leads)
self.assertEqual(convert.team_id, self.sales_team_1)
# lost leads are not converted (see crm_lead.convert_opportunity())
self.assertFalse(lead_w_email_lost.active)
self.assertFalse(lead_w_email_lost.date_conversion)
self.assertEqual(lead_w_email_lost.partner_id, self.env['res.partner'])
self.assertEqual(lead_w_email_lost.stage_id, self.stage_team1_2) # did not change
# other leads are converted into opportunities
for opp in (self.lead_1 | lead_w_partner | lead_w_contact):
# team management update: opportunity linked to chosen wizard values
self.assertEqual(opp.type, 'opportunity')
self.assertTrue(opp.active)
self.assertEqual(opp.user_id, convert.user_id)
self.assertEqual(opp.team_id, convert.team_id)
# dates update: convert set them to now
self.assertEqual(opp.date_open, date)
self.assertEqual(opp.date_conversion, date)
# stage update (depends on previous value)
if opp == self.lead_1:
self.assertEqual(opp.stage_id, self.stage_team1_1) # did not change
elif opp == lead_w_partner:
self.assertEqual(opp.stage_id, self.stage_team1_1) # is set to default stage of sales_team_1
elif opp == lead_w_contact:
self.assertEqual(opp.stage_id, self.stage_gen_1) # did not change
else:
self.assertFalse(True)