diff --git a/addons/crm/tests/common.py b/addons/crm/tests/common.py index 1295806a8fd..62ebbd5c141 100644 --- a/addons/crm/tests/common.py +++ b/addons/crm/tests/common.py @@ -2,13 +2,15 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from ast import literal_eval +from contextlib import contextmanager from unittest.mock import patch +from odoo.addons.crm.models.crm_lead import CRM_LEAD_FIELDS_TO_MERGE from odoo.addons.mail.tests.common import MailCase, mail_new_test_user from odoo.addons.phone_validation.tools import phone_validation from odoo.addons.sales_team.tests.common import TestSalesCommon from odoo.fields import Datetime -from odoo import tools +from odoo import models, tools INCOMING_EMAIL = """Return-Path: {return_path} X-Original-To: {to} @@ -44,6 +46,14 @@ Somebody.""" class TestCrmCommon(TestSalesCommon, MailCase): + FIELDS_FIRST_SET = [ + 'name', 'partner_id', 'campaign_id', 'company_id', 'country_id', + 'team_id', 'state_id', 'stage_id', 'medium_id', 'source_id', 'user_id', + 'title', 'city', 'contact_name', 'description', 'mobile', 'partner_name', + 'phone', 'probability', 'expected_revenue', 'street', 'street2', 'zip', + 'create_date', 'date_action_last', 'email_from', 'email_cc', 'website' + ] + @classmethod def setUpClass(cls): super(TestCrmCommon, cls).setUpClass() @@ -299,6 +309,71 @@ class TestCrmCommon(TestSalesCommon, MailCase): new_leads.flush() # compute notably probability return new_leads + @contextmanager + def assertLeadMerged(self, opportunity, leads, **expected): + """ Assert result of lead _merge_opportunity process. This is done using + a context manager in order to save original opportunity (master lead) + values. Indeed those will be modified during merge process. We have to + ensure final values are correct taking into account all leads values + before merging them. + + :param opportunity: final opportunity + :param leads: merged leads (including opportunity) + """ + self.assertIn(opportunity, leads) + + # save opportunity value before being modified by merge process + fields_all = self.FIELDS_FIRST_SET + ['type', 'priority'] + # ensure tests are synchronized with crm code + self.assertTrue(all(field in fields_all for field in CRM_LEAD_FIELDS_TO_MERGE)) + original_opp_values = dict( + (fname, opportunity[fname]) + for fname in fields_all + ) + + def _find_value(lead, fname): + if lead == opportunity: + return original_opp_values[fname] + return lead[fname] + + def _first_set(fname): + values = [_find_value(lead, fname) for lead in leads] + return next((value for value in values if value), False) + + def _get_type(): + values = [_find_value(lead, 'type') for lead in leads] + return 'opportunity' if 'opportunity' in values else 'lead' + + def _get_description(): + values = [_find_value(lead, 'description') for lead in leads] + return '\n\n'.join(value for value in values if value) + + def _get_priority(): + return original_opp_values['priority'] + + try: + # merge process will modify opportunity + yield + finally: + # support specific values caller may want to check in addition to generic tests + for fname, expected in expected.items(): + self.assertEqual(opportunity[fname], expected) + + # classic fields: first not void wins + for fname in fields_all: + opp_value = opportunity[fname] + if fname == 'description': + self.assertEqual(opp_value, _get_description()) + elif fname == 'type': + self.assertEqual(opp_value, _get_type()) + elif fname == 'priority': + self.assertEqual(opp_value, _get_priority()) + else: + self.assertEqual( + opp_value if opp_value or not isinstance(opp_value, models.BaseModel) else False, + _first_set(fname) + ) + class TestLeadConvertCommon(TestCrmCommon): @@ -454,6 +529,7 @@ class TestLeadConvertMassCommon(TestLeadConvertCommon): cls.lead_w_partner = cls.env['crm.lead'].create({ 'name': 'New1', 'type': 'lead', + 'priority': '0', 'probability': 10, 'user_id': cls.user_sales_manager.id, 'stage_id': False, @@ -481,6 +557,7 @@ class TestLeadConvertMassCommon(TestLeadConvertCommon): cls.lead_w_email = cls.env['crm.lead'].create({ 'name': 'LeadEmailAsContact', 'type': 'lead', + 'priority': '2', 'probability': 15, 'email_from': 'contact.email@test.example.com', 'user_id': cls.user_sales_salesman.id, diff --git a/addons/crm/tests/test_crm_lead_merge.py b/addons/crm/tests/test_crm_lead_merge.py index 1bcf1c2a640..f9e534dabe8 100644 --- a/addons/crm/tests/test_crm_lead_merge.py +++ b/addons/crm/tests/test_crm_lead_merge.py @@ -141,3 +141,21 @@ class TestLeadMerge(TestLeadConvertMassCommon): # 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) + + @users('user_sales_manager') + def test_merge_method(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'}) + leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True) + with self.assertLeadMerged(self.lead_1, leads, + name='Nibbler Spacecraft Request', + partner_id=self.contact_company_1): + leads._merge_opportunity(auto_unlink=False, max_length=None)