diff --git a/addons/crm/tests/__init__.py b/addons/crm/tests/__init__.py index dfcb9be62a3..22d6d774639 100644 --- a/addons/crm/tests/__init__.py +++ b/addons/crm/tests/__init__.py @@ -3,6 +3,7 @@ from . import test_crm_lead from . import test_crm_lead_notification from . import test_crm_lead_convert +from . import test_crm_lead_merge from . import test_crm_activity from . import test_crm_ui from . import test_crm_pls diff --git a/addons/crm/tests/test_crm_lead.py b/addons/crm/tests/test_crm_lead.py index bf7933df43e..d8c2e7d74f9 100644 --- a/addons/crm/tests/test_crm_lead.py +++ b/addons/crm/tests/test_crm_lead.py @@ -82,110 +82,3 @@ class TestCRMLead(TestCrmCommon): new_partner = self.env['res.partner'].with_user(self.user_sales_manager).browse(new_partner_id) self.assertEqual(new_partner.email, 'unknown.sender@test.example.com') self.assertEqual(new_partner.team_id, self.sales_team_1) - - def test_crm_lead_merge(self): - # During a mixed merge (involving leads and opps), data should be handled a certain way following their type - # (m2o, m2m, text, ...) Start by creating two leads and an opp and giving the rights of Sales manager. - default_stage_id = self.stage_team1_1.id - LeadSalesmanager = self.env['crm.lead'].with_user(self.user_sales_manager) - - # TEST CASE 1 - test_crm_opp_01 = LeadSalesmanager.create({ - 'type': 'opportunity', - 'name': 'Test opportunity 1', - 'partner_id': self.contact_2.id, - 'stage_id': default_stage_id, - 'description': 'This is the description of the test opp 1.' - }) - - test_crm_lead_01 = LeadSalesmanager.create({ - 'type': 'lead', - 'name': 'Test lead first', - 'partner_id': self.contact_1.id, - 'stage_id': default_stage_id, - 'description': 'This is the description of the test lead first.' - }) - - test_crm_lead_02 = LeadSalesmanager.create({ - 'type': 'lead', - 'name': 'Test lead second', - 'partner_id': self.contact_1.id, - 'stage_id': default_stage_id, - 'description': 'This is the description of the test lead second.' - }) - - lead_ids = [test_crm_opp_01.id, test_crm_lead_01.id, test_crm_lead_02.id] - additionnal_context = {'active_model': 'crm.lead', 'active_ids': lead_ids, 'active_id': lead_ids[0]} - - # I create a merge wizard and merge the leads and opp together in the first item of the list. - merge_opp_wizard_01 = self.env['crm.merge.opportunity'].with_user(self.user_sales_manager).with_context(**additionnal_context).create({}) - merge_opp_wizard_01.action_merge() - - # I check for the resulting merged opp (based on name and partner) - merged_lead = self.env['crm.lead'].search([('name', '=', 'Test opportunity 1'), ('partner_id', '=', self.contact_2.id)], limit=1) - self.assertTrue(merged_lead, 'Fail to create merge opportunity wizard') - self.assertEqual(merged_lead.description, 'This is the description of the test opp 1.\n\nThis is the description of the test lead first.\n\nThis is the description of the test lead second.', 'Description mismatch: when merging leads/opps with different text values, these values should get concatenated and separated with line returns') - self.assertEqual(merged_lead.type, 'opportunity', 'Type mismatch: when at least one opp in involved in the merge, the result should be a new opp (instead of %s)' % merged_lead.type) - - # The other (tailing) leads/opps shouldn't exist anymore - self.assertFalse(test_crm_lead_01.exists(), 'This tailing lead (id %s) should not exist anymore' % test_crm_lead_02.id) - self.assertFalse(test_crm_lead_02.exists(), 'This tailing opp (id %s) should not exist anymore' % test_crm_opp_01.id) - - # TEST CASE 2 - # I want to test leads merge. Start by creating two leads (with the same partner) - test_crm_lead_03 = LeadSalesmanager.create({ - 'type': 'lead', - 'name': 'Test lead 3', - 'partner_id': self.contact_1.id, - 'stage_id': default_stage_id - }) - - test_crm_lead_04 = LeadSalesmanager.create({ - 'type': 'lead', - 'name': 'Test lead 4', - 'partner_id': self.contact_1.id, - 'stage_id': default_stage_id - }) - - lead_ids = [test_crm_lead_03.id, test_crm_lead_04.id] - additionnal_context = {'active_model': 'crm.lead', 'active_ids': lead_ids, 'active_id': lead_ids[0]} - - # I create a merge wizard and merge the leads together. - merge_opp_wizard_02 = self.env['crm.merge.opportunity'].with_user(self.user_sales_manager).with_context(**additionnal_context).create({}) - merge_opp_wizard_02.action_merge() - - # I check for the resulting merged lead (based on name and partner) - merged_lead = self.env['crm.lead'].search([('name', '=', 'Test lead 3'), ('partner_id', '=', self.contact_1.id)], limit=1) - self.assertTrue(merged_lead, 'Fail to create merge opportunity wizard') - self.assertEqual(merged_lead.partner_id.id, self.contact_1.id, 'Partner mismatch') - self.assertEqual(merged_lead.type, 'lead', 'Type mismatch: when leads get merged together, the result should be a new lead (instead of %s)' % merged_lead.type) - self.assertFalse(test_crm_lead_04.exists(), 'This tailing lead (id %s) should not exist anymore' % test_crm_lead_04.id) - - # TEST CASE 3 - # I want to test opps merge. Start by creating two opportunities (with the same partner). - test_crm_opp_02 = LeadSalesmanager.create({ - 'type': 'opportunity', - 'name': 'Test opportunity 2', - 'partner_id': self.contact_2.id, - 'stage_id': default_stage_id - }) - - test_crm_opp_03 = LeadSalesmanager.create({ - 'type': 'opportunity', - 'name': 'Test opportunity 3', - 'partner_id': self.contact_2.id, - 'stage_id': default_stage_id - }) - - opportunity_ids = [test_crm_opp_02.id, test_crm_opp_03.id] - additionnal_context = {'active_model': 'crm.lead', 'active_ids': opportunity_ids, 'active_id': opportunity_ids[0]} - - # I create a merge wizard and merge the opps together. - merge_opp_wizard_03 = self.env['crm.merge.opportunity'].with_user(self.user_sales_manager).with_context(**additionnal_context).create({}) - merge_opp_wizard_03.action_merge() - - merged_opportunity = self.env['crm.lead'].search([('name', '=', 'Test opportunity 2'), ('partner_id', '=', self.contact_2.id)], limit=1) - self.assertTrue(merged_opportunity, 'Fail to create merge opportunity wizard') - self.assertEqual(merged_opportunity.partner_id.id, self.contact_2.id, 'Partner mismatch') - self.assertEqual(merged_opportunity.type, 'opportunity', 'Type mismatch: when opps get merged together, the result should be a new opp (instead of %s)' % merged_opportunity.type) - self.assertFalse(test_crm_opp_03.exists(), 'This tailing opp (id %s) should not exist anymore' % test_crm_opp_03.id) diff --git a/addons/crm/tests/test_crm_lead_merge.py b/addons/crm/tests/test_crm_lead_merge.py new file mode 100644 index 00000000000..22d103f4f1f --- /dev/null +++ b/addons/crm/tests/test_crm_lead_merge.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.crm.tests.common import TestLeadConvertMassCommon +from odoo.tests.common import tagged, users + + +@tagged('lead_manage') +class TestLeadMerge(TestLeadConvertMassCommon): + """ During a mixed merge (involving leads and opps), data should be handled a certain way following their type + (m2o, m2m, text, ...). """ + + @classmethod + def setUpClass(cls): + super(TestLeadMerge, cls).setUpClass() + + cls.leads = cls.lead_1 + cls.lead_w_partner + cls.lead_w_contact + cls.lead_w_email + cls.lead_w_partner_company + cls.lead_w_email_lost + # reset some assigned users to test salesmen assign + (cls.lead_w_partner | cls.lead_w_email_lost).write({ + 'user_id': False + }) + + cls.lead_w_contact.write({'description': 'lead_w_contact'}) + cls.lead_w_email.write({'description': 'lead_w_email'}) + cls.lead_1.write({'description': 'lead_1'}) + cls.lead_w_partner.write({'description': 'lead_w_partner'}) + + cls.assign_users = cls.user_sales_manager + cls.user_sales_leads_convert + cls.user_sales_salesman + + @users('user_sales_manager') + def test_lead_merge_internals(self): + """ Test internals of merge wizard. In this test leads are ordered as + + lead_w_contact --lead---seq=30 + lead_w_email ----lead---seq=3 + lead_1 ----------lead---seq=1 + lead_w_partner --lead---seq=False + """ + # ensure initial data + 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.lead_w_partner_company.action_set_won() # won opps should be excluded + + merge = self.env['crm.merge.opportunity'].with_context({ + 'active_model': 'crm.lead', + 'active_ids': self.leads.ids, + 'active_id': False, + }).create({ + 'user_id': self.user_sales_leads_convert.id, + }) + # check user correctly triggered its sales team + merge._onchange_user() + self.assertEqual(merge.team_id, self.sales_team_convert) + + # TDE FIXME: not sure the browse in default get of wizard intended to exlude lost, as it browse ids + # and exclude inactive leads, but that's not written anywhere ... intended ?? + self.assertEqual(merge.opportunity_ids, self.leads - self.lead_w_partner_company - self.lead_w_email_lost) + ordered_merge = self.lead_w_contact + self.lead_w_email + self.lead_1 + self.lead_w_partner + ordered_merge_description = '\n\n'.join(l.description for l in ordered_merge) + + # merged opportunity: in this test, all input are leads. Confidence is based on stage + # sequence -> lead_w_contact has a stage sequence of 30 + result = merge.action_merge() + merge_opportunity = self.env['crm.lead'].browse(result['res_id']) + self.assertFalse((ordered_merge - merge_opportunity).exists()) + self.assertEqual(merge_opportunity, self.lead_w_contact) + self.assertEqual(merge_opportunity.type, 'lead') + self.assertEqual(merge_opportunity.description, ordered_merge_description) + # merged opportunity has updated salesman / team / stage is ok as generic + self.assertEqual(merge_opportunity.user_id, self.user_sales_leads_convert) + self.assertEqual(merge_opportunity.team_id, self.sales_team_convert) + self.assertEqual(merge_opportunity.stage_id, self.stage_gen_1) + + @users('user_sales_manager') + def test_lead_merge_mixed(self): + """ In case of mix, opportunities are on top, and result is an opportunity + + lead_1 -------------------opp----seq=1 + lead_w_partner_company ---opp----seq=1 (ID greater) + lead_w_contact -----------lead---seq=30 + lead_w_email -------------lead---seq=3 + lead_w_partner -----------lead---seq=False + """ + # ensure initial data + (self.lead_w_partner_company | self.lead_1).write({'type': 'opportunity'}) + self.assertEqual(self.lead_w_partner_company.stage_id.sequence, 1) + self.assertEqual(self.lead_1.stage_id.sequence, 1) + + merge = self.env['crm.merge.opportunity'].with_context({ + 'active_model': 'crm.lead', + 'active_ids': self.leads.ids, + 'active_id': False, + }).create({ + 'team_id': self.sales_team_convert.id, + 'user_id': False, + }) + # TDE FIXME: see aa44700dccdc2618e0b8bc94252789264104047c -> no user, no team -> strange + merge._onchange_user() + merge.write({'team_id': self.sales_team_convert.id}) + + # TDE FIXME: not sure the browse in default get of wizard intended to exlude lost, as it browse ids + # and exclude inactive leads, but that's not written anywhere ... intended ?? + self.assertEqual(merge.opportunity_ids, self.leads - self.lead_w_email_lost) + ordered_merge = self.lead_w_partner_company + self.lead_w_contact + self.lead_w_email + self.lead_w_partner + + result = merge.action_merge() + merge_opportunity = self.env['crm.lead'].browse(result['res_id']) + self.assertFalse((ordered_merge - merge_opportunity).exists()) + self.assertEqual(merge_opportunity, self.lead_1) + self.assertEqual(merge_opportunity.type, 'opportunity') + + # merged opportunity has same salesman (not updated in wizard) + self.assertEqual(merge_opportunity.user_id, self.user_sales_leads) + # TDE FIXME: as same uer_id is enforced, team is updated through onchange and therefore stage + # self.assertEqual(merge_opportunity.team_id, self.sales_team_convert) + self.assertEqual(merge_opportunity.team_id, self.sales_team_1) + # TDE FIXME: BUT team_id is computed after checking stage, based on wizard's team_id + self.assertEqual(merge_opportunity.stage_id, self.stage_team_convert_1)