mirror of
https://github.com/bringout/oca-ocb-crm.git
synced 2026-04-22 10:12:09 +02:00
19.0 vanilla
This commit is contained in:
parent
dc68f80d3f
commit
7221b9ac46
610 changed files with 135477 additions and 161677 deletions
|
|
@ -8,12 +8,13 @@ from . import test_crm_lead_notification
|
|||
from . import test_crm_lead_convert
|
||||
from . import test_crm_lead_convert_mass
|
||||
from . import test_crm_lead_duplicates
|
||||
from . import test_crm_lead_lost
|
||||
from . import test_crm_lead_merge
|
||||
from . import test_crm_lead_multicompany
|
||||
from . import test_crm_lead_smart_calendar
|
||||
from . import test_crm_ui
|
||||
from . import test_crm_pls
|
||||
from . import test_crm_rainbowman
|
||||
from . import test_digest
|
||||
from . import test_performances
|
||||
from . import test_res_partner
|
||||
from . import test_sales_team_ui
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from ast import literal_eval
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.crm.models.crm_lead import PARTNER_ADDRESS_FIELDS_TO_SYNC
|
||||
|
|
@ -37,7 +39,7 @@ Content-Transfer-Encoding: 8bit
|
|||
|
||||
This is an example email. All sensitive content has been stripped out.
|
||||
|
||||
ALL GLORY TO THE HYPNOTOAD !
|
||||
ALL GLORY TO THE HYPNOTOAD!
|
||||
|
||||
Cheers,
|
||||
|
||||
|
|
@ -49,9 +51,9 @@ 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', 'mobile', 'partner_name',
|
||||
'city', 'contact_name', 'partner_name',
|
||||
'phone', 'probability', 'expected_revenue', 'street', 'street2', 'zip',
|
||||
'create_date', 'date_action_last', 'email_from', 'email_cc', 'website'
|
||||
'create_date', 'date_automation_last', 'email_from', 'email_cc', 'website'
|
||||
]
|
||||
merge_fields = ['description', 'type', 'priority']
|
||||
|
||||
|
|
@ -91,35 +93,35 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
})
|
||||
|
||||
(cls.user_sales_manager + cls.user_sales_leads + cls.user_sales_salesman).write({
|
||||
'groups_id': [(4, cls.env.ref('crm.group_use_lead').id)]
|
||||
'group_ids': [(4, cls.env.ref('crm.group_use_lead').id)]
|
||||
})
|
||||
|
||||
cls.env['crm.stage'].search([]).write({'sequence': 9999}) # ensure search will find test data first
|
||||
cls.stage_team1_1 = cls.env['crm.stage'].create({
|
||||
'name': 'New',
|
||||
'sequence': 1,
|
||||
'team_id': cls.sales_team_1.id,
|
||||
'team_ids': [cls.sales_team_1.id],
|
||||
})
|
||||
cls.stage_team1_2 = cls.env['crm.stage'].create({
|
||||
'name': 'Proposition',
|
||||
'sequence': 5,
|
||||
'team_id': cls.sales_team_1.id,
|
||||
'team_ids': [cls.sales_team_1.id],
|
||||
})
|
||||
cls.stage_team1_won = cls.env['crm.stage'].create({
|
||||
'name': 'Won',
|
||||
'sequence': 70,
|
||||
'team_id': cls.sales_team_1.id,
|
||||
'team_ids': [cls.sales_team_1.id],
|
||||
'is_won': True,
|
||||
})
|
||||
cls.stage_gen_1 = cls.env['crm.stage'].create({
|
||||
'name': 'Generic stage',
|
||||
'sequence': 3,
|
||||
'team_id': False,
|
||||
'team_ids': False,
|
||||
})
|
||||
cls.stage_gen_won = cls.env['crm.stage'].create({
|
||||
'name': 'Generic Won',
|
||||
'sequence': 30,
|
||||
'team_id': False,
|
||||
'team_ids': False,
|
||||
'is_won': True,
|
||||
})
|
||||
|
||||
|
|
@ -158,7 +160,7 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
})
|
||||
cls.lead_team_1_won.action_set_won()
|
||||
cls.lead_team_1_lost = cls.env['crm.lead'].create({
|
||||
'name': 'Already Won',
|
||||
'name': 'Already Lost',
|
||||
'type': 'lead',
|
||||
'user_id': cls.user_sales_leads.id,
|
||||
'team_id': cls.sales_team_1.id,
|
||||
|
|
@ -166,6 +168,11 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
cls.lead_team_1_lost.action_set_lost()
|
||||
(cls.lead_team_1_won + cls.lead_team_1_lost).flush_recordset()
|
||||
|
||||
# make lead 1 take team history into account for its automated proba.
|
||||
# it should now be 50% as auto proba. (1 lost 1 won for team 1)
|
||||
cls.lead_1._compute_probabilities()
|
||||
cls.lead_1.flush_recordset()
|
||||
|
||||
# email / phone data
|
||||
cls.test_email_data = [
|
||||
'"Planet Express" <planet.express@test.example.com>',
|
||||
|
|
@ -201,8 +208,6 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
cls.contact_1 = cls.env['res.partner'].create({
|
||||
'name': 'Philip J Fry',
|
||||
'email': cls.test_email_data[1],
|
||||
'mobile': cls.test_phone_data[0],
|
||||
'title': cls.env.ref('base.res_partner_title_mister').id,
|
||||
'function': 'Delivery Boy',
|
||||
'lang': cls.lang_en.code,
|
||||
'phone': False,
|
||||
|
|
@ -217,7 +222,6 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
'name': 'Turanga Leela',
|
||||
'email': cls.test_email_data[2],
|
||||
'lang': cls.lang_en.code,
|
||||
'mobile': cls.test_phone_data[1],
|
||||
'phone': cls.test_phone_data[2],
|
||||
'parent_id': False,
|
||||
'is_company': False,
|
||||
|
|
@ -234,7 +238,6 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
'city': 'New new York',
|
||||
'country_id': base_us.id,
|
||||
'lang': cls.lang_en.code,
|
||||
'mobile': '+1 202 555 0888',
|
||||
'zip': '87654',
|
||||
})
|
||||
|
||||
|
|
@ -253,10 +256,6 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
'res_id': cls.activity_type_1.id,
|
||||
})
|
||||
|
||||
def setUp(self):
|
||||
super(TestCrmCommon, self).setUp()
|
||||
self.flush_tracking()
|
||||
|
||||
@classmethod
|
||||
def _activate_multi_company(cls):
|
||||
cls.company_2 = cls.env['res.company'].create({
|
||||
|
|
@ -265,6 +264,18 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
'email': 'company.2@test.example.com',
|
||||
'name': 'New Test Company',
|
||||
})
|
||||
cls.alias_bounce_c2 = 'bounce.c2'
|
||||
cls.alias_catchall_c2 = 'catchall.c2'
|
||||
cls.alias_default_from_c2 = 'notifications.c2'
|
||||
cls.alias_domain_c2_name = 'test.mycompany2.com'
|
||||
cls.mail_alias_domain_c2 = cls.env['mail.alias.domain'].create({
|
||||
'bounce_alias': cls.alias_bounce_c2,
|
||||
'catchall_alias': cls.alias_catchall_c2,
|
||||
'company_ids': [(4, cls.company_2.id)],
|
||||
'default_from': cls.alias_default_from_c2,
|
||||
'name': cls.alias_domain_c2_name,
|
||||
'sequence': 2,
|
||||
})
|
||||
|
||||
cls.user_sales_manager_mc = mail_new_test_user(
|
||||
cls.env,
|
||||
|
|
@ -305,7 +316,7 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
|
||||
def _create_leads_batch(self, lead_type='lead', count=10, email_dup_count=0,
|
||||
partner_count=0, partner_ids=None, user_ids=None,
|
||||
country_ids=None, probabilities=None, suffix=''):
|
||||
country_ids=None, probabilities=None, suffix='', additional_lead_values=defaultdict(None)):
|
||||
""" Helper tool method creating a batch of leads, useful when dealing
|
||||
with batch processes. Please update me.
|
||||
|
||||
|
|
@ -323,6 +334,7 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
'name': f'TestLead{suffix}_{x:04d}',
|
||||
'type': lead_type if lead_type else types[x % 2],
|
||||
'priority': '%s' % (x % 3),
|
||||
**additional_lead_values,
|
||||
} for x in range(count)]
|
||||
|
||||
# generate customer information
|
||||
|
|
@ -381,7 +393,7 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
# duplicates (currently only with email)
|
||||
dups_data = []
|
||||
if email_dup_count and not partner_ids:
|
||||
for idx, lead_data in enumerate(leads_data):
|
||||
for lead_data in leads_data:
|
||||
if not lead_data.get('partner_id') and lead_data['email_from']:
|
||||
dup_data = dict(lead_data)
|
||||
dup_data['name'] = 'Duplicated-%s' % dup_data['name']
|
||||
|
|
@ -456,6 +468,8 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
:param leads: merged leads (including opportunity)
|
||||
"""
|
||||
self.assertIn(opportunity, leads)
|
||||
opportunity = opportunity.sudo()
|
||||
leads = leads.sudo()
|
||||
|
||||
# save opportunity value before being modified by merge process
|
||||
fields_all = self.FIELDS_FIRST_SET + self.merge_fields
|
||||
|
|
@ -498,11 +512,11 @@ class TestCrmCommon(TestSalesCommon, MailCase):
|
|||
yield
|
||||
finally:
|
||||
# support specific values caller may want to check in addition to generic tests
|
||||
for fname, expected in expected.items():
|
||||
if expected is False:
|
||||
for fname, e_val in expected.items():
|
||||
if e_val is False:
|
||||
self.assertFalse(opportunity[fname], "%s must be False" % fname)
|
||||
else:
|
||||
self.assertEqual(opportunity[fname], expected, "%s must be equal to %s" % (fname, expected))
|
||||
self.assertEqual(opportunity[fname], e_val, "%s must be equal to %s" % (fname, e_val))
|
||||
|
||||
# classic fields: first not void wins or specific computation
|
||||
for fname in fields_all:
|
||||
|
|
@ -568,7 +582,7 @@ class TestLeadConvertCommon(TestCrmCommon):
|
|||
cls.stage_team_convert_1 = cls.env['crm.stage'].create({
|
||||
'name': 'New',
|
||||
'sequence': 1,
|
||||
'team_id': cls.sales_team_convert.id,
|
||||
'team_ids': [cls.sales_team_convert.id],
|
||||
})
|
||||
|
||||
cls.lead_1.write({'date_open': Datetime.from_string('2020-01-15 11:30:00')})
|
||||
|
|
@ -632,8 +646,12 @@ class TestLeadConvertCommon(TestCrmCommon):
|
|||
|
||||
def assertMemberAssign(self, member, count):
|
||||
""" Check assign result and that domains are effectively taken into account """
|
||||
self.assertEqual(member.lead_month_count, count)
|
||||
member_leads = self.env['crm.lead'].search(member._get_lead_month_domain())
|
||||
self.assertEqual(member.lead_day_count, count)
|
||||
member_leads = self.env['crm.lead'].search([
|
||||
('user_id', '=', member.user_id.id),
|
||||
('team_id', '=', member.crm_team_id.id),
|
||||
('date_open', '>=', Datetime.now() - timedelta(hours=24)),
|
||||
])
|
||||
self.assertEqual(len(member_leads), count)
|
||||
if member.assignment_domain:
|
||||
self.assertEqual(
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@
|
|||
from datetime import date, timedelta
|
||||
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon
|
||||
from odoo.tests.common import users
|
||||
from odoo.tests.common import tagged, users
|
||||
|
||||
|
||||
@tagged('mail_activity')
|
||||
class TestCrmMailActivity(TestCrmCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCrmMailActivity, cls).setUpClass()
|
||||
super().setUpClass()
|
||||
|
||||
cls.activity_type_1 = cls.env['mail.activity.type'].create({
|
||||
'name': 'Initial Contact',
|
||||
|
|
@ -22,7 +23,7 @@ class TestCrmMailActivity(TestCrmCommon):
|
|||
cls.activity_type_2 = cls.env['mail.activity.type'].create({
|
||||
'name': 'Call for Demo',
|
||||
'delay_count': 6,
|
||||
'summary': 'ACT 2 : I want to show you my ERP !',
|
||||
'summary': 'ACT 2 : I want to show you my ERP!',
|
||||
'res_model': 'crm.lead',
|
||||
})
|
||||
for activity_type in cls.activity_type_1 + cls.activity_type_2:
|
||||
|
|
@ -70,10 +71,10 @@ class TestCrmMailActivity(TestCrmCommon):
|
|||
deadline_in2d, False, False, False, False]
|
||||
|
||||
test_leads[0:4].activity_schedule(act_type_xmlid='crm.call_for_demo', user_id=self.user_sales_manager.id, date_deadline=deadline_in1d)
|
||||
test_leads[0:3].activity_schedule(act_type_xmlid='crm.initial_contact', date_deadline=deadline_in2d)
|
||||
test_leads[5].activity_schedule(act_type_xmlid='crm.initial_contact', date_deadline=deadline_in2d)
|
||||
(test_leads[1] | test_leads[3]).activity_schedule(act_type_xmlid='crm.initial_contact', date_deadline=deadline_was1d)
|
||||
(test_leads[2] | test_leads[4]).activity_schedule(act_type_xmlid='crm.call_for_demo', date_deadline=deadline_was2d)
|
||||
test_leads[0:3].activity_schedule(act_type_xmlid='crm.initial_contact', user_id=self.user_sales_leads.id, date_deadline=deadline_in2d)
|
||||
test_leads[5].activity_schedule(act_type_xmlid='crm.initial_contact', user_id=self.user_sales_leads.id, date_deadline=deadline_in2d)
|
||||
(test_leads[1] | test_leads[3]).activity_schedule(act_type_xmlid='crm.initial_contact', user_id=self.user_sales_leads.id, date_deadline=deadline_was1d)
|
||||
(test_leads[2] | test_leads[4]).activity_schedule(act_type_xmlid='crm.call_for_demo', user_id=self.user_sales_leads.id, date_deadline=deadline_was2d)
|
||||
test_leads.invalidate_recordset()
|
||||
|
||||
expected_ids_asc = [2, 4, 1, 3, 5, 0, 8, 7, 9, 6]
|
||||
|
|
@ -137,8 +138,9 @@ class TestCrmMailActivity(TestCrmCommon):
|
|||
|
||||
# mark as done, check lead and posted message
|
||||
activity.action_done()
|
||||
self.assertFalse(self.lead_1.activity_type_id.id)
|
||||
self.assertFalse(self.lead_1.activity_ids)
|
||||
self.lead_1.invalidate_recordset(fnames=["activity_type_id"]) # archive does not trigger recompute
|
||||
self.assertFalse(self.lead_1.activity_type_id)
|
||||
activity_message = self.lead_1.message_ids[0]
|
||||
self.assertEqual(activity_message.notified_partner_ids, self.user_sales_manager.partner_id)
|
||||
self.assertEqual(activity_message.subtype_id, self.env.ref('mail.mt_activities'))
|
||||
|
|
@ -146,18 +148,19 @@ class TestCrmMailActivity(TestCrmCommon):
|
|||
def test_crm_activity_next_action(self):
|
||||
""" This test case set the next activity on a lead, log another, and schedule a third. """
|
||||
# Add the next activity (like we set it from a form view)
|
||||
test_lead = self.lead_1.with_user(self.user_sales_manager)
|
||||
lead_model_id = self.env['ir.model']._get('crm.lead').id
|
||||
activity = self.env['mail.activity'].with_user(self.user_sales_manager).create({
|
||||
'activity_type_id': self.activity_type_1.id,
|
||||
'summary': 'My Own Summary',
|
||||
'res_id': self.lead_1.id,
|
||||
'res_id': test_lead.id,
|
||||
'res_model_id': lead_model_id,
|
||||
})
|
||||
activity._onchange_activity_type_id()
|
||||
|
||||
# Check the next activity is correct
|
||||
self.assertEqual(self.lead_1.activity_summary, activity.summary)
|
||||
self.assertEqual(self.lead_1.activity_type_id, activity.activity_type_id)
|
||||
self.assertEqual(test_lead.activity_summary, activity.summary)
|
||||
self.assertEqual(test_lead.activity_type_id, activity.activity_type_id)
|
||||
# self.assertEqual(fields.Datetime.from_string(self.lead.activity_date_deadline), datetime.now() + timedelta(days=activity.activity_type_id.days))
|
||||
|
||||
activity.write({
|
||||
|
|
@ -167,11 +170,14 @@ class TestCrmMailActivity(TestCrmCommon):
|
|||
})
|
||||
activity._onchange_activity_type_id()
|
||||
|
||||
self.assertEqual(self.lead_1.activity_summary, activity.activity_type_id.summary)
|
||||
self.assertEqual(self.lead_1.activity_type_id, activity.activity_type_id)
|
||||
self.assertEqual(test_lead.activity_summary, activity.activity_type_id.summary)
|
||||
self.assertEqual(test_lead.activity_type_id, activity.activity_type_id)
|
||||
# self.assertEqual(fields.Datetime.from_string(self.lead.activity_date_deadline), datetime.now() + timedelta(days=activity.activity_type_id.days))
|
||||
|
||||
self.assertEqual(test_lead.activity_ids, activity)
|
||||
activity.action_done()
|
||||
|
||||
# Check the next activity on the lead has been removed
|
||||
self.assertFalse(self.lead_1.activity_type_id)
|
||||
self.assertFalse(test_lead.activity_ids)
|
||||
test_lead.invalidate_recordset(fnames=["activity_type_id"]) # archive does not trigger recompute
|
||||
self.assertFalse(test_lead.activity_type_id)
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@ from datetime import datetime
|
|||
from freezegun import freeze_time
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import fields
|
||||
from odoo.addons.base.tests.test_format_address_mixin import FormatAddressCase
|
||||
from odoo.addons.crm.models.crm_lead import PARTNER_FIELDS_TO_SYNC, PARTNER_ADDRESS_FIELDS_TO_SYNC
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon, INCOMING_EMAIL
|
||||
from odoo.addons.mail.tests.common_tracking import MailTrackingDurationMixinCase
|
||||
from odoo.addons.phone_validation.tools.phone_validation import phone_format
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import Form, tagged, users
|
||||
from odoo.tests import Form, tagged, users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
|
|
@ -76,6 +78,37 @@ class TestCRMLead(TestCrmCommon):
|
|||
self.assertEqual(self.contact_company_1.lang, self.lang_en.code)
|
||||
self.assertEqual(lead.lang_id, self.lang_en)
|
||||
|
||||
@users('user_sales_leads')
|
||||
def test_crm_lead_compute_commercial_partner(self):
|
||||
company_partner, child_partner, orphan_partner = self.env['res.partner'].create([
|
||||
{
|
||||
'name': 'test_crm_lead_compute_commercial_partner',
|
||||
'is_company': True,
|
||||
'email': 'test_crm_lead_compute_commercial_partner@test.lan',
|
||||
},
|
||||
{'name': 'Test Child'},
|
||||
{'name': 'Test Orphan'},
|
||||
])
|
||||
child_partner.parent_id = company_partner
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'Test Lead',
|
||||
'partner_name': 'test_crm_lead_compute_commercial_partner',
|
||||
})
|
||||
self.assertEqual(lead.commercial_partner_id, company_partner)
|
||||
lead.partner_id = orphan_partner
|
||||
self.assertFalse(lead.commercial_partner_id)
|
||||
lead.partner_id = child_partner
|
||||
self.assertEqual(lead.commercial_partner_id, company_partner)
|
||||
lead.write({
|
||||
'partner_id': False,
|
||||
'partner_name': False,
|
||||
})
|
||||
self.assertFalse(lead.commercial_partner_id)
|
||||
lead.partner_id = company_partner
|
||||
# this is mostly because we use it to set "parent_id" in most flows
|
||||
# and it doesn't really make sense to have it be its own parent
|
||||
self.assertFalse(lead.commercial_partner_id, "If a partner is its own commercial_partner_id, the lead is considered to have none.")
|
||||
|
||||
@users('user_sales_leads')
|
||||
def test_crm_lead_creation_no_partner(self):
|
||||
lead_data = {
|
||||
|
|
@ -184,8 +217,7 @@ class TestCRMLead(TestCrmCommon):
|
|||
'name': 'Empty partner',
|
||||
'is_company': True,
|
||||
'lang': 'en_US',
|
||||
'mobile': '123456789',
|
||||
'title': self.env.ref('base.res_partner_title_mister').id,
|
||||
'phone': '0485112233',
|
||||
'function': 'My function',
|
||||
})
|
||||
lead_data = {
|
||||
|
|
@ -195,7 +227,6 @@ class TestCRMLead(TestCrmCommon):
|
|||
'country_id': self.country_ref.id,
|
||||
'email_from': self.test_email,
|
||||
'phone': self.test_phone,
|
||||
'mobile': '987654321',
|
||||
'website': 'http://mywebsite.org',
|
||||
}
|
||||
lead = self.env['crm.lead'].create(lead_data)
|
||||
|
|
@ -214,8 +245,6 @@ class TestCRMLead(TestCrmCommon):
|
|||
# PARTNER_FIELDS_TO_SYNC
|
||||
self.assertEqual(lead.lang_id, self.lang_en)
|
||||
self.assertEqual(lead.phone, lead_data['phone'], "Phone should keep its initial value")
|
||||
self.assertEqual(lead.mobile, empty_partner.mobile, "Mobile from partner should be set on the lead")
|
||||
self.assertEqual(lead.title, empty_partner.title, "Title from partner should be set on the lead")
|
||||
self.assertEqual(lead.function, empty_partner.function, "Function from partner should be set on the lead")
|
||||
self.assertEqual(lead.website, lead_data['website'], "Website should keep its initial value")
|
||||
|
||||
|
|
@ -247,27 +276,43 @@ class TestCRMLead(TestCrmCommon):
|
|||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_currency_sync(self):
|
||||
lead = self.env['crm.lead'].create({
|
||||
lead_company = self.env['res.company'].sudo().create({
|
||||
'name': 'EUR company',
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
})
|
||||
lead = self.env['crm.lead'].with_company(lead_company).create({
|
||||
'name': 'Lead 1',
|
||||
'company_id': self.company_main.id
|
||||
'company_id': lead_company.id
|
||||
})
|
||||
self.assertEqual(lead.company_currency, self.env.ref('base.EUR'))
|
||||
|
||||
self.company_main.currency_id = self.env.ref('base.CHF')
|
||||
lead.with_company(self.company_main).update({'company_id': False})
|
||||
lead_company.currency_id = self.env.ref('base.CHF')
|
||||
lead.update({'company_id': False})
|
||||
self.assertEqual(lead.company_currency, self.env.ref('base.CHF'))
|
||||
#set back original currency
|
||||
self.company_main.currency_id = self.env.ref('base.EUR')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_date_closed(self):
|
||||
# ensure a lead created directly in a won stage gets a date_closed
|
||||
lead_in_won = self.env['crm.lead'].create({
|
||||
'name': 'Created in Won',
|
||||
'type': 'opportunity',
|
||||
'stage_id': self.stage_team1_won.id,
|
||||
'expected_revenue': 123.45,
|
||||
})
|
||||
# date_closed must be set at creation when stage is won
|
||||
self.assertTrue(lead_in_won.date_closed, "Lead created in a won stage must have date_closed set")
|
||||
self.assertIsInstance(lead_in_won.date_closed, datetime)
|
||||
# Test for one won lead
|
||||
stage_team1_won2 = self.env['crm.stage'].create({
|
||||
'name': 'Won2',
|
||||
'sequence': 75,
|
||||
'team_id': self.sales_team_1.id,
|
||||
'team_ids': [self.sales_team_1.id],
|
||||
'is_won': True,
|
||||
})
|
||||
old_date_closed = lead_in_won.date_closed
|
||||
with freeze_time('2020-02-02 18:00'):
|
||||
lead_in_won.stage_id = stage_team1_won2
|
||||
self.assertEqual(lead_in_won.date_closed, old_date_closed, 'Moving between won stages should not change existing date_closed')
|
||||
won_lead = self.lead_team_1_won.with_env(self.env)
|
||||
other_lead = self.lead_1.with_env(self.env)
|
||||
old_date_closed = won_lead.date_closed
|
||||
|
|
@ -301,6 +346,36 @@ class TestCRMLead(TestCrmCommon):
|
|||
lead.action_set_lost()
|
||||
self.assertEqual(lead.date_closed, datetime.now(), "Closed date is updated after marking lead as lost")
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_meeting_display_fields(self):
|
||||
lead = self.env['crm.lead'].create({'name': 'Lead With Meetings'})
|
||||
meeting_1, meeting_2, meeting_3 = self.env['calendar.event'].create([{
|
||||
'name': 'Meeting 1 of Lead',
|
||||
'opportunity_id': lead.id,
|
||||
'start': '2022-07-12 08:00:00',
|
||||
'stop': '2022-07-12 10:00:00',
|
||||
}, {
|
||||
'name': 'Meeting 2 of Lead',
|
||||
'opportunity_id': lead.id,
|
||||
'start': '2022-07-14 08:00:00',
|
||||
'stop': '2022-07-14 10:00:00',
|
||||
}, {
|
||||
'name': 'Meeting 3 of Lead',
|
||||
'opportunity_id': lead.id,
|
||||
'start': '2022-07-15 08:00:00',
|
||||
'stop': '2022-07-15 10:00:00',
|
||||
}])
|
||||
|
||||
with freeze_time('2022-07-13 11:00:00'):
|
||||
self.assertEqual(lead.meeting_display_date, fields.Date.from_string('2022-07-14'))
|
||||
self.assertEqual(lead.meeting_display_label, 'Next Meeting')
|
||||
(meeting_2 | meeting_3).unlink()
|
||||
self.assertEqual(lead.meeting_display_date, fields.Date.from_string('2022-07-12'))
|
||||
self.assertEqual(lead.meeting_display_label, 'Last Meeting')
|
||||
meeting_1.unlink()
|
||||
self.assertFalse(lead.meeting_display_date)
|
||||
self.assertEqual(lead.meeting_display_label, 'No Meeting')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_partner_sync(self):
|
||||
lead, partner = self.lead_1.with_user(self.env.user), self.contact_2
|
||||
|
|
@ -325,11 +400,13 @@ class TestCRMLead(TestCrmCommon):
|
|||
self.assertEqual(lead.email_from, partner_email)
|
||||
self.assertEqual(lead.phone, '+1 202 555 6666')
|
||||
|
||||
# resetting lead values also resets partner
|
||||
# resetting lead values should not reset partner: voiding lead info (because
|
||||
# of some reasons) should not prevent from using the contact in other records
|
||||
lead.email_from, lead.phone = False, False
|
||||
self.assertFalse(partner.email)
|
||||
self.assertFalse(partner.email_normalized)
|
||||
self.assertFalse(partner.phone)
|
||||
self.assertFalse(lead.email_from)
|
||||
self.assertFalse(lead.phone)
|
||||
self.assertEqual(partner.email, partner_email)
|
||||
self.assertEqual(partner.phone, '+1 202 555 6666')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_partner_sync_email_phone(self):
|
||||
|
|
@ -345,19 +422,14 @@ class TestCRMLead(TestCrmCommon):
|
|||
lead_form = Form(lead)
|
||||
|
||||
# reset partner phone to a local number and prepare formatted / sanitized values
|
||||
partner_phone, partner_mobile = self.test_phone_data[2], self.test_phone_data[1]
|
||||
partner_phone = self.test_phone_data[2]
|
||||
partner_phone_formatted = phone_format(partner_phone, 'US', '1', force_format='INTERNATIONAL')
|
||||
partner_phone_sanitized = phone_format(partner_phone, 'US', '1', force_format='E164')
|
||||
partner_mobile_formatted = phone_format(partner_mobile, 'US', '1', force_format='INTERNATIONAL')
|
||||
partner_mobile_sanitized = phone_format(partner_mobile, 'US', '1', force_format='E164')
|
||||
partner_email, partner_email_normalized = self.test_email_data[2], self.test_email_data_normalized[2]
|
||||
self.assertEqual(partner_phone_formatted, '+1 202-555-0888')
|
||||
self.assertEqual(partner_phone_sanitized, self.test_phone_data_sanitized[2])
|
||||
self.assertEqual(partner_mobile_formatted, '+1 202-555-0999')
|
||||
self.assertEqual(partner_mobile_sanitized, self.test_phone_data_sanitized[1])
|
||||
# ensure initial data
|
||||
self.assertEqual(partner.phone, partner_phone)
|
||||
self.assertEqual(partner.mobile, partner_mobile)
|
||||
self.assertEqual(partner.email, partner_email)
|
||||
|
||||
# LEAD/PARTNER SYNC: email and phone are propagated to lead
|
||||
|
|
@ -366,8 +438,6 @@ class TestCRMLead(TestCrmCommon):
|
|||
self.assertEqual(lead_form.email_from, partner_email)
|
||||
self.assertEqual(lead_form.phone, partner_phone_formatted,
|
||||
'Lead: form automatically formats numbers')
|
||||
self.assertEqual(lead_form.mobile, partner_mobile_formatted,
|
||||
'Lead: form automatically formats numbers')
|
||||
self.assertFalse(lead_form.partner_email_update)
|
||||
self.assertFalse(lead_form.partner_phone_update)
|
||||
|
||||
|
|
@ -380,9 +450,7 @@ class TestCRMLead(TestCrmCommon):
|
|||
'Lead / Partner: equal emails should lead to equal normalized emails')
|
||||
self.assertEqual(lead.phone, partner_phone_formatted,
|
||||
'Lead / Partner: partner values (formatted) sent to lead')
|
||||
self.assertEqual(lead.mobile, partner_mobile_formatted,
|
||||
'Lead / Partner: partner values (formatted) sent to lead')
|
||||
self.assertEqual(lead.phone_sanitized, partner_mobile_sanitized,
|
||||
self.assertEqual(lead.phone_sanitized, partner_phone_sanitized,
|
||||
'Lead: phone_sanitized computed field on mobile')
|
||||
|
||||
# for email_from, if only formatting differs, warning should not appear and
|
||||
|
|
@ -406,6 +474,7 @@ class TestCRMLead(TestCrmCommon):
|
|||
self.assertTrue(lead_form.partner_email_update)
|
||||
new_phone = '+1 202 555 7799'
|
||||
new_phone_formatted = phone_format(new_phone, 'US', '1', force_format="INTERNATIONAL")
|
||||
new_phone_sanitized = phone_format(new_phone, 'US', '1', force_format="E164")
|
||||
lead_form.phone = new_phone
|
||||
self.assertEqual(lead_form.phone, new_phone_formatted)
|
||||
self.assertTrue(lead_form.partner_email_update)
|
||||
|
|
@ -416,30 +485,21 @@ class TestCRMLead(TestCrmCommon):
|
|||
self.assertEqual(partner.email_normalized, new_email_normalized)
|
||||
self.assertEqual(partner.phone, new_phone_formatted)
|
||||
|
||||
# LEAD/PARTNER SYNC: mobile does not update partner
|
||||
new_mobile = '+1 202 555 6543'
|
||||
new_mobile_formatted = phone_format(new_mobile, 'US', '1', force_format="INTERNATIONAL")
|
||||
lead_form.mobile = new_mobile
|
||||
# LEAD/PARTNER SYNC: resetting lead values should not reset partner
|
||||
# # voiding lead info (because of some reasons) should not prevent
|
||||
# # from using the contact in other records
|
||||
lead_form.email_from, lead_form.phone = False, False
|
||||
self.assertFalse(lead_form.partner_email_update)
|
||||
self.assertFalse(lead_form.partner_phone_update)
|
||||
lead_form.save()
|
||||
self.assertEqual(lead.mobile, new_mobile_formatted)
|
||||
self.assertEqual(partner.mobile, partner_mobile)
|
||||
|
||||
# LEAD/PARTNER SYNC: reseting lead values also resets partner for email
|
||||
# and phone, but not for mobile
|
||||
lead_form.email_from, lead_form.phone, lead.mobile = False, False, False
|
||||
self.assertTrue(lead_form.partner_email_update)
|
||||
self.assertTrue(lead_form.partner_phone_update)
|
||||
lead_form.save()
|
||||
self.assertFalse(partner.email)
|
||||
self.assertFalse(partner.email_normalized)
|
||||
self.assertFalse(partner.phone)
|
||||
self.assertEqual(partner.email, new_email)
|
||||
self.assertEqual(partner.email_normalized, new_email_normalized)
|
||||
self.assertEqual(partner.phone, new_phone_formatted)
|
||||
self.assertFalse(lead.phone)
|
||||
self.assertFalse(lead.mobile)
|
||||
self.assertFalse(lead.phone_sanitized)
|
||||
self.assertEqual(partner.mobile, partner_mobile)
|
||||
# if SMS is uninstalled, phone_sanitized is not available on partner
|
||||
if 'phone_sanitized' in partner:
|
||||
self.assertEqual(partner.phone_sanitized, partner_mobile_sanitized,
|
||||
self.assertEqual(partner.phone_sanitized, new_phone_sanitized,
|
||||
'Partner sanitized should be computed on mobile')
|
||||
|
||||
@users('user_sales_manager')
|
||||
|
|
@ -453,7 +513,6 @@ class TestCRMLead(TestCrmCommon):
|
|||
'name': 'NoContact Partner',
|
||||
'phone': '',
|
||||
'email': '',
|
||||
'mobile': '',
|
||||
})
|
||||
|
||||
# This is a type == 'lead', not a type == 'opportunity'
|
||||
|
|
@ -550,6 +609,41 @@ class TestCRMLead(TestCrmCommon):
|
|||
self.assertEqual(lead.probability, 100.0)
|
||||
self.assertEqual(lead.stage_id, self.stage_gen_won) # generic won stage has lower sequence than team won stage
|
||||
|
||||
def test_crm_lead_stages_with_multiple_possible_teams(self):
|
||||
""" Test lead stage is properly set when switching between multiple teams. """
|
||||
self.sales_team_2 = self.env['crm.team'].create({
|
||||
'name': 'Test Sales Team 2',
|
||||
'company_id': False,
|
||||
'user_id': self.user_sales_manager.id,
|
||||
})
|
||||
self.sales_team_2_m1 = self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_leads.id,
|
||||
'crm_team_id': self.sales_team_2.id,
|
||||
})
|
||||
|
||||
user_teams = self.env['crm.team'].search([
|
||||
('crm_team_member_all_ids.user_id', '=', self.user_sales_leads.id),
|
||||
])
|
||||
self.assertIn(self.sales_team_1, user_teams)
|
||||
self.assertIn(self.sales_team_2, user_teams)
|
||||
|
||||
self.stage_team2_1 = self.env['crm.stage'].create({
|
||||
'name': 'New (T2)',
|
||||
'team_ids': [self.sales_team_2.id],
|
||||
})
|
||||
|
||||
lead = self.env['crm.lead'].with_user(self.user_sales_leads).create({
|
||||
'name': 'Test',
|
||||
'contact_name': 'Test Contact',
|
||||
'team_id': self.sales_team_1.id,
|
||||
})
|
||||
self.assertEqual(lead.team_id, self.sales_team_1)
|
||||
self.assertEqual(lead.stage_id, self.stage_team1_1)
|
||||
|
||||
lead.team_id = self.sales_team_2
|
||||
self.assertEqual(lead.team_id, self.sales_team_2)
|
||||
self.assertEqual(lead.stage_id, self.stage_team2_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_unlink_calendar_event(self):
|
||||
""" Test res_id / res_model is reset (and hide document button in calendar
|
||||
|
|
@ -571,7 +665,7 @@ class TestCRMLead(TestCrmCommon):
|
|||
'stop': '2022-07-13 10:00:00',
|
||||
}
|
||||
])
|
||||
self.assertEqual(lead.calendar_event_count, 1)
|
||||
self.assertEqual(len(lead.calendar_event_ids), 1)
|
||||
self.assertEqual(meetings.opportunity_id, lead)
|
||||
self.assertEqual(meetings.mapped('res_id'), [lead.id, lead.id])
|
||||
self.assertEqual(meetings.mapped('res_model'), ['crm.lead', 'crm.lead'])
|
||||
|
|
@ -728,28 +822,37 @@ class TestCRMLead(TestCrmCommon):
|
|||
"""Test that the help message is the right one if we are on multiple team with different settings."""
|
||||
# archive other teams
|
||||
self.env['crm.team'].search([]).active = False
|
||||
self.env['ir.config_parameter'].sudo().set_param("sales_team.membership_multi", True)
|
||||
|
||||
self._activate_multi_company()
|
||||
team_other_comp = self.team_company2
|
||||
|
||||
user_team_leads, team_leads, user_team_opport, team_opport = self.env['crm.team'].create([{
|
||||
'name': 'UserTeamLeads',
|
||||
'company_id': self.env.company.id,
|
||||
'use_leads': True,
|
||||
'member_ids': [(6, 0, [self.env.user.id])],
|
||||
}, {
|
||||
'name': 'TeamLeads',
|
||||
'company_id': self.env.company.id,
|
||||
'use_leads': True,
|
||||
'member_ids': [],
|
||||
}, {
|
||||
'name': 'UserTeamOpportunities',
|
||||
'company_id': self.env.company.id,
|
||||
'use_leads': False,
|
||||
'member_ids': [(6, 0, [self.env.user.id])],
|
||||
}, {
|
||||
'name': 'TeamOpportunities',
|
||||
'company_id': self.env.company.id,
|
||||
'use_leads': False,
|
||||
'member_ids': [],
|
||||
}])
|
||||
|
||||
# Additional check to ensure proper team creation
|
||||
user_team_leads.invalidate_recordset(fnames=['member_ids'])
|
||||
self.assertEqual(user_team_leads.member_ids.ids, [self.env.user.id])
|
||||
|
||||
self.env['crm.lead'].create([{
|
||||
'name': 'LeadOurTeam',
|
||||
'team_id': user_team_leads.id,
|
||||
|
|
@ -781,11 +884,10 @@ class TestCRMLead(TestCrmCommon):
|
|||
|
||||
for team in teams:
|
||||
with self.subTest(team=team):
|
||||
team_mail = f"{team.alias_name}@{team.alias_domain}"
|
||||
if team != team_other_comp:
|
||||
self.assertIn(f"<a href='mailto:{team_mail}'>{team_mail}</a>", self.env['crm.lead'].sudo().get_empty_list_help(""))
|
||||
self.assertIn(f"<a href='mailto:{team.alias_email}'>{team.alias_email}</a>", self.env['crm.lead'].sudo().get_empty_list_help(""))
|
||||
else:
|
||||
self.assertNotIn(f"<a href='mailto:{team_mail}'>{team_mail}</a>", self.env['crm.lead'].sudo().get_empty_list_help(""))
|
||||
self.assertNotIn(f"<a href='mailto:{team.alias_email}'>{team.alias_email}</a>", self.env['crm.lead'].sudo().get_empty_list_help(""))
|
||||
team.active = False
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
|
|
@ -793,7 +895,7 @@ class TestCRMLead(TestCrmCommon):
|
|||
new_lead = self.format_and_process(
|
||||
INCOMING_EMAIL,
|
||||
'unknown.sender@test.example.com',
|
||||
'%s@%s' % (self.sales_team_1.alias_name, self.alias_domain),
|
||||
self.sales_team_1.alias_email,
|
||||
subject='Delivery cost inquiry',
|
||||
target_model='crm.lead',
|
||||
)
|
||||
|
|
@ -802,49 +904,12 @@ class TestCRMLead(TestCrmCommon):
|
|||
self.assertEqual(new_lead.name, 'Delivery cost inquiry')
|
||||
|
||||
message = new_lead.with_user(self.user_sales_manager).message_post(
|
||||
body='Here is my offer !',
|
||||
body='Here is my offer!',
|
||||
subtype_xmlid='mail.mt_comment')
|
||||
self.assertEqual(message.author_id, self.user_sales_manager.partner_id)
|
||||
|
||||
new_lead._handle_partner_assignment(create_missing=True)
|
||||
self.assertEqual(new_lead.partner_id.email, 'unknown.sender@test.example.com')
|
||||
self.assertEqual(new_lead.partner_id.team_id, self.sales_team_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_message_get_suggested_recipients(self):
|
||||
"""This test checks that creating a contact from a lead with an inactive language will ignore the language
|
||||
while creating a contact from a lead with an active language will take it into account """
|
||||
ResLang = self.env['res.lang'].sudo().with_context(active_test=False)
|
||||
|
||||
# Create a lead with an inactive language -> should ignore the preset language
|
||||
lang_fr = ResLang.search([('code', '=', 'fr_FR')])
|
||||
if not lang_fr:
|
||||
lang_fr = ResLang._create_lang('fr_FR')
|
||||
# set French language as inactive then try to call "_message_get_suggested_recipients"
|
||||
# -> lang code should be ignored
|
||||
lang_fr.active = False
|
||||
lead1 = self.env['crm.lead'].create({
|
||||
'name': 'TestLead',
|
||||
'email_from': self.test_email,
|
||||
'lang_id': lang_fr.id,
|
||||
})
|
||||
data = lead1._message_get_suggested_recipients()[lead1.id]
|
||||
self.assertEqual(data, [(False, self.test_email, None, 'Customer Email')])
|
||||
|
||||
# Create a lead with an active language -> should keep the preset language for recipients
|
||||
lang_en = ResLang.search([('code', '=', 'en_US')])
|
||||
if not lang_en:
|
||||
lang_en = ResLang._create_lang('en_US')
|
||||
# set American English language as active then try to call "_message_get_suggested_recipients"
|
||||
# -> lang code should be kept
|
||||
lang_en.active = True
|
||||
lead2 = self.env['crm.lead'].create({
|
||||
'name': 'TestLead',
|
||||
'email_from': self.test_email,
|
||||
'lang_id': lang_en.id,
|
||||
})
|
||||
data = lead2._message_get_suggested_recipients()[lead2.id]
|
||||
self.assertEqual(data, [(False, self.test_email, "en_US", 'Customer Email')])
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_phone_mobile_search(self):
|
||||
|
|
@ -870,10 +935,9 @@ class TestCRMLead(TestCrmCommon):
|
|||
})
|
||||
|
||||
# search term containing less than 3 characters should throw an error (some currently not working)
|
||||
self.env['crm.lead'].search([('phone_mobile_search', 'like', '')]) # no restriction, returns all
|
||||
with self.assertRaises(UserError):
|
||||
self.env['crm.lead'].search([('phone_mobile_search', 'like', '')])
|
||||
# with self.assertRaises(UserError):
|
||||
# self.env['crm.lead'].search([('phone_mobile_search', 'like', '7 ')])
|
||||
self.env['crm.lead'].search([('phone_mobile_search', 'like', '7 ')])
|
||||
with self.assertRaises(UserError):
|
||||
self.env['crm.lead'].search([('phone_mobile_search', 'like', 'c')])
|
||||
with self.assertRaises(UserError):
|
||||
|
|
@ -960,27 +1024,193 @@ class TestCRMLead(TestCrmCommon):
|
|||
'phone': self.test_phone_data[0],
|
||||
})
|
||||
self.assertEqual(lead.phone, self.test_phone_data[0])
|
||||
self.assertFalse(lead.mobile)
|
||||
self.assertEqual(lead.phone_sanitized, self.test_phone_data_sanitized[0])
|
||||
|
||||
lead.write({'phone': False, 'mobile': self.test_phone_data[1]})
|
||||
lead.write({'phone': False})
|
||||
self.assertFalse(lead.phone)
|
||||
self.assertEqual(lead.mobile, self.test_phone_data[1])
|
||||
self.assertEqual(lead.phone_sanitized, self.test_phone_data_sanitized[1])
|
||||
self.assertEqual(lead.phone_sanitized, False)
|
||||
|
||||
lead.write({'phone': self.test_phone_data[1], 'mobile': self.test_phone_data[2]})
|
||||
lead.write({'phone': self.test_phone_data[1]})
|
||||
self.assertEqual(lead.phone, self.test_phone_data[1])
|
||||
self.assertEqual(lead.mobile, self.test_phone_data[2])
|
||||
self.assertEqual(lead.phone_sanitized, self.test_phone_data_sanitized[2])
|
||||
self.assertEqual(lead.phone_sanitized, self.test_phone_data_sanitized[1])
|
||||
|
||||
# updating country should trigger sanitize computation
|
||||
lead.write({'country_id': self.env.ref('base.be').id})
|
||||
self.assertEqual(lead.phone, self.test_phone_data[1])
|
||||
self.assertEqual(lead.mobile, self.test_phone_data[2])
|
||||
self.assertFalse(lead.phone_sanitized)
|
||||
|
||||
|
||||
class TestLeadFormatAddress(FormatAddressCase):
|
||||
class TestCRMLeadRotting(TestCrmCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.stage_team1_1.rotting_threshold_days = 5
|
||||
cls.stage_team1_2.rotting_threshold_days = 3
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_leads_rotting(self):
|
||||
rotten_leads = self.env['crm.lead']
|
||||
clean_leads = self.env['crm.lead']
|
||||
|
||||
close_future = datetime(2025, 1, 24, 12, 0, 0)
|
||||
now = datetime(2025, 1, 20, 12, 0, 0)
|
||||
close_past = datetime(2025, 1, 18, 12, 0, 0)
|
||||
past = datetime(2025, 1, 10, 12, 0, 0)
|
||||
last_year = datetime(2024, 1, 20, 12, 0, 0)
|
||||
|
||||
with self.mock_datetime_and_now(past):
|
||||
rotten_leads += self.env['crm.lead'].create([
|
||||
{
|
||||
'name': 'Opportunity',
|
||||
'type': 'opportunity',
|
||||
'stage_id': self.stage_team1_1.id,
|
||||
} for x in range(3)
|
||||
])
|
||||
rotten_leads.flush_recordset(['date_last_stage_update']) # precalculate stage update
|
||||
|
||||
with self.mock_datetime_and_now(close_past):
|
||||
clean_leads += self.env['crm.lead'].create({
|
||||
'name': "Lead that won't have time to rot",
|
||||
'type': 'opportunity',
|
||||
'stage_id': self.stage_team1_1.id,
|
||||
})
|
||||
clean_leads.flush_recordset(['date_last_stage_update']) # precalculate stage update
|
||||
with self.mock_datetime_and_now(last_year):
|
||||
clean_leads += self.env['crm.lead'].create({
|
||||
'name': 'Opportuniy in Won Stage',
|
||||
'type': 'opportunity',
|
||||
'stage_id': self.stage_gen_won.id,
|
||||
})
|
||||
clean_leads.flush_recordset(['date_last_stage_update']) # precalculate stage update
|
||||
|
||||
with self.mock_datetime_and_now(now):
|
||||
for lead in rotten_leads:
|
||||
self.assertTrue(lead.is_rotting)
|
||||
self.assertEqual(lead.rotting_days, 10)
|
||||
for lead in clean_leads:
|
||||
self.assertFalse(lead.is_rotting)
|
||||
self.assertEqual(lead.rotting_days, 0)
|
||||
|
||||
rotten_leads_iterator = iter(rotten_leads)
|
||||
|
||||
lead_edited = next(rotten_leads_iterator)
|
||||
lead_edited.name = 'Edited Opportunity'
|
||||
self.assertTrue(
|
||||
lead_edited.is_rotting,
|
||||
'Editing the lead has no effect on rotting status',
|
||||
)
|
||||
|
||||
lead_changed_stage = next(rotten_leads_iterator)
|
||||
lead_changed_stage.stage_id = self.stage_team1_2.id
|
||||
self.assertFalse(
|
||||
lead_changed_stage.is_rotting,
|
||||
'Changing the stage disables rotting status',
|
||||
)
|
||||
|
||||
lead_changed_rotting_threshold = next(rotten_leads_iterator)
|
||||
old_rotting_threshold = self.stage_team1_1.rotting_threshold_days
|
||||
self.stage_team1_1.rotting_threshold_days = 50
|
||||
self.assertFalse(
|
||||
lead_changed_rotting_threshold.is_rotting,
|
||||
'Changing the rotting threshold to a higher value does affect rotten leads\' status',
|
||||
)
|
||||
self.stage_team1_1.rotting_threshold_days = old_rotting_threshold # Revert rotting threshold
|
||||
self.assertTrue(
|
||||
lead_changed_rotting_threshold.is_rotting,
|
||||
'Changing the threshold back should affect the status again',
|
||||
)
|
||||
|
||||
self.stage_team1_1.rotting_threshold_days = 0
|
||||
self.assertFalse(
|
||||
lead_changed_rotting_threshold.is_rotting,
|
||||
'A 0-day rotting threshold disables rotting',
|
||||
)
|
||||
self.stage_team1_1.rotting_threshold_days = old_rotting_threshold
|
||||
|
||||
# create a new lead in the New stage
|
||||
jan20_lead = self.env['crm.lead'].create({
|
||||
'name': 'Fresh Opportuniy',
|
||||
'type': 'opportunity',
|
||||
'stage_id': self.stage_team1_1.id,
|
||||
})
|
||||
|
||||
# 4 days later:
|
||||
with self.mock_datetime_and_now(close_future):
|
||||
rotten_leads.invalidate_recordset(['is_rotting', 'rotting_days'])
|
||||
self.assertEqual(
|
||||
lead_changed_rotting_threshold.rotting_days,
|
||||
14,
|
||||
'Since this lead has not seen a stage change, it has been rotting for 14 days total',
|
||||
)
|
||||
self.assertFalse(
|
||||
jan20_lead.is_rotting,
|
||||
'Since this lead remained in a stage with a higher threshold, it\'s not rotting yet',
|
||||
)
|
||||
self.assertTrue(
|
||||
lead_changed_stage.is_rotting,
|
||||
'As its new stage has a lower rotting threshold, this lead should be rotting 3 days after its last stage change',
|
||||
)
|
||||
self.assertEqual(lead_changed_stage.rotting_days, 4)
|
||||
|
||||
def test_search_leads_rotting(self):
|
||||
"""
|
||||
This test checks that the result of search_leads_rotting accurately matches is_rotting computation results
|
||||
"""
|
||||
past = datetime(2025, 1, 1)
|
||||
now = datetime(2025, 1, 10)
|
||||
with self.mock_datetime_and_now(past):
|
||||
all_leads = self.env['crm.lead'].create([{
|
||||
'name': 'TestLead Rotting opportunity',
|
||||
'type': 'opportunity',
|
||||
'stage_id': self.stage_team1_1.id,
|
||||
}] * 5 + [{
|
||||
'name': 'TestLead Lead',
|
||||
'type': 'lead',
|
||||
'stage_id': self.stage_team1_1.id,
|
||||
}] * 3 + [{
|
||||
'name': 'TestLead Won Opportunity',
|
||||
'type': 'opportunity',
|
||||
'stage_id': self.stage_gen_won.id,
|
||||
}] * 4)
|
||||
|
||||
all_leads.flush_recordset(['date_last_stage_update'])
|
||||
rotten_leads = all_leads.filtered(lambda lead: 'Rotting' in lead.name)
|
||||
clean_leads = all_leads - rotten_leads
|
||||
|
||||
with self.mock_datetime_and_now(now):
|
||||
rot = self.env['crm.lead'].search([
|
||||
('name', 'ilike', 'TestLead'),
|
||||
('is_rotting', '=', True),
|
||||
], order='id ASC')
|
||||
norot = self.env['crm.lead'].search([
|
||||
('name', 'ilike', 'TestLead'),
|
||||
('is_rotting', '=', False),
|
||||
], order='id ASC')
|
||||
|
||||
self.assertEqual(rot, rotten_leads)
|
||||
self.assertEqual(norot, clean_leads)
|
||||
|
||||
|
||||
@tagged('lead_internals')
|
||||
class TestLeadFormTools(FormatAddressCase):
|
||||
|
||||
def test_address_view(self):
|
||||
self.env.company.country_id = self.env.ref('base.us')
|
||||
self.assertAddressView('crm.lead')
|
||||
|
||||
|
||||
@tagged('lead_internals', 'is_query_count')
|
||||
class TestCrmLeadMailTrackingDuration(MailTrackingDurationMixinCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass('crm.lead')
|
||||
|
||||
def test_crm_lead_mail_tracking_duration(self):
|
||||
self._test_record_duration_tracking()
|
||||
|
||||
def test_crm_lead_mail_tracking_duration_batch(self):
|
||||
self._test_record_duration_tracking_batch()
|
||||
|
||||
def test_crm_lead_queries_batch_mail_tracking_duration(self):
|
||||
self._test_queries_batch_duration_tracking()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
import random
|
||||
|
||||
from datetime import datetime
|
||||
from ast import literal_eval
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
|
|
@ -11,6 +12,7 @@ from odoo import fields
|
|||
from odoo.addons.crm.tests.common import TestLeadConvertCommon
|
||||
from odoo.tests.common import tagged
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.fields import Datetime
|
||||
|
||||
|
||||
class TestLeadAssignCommon(TestLeadConvertCommon):
|
||||
|
|
@ -142,9 +144,9 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
count=14,
|
||||
suffix='Existing')
|
||||
self.assertEqual(existing_leads.team_id, self.sales_team_1, "Team should have lower sequence")
|
||||
existing_leads[0].active = False # lost
|
||||
existing_leads[1].probability = 100 # not won
|
||||
existing_leads[2].probability = 0 # not lost
|
||||
existing_leads[0].action_set_lost() # lost
|
||||
existing_leads[1].probability = 100 # not won as stage is not won.
|
||||
existing_leads[2].probability = 0 # not lost as active
|
||||
existing_leads.flush_recordset()
|
||||
|
||||
self.members.invalidate_model(['lead_month_count'])
|
||||
|
|
@ -160,8 +162,9 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
|
||||
# sales_team_1_m2 is opt-out (new field in 14.3) -> even with max, no lead assigned
|
||||
self.sales_team_1_m2.update({'assignment_max': 45, 'assignment_optout': True})
|
||||
self.sales_team_1_m3.update({'assignment_max': 45})
|
||||
with self.with_user('user_sales_manager'):
|
||||
teams_data, members_data = self.sales_team_1._action_assign_leads(work_days=4)
|
||||
teams_data, members_data = self.sales_team_1._action_assign_leads(force_quota=True)
|
||||
|
||||
Leads = self.env['crm.lead']
|
||||
|
||||
|
|
@ -175,8 +178,12 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
['TestLeadInitial_0003']
|
||||
)
|
||||
|
||||
# TestLeadInitial_0007 has same partner as TestLeadInitial_0003
|
||||
self.assertEqual(len(teams_data[self.sales_team_1]['duplicates']), 1)
|
||||
|
||||
# TestLeadInitial_0005 had a 0 auto_proba when its proba was set to 0.
|
||||
# Therefore, it is auto_proba. At this point, its proba is 9x.xx %, and it is selected.
|
||||
# These are the two leads with the highest probabilities, as they are sorted before assignment.
|
||||
self.assertEqual(
|
||||
sorted(members_data[self.sales_team_1_m3]['assigned'].mapped('name')),
|
||||
['TestLeadInitial_0000', 'TestLeadInitial_0005']
|
||||
|
|
@ -186,16 +193,16 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
self.members.invalidate_model(['lead_month_count'])
|
||||
self.assertEqual(self.sales_team_1_m1.lead_month_count, 0) # archived do not get leads
|
||||
self.assertEqual(self.sales_team_1_m2.lead_month_count, 0) # opt-out through assignment_max = 0
|
||||
self.assertEqual(self.sales_team_1_m3.lead_month_count, 14) # 15 max on 4 days (2) + existing 12
|
||||
self.assertEqual(self.sales_team_1_m3.lead_month_count, 14) # ignore actual quota (round(45/30) => +2) + existing 12
|
||||
|
||||
with self.with_user('user_sales_manager'):
|
||||
self.env['crm.team'].browse(self.sales_team_1.ids)._action_assign_leads(work_days=4)
|
||||
self.env['crm.team'].browse(self.sales_team_1.ids)._action_assign_leads(force_quota=True)
|
||||
|
||||
# salespersons assign
|
||||
self.members.invalidate_model(['lead_month_count'])
|
||||
self.assertEqual(self.sales_team_1_m1.lead_month_count, 0) # archived do not get leads
|
||||
self.assertEqual(self.sales_team_1_m2.lead_month_count, 0) # opt-out through assignment_max = 0
|
||||
self.assertEqual(self.sales_team_1_m3.lead_month_count, 16) # 15 max on 4 days (2) + existing 14 and not capped anymore
|
||||
self.assertEqual(self.sales_team_1_m3.lead_month_count, 16) # ignore actual quota (round(45/30) => +2) + existing 14 and not capped anymore
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_assign_duplicates(self):
|
||||
|
|
@ -224,7 +231,7 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
leads.flush_recordset()
|
||||
|
||||
with self.with_user('user_sales_manager'):
|
||||
self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads(work_days=2)
|
||||
self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads()
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
|
|
@ -238,12 +245,12 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
self.assertEqual(len(leads_st1) + len(leads_stc), len(leads)) # Make sure all lead are assigned
|
||||
|
||||
# salespersons assign
|
||||
self.members.invalidate_model(['lead_month_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 11) # 45 max on 2 days (3) + compensation (8.4)
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 4) # 15 max on 2 days (1) + compensation (2.8)
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 4) # 15 max on 2 days (1) + compensation (2.8)
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 8) # 30 max on 15 (2) + compensation (5.6)
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 15) # 60 max on 15 (4) + compsantion (11.2)
|
||||
self.members.invalidate_model(['lead_month_count', 'lead_day_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 2) # 45 max on one month -> 2 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 1) # 30 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 2) # 60 max on one month -> 2 daily
|
||||
|
||||
# teams assign: everything should be done due to duplicates
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
|
|
@ -281,7 +288,7 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
leads.flush_recordset()
|
||||
|
||||
with self.with_user('user_sales_manager'):
|
||||
self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads(work_days=2)
|
||||
self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads()
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
|
|
@ -295,18 +302,18 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
self.assertEqual(len(leads_st1) + len(leads_stc), len(leads)) # Make sure all lead are assigned
|
||||
|
||||
# salespersons assign
|
||||
self.members.invalidate_model(['lead_month_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 11) # 45 max on 2 days (3) + compensation (8.4)
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 4) # 15 max on 2 days (1) + compensation (2.8)
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 4) # 15 max on 2 days (1) + compensation (2.8)
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 8) # 30 max on 15 (2) + compensation (5.6)
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 15) # 60 max on 15 (4) + compensation (11.2)
|
||||
self.members.invalidate_model(['lead_month_count', 'lead_day_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 2) # 45 max on one month -> 2 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 1) # 30 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 2) # 60 max on one month -> 2 daily
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_assign_populated(self):
|
||||
""" Test assignment on a more high volume oriented test set in order to
|
||||
test more real life use cases. """
|
||||
# fix the seed and avoid randomness (funny: try 1870)
|
||||
# fix the seed and avoid randomness
|
||||
random.seed(1871)
|
||||
|
||||
# create leads enough to assign one month of work
|
||||
|
|
@ -367,7 +374,7 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
leads.flush_recordset()
|
||||
|
||||
with self.with_user('user_sales_manager'):
|
||||
self.env['crm.team'].browse(sales_teams.ids)._action_assign_leads(work_days=30)
|
||||
self.env['crm.team'].browse(sales_teams.ids)._action_assign_leads()
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)])
|
||||
|
|
@ -379,23 +386,80 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
leads_st1 = leads.filtered_domain([('team_id', '=', self.sales_team_1.id)])
|
||||
leads_st2 = leads.filtered_domain([('team_id', '=', self.sales_team_convert.id)])
|
||||
leads_st3 = leads.filtered_domain([('team_id', '=', sales_team_3.id)])
|
||||
self.assertLessEqual(len(leads_st1), 225) # 75 * 600 / 300 * 1.5 (because random)
|
||||
self.assertLessEqual(len(leads_st2), 270) # 90 * 600 / 300 * 1.5 (because random)
|
||||
self.assertLessEqual(len(leads_st3), 405) # 135 * 600 / 300 * 1.5 (because random)
|
||||
self.assertGreaterEqual(len(leads_st1), 75) # 75 * 600 / 300 * 0.5 (because random)
|
||||
self.assertGreaterEqual(len(leads_st2), 90) # 90 * 600 / 300 * 0.5 (because random)
|
||||
self.assertGreaterEqual(len(leads_st3), 135) # 135 * 600 / 300 * 0.5 (because random)
|
||||
self.assertEqual(len(leads_st1), 170)
|
||||
self.assertEqual(len(leads_st2), 116)
|
||||
self.assertEqual(len(leads_st3), 314)
|
||||
|
||||
# salespersons assign
|
||||
self.members.invalidate_model(['lead_month_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 45) # 45 max on one month
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 15) # 15 max on one month
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 15) # 15 max on one month
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 30) # 30 max on one month
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 60) # 60 max on one month
|
||||
self.assertMemberAssign(sales_team_3_m1, 60) # 60 max on one month
|
||||
self.assertMemberAssign(sales_team_3_m2, 60) # 60 max on one month
|
||||
self.assertMemberAssign(sales_team_3_m3, 15) # 15 max on one month
|
||||
self.members.invalidate_model(['lead_month_count', 'lead_day_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 2) # 45 max on one month -> 2 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 1) # 30 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 2) # 60 max on one month -> 2 daily
|
||||
self.assertMemberAssign(sales_team_3_m1, 2) # 60 max on one month -> 2 daily
|
||||
self.assertMemberAssign(sales_team_3_m2, 2) # 60 max on one month -> 2 daily
|
||||
self.assertMemberAssign(sales_team_3_m3, 1) # 15 max on one month -> 1 daily
|
||||
|
||||
def test_assign_preferred_domain(self):
|
||||
""" Test preferred domain use """
|
||||
random.seed(1914)
|
||||
preferred_tag = self.env['crm.tag'].create({'name': 'preferred'})
|
||||
|
||||
leads = self._create_leads_batch(
|
||||
lead_type='lead',
|
||||
user_ids=[False],
|
||||
count=11,
|
||||
)
|
||||
leads[:8].write({'tag_ids': [(6, 0, preferred_tag.ids)]})
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
self.assertInitialData()
|
||||
test_sales_team = self.env['crm.team'].create({
|
||||
'name': 'Sales Team 5',
|
||||
'sequence': 15,
|
||||
'alias_name': False,
|
||||
'use_leads': True,
|
||||
'use_opportunities': True,
|
||||
'company_id': False,
|
||||
'user_id': False,
|
||||
})
|
||||
test_sales_team_m1 = self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_manager.id,
|
||||
'crm_team_id': test_sales_team.id,
|
||||
'assignment_max': 150,
|
||||
'assignment_domain': False,
|
||||
'assignment_domain_preferred': "[('tag_ids', 'in', %s)]" % preferred_tag.ids,
|
||||
})
|
||||
test_sales_team_m2 = self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_leads.id,
|
||||
'crm_team_id': test_sales_team.id,
|
||||
'assignment_max': 150,
|
||||
'assignment_domain': False,
|
||||
'assignment_domain_preferred': False,
|
||||
})
|
||||
test_sales_team_m3 = self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_salesman.id,
|
||||
'crm_team_id': test_sales_team.id,
|
||||
'assignment_max': 150,
|
||||
'assignment_domain': False,
|
||||
'assignment_domain_preferred': False,
|
||||
})
|
||||
|
||||
test_sales_team._action_assign_leads()
|
||||
|
||||
member_leads = self.env['crm.lead'].search([
|
||||
('user_id', '=', test_sales_team_m1.user_id.id),
|
||||
('team_id', '=', test_sales_team_m1.crm_team_id.id),
|
||||
('date_open', '>=', Datetime.now() - timedelta(hours=24)),
|
||||
])
|
||||
self.assertEqual(
|
||||
member_leads.filtered_domain(literal_eval(test_sales_team_m1.assignment_domain_preferred)),
|
||||
member_leads
|
||||
)
|
||||
self.assertMemberAssign(test_sales_team_m1, 5)
|
||||
self.assertMemberAssign(test_sales_team_m2, 3)
|
||||
self.assertMemberAssign(test_sales_team_m3, 3)
|
||||
|
||||
def test_assign_quota(self):
|
||||
""" Test quota computation """
|
||||
|
|
@ -403,26 +467,9 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
|
||||
# quota computation without existing leads
|
||||
self.assertEqual(
|
||||
self.sales_team_1_m1._get_assignment_quota(work_days=1),
|
||||
10,
|
||||
"Assignment quota: 45 max on 1 days -> 1.5, compensation (45-1.5)/5 -> 8.7"
|
||||
)
|
||||
self.assertEqual(
|
||||
self.sales_team_1_m1._get_assignment_quota(work_days=2),
|
||||
11,
|
||||
"Assignment quota: 45 max on 2 days -> 3, compensation (45-3)/5 -> 8.4"
|
||||
)
|
||||
|
||||
# quota should not exceed maximum
|
||||
self.assertEqual(
|
||||
self.sales_team_1_m1._get_assignment_quota(work_days=30),
|
||||
45,
|
||||
"Assignment quota: no compensation as exceeding monthly count"
|
||||
)
|
||||
self.assertEqual(
|
||||
self.sales_team_1_m1._get_assignment_quota(work_days=60),
|
||||
90,
|
||||
"Assignment quota: no compensation and no limit anymore (do as asked)"
|
||||
self.sales_team_1_m1._get_assignment_quota(),
|
||||
2,
|
||||
"Assignment quota: 45 max -> 2 daily (round(45/30))"
|
||||
)
|
||||
|
||||
# create exiting leads for user_sales_leads (sales_team_1_m1)
|
||||
|
|
@ -433,31 +480,20 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
self.assertEqual(existing_leads.team_id, self.sales_team_1, "Team should have lower sequence")
|
||||
existing_leads.flush_recordset()
|
||||
|
||||
self.sales_team_1_m1.invalidate_model(['lead_month_count'])
|
||||
self.sales_team_1_m1.invalidate_model(['lead_month_count', 'lead_day_count'])
|
||||
self.assertEqual(self.sales_team_1_m1.lead_month_count, 30)
|
||||
self.assertEqual(self.sales_team_1_m1.lead_day_count, 30)
|
||||
|
||||
# quota computation with existing leads
|
||||
self.assertEqual(
|
||||
self.sales_team_1_m1._get_assignment_quota(work_days=1),
|
||||
4,
|
||||
"Assignment quota: 45 max on 1 days -> 1.5, compensation (45-30-1.5)/5 -> 2.7"
|
||||
self.sales_team_1_m1._get_assignment_quota(),
|
||||
-28,
|
||||
"Assignment quota: 45 max -> 2 daily ; 30 daily lead already assign -> 2 - 30 -> -28"
|
||||
)
|
||||
self.assertEqual(
|
||||
self.sales_team_1_m1._get_assignment_quota(work_days=2),
|
||||
5,
|
||||
"Assignment quota: 45 max on 2 days -> 3, compensation (45-30-3)/5 -> 2.4"
|
||||
)
|
||||
|
||||
# quota should not exceed maximum
|
||||
self.assertEqual(
|
||||
self.sales_team_1_m1._get_assignment_quota(work_days=30),
|
||||
45,
|
||||
"Assignment quota: no compensation and no limit anymore (do as asked even with 30 already assigned)"
|
||||
)
|
||||
self.assertEqual(
|
||||
self.sales_team_1_m1._get_assignment_quota(work_days=60),
|
||||
90,
|
||||
"Assignment quota: no compensation and no limit anymore (do as asked even with 30 already assigned)"
|
||||
self.sales_team_1_m1._get_assignment_quota(True),
|
||||
2,
|
||||
"Assignment quota: 45 max ignoring existing daily lead -> 2"
|
||||
)
|
||||
|
||||
def test_assign_specific_won_lost(self):
|
||||
|
|
@ -480,8 +516,9 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
|
||||
self.sales_team_1.crm_team_member_ids.write({'assignment_max': 45})
|
||||
with self.with_user('user_sales_manager'):
|
||||
self.env['crm.team'].browse(self.sales_team_1.ids)._action_assign_leads(work_days=4)
|
||||
self.env['crm.team'].browse(self.sales_team_1.ids)._action_assign_leads()
|
||||
|
||||
self.assertEqual(leads[0].team_id, self.env['crm.team'], 'Won lead should not be assigned')
|
||||
self.assertEqual(leads[0].user_id, self.env['res.users'], 'Won lead should not be assigned')
|
||||
|
|
@ -493,6 +530,26 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
self.assertEqual(leads[5].team_id, self.sales_team_convert, 'Assigned lead should not be reassigned')
|
||||
self.assertEqual(leads[5].user_id, self.user_sales_manager, 'Assigned lead should not be reassigned')
|
||||
|
||||
def test_assign_team_and_salesperson_on_duplicate_lead(self):
|
||||
"""Ensure leads duplicated from an existing lead are assigned correctly."""
|
||||
duplicate_lead = self.env['crm.lead'].create({
|
||||
'name': 'Test Lead',
|
||||
'type': 'opportunity',
|
||||
'probability': 15,
|
||||
'partner_id': self.contact_1.id,
|
||||
'team_id': False,
|
||||
'user_id': False,
|
||||
}).copy()
|
||||
self.assertFalse(duplicate_lead.date_open)
|
||||
|
||||
sales_team = self.sales_team_1
|
||||
sales_team.assignment_domain = [('user_id', '=', False)]
|
||||
with self.with_user('user_sales_manager'):
|
||||
sales_team._action_assign_leads()
|
||||
|
||||
self.assertEqual(duplicate_lead.team_id, sales_team)
|
||||
self.assertTrue(duplicate_lead.user_id)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_merge_assign_keep_master_team(self):
|
||||
""" Check existing opportunity keep its team and salesman when merged with a new lead """
|
||||
|
|
@ -530,7 +587,7 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
'user_id': False,
|
||||
})
|
||||
|
||||
sales_team_dupe._action_assign_leads(work_days=2)
|
||||
sales_team_dupe._action_assign_leads()
|
||||
self.assertFalse(dupe_lead.exists())
|
||||
self.assertEqual(master_opp.team_id, self.sales_team_1, 'Opportunity: should keep its sales team')
|
||||
self.assertEqual(master_opp.user_id, self.user_sales_manager, 'Opportunity: should keep its salesman')
|
||||
|
|
@ -548,18 +605,17 @@ class TestLeadAssign(TestLeadAssignCommon):
|
|||
'name': 'Sales Team 4',
|
||||
'sequence': 15,
|
||||
'use_leads': True,
|
||||
})
|
||||
})
|
||||
sales_team_4_m1 = self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_salesman.id,
|
||||
'crm_team_id': sales_team_4.id,
|
||||
'assignment_max': 30,
|
||||
})
|
||||
|
||||
sales_team_4_m1.lead_month_count = 50
|
||||
sales_team_4_m1.lead_month_count = 30
|
||||
sales_team_4_m1.lead_day_count = 2
|
||||
leads.team_id = sales_team_4.id
|
||||
|
||||
members_data = sales_team_4_m1._assign_and_convert_leads(work_days=0.2)
|
||||
self.assertEqual(
|
||||
len(members_data[sales_team_4_m1]['assigned']),
|
||||
0,
|
||||
members_data = sales_team_4._assign_and_convert_leads()
|
||||
self.assertFalse(members_data,
|
||||
"If team member has lead count greater than max assign,then do not assign any more")
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from itertools import product
|
||||
|
||||
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
|
||||
from odoo.tests import Form, tagged, users
|
||||
|
||||
@tagged('lead_manage')
|
||||
class TestLeadConvertForm(crm_common.TestLeadConvertCommon):
|
||||
|
|
@ -59,13 +57,13 @@ class TestLeadConvertForm(crm_common.TestLeadConvertCommon):
|
|||
@tagged('lead_manage')
|
||||
class TestLeadConvert(crm_common.TestLeadConvertCommon):
|
||||
"""
|
||||
TODO: created partner (handle assignation) has team of lead
|
||||
TODO: created partner (handle assignment) has team of lead
|
||||
TODO: create partner has user_id coming from wizard
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestLeadConvert, cls).setUpClass()
|
||||
super().setUpClass()
|
||||
date = Datetime.from_string('2020-01-20 16:00:00')
|
||||
cls.crm_lead_dt_mock.now.return_value = date
|
||||
|
||||
|
|
@ -80,6 +78,11 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
|
|||
'email_from': test_lead.email_from,
|
||||
'probability': 0, 'active': False,
|
||||
},
|
||||
{'name': 'Duplicate lead: same email_from, archived (not lost)',
|
||||
'type': 'lead',
|
||||
'email_from': test_lead.email_from,
|
||||
'probability': 50, 'active': False,
|
||||
},
|
||||
{'name': 'Duplicate lead: same email_from, proba 0 but not lost',
|
||||
'type': 'lead',
|
||||
'email_from': test_lead.email_from,
|
||||
|
|
@ -94,37 +97,50 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
|
|||
'type': 'opportunity',
|
||||
'email_from': test_lead.email_from,
|
||||
'probability': 100, 'stage_id': self.stage_team1_2.id,
|
||||
},
|
||||
{'name': 'Duplicate opp: same email_from, archived (not lost)',
|
||||
'type': 'opportunity',
|
||||
'email_from': test_lead.email_from,
|
||||
'probability': 50, 'stage_id': self.stage_team1_2.id,
|
||||
'active': False,
|
||||
}
|
||||
])
|
||||
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')
|
||||
self.assertEqual(len(dup_leads), 10, 'Be sure below quick access are relevant')
|
||||
opp_lost = dup_leads[3]
|
||||
lead_lost = dup_leads[4]
|
||||
lead_archived = dup_leads[5]
|
||||
opp_won = dup_leads[7]
|
||||
_opp_proba100 = dup_leads[8]
|
||||
opp_archived = dup_leads[9]
|
||||
|
||||
test_lead.write({'partner_id': customer.id})
|
||||
|
||||
# not include_lost = remove archived leads as well as 'won' opportunities
|
||||
# not include_lost = remove archived leads/opps, lost leads/opps 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))
|
||||
self.assertEqual(
|
||||
result,
|
||||
test_lead + dup_leads - (lead_lost + lead_archived + opp_won + opp_archived + opp_lost),
|
||||
'Should not include: lost lead or opp, archived lead / opp (aka: only active lead/opp not won nor lost)'
|
||||
)
|
||||
|
||||
# include_lost = remove archived opp only
|
||||
# include_lost = remove archived lead only (archived opp is ok)
|
||||
result = test_lead._get_lead_duplicates(
|
||||
partner=test_lead.partner_id,
|
||||
email=test_lead.email_from,
|
||||
include_lost=True
|
||||
include_lost=True,
|
||||
)
|
||||
self.assertEqual(result, test_lead + dup_leads - (lead_lost))
|
||||
self.assertEqual(result, test_lead + dup_leads - (lead_lost + lead_archived + opp_won))
|
||||
|
||||
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)
|
||||
|
|
@ -149,7 +165,6 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
|
|||
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)
|
||||
|
|
@ -317,24 +332,6 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
|
|||
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
|
||||
|
|
@ -396,19 +393,11 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
|
|||
}]
|
||||
self.lead_1.convert_opportunity(False)
|
||||
self.assertEqual(self.lead_1.team_id, initial_team)
|
||||
self.assertEqual(self.lead_1.lead_properties, [{
|
||||
'name': 'test',
|
||||
'type': 'char',
|
||||
'value': 'test value',
|
||||
}])
|
||||
self.assertEqual(self.lead_1.lead_properties, {'test': 'test value'})
|
||||
|
||||
# re-writing the team, but keeping the same value should not reset the properties
|
||||
self.lead_1.write({'team_id': self.lead_1.team_id.id})
|
||||
self.assertEqual(self.lead_1.lead_properties, [{
|
||||
'name': 'test',
|
||||
'type': 'char',
|
||||
'value': 'test value',
|
||||
}])
|
||||
self.assertEqual(self.lead_1.lead_properties, {'test': 'test value'})
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_properties_reset(self):
|
||||
|
|
@ -424,6 +413,58 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
|
|||
self.assertNotEqual(self.lead_1.team_id, initial_team)
|
||||
self.assertFalse(self.lead_1.lead_properties)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_wizard_new_partner(self):
|
||||
no_partner = self.env['res.partner']
|
||||
test_partner_lead, test_partner_wizard, commercial_partner = self.env['res.partner'].create([
|
||||
{'name': 'Lead Test Partner'},
|
||||
{'name': 'Wizard Test Partner'},
|
||||
{'name': 'Company Partner', 'is_company': True},
|
||||
])
|
||||
case_values = product(
|
||||
[no_partner, test_partner_lead],
|
||||
[False, 'New Company'],
|
||||
[no_partner, commercial_partner],
|
||||
[no_partner, test_partner_wizard],
|
||||
['create', 'exist'],
|
||||
)
|
||||
for (lead_partner, lead_company_name, wizard_company, wizard_contact, wizard_action) in case_values:
|
||||
(test_partner_lead + test_partner_wizard).parent_id = False
|
||||
commercial_partner.invalidate_recordset()
|
||||
lead_contact_name = lead_partner.name or 'Test Contact Name'
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'Test Lead',
|
||||
'contact_name': lead_contact_name,
|
||||
'partner_id': lead_partner.id,
|
||||
'partner_name': lead_company_name,
|
||||
})
|
||||
wizard = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': lead.id,
|
||||
'active_ids': lead.ids,
|
||||
}).create({})
|
||||
wizard.write({'action': wizard_action, 'name': 'convert'})
|
||||
if wizard_contact:
|
||||
wizard.partner_id = wizard_contact
|
||||
if wizard_company:
|
||||
wizard.commercial_partner_id = wizard_company
|
||||
with self.subTest(
|
||||
lead_company_name=lead_company_name, lead_partner=lead_partner.name,
|
||||
wizard_company=wizard_company.name, wizard_contact=wizard_contact.name, wizard_action=wizard_action
|
||||
):
|
||||
wizard.action_apply()
|
||||
self.assertEqual(lead.type, 'opportunity')
|
||||
self.assertEqual(bool(lead.partner_id), bool(wizard_action == 'create' or lead_partner or wizard_contact))
|
||||
if wizard_action == 'exist' and (lead_partner or wizard_contact):
|
||||
self.assertEqual(lead.partner_id, wizard_contact or lead_partner)
|
||||
if wizard_action == 'create' and not lead_partner and not wizard_contact and wizard_company:
|
||||
self.assertTrue(lead.partner_id)
|
||||
self.assertEqual(lead.partner_id.name, lead_contact_name)
|
||||
self.assertEqual(lead.partner_id.parent_id, wizard_company)
|
||||
if wizard_action == 'create' and (wizard_contact or lead_partner):
|
||||
self.assertEqual(lead.partner_id, wizard_contact or lead_partner)
|
||||
self.assertFalse(lead.partner_id.parent_id)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_merge(self):
|
||||
""" Test convert wizard working in merge mode """
|
||||
|
|
|
|||
|
|
@ -15,6 +15,13 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon):
|
|||
cls.leads = cls.lead_1 + cls.lead_w_partner + cls.lead_w_email_lost
|
||||
cls.assign_users = cls.user_sales_manager + cls.user_sales_leads_convert + cls.user_sales_salesman
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# patch registry to simulate a ready environment
|
||||
self.patch(self.env.registry, 'ready', True)
|
||||
# we don't use mock_mail_gateway thus want to mock smtp to test the stack
|
||||
self._mock_smtplib_connection()
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_assignment_salesmen(self):
|
||||
test_leads = self._create_leads_batch(count=50, user_ids=[False])
|
||||
|
|
@ -24,7 +31,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon):
|
|||
with self.assertQueryCount(user_sales_manager=0):
|
||||
test_leads = self.env['crm.lead'].browse(test_leads.ids)
|
||||
|
||||
with self.assertQueryCount(user_sales_manager=543): # crm 537 / com 543 / ent 537
|
||||
with self.assertQueryCount(user_sales_manager=531): # crm 605 / com 605 / ent 605
|
||||
test_leads._handle_salesmen_assignment(user_ids=user_ids, team_id=False)
|
||||
|
||||
self.assertEqual(test_leads.team_id, self.sales_team_convert | self.sales_team_1)
|
||||
|
|
@ -42,7 +49,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon):
|
|||
with self.assertQueryCount(user_sales_manager=0):
|
||||
test_leads = self.env['crm.lead'].browse(test_leads.ids)
|
||||
|
||||
with self.assertQueryCount(user_sales_manager=524): # crm 521 / com 524
|
||||
with self.assertQueryCount(user_sales_manager=483): # crm 544 / com 546 / ent 585
|
||||
test_leads._handle_salesmen_assignment(user_ids=user_ids, team_id=team_id)
|
||||
|
||||
self.assertEqual(test_leads.team_id, self.sales_team_convert)
|
||||
|
|
@ -89,7 +96,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon):
|
|||
self.assertEqual(new_partner.name, 'Amy Wong')
|
||||
self.assertEqual(new_partner.email, 'amy.wong@test.example.com')
|
||||
|
||||
# test unforced assignation
|
||||
# test unforced assignment
|
||||
mass_convert.write({
|
||||
'user_ids': self.user_sales_salesman.ids,
|
||||
})
|
||||
|
|
@ -167,7 +174,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon):
|
|||
user_ids = self.assign_users.ids
|
||||
|
||||
# randomness: at least 1 query
|
||||
with self.assertQueryCount(user_sales_manager=1704): # crm 1410 / com 1697
|
||||
with self.assertQueryCount(user_sales_manager=1442): # crm ??
|
||||
mass_convert = self.env['crm.lead2opportunity.partner.mass'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_ids': test_leads.ids,
|
||||
|
|
@ -211,3 +218,20 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon):
|
|||
self.assertEqual(lead.type, 'opportunity')
|
||||
assigned_user = self.assign_users[idx % len(self.assign_users)]
|
||||
self.assertEqual(lead.user_id, assigned_user)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_mass_convert_with_original_and_duplicate_selected(self):
|
||||
_customer, lead_1_dups = self._create_duplicates(self.lead_1, create_opp=False)
|
||||
|
||||
mass_convert = self.env['crm.lead2opportunity.partner.mass'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_ids': (self.lead_1 + lead_1_dups).ids,
|
||||
}).create({
|
||||
'deduplicate': True,
|
||||
})
|
||||
|
||||
mass_convert.action_mass_convert()
|
||||
|
||||
remaining_leads = (self.lead_1 + lead_1_dups).exists()
|
||||
self.assertEqual(len(remaining_leads), 1)
|
||||
self.assertEqual(remaining_leads.type, 'opportunity')
|
||||
|
|
|
|||
|
|
@ -2,227 +2,226 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon
|
||||
from odoo.addons.iap.tools import iap_tools
|
||||
from odoo.tests.common import tagged, users
|
||||
|
||||
|
||||
@tagged('lead_manage')
|
||||
class TestLeadConvert(TestCrmCommon):
|
||||
@tagged('lead_internals')
|
||||
class TestCRMLead(TestCrmCommon):
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_potential_duplicates(self):
|
||||
company = self.env['res.partner'].create({
|
||||
'name': 'My company',
|
||||
'email': 'mycompany@company.com',
|
||||
'is_company': True,
|
||||
'street': '57th Street',
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# To avoid magic phone sanitization
|
||||
cls.env.company.country_id = cls.env.ref('base.us')
|
||||
|
||||
cls.emails_provider_generic = {
|
||||
('robert.poilvert@gmail.com', 'robert.poilvert@gmail.com'),
|
||||
('fp@odoo.com', 'fp@odoo.com'),
|
||||
('fp.alias@mail.odoo.com', 'fp.alias@mail.odoo.com'),
|
||||
}
|
||||
cls.emails_provider_company = {
|
||||
('robert.poilvert@mycompany.com', 'mycompany.com'),
|
||||
('fp@subdomain.odoo.com', 'subdomain.odoo.com'),
|
||||
}
|
||||
|
||||
# customer data
|
||||
country_us_id = cls.env.ref('base.us').id
|
||||
cls.test_company = cls.env['res.partner'].create({
|
||||
'city': 'New New York',
|
||||
'country_id': self.env.ref('base.us').id,
|
||||
'country_id': country_us_id,
|
||||
'email': 'test.company@another.email.company.com',
|
||||
'is_company': True,
|
||||
'name': 'My company',
|
||||
'street': '57th Street',
|
||||
'zip': '12345',
|
||||
})
|
||||
cls.test_partners = cls.env['res.partner'].create([
|
||||
{
|
||||
'city': 'New York',
|
||||
'country_id': country_us_id,
|
||||
'email': 'dave@another.email.company.com',
|
||||
'is_company': False,
|
||||
'name': 'Dave',
|
||||
'phone': '+1 202 000 0123',
|
||||
'parent_id': cls.test_company.id,
|
||||
'street': 'Pearl street',
|
||||
'zip': '12345',
|
||||
},
|
||||
{
|
||||
'city': 'New York',
|
||||
'country_id': country_us_id,
|
||||
'email': 'eve@another.email.company.com',
|
||||
'is_company': False,
|
||||
'name': 'Eve',
|
||||
'parent_id': cls.test_company.id,
|
||||
'phone': '+1 202 000 3210',
|
||||
'street': 'Wall street',
|
||||
'zip': '12345',
|
||||
}
|
||||
])
|
||||
|
||||
partner_1 = self.env['res.partner'].create({
|
||||
'name': 'Dave',
|
||||
'email': 'dave@odoo.com',
|
||||
'mobile': '+1 202 555 0123',
|
||||
'phone': False,
|
||||
'parent_id': company.id,
|
||||
'is_company': False,
|
||||
'street': 'Pearl street',
|
||||
'city': 'California',
|
||||
'country_id': self.env.ref('base.us').id,
|
||||
'zip': '95826',
|
||||
})
|
||||
partner_2 = self.env['res.partner'].create({
|
||||
'name': 'Eve',
|
||||
'email': 'eve@odoo.com',
|
||||
'mobile': '+1 202 555 3210',
|
||||
'phone': False,
|
||||
'parent_id': company.id,
|
||||
'is_company': False,
|
||||
'street': 'Wall street',
|
||||
'city': 'New York',
|
||||
'country_id': self.env.ref('base.us').id,
|
||||
'zip': '54321',
|
||||
})
|
||||
|
||||
lead_1 = self.env['crm.lead'].create({
|
||||
'name': 'Lead 1',
|
||||
# base leads on which duplicate detection is performed
|
||||
cls.lead_generic = cls.env['crm.lead'].create({
|
||||
'country_id': country_us_id,
|
||||
'email_from': 'FP@odoo.com',
|
||||
'name': 'Generic 1',
|
||||
'partner_id': cls.test_partners[0].id,
|
||||
'phone': '+1 202 555 0123',
|
||||
'type': 'lead',
|
||||
'partner_name': 'Alice',
|
||||
'email_from': 'alice@odoo.com',
|
||||
})
|
||||
lead_2 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 1',
|
||||
'type': 'opportunity',
|
||||
'email_from': 'alice@odoo.com',
|
||||
})
|
||||
lead_3 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 2',
|
||||
'type': 'opportunity',
|
||||
'email_from': 'alice@odoo.com',
|
||||
})
|
||||
lead_4 = self.env['crm.lead'].create({
|
||||
'name': 'Lead 2',
|
||||
'type': 'lead',
|
||||
'partner_name': 'Alice Doe'
|
||||
})
|
||||
lead_5 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 3',
|
||||
'type': 'opportunity',
|
||||
'partner_name': 'Alice Doe'
|
||||
})
|
||||
lead_6 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 4',
|
||||
'type': 'opportunity',
|
||||
'partner_name': 'Bob Doe'
|
||||
})
|
||||
lead_7 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 5',
|
||||
'type': 'opportunity',
|
||||
'partner_name': 'Bob Doe',
|
||||
'email_from': 'bob@odoo.com',
|
||||
})
|
||||
lead_8 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 6',
|
||||
'type': 'opportunity',
|
||||
'email_from': 'bob@mymail.com',
|
||||
})
|
||||
lead_9 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 7',
|
||||
'type': 'opportunity',
|
||||
'email_from': 'alice@mymail.com',
|
||||
})
|
||||
lead_10 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 8',
|
||||
'type': 'opportunity',
|
||||
'probability': 0,
|
||||
'active': False,
|
||||
'email_from': 'alice@mymail.com',
|
||||
})
|
||||
lead_11 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 9',
|
||||
'type': 'opportunity',
|
||||
'contact_name': 'charlie'
|
||||
})
|
||||
lead_12 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 10',
|
||||
'type': 'opportunity',
|
||||
'contact_name': 'Charlie Chapelin',
|
||||
})
|
||||
lead_13 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 8',
|
||||
'type': 'opportunity',
|
||||
'partner_id': partner_1.id
|
||||
})
|
||||
lead_14 = self.env['crm.lead'].create({
|
||||
'name': 'Lead 3',
|
||||
'type': 'lead',
|
||||
'partner_id': partner_2.id
|
||||
})
|
||||
|
||||
self.assertEqual(lead_1 + lead_2 + lead_3, lead_1.duplicate_lead_ids)
|
||||
self.assertEqual(lead_1 + lead_2 + lead_3, lead_2.duplicate_lead_ids)
|
||||
self.assertEqual(lead_1 + lead_2 + lead_3, lead_3.duplicate_lead_ids)
|
||||
self.assertEqual(lead_4 + lead_5, lead_4.duplicate_lead_ids)
|
||||
self.assertEqual(lead_4 + lead_5, lead_5.duplicate_lead_ids)
|
||||
self.assertEqual(lead_6 + lead_7, lead_6.duplicate_lead_ids)
|
||||
self.assertEqual(lead_6 + lead_7, lead_7.duplicate_lead_ids)
|
||||
self.assertEqual(lead_8 + lead_9 + lead_10, lead_8.duplicate_lead_ids)
|
||||
self.assertEqual(lead_8 + lead_9 + lead_10, lead_9.duplicate_lead_ids)
|
||||
self.assertEqual(lead_8 + lead_9 + lead_10, lead_10.duplicate_lead_ids)
|
||||
self.assertEqual(lead_11 + lead_12, lead_11.duplicate_lead_ids)
|
||||
self.assertEqual(lead_12, lead_12.duplicate_lead_ids)
|
||||
self.assertEqual(lead_13 + lead_14, lead_13.duplicate_lead_ids)
|
||||
self.assertEqual(lead_13 + lead_14, lead_14.duplicate_lead_ids)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_potential_duplicates_with_phone(self):
|
||||
customer = self.env['res.partner'].create({
|
||||
'email': 'customer1@duplicate.example.com',
|
||||
'mobile': '+32485001122',
|
||||
'name': 'Customer1',
|
||||
'phone': '(803)-456-6126',
|
||||
})
|
||||
base_lead = self.env['crm.lead'].create({
|
||||
'name': 'Base Lead',
|
||||
'partner_id': customer.id,
|
||||
cls.lead_company = cls.env['crm.lead'].create({
|
||||
'country_id': country_us_id,
|
||||
'email_from': 'floppy@MYCOMPANY.com',
|
||||
'partner_id': False,
|
||||
'name': 'CompanyMail 1',
|
||||
'phone': '+1 202 666 4567',
|
||||
'type': 'lead',
|
||||
})
|
||||
|
||||
self.assertEqual(base_lead.contact_name, customer.name)
|
||||
self.assertEqual(base_lead.mobile, customer.mobile)
|
||||
self.assertFalse(base_lead.partner_name)
|
||||
self.assertEqual(base_lead.phone, customer.phone)
|
||||
# duplicates
|
||||
cls.lead_generic_email_dupes = cls.env['crm.lead'].create([
|
||||
# email based: normalized version used for email domain criterion
|
||||
{
|
||||
'email_from': '"Fabulous Fab" <fp@ODOO.COM>',
|
||||
'name': 'Dupe1 of fp@odoo.com (same email)',
|
||||
'type': 'lead',
|
||||
},
|
||||
{
|
||||
'email_from': 'FP@odoo.com',
|
||||
'name': 'Dupe2 of fp@odoo.com (same email)',
|
||||
'type': 'lead',
|
||||
},
|
||||
# phone_sanitized based
|
||||
{
|
||||
'email_from': 'not.fp@not.odoo.com',
|
||||
'name': 'Dupe3 of fp@odoo.com (same phone sanitized)',
|
||||
'phone': '+1 202 555 0123',
|
||||
'type': 'lead',
|
||||
},
|
||||
{
|
||||
'email_from': 'not.fp@not.odoo.com',
|
||||
'phone': '+1 202 555 0123',
|
||||
'name': 'Dupe4 of fp@odoo.com (same phone sanitized)',
|
||||
'type': 'lead',
|
||||
},
|
||||
# same commercial entity
|
||||
{
|
||||
'name': 'Dupe5 of fp@odoo.com (same commercial entity)',
|
||||
'partner_id': cls.test_partners[1].id,
|
||||
},
|
||||
{
|
||||
'name': 'Dupe6 of fp@odoo.com (same commercial entity)',
|
||||
'partner_id': cls.test_company.id,
|
||||
}
|
||||
])
|
||||
cls.lead_generic_email_notdupes = cls.env['crm.lead'].create([
|
||||
# email: check for exact match
|
||||
{
|
||||
'email_from': 'not.fp@odoo.com',
|
||||
'name': 'NotADupe1',
|
||||
'type': 'lead',
|
||||
},
|
||||
])
|
||||
cls.lead_company_email_dupes = cls.env['crm.lead'].create([
|
||||
# email based: normalized version used for email domain criterion
|
||||
{
|
||||
'email_from': '"The Other Fabulous Fab" <fp@mycompany.COM>',
|
||||
'name': 'Dupe1 of mycompany@mycompany.com (same company)',
|
||||
'type': 'lead',
|
||||
},
|
||||
{
|
||||
'email_from': '"Same Email" <floppy@mycompany.com>',
|
||||
'name': 'Dupe2 of mycompany@mycompany.com (same company)',
|
||||
'type': 'lead',
|
||||
},
|
||||
# phone_sanitized based
|
||||
{
|
||||
'email_from': 'not.floppy@not.mycompany.com',
|
||||
'name': 'Dupe3 of fp@odoo.com (same phone sanitized)',
|
||||
'phone': '+1 202 666 4567',
|
||||
'type': 'lead',
|
||||
},
|
||||
{
|
||||
'email_from': 'not.floppy@not.mycompany.com',
|
||||
'phone': '+1 202 666 4567',
|
||||
'name': 'Dupe4 of fp@odoo.com (same phone sanitized)',
|
||||
'type': 'lead',
|
||||
},
|
||||
])
|
||||
cls.lead_company_email_notdupes = cls.env['crm.lead'].create([
|
||||
# email: check same company
|
||||
{
|
||||
'email_from': 'floppy@zboing.MYCOMPANY.com',
|
||||
'name': 'NotADupe2',
|
||||
'type': 'lead',
|
||||
},
|
||||
])
|
||||
|
||||
dup1_1 = self.env['crm.lead'].create({
|
||||
'name': 'Base Lead Dup1',
|
||||
'type': 'lead',
|
||||
'phone': '456-6126', # shorter version of base_lead
|
||||
'mobile': ' ', # empty string shouldn't crash Odoo
|
||||
'partner_name': 'Partner Name 1',
|
||||
})
|
||||
dup1_2 = self.env['crm.lead'].create({
|
||||
'name': 'Base Lead Dup2',
|
||||
'mobile': '8034566126',
|
||||
'partner_name': 'Partner Name 2',
|
||||
'type': 'lead',
|
||||
})
|
||||
dup1_3 = self.env['crm.lead'].create({
|
||||
'name': 'Base Lead Dup3',
|
||||
'partner_name': 'Partner Name 3',
|
||||
'phone': '(803)-456-6126',
|
||||
'type': 'lead',
|
||||
})
|
||||
dup1_4 = self.env['crm.lead'].create({
|
||||
'mobile': '0032485001122',
|
||||
# 'mobile': '0485001122', # note: does not work
|
||||
'name': 'Base Lead Dup4',
|
||||
'partner_name': 'Partner Name 4',
|
||||
'phone': False,
|
||||
'type': 'lead',
|
||||
})
|
||||
def test_assert_initial_values(self):
|
||||
""" Just be sure of initial value for those tests """
|
||||
lead_generic = self.lead_generic.with_env(self.env)
|
||||
self.assertEqual(lead_generic.phone_sanitized, '+12025550123')
|
||||
self.assertEqual(lead_generic.email_domain_criterion, 'fp@odoo.com')
|
||||
self.assertEqual(lead_generic.email_normalized, 'fp@odoo.com')
|
||||
|
||||
expected = base_lead + dup1_2 + dup1_3 + dup1_4 # dup1_1 is shorter than lead -> not a dupe
|
||||
self.assertEqual(
|
||||
base_lead.duplicate_lead_ids, expected,
|
||||
'CRM: missing %s, extra %s' % ((expected - base_lead.duplicate_lead_ids).mapped('name'), (base_lead.duplicate_lead_ids - expected).mapped('name'))
|
||||
)
|
||||
expected = base_lead + dup1_1 + dup1_2 + dup1_3 # dup1_4 has mobile of customer, but no link with dup1_1
|
||||
self.assertEqual(
|
||||
dup1_1.duplicate_lead_ids, expected,
|
||||
'CRM: missing %s, extra %s' % ((expected - dup1_1.duplicate_lead_ids).mapped('name'), (dup1_1.duplicate_lead_ids - expected).mapped('name'))
|
||||
)
|
||||
lead_company = self.lead_company.with_env(self.env)
|
||||
self.assertEqual(lead_company.phone_sanitized, '+12026664567')
|
||||
self.assertEqual(lead_company.email_domain_criterion, '@mycompany.com')
|
||||
self.assertEqual(lead_company.email_normalized, 'floppy@mycompany.com')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_potential_duplicates_with_invalid_email(self):
|
||||
lead_1 = self.env['crm.lead'].create({
|
||||
'name': 'Lead 1',
|
||||
'type': 'lead',
|
||||
'email_from': 'mail"1@mymail.com'
|
||||
})
|
||||
lead_2 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 1',
|
||||
'type': 'opportunity',
|
||||
'email_from': 'mail2@mymail.com'
|
||||
})
|
||||
lead_3 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 2',
|
||||
'type': 'lead',
|
||||
'email_from': 'odoo.com'
|
||||
})
|
||||
lead_4 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 3',
|
||||
'type': 'opportunity',
|
||||
'email_from': 'odoo.com'
|
||||
})
|
||||
lead_5 = self.env['crm.lead'].create({
|
||||
'name': 'Opportunity 3',
|
||||
'type': 'opportunity',
|
||||
'email_from': 'myodoo.com'
|
||||
})
|
||||
@users('user_sales_leads')
|
||||
def test_crm_lead_duplicates_fetch(self):
|
||||
""" Test heuristic to find duplicates of a given lead. """
|
||||
# generic provider-based email
|
||||
lead_generic = self.lead_generic.with_env(self.env)
|
||||
|
||||
self.assertEqual(lead_1 + lead_2, lead_1.duplicate_lead_ids)
|
||||
self.assertEqual(lead_1 + lead_2, lead_2.duplicate_lead_ids)
|
||||
self.assertEqual(lead_3 + lead_4 + lead_5, lead_3.duplicate_lead_ids)
|
||||
self.assertEqual(lead_3 + lead_4 + lead_5, lead_4.duplicate_lead_ids)
|
||||
self.assertEqual(lead_5, lead_5.duplicate_lead_ids)
|
||||
self.assertEqual(lead_generic.duplicate_lead_ids,
|
||||
lead_generic + self.lead_generic_email_dupes,
|
||||
'Duplicates: exact email matching (+ self)')
|
||||
|
||||
# company-based email
|
||||
lead_company = self.lead_company.with_env(self.env)
|
||||
self.assertEqual(lead_company.duplicate_lead_ids,
|
||||
lead_company + self.lead_company_email_dupes,
|
||||
'Duplicates: exact email matching (+ self)')
|
||||
|
||||
@users('user_sales_leads')
|
||||
def test_crm_lead_email_domain_criterion(self):
|
||||
""" Test computed field 'email_domain_criterion' used notably to fetch
|
||||
duplicates. """
|
||||
for test_email, provider in self.emails_provider_generic:
|
||||
with self.subTest(test_email=test_email, provider=provider):
|
||||
lead = self.env['crm.lead'].create({
|
||||
'email_from': test_email,
|
||||
'name': test_email,
|
||||
})
|
||||
self.assertEqual(lead.email_domain_criterion, provider)
|
||||
|
||||
for test_email, provider in self.emails_provider_company:
|
||||
with self.subTest(test_email=test_email, provider=provider):
|
||||
lead = self.env['crm.lead'].create({
|
||||
'email_from': test_email,
|
||||
'name': test_email,
|
||||
})
|
||||
self.assertEqual(lead.email_domain_criterion, f'@{provider}',)
|
||||
|
||||
@users('user_sales_leads')
|
||||
def test_iap_tools(self):
|
||||
""" Test iap tools specifically """
|
||||
for test_email, provider in self.emails_provider_generic:
|
||||
with self.subTest(test_email=test_email, provider=provider):
|
||||
self.assertEqual(
|
||||
iap_tools.mail_prepare_for_domain_search(test_email),
|
||||
test_email,
|
||||
'As provider is a generic one, complete email should be returned for a company-based mail search'
|
||||
)
|
||||
|
||||
for test_email, provider in self.emails_provider_company:
|
||||
with self.subTest(test_email=test_email, provider=provider):
|
||||
self.assertEqual(
|
||||
iap_tools.mail_prepare_for_domain_search(test_email),
|
||||
f'@{provider}',
|
||||
'As provider is a company one, only the domain part should be returned for a company-based mail search'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,132 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.crm.tests import common as crm_common
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tests.common import tagged, users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('lead_manage', 'lead_lost')
|
||||
class TestLeadConvert(crm_common.TestCrmCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestLeadConvert, cls).setUpClass()
|
||||
cls.lost_reason = cls.env['crm.lost.reason'].create({
|
||||
'name': 'Test Reason'
|
||||
})
|
||||
|
||||
@users('user_sales_salesman')
|
||||
def test_lead_lost(self):
|
||||
""" Test setting a lead as lost using the wizard. Also check that an
|
||||
'html editor' void content used as feedback is not logged on the lead. """
|
||||
# Initial data
|
||||
self.assertEqual(len(self.lead_1.message_ids), 1, 'Should contain creation message')
|
||||
creation_message = self.lead_1.message_ids[0]
|
||||
self.assertEqual(creation_message.subtype_id, self.env.ref('crm.mt_lead_create'))
|
||||
|
||||
# Update responsible as ACLs is "own only" for user_sales_salesman
|
||||
self.lead_1.with_user(self.user_sales_manager).write({
|
||||
'user_id': self.user_sales_salesman.id,
|
||||
'probability': 32,
|
||||
})
|
||||
self.flush_tracking()
|
||||
|
||||
lead = self.env['crm.lead'].browse(self.lead_1.ids)
|
||||
self.assertFalse(lead.lost_reason_id)
|
||||
self.assertEqual(lead.probability, 32)
|
||||
self.assertEqual(len(lead.message_ids), 2, 'Should have tracked new responsible')
|
||||
update_message = lead.message_ids[0]
|
||||
self.assertEqual(update_message.subtype_id, self.env.ref('mail.mt_note'))
|
||||
|
||||
# mark as lost using the wizard
|
||||
lost_wizard = self.env['crm.lead.lost'].with_context({
|
||||
'active_ids': lead.ids,
|
||||
}).create({
|
||||
'lost_reason_id': self.lost_reason.id,
|
||||
'lost_feedback': '<p></p>', # void content
|
||||
})
|
||||
|
||||
lost_wizard.action_lost_reason_apply()
|
||||
self.flush_tracking()
|
||||
|
||||
# check lead update
|
||||
self.assertFalse(lead.active)
|
||||
self.assertEqual(lead.automated_probability, 0)
|
||||
self.assertEqual(lead.lost_reason_id, self.lost_reason) # TDE FIXME: should be called lost_reason_id non didjou
|
||||
self.assertEqual(lead.probability, 0)
|
||||
# check messages
|
||||
self.assertEqual(len(lead.message_ids), 3, 'Should have logged a tracking message for lost lead with reason')
|
||||
update_message = lead.message_ids[0]
|
||||
self.assertEqual(update_message.subtype_id, self.env.ref('crm.mt_lead_lost'))
|
||||
self.assertEqual(len(update_message.tracking_value_ids), 2, 'Tracking: active, lost reason')
|
||||
self.assertTracking(
|
||||
update_message,
|
||||
[('active', 'boolean', True, False),
|
||||
('lost_reason_id', 'many2one', False, self.lost_reason)
|
||||
]
|
||||
)
|
||||
|
||||
@users('user_sales_leads')
|
||||
def test_lead_lost_batch_wfeedback(self):
|
||||
""" Test setting leads as lost in batch using the wizard, including a log
|
||||
message. """
|
||||
leads = self._create_leads_batch(lead_type='lead', count=10, probabilities=[10, 20, 30])
|
||||
self.assertEqual(len(leads), 10)
|
||||
self.flush_tracking()
|
||||
|
||||
lost_wizard = self.env['crm.lead.lost'].with_context({
|
||||
'active_ids': leads.ids,
|
||||
}).create({
|
||||
'lost_reason_id': self.lost_reason.id,
|
||||
'lost_feedback': '<p>I cannot find it. It was in my closet and pouf, disappeared.</p>',
|
||||
})
|
||||
lost_wizard.action_lost_reason_apply()
|
||||
self.flush_tracking()
|
||||
|
||||
for lead in leads:
|
||||
# check content
|
||||
self.assertFalse(lead.active)
|
||||
self.assertEqual(lead.automated_probability, 0)
|
||||
self.assertEqual(lead.probability, 0)
|
||||
self.assertEqual(lead.lost_reason_id, self.lost_reason)
|
||||
# check messages
|
||||
self.assertEqual(len(lead.message_ids), 2, 'Should have 2 messages: creation, lost with log')
|
||||
lost_message = lead.message_ids.filtered(lambda msg: msg.subtype_id == self.env.ref('crm.mt_lead_lost'))
|
||||
self.assertTrue(lost_message)
|
||||
self.assertTracking(
|
||||
lost_message,
|
||||
[('active', 'boolean', True, False),
|
||||
('lost_reason_id', 'many2one', False, self.lost_reason)
|
||||
]
|
||||
)
|
||||
self.assertIn('<p>I cannot find it. It was in my closet and pouf, disappeared.</p>', lost_message.body,
|
||||
'Feedback should be included directly within tracking message')
|
||||
|
||||
@users('user_sales_salesman')
|
||||
@mute_logger('odoo.addons.base.models')
|
||||
def test_lead_lost_crm_rights(self):
|
||||
""" Test ACLs of lost reasons management and usage """
|
||||
lead = self.lead_1.with_user(self.env.user)
|
||||
|
||||
# nice try little salesman but only managers can create lost reason to avoid bloating the DB
|
||||
with self.assertRaises(AccessError):
|
||||
lost_reason = self.env['crm.lost.reason'].create({
|
||||
'name': 'Test Reason'
|
||||
})
|
||||
|
||||
with self.with_user('user_sales_manager'):
|
||||
lost_reason = self.env['crm.lost.reason'].create({
|
||||
'name': 'Test Reason'
|
||||
})
|
||||
|
||||
lost_wizard = self.env['crm.lead.lost'].with_context({
|
||||
'active_ids': lead.ids
|
||||
}).create({
|
||||
'lost_reason_id': lost_reason.id
|
||||
})
|
||||
|
||||
# nice try little salesman, you cannot invoke a wizard to update other people leads
|
||||
with self.assertRaises(AccessError):
|
||||
lost_wizard.action_lost_reason_apply()
|
||||
|
|
@ -63,6 +63,9 @@ class TestLeadMerge(TestLeadMergeCommon):
|
|||
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.probability, 20)
|
||||
self.assertTrue(self.lead_1.automated_probability > 0)
|
||||
self.assertFalse(self.lead_1.is_automated_probability)
|
||||
|
||||
self.assertEqual(self.lead_w_partner.stage_id, self.env['crm.stage'])
|
||||
self.assertEqual(self.lead_w_partner.user_id, self.env['res.users'])
|
||||
|
|
@ -173,9 +176,7 @@ class TestLeadMerge(TestLeadMergeCommon):
|
|||
})
|
||||
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)
|
||||
self.assertEqual(merge.opportunity_ids, self.leads - self.lead_w_partner_company, 'Should not keep won opps')
|
||||
ordered_merge = self.lead_w_contact + self.lead_w_email + self.lead_1 + self.lead_w_partner
|
||||
ordered_merge_description = '<br><br>'.join(l.description for l in ordered_merge)
|
||||
|
||||
|
|
@ -221,8 +222,9 @@ class TestLeadMerge(TestLeadMergeCommon):
|
|||
# TDE FIXME: see aa44700dccdc2618e0b8bc94252789264104047c -> no user, no team -> strange
|
||||
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, 'Even lost are included if asked by user')
|
||||
# remove lost lead, otherwise limit of 5 is going to raise
|
||||
merge.write({'opportunity_ids': [(3, self.lead_w_email_lost.id)]})
|
||||
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
|
||||
|
||||
|
|
@ -284,6 +286,7 @@ class TestLeadMerge(TestLeadMergeCommon):
|
|||
leads = self.env['crm.lead'].browse((self.lead_1 + self.lead_w_partner + self.lead_w_partner_company).ids)
|
||||
merged_lead = self._run_merge_wizard(leads)
|
||||
self.assertEqual(merged_lead, self.lead_1)
|
||||
self.assertTrue(self.lead_1.automated_probability > 0)
|
||||
self.assertEqual(merged_lead.probability, 0, "Manual Probability should remain the same after the merge")
|
||||
self.assertFalse(merged_lead.is_automated_probability)
|
||||
|
||||
|
|
@ -446,7 +449,7 @@ class TestLeadMerge(TestLeadMergeCommon):
|
|||
} for idx in range(4)
|
||||
])
|
||||
lead_1 = self.env['crm.lead'].browse(self.lead_1.ids)
|
||||
activity = lead_1.activity_schedule('crm.lead_test_activity_1')
|
||||
activity = lead_1.activity_schedule('crm.lead_test_activity_1', user_id=self.user_sales_manager.id)
|
||||
calendar_event = self.env['calendar.event'].create({
|
||||
'name': 'Meeting with partner',
|
||||
'activity_ids': [(4, activity.id)],
|
||||
|
|
@ -460,7 +463,9 @@ class TestLeadMerge(TestLeadMergeCommon):
|
|||
# run merge and check documents are moved to the master record
|
||||
merge = self.env['crm.merge.opportunity'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_ids': self.leads.ids,
|
||||
# with 'active_test' context key, lost are included, which would make 6 leads
|
||||
# while 5 is maximum for manual merge -> exclude it directly
|
||||
'active_ids': (self.leads - self.lead_w_email_lost).ids,
|
||||
'active_id': False,
|
||||
}).create({
|
||||
'team_id': self.sales_team_convert.id,
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ class TestCRMLeadMultiCompany(TestCrmCommon):
|
|||
# writing current user on lead would imply putting its team and team's company
|
||||
# on lead (aka self.company_2), and this clashes with company restriction on
|
||||
# customer
|
||||
self.env.user.company_ids -= self.company_main
|
||||
with self.assertRaises(UserError):
|
||||
lead.write({
|
||||
'user_id': self.env.user,
|
||||
|
|
@ -301,22 +302,26 @@ class TestCRMLeadMultiCompany(TestCrmCommon):
|
|||
self.assertEqual(crm_lead_form.company_id, self.company_2, 'Crm: company comes from partner')
|
||||
|
||||
def test_gateway_incompatible_company_error_on_incoming_email(self):
|
||||
# set automatic assignment to Manual to circumvent automatic lead assignment,
|
||||
# so as to reproduce the conditions in which the original bug appeared
|
||||
self.env['ir.config_parameter'].set_param('crm.lead.auto.assignment', True)
|
||||
self.assertTrue(self.sales_team_1.alias_name)
|
||||
self.assertFalse(self.sales_team_1.company_id)
|
||||
customer_company = self.env['res.partner'].create({
|
||||
'company_id': self.company_2.id,
|
||||
'email': 'customer.another.company@test.customer.com',
|
||||
'mobile': '+32455000000',
|
||||
'phone': '+32455000000',
|
||||
'name': 'InCompany Customer',
|
||||
})
|
||||
|
||||
new_lead = self.format_and_process(
|
||||
INCOMING_EMAIL,
|
||||
customer_company.email,
|
||||
'%s@%s' % (self.sales_team_1.alias_name, self.alias_domain),
|
||||
self.sales_team_1.alias_email,
|
||||
subject='Team having partner in company',
|
||||
target_model='crm.lead',
|
||||
)
|
||||
self.assertFalse(new_lead.user_id)
|
||||
self.assertEqual(new_lead.company_id, self.company_2)
|
||||
self.assertEqual(new_lead.email_from, customer_company.email)
|
||||
self.assertEqual(new_lead.partner_id, customer_company)
|
||||
|
|
|
|||
|
|
@ -1,26 +1,49 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon
|
||||
from odoo.addons.crm.tests.common import INCOMING_EMAIL, TestCrmCommon
|
||||
from odoo.tests import tagged, users
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tools import email_normalize, formataddr, mute_logger
|
||||
|
||||
|
||||
@tagged('mail_thread', 'mail_gateway')
|
||||
class NewLeadNotification(TestCrmCommon):
|
||||
""" Test mail features support on lead + specific overrides and support """
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
""" Activate some langs to test lang propagation in various mail flows """
|
||||
super(NewLeadNotification, cls).setUpClass()
|
||||
cls._activate_multi_company()
|
||||
|
||||
cls.test_email = '"Test Email" <test.email@example.com>'
|
||||
model_lang = cls.env['res.lang'].sudo().with_context(active_test=False)
|
||||
|
||||
# Create a lead with an inactive language -> should ignore the preset language
|
||||
cls.lang_fr = model_lang.search([('code', '=', 'fr_FR')])
|
||||
if not cls.lang_fr:
|
||||
cls.lang_fr = model_lang._create_lang('fr_FR')
|
||||
# set French language as inactive then try to call "_message_get_suggested_recipients"
|
||||
# -> lang code should be ignored
|
||||
cls.lang_fr.active = False
|
||||
# Create a lead with an active language -> should keep the preset language for recipients
|
||||
cls.lang_en = model_lang.search([('code', '=', 'en_US')])
|
||||
if not cls.lang_en:
|
||||
cls.lang_en = model_lang._create_lang('en_US')
|
||||
# set American English language as active then try to call "_message_get_suggested_recipients"
|
||||
# -> lang code should be kept
|
||||
cls.lang_en.active = True
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_message_get_suggested_recipient(self):
|
||||
""" Test '_message_get_suggested_recipients' and its override in lead. """
|
||||
def test_lead_message_get_suggested_recipients(self):
|
||||
""" Test '_message_get_suggested_recipients' and its override in lead
|
||||
when dealing with various emails. """
|
||||
self.maxDiff = None # to ease assertDictEqual usage
|
||||
company_partner = self.env['res.partner'].create(
|
||||
{'name': 'test_lead_message_get_suggested_recipients_company_partner', 'is_company': True}
|
||||
)
|
||||
partner_no_email = self.env['res.partner'].create({'name': 'Test Partner', 'email': False})
|
||||
(
|
||||
lead_format,
|
||||
lead_multi,
|
||||
lead_from,
|
||||
lead_partner,
|
||||
lead_partner_no_email,
|
||||
lead_partner_no_email_with_cc
|
||||
) = self.env['crm.lead'].create([
|
||||
leads = self.env['crm.lead'].create([
|
||||
{
|
||||
'email_from': '"New Customer" <new.customer.format@test.example.com>',
|
||||
'name': 'Test Suggestion (email_from with format)',
|
||||
|
|
@ -31,42 +54,202 @@ class NewLeadNotification(TestCrmCommon):
|
|||
'name': 'Test Suggestion (email_from multi)',
|
||||
'partner_name': 'Multi Name',
|
||||
'user_id': self.user_sales_leads.id,
|
||||
}, {
|
||||
'email_from': 'new.customer.with.parent@test.example.com',
|
||||
'name': 'Test Suggestion (email_from with matching partner_name)',
|
||||
'partner_name': 'test_lead_message_get_suggested_recipients_company_partner',
|
||||
'user_id': self.user_sales_leads.id,
|
||||
}, {
|
||||
'email_from': 'new.customer.simple@test.example.com',
|
||||
'name': 'Test Suggestion (email_from)',
|
||||
'partner_name': 'Std Name',
|
||||
'contact_name': 'Std Name',
|
||||
'user_id': self.user_sales_leads.id,
|
||||
}, {
|
||||
'email_from': 'test.lang@test.example.com',
|
||||
'lang_id': self.lang_en.id,
|
||||
'name': 'Test Suggestion (lang)',
|
||||
'user_id': False,
|
||||
}, {
|
||||
'name': 'Test Suggestion (partner_id)',
|
||||
'partner_id': self.contact_1.id,
|
||||
'user_id': self.user_sales_leads.id,
|
||||
}, {
|
||||
'name': 'Test Suggestion (partner no email)',
|
||||
'partner_id': partner_no_email.id
|
||||
'name': 'Test Suggestion (partner no email)',
|
||||
'partner_id': partner_no_email.id,
|
||||
'user_id': self.user_sales_leads.id
|
||||
}, {
|
||||
'name': 'Test Suggestion (partner no email with cc email)',
|
||||
'partner_id': partner_no_email.id,
|
||||
'email_cc': 'test_cc@odoo.com'
|
||||
'name': 'Test Suggestion (partner no email with cc email)',
|
||||
'partner_id': partner_no_email.id,
|
||||
'email_cc': 'test_cc@odoo.com',
|
||||
'user_id': self.user_sales_leads.id
|
||||
}
|
||||
])
|
||||
for lead, expected_suggested in zip(
|
||||
lead_format + lead_multi + lead_from + lead_partner + lead_partner_no_email + lead_partner_no_email_with_cc,
|
||||
for lead, expected_suggested in zip(leads, [
|
||||
[
|
||||
[(False, '"New Customer" <new.customer.format@test.example.com>', None, 'Customer Email')],
|
||||
[(False, '"Multi Name" <new.customer.multi.1@test.example.com,new.customer.2@test.example.com>', None, 'Customer Email')],
|
||||
[(False, '"Std Name" <new.customer.simple@test.example.com>', None, 'Customer Email')],
|
||||
[(self.contact_1.id, '"Philip J Fry" <philip.j.fry@test.example.com>', self.contact_1.lang, 'Customer')],
|
||||
[(partner_no_email.id, 'Test Partner', partner_no_email.lang, 'Customer')],
|
||||
[
|
||||
(False, 'test_cc@odoo.com', None, 'CC Email'),
|
||||
(partner_no_email.id, 'Test Partner', partner_no_email.lang, 'Customer')
|
||||
]
|
||||
]
|
||||
):
|
||||
with self.subTest(lead=lead, email_from=lead.email_from):
|
||||
res = lead._message_get_suggested_recipients()[lead.id]
|
||||
# here contact_name is guessed based on formatted email
|
||||
{
|
||||
'name': 'New Customer',
|
||||
'email': 'new.customer.format@test.example.com',
|
||||
'partner_id': False,
|
||||
'create_values': {
|
||||
'company_name': 'Format Name',
|
||||
'is_company': False,
|
||||
'type': 'contact',
|
||||
'user_id': self.user_sales_leads.id,
|
||||
},
|
||||
},
|
||||
], [
|
||||
# here no contact name, just a partner name -> use email and set company_name
|
||||
{
|
||||
'name': 'new.customer.multi.1@test.example.com, new.customer.2@test.example.com',
|
||||
'email': 'new.customer.multi.1@test.example.com', # only first found normalized email is kept
|
||||
'partner_id': False,
|
||||
'create_values': {
|
||||
'company_name': 'Multi Name',
|
||||
'is_company': False,
|
||||
'type': 'contact',
|
||||
'user_id': self.user_sales_leads.id,
|
||||
},
|
||||
}, {
|
||||
'name': '',
|
||||
'email': 'new.customer.2@test.example.com', # second found creates another contact
|
||||
'partner_id': False,
|
||||
'create_values': {}, # not targeted as primary lead customer hence no values
|
||||
},
|
||||
], [
|
||||
# here no contact name, a partner name, but there exists a company with that name -> company
|
||||
{
|
||||
'name': 'new.customer.with.parent@test.example.com',
|
||||
'email': 'new.customer.with.parent@test.example.com',
|
||||
'partner_id': False,
|
||||
'create_values': {
|
||||
'is_company': False,
|
||||
'parent_id': company_partner.id,
|
||||
'type': 'contact',
|
||||
'user_id': self.user_sales_leads.id,
|
||||
},
|
||||
},
|
||||
], [
|
||||
# here contact name -> individual
|
||||
{
|
||||
'name': 'Std Name',
|
||||
'email': 'new.customer.simple@test.example.com',
|
||||
'partner_id': False,
|
||||
'create_values': {
|
||||
'is_company': False,
|
||||
'type': 'contact',
|
||||
'user_id': self.user_sales_leads.id,
|
||||
},
|
||||
},
|
||||
], [
|
||||
# here check lang is in create_values
|
||||
{
|
||||
'name': 'test.lang@test.example.com',
|
||||
'email': 'test.lang@test.example.com',
|
||||
'partner_id': False,
|
||||
'create_values': {
|
||||
'is_company': False,
|
||||
'lang': 'en_US',
|
||||
'type': 'contact',
|
||||
},
|
||||
},
|
||||
], [
|
||||
{
|
||||
'partner_id': self.contact_1.id,
|
||||
'name': 'Philip J Fry',
|
||||
'email': 'philip.j.fry@test.example.com',
|
||||
'create_values': {},
|
||||
},
|
||||
], [
|
||||
{
|
||||
'partner_id': partner_no_email.id,
|
||||
'name': 'Test Partner',
|
||||
'email': False,
|
||||
'create_values': {},
|
||||
},
|
||||
], [
|
||||
{
|
||||
'partner_id': partner_no_email.id,
|
||||
'email': False,
|
||||
'name': 'Test Partner',
|
||||
'create_values': {},
|
||||
}, {
|
||||
'name': '',
|
||||
'email': 'test_cc@odoo.com',
|
||||
'partner_id': False,
|
||||
'create_values': {},
|
||||
},
|
||||
],
|
||||
]):
|
||||
with self.subTest(lead_name=lead.name, email_from=lead.email_from):
|
||||
res = lead._message_get_suggested_recipients(no_create=True)
|
||||
self.assertEqual(len(res), len(expected_suggested))
|
||||
self.assertEqual(res, expected_suggested)
|
||||
for received, expected in zip(res, expected_suggested):
|
||||
self.assertDictEqual(received, expected)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_message_get_suggested_recipients_values_for_create(self):
|
||||
"""Check default creates value used when creating client from suggested
|
||||
recipients (customer)."""
|
||||
lead_details_for_contact = {
|
||||
'street': '3rd Floor, Room 3-C',
|
||||
'street2': '123 Arlington Avenue',
|
||||
'zip': '13202',
|
||||
'city': 'New York',
|
||||
'country_id': self.env.ref('base.us').id,
|
||||
'state_id': self.env.ref('base.state_us_39').id,
|
||||
'website': 'https://www.arlington123.com/3f3c',
|
||||
'phone': '678-728-0949',
|
||||
'function': 'Delivery Boy',
|
||||
'user_id': self.user_sales_manager.id,
|
||||
}
|
||||
|
||||
for partner_name, contact_name, email in [
|
||||
(False, 'ContactOnly', 'test_default_create@example.com'),
|
||||
('Delivery Boy company', 'ContactAndCompany', 'default_create_with_partner@example.com'),
|
||||
('Delivery Boy company', '', '"Contact Name" <default_create_with_name_in_email@example.com>'),
|
||||
('Delivery Boy company', '', 'default_create_with_partner_no_name@example.com'),
|
||||
('', '', 'lenny.bar@gmail.com'),
|
||||
]:
|
||||
if email == '"Contact Name" <default_create_with_name_in_email@example.com>':
|
||||
suggested_email = 'default_create_with_name_in_email@example.com'
|
||||
suggested_name = "Contact Name"
|
||||
else:
|
||||
suggested_email = email
|
||||
# if no contact_name: fallback on the email to at least have something
|
||||
suggested_name = contact_name or email
|
||||
with self.subTest(partner_name=partner_name, contact_name=contact_name, email=email):
|
||||
description = '<p>Top</p>'
|
||||
lead1 = self.env['crm.lead'].create({
|
||||
'name': 'TestLead',
|
||||
'contact_name': contact_name,
|
||||
'email_from': email,
|
||||
'lang_id': self.lang_en.id,
|
||||
'description': description,
|
||||
'partner_name': partner_name,
|
||||
**lead_details_for_contact,
|
||||
})
|
||||
suggestion = lead1._message_get_suggested_recipients(no_create=True)[0]
|
||||
self.assertFalse(suggestion.get('partner_id'))
|
||||
self.assertEqual(suggestion['email'], suggested_email)
|
||||
self.assertEqual(suggestion['name'], suggested_name)
|
||||
|
||||
create_values = suggestion['create_values']
|
||||
customer_information = lead1._get_customer_information().get(email_normalize(email), {})
|
||||
customer_information.pop('name', False) # not keps in create_values, as already in name / email info
|
||||
self.assertEqual(create_values, customer_information)
|
||||
for field, value in lead_details_for_contact.items():
|
||||
self.assertEqual(create_values.get(field), value)
|
||||
self.assertEqual(create_values['comment'], description) # description -> comment
|
||||
# parent company not created even if partner_name is set
|
||||
self.assertFalse(create_values.get('parent_id')) # not supported, even if partner_name set
|
||||
# company_name set only for contacts with partner_name (and no contact_name nor name in email)
|
||||
if partner_name:
|
||||
self.assertEqual(create_values['company_name'], partner_name) # partner_name -> company_name
|
||||
else:
|
||||
self.assertFalse('company_name' in create_values)
|
||||
# it will normally never be a company, unless called despite a contact being already present (shouldn't happen)
|
||||
self.assertEqual(create_values['is_company'], False)
|
||||
|
||||
def test_new_lead_notification(self):
|
||||
""" Test newly create leads like from the website. People and channels
|
||||
|
|
@ -104,15 +287,14 @@ class NewLeadNotification(TestCrmCommon):
|
|||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def test_new_lead_from_email_multicompany(self):
|
||||
company0 = self.env.company
|
||||
company1 = self.env['res.company'].create({'name': 'new_company'})
|
||||
company1 = self.company_2
|
||||
|
||||
self.env.user.write({
|
||||
'company_ids': [(4, company0.id, False), (4, company1.id, False)],
|
||||
})
|
||||
|
||||
crm_team_model = self.env['ir.model'].search([('model', '=', 'crm.team')])
|
||||
crm_lead_model = self.env['ir.model'].search([('model', '=', 'crm.lead')])
|
||||
self.env["ir.config_parameter"].sudo().set_param("mail.catchall.domain", 'aqualung.com')
|
||||
crm_team_model_id = self.env['ir.model']._get_id('crm.team')
|
||||
crm_lead_model_id = self.env['ir.model']._get_id('crm.lead')
|
||||
|
||||
crm_team0 = self.env['crm.team'].create({
|
||||
'name': 'crm team 0',
|
||||
|
|
@ -124,16 +306,18 @@ class NewLeadNotification(TestCrmCommon):
|
|||
})
|
||||
|
||||
mail_alias0 = self.env['mail.alias'].create({
|
||||
'alias_domain_id': company0.alias_domain_id.id,
|
||||
'alias_name': 'sale_team_0',
|
||||
'alias_model_id': crm_lead_model.id,
|
||||
'alias_parent_model_id': crm_team_model.id,
|
||||
'alias_model_id': crm_lead_model_id,
|
||||
'alias_parent_model_id': crm_team_model_id,
|
||||
'alias_parent_thread_id': crm_team0.id,
|
||||
'alias_defaults': "{'type': 'opportunity', 'team_id': %s}" % crm_team0.id,
|
||||
})
|
||||
mail_alias1 = self.env['mail.alias'].create({
|
||||
'alias_domain_id': company1.alias_domain_id.id,
|
||||
'alias_name': 'sale_team_1',
|
||||
'alias_model_id': crm_lead_model.id,
|
||||
'alias_parent_model_id': crm_team_model.id,
|
||||
'alias_model_id': crm_lead_model_id,
|
||||
'alias_parent_model_id': crm_team_model_id,
|
||||
'alias_parent_thread_id': crm_team1.id,
|
||||
'alias_defaults': "{'type': 'opportunity', 'team_id': %s}" % crm_team1.id,
|
||||
})
|
||||
|
|
@ -141,12 +325,12 @@ class NewLeadNotification(TestCrmCommon):
|
|||
crm_team0.write({'alias_id': mail_alias0.id})
|
||||
crm_team1.write({'alias_id': mail_alias1.id})
|
||||
|
||||
new_message0 = """MIME-Version: 1.0
|
||||
new_message0 = f"""MIME-Version: 1.0
|
||||
Date: Thu, 27 Dec 2018 16:27:45 +0100
|
||||
Message-ID: <blablabla0>
|
||||
Subject: sale team 0 in company 0
|
||||
From: A client <client_a@someprovider.com>
|
||||
To: sale_team_0@aqualung.com
|
||||
To: {mail_alias0.display_name}
|
||||
Content-Type: multipart/alternative; boundary="000000000000a47519057e029630"
|
||||
|
||||
--000000000000a47519057e029630
|
||||
|
|
@ -162,12 +346,12 @@ Content-Transfer-Encoding: quoted-printable
|
|||
--000000000000a47519057e029630--
|
||||
"""
|
||||
|
||||
new_message1 = """MIME-Version: 1.0
|
||||
new_message1 = f"""MIME-Version: 1.0
|
||||
Date: Thu, 27 Dec 2018 16:27:45 +0100
|
||||
Message-ID: <blablabla1>
|
||||
Subject: sale team 1 in company 1
|
||||
From: B client <client_b@someprovider.com>
|
||||
To: sale_team_1@aqualung.com
|
||||
To: {mail_alias1.display_name}
|
||||
Content-Type: multipart/alternative; boundary="000000000000a47519057e029630"
|
||||
|
||||
--000000000000a47519057e029630
|
||||
|
|
@ -193,3 +377,46 @@ Content-Transfer-Encoding: quoted-printable
|
|||
|
||||
self.assertEqual(crm_lead0.company_id, company0)
|
||||
self.assertEqual(crm_lead1.company_id, company1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_incoming_email_automatic_lead_assignment(self):
|
||||
# create a second team with a set leader
|
||||
leader_team_2 = self.env['res.users'].sudo().create({'name': 'bob', 'login': 'bob'})
|
||||
team_2 = self.env['crm.team'].create({
|
||||
'name': 'team_2',
|
||||
'alias_name': 'team.2',
|
||||
'user_id': leader_team_2.id,
|
||||
})
|
||||
|
||||
# send three emails to the aliases of both teams
|
||||
for x in range(3):
|
||||
self.format_and_process(
|
||||
INCOMING_EMAIL,
|
||||
f'source.email@customerOfTeam1{x}.be',
|
||||
self.sales_team_1.alias_email,
|
||||
subject=f'OpportunityTeam1{x}',
|
||||
target_model='crm.lead',
|
||||
)
|
||||
self.format_and_process(
|
||||
INCOMING_EMAIL,
|
||||
f'source.email@customerOfTeam2{x}.be',
|
||||
team_2.alias_email,
|
||||
subject=f'OpportunityTeam2{x}',
|
||||
target_model='crm.lead',
|
||||
)
|
||||
|
||||
# each team should receive all three of their new opportunities and none of the others'
|
||||
team1_leads = self.env['crm.lead'].search([
|
||||
('team_id', '=', self.sales_team_1.id),
|
||||
('email_from', 'ilike', 'source.email@customerOfTeam'),
|
||||
])
|
||||
team2_leads = self.env['crm.lead'].search([
|
||||
('team_id', '=', team_2.id),
|
||||
('email_from', 'ilike', 'source.email@customerOfTeam'),
|
||||
])
|
||||
for lead in team1_leads:
|
||||
self.assertTrue('source.email@customerOfTeam1' in lead.email_from)
|
||||
self.assertTrue(lead.user_id == self.user_sales_manager)
|
||||
for lead in team2_leads:
|
||||
self.assertTrue('source.email@customerOfTeam2' in lead.email_from)
|
||||
self.assertTrue(lead.user_id == leader_team_2)
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import tools
|
||||
from odoo import exceptions, tools
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.fields import Date
|
||||
from odoo.tests import Form, tagged, users, loaded_demo_data
|
||||
from odoo.tests import Form, tagged, users
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('crm_lead_pls')
|
||||
class TestCRMPLS(TransactionCase):
|
||||
class CrmPlsCommon(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
""" Keep a limited setup to ensure tests are not impacted by other
|
||||
records created in CRM common. """
|
||||
super(TestCRMPLS, cls).setUpClass()
|
||||
super().setUpClass()
|
||||
|
||||
cls.company_main = cls.env.user.company_id
|
||||
cls.user_sales_manager = mail_new_test_user(
|
||||
|
|
@ -32,26 +30,28 @@ class TestCRMPLS(TransactionCase):
|
|||
'name': 'PLS Team',
|
||||
})
|
||||
|
||||
# Ensure independance on demo data
|
||||
# Ensure independence on demo data
|
||||
cls.env['crm.lead'].with_context({'active_test': False}).search([]).unlink()
|
||||
cls.env['crm.lead.scoring.frequency'].search([]).unlink()
|
||||
cls.cr.flush()
|
||||
|
||||
def _get_lead_values(self, team_id, name_suffix, country_id, state_id, email_state, phone_state, source_id, stage_id):
|
||||
def _prepare_test_lead_values(self, team_id, name_suffix, country_id, state_id, email_state, phone_state, source_id, stage_id):
|
||||
return {
|
||||
'name': 'lead_' + name_suffix,
|
||||
'stage_id': stage_id,
|
||||
'team_id': team_id,
|
||||
'type': 'opportunity',
|
||||
'state_id': state_id,
|
||||
# contact
|
||||
'email_state': email_state,
|
||||
'phone_state': phone_state,
|
||||
'source_id': source_id,
|
||||
'stage_id': stage_id,
|
||||
# address
|
||||
'country_id': country_id,
|
||||
'team_id': team_id
|
||||
'state_id': state_id,
|
||||
# misc
|
||||
'source_id': source_id,
|
||||
}
|
||||
|
||||
def generate_leads_with_tags(self, tag_ids):
|
||||
Lead = self.env['crm.lead']
|
||||
def _generate_leads_with_tags(self, tag_ids):
|
||||
team_id = self.env['crm.team'].create({
|
||||
'name': 'blup',
|
||||
}).id
|
||||
|
|
@ -77,15 +77,18 @@ class TestCRMPLS(TransactionCase):
|
|||
'team_id': team_id
|
||||
})
|
||||
|
||||
leads_with_tags = Lead.create(leads_to_create)
|
||||
leads_with_tags = self.env['crm.lead'].create(leads_to_create)
|
||||
|
||||
return leads_with_tags
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'crm_lead_pls')
|
||||
class TestConfig(CrmPlsCommon):
|
||||
|
||||
def test_crm_lead_pls_update(self):
|
||||
""" We test here that the wizard for updating probabilities from settings
|
||||
is getting correct value from config params and after updating values
|
||||
from the wizard, the config params are correctly updated
|
||||
"""
|
||||
""" Test the wizard for updating probabilities from settings is getting
|
||||
correct value from config params and after updating values from the wizard
|
||||
config params are correctly updated. """
|
||||
# Set the PLS config
|
||||
frequency_fields = self.env['crm.lead.scoring.frequency.field'].search([])
|
||||
pls_fields_str = ','.join(frequency_fields.mapped('field_id.name'))
|
||||
|
|
@ -115,6 +118,25 @@ class TestCRMPLS(TransactionCase):
|
|||
self.assertEqual(IrConfigSudo.get_param("crm.pls_start_date"), date_to_update, 'Correct date is updated in config')
|
||||
self.assertEqual(IrConfigSudo.get_param("crm.pls_fields"), fields_after_updation_str, 'Correct fields are updated in config')
|
||||
|
||||
def test_settings_pls_start_date(self):
|
||||
""" Test various use cases of 'crm.pls_start_date' """
|
||||
str_date_8_days_ago = Date.to_string(Date.today() - timedelta(days=8))
|
||||
|
||||
for value, expected in [
|
||||
("2021-10-10", "2021-10-10"),
|
||||
# empty of invalid value -> set to 8 days before today
|
||||
("", str_date_8_days_ago),
|
||||
("One does not simply walk into system parameters to corrupt them", str_date_8_days_ago),
|
||||
]:
|
||||
with self.subTest(value=value):
|
||||
self.env['ir.config_parameter'].sudo().set_param('crm.pls_start_date', value)
|
||||
res_config_new = self.env['res.config.settings'].new()
|
||||
self.assertEqual(Date.to_string(res_config_new.predictive_lead_scoring_start_date), expected)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'crm_lead_pls')
|
||||
class TestCrmPls(CrmPlsCommon):
|
||||
|
||||
def test_predictive_lead_scoring(self):
|
||||
""" We test here computation of lead probability based on PLS Bayes.
|
||||
We will use 3 different values for each possible variables:
|
||||
|
|
@ -141,32 +163,32 @@ class TestCRMPLS(TransactionCase):
|
|||
# for team 1
|
||||
for i in range(3):
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(team_ids[0], 'team_1_%s' % str(i), country_ids[i], state_ids[i], state_values[i], state_values[i], source_ids[i], stage_ids[i]))
|
||||
self._prepare_test_lead_values(team_ids[0], 'team_1_%s' % str(i), country_ids[i], state_ids[i], state_values[i], state_values[i], source_ids[i], stage_ids[i]))
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(team_ids[0], 'team_1_%s' % str(3), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[2], stage_ids[1]))
|
||||
self._prepare_test_lead_values(team_ids[0], 'team_1_%s' % str(3), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[2], stage_ids[1]))
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(team_ids[0], 'team_1_%s' % str(4), country_ids[1], state_ids[1], state_values[1], state_values[0], source_ids[1], stage_ids[0]))
|
||||
self._prepare_test_lead_values(team_ids[0], 'team_1_%s' % str(4), country_ids[1], state_ids[1], state_values[1], state_values[0], source_ids[1], stage_ids[0]))
|
||||
# for team 2
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(team_ids[1], 'team_2_%s' % str(5), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[1], stage_ids[2]))
|
||||
self._prepare_test_lead_values(team_ids[1], 'team_2_%s' % str(5), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[1], stage_ids[2]))
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(team_ids[1], 'team_2_%s' % str(6), country_ids[0], state_ids[1], state_values[0], state_values[1], source_ids[2], stage_ids[1]))
|
||||
self._prepare_test_lead_values(team_ids[1], 'team_2_%s' % str(6), country_ids[0], state_ids[1], state_values[0], state_values[1], source_ids[2], stage_ids[1]))
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(team_ids[1], 'team_2_%s' % str(7), country_ids[0], state_ids[2], state_values[0], state_values[1], source_ids[2], stage_ids[0]))
|
||||
self._prepare_test_lead_values(team_ids[1], 'team_2_%s' % str(7), country_ids[0], state_ids[2], state_values[0], state_values[1], source_ids[2], stage_ids[0]))
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(team_ids[1], 'team_2_%s' % str(8), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[2], stage_ids[1]))
|
||||
self._prepare_test_lead_values(team_ids[1], 'team_2_%s' % str(8), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[2], stage_ids[1]))
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(team_ids[1], 'team_2_%s' % str(9), country_ids[1], state_ids[0], state_values[1], state_values[0], source_ids[1], stage_ids[1]))
|
||||
self._prepare_test_lead_values(team_ids[1], 'team_2_%s' % str(9), country_ids[1], state_ids[0], state_values[1], state_values[0], source_ids[1], stage_ids[1]))
|
||||
|
||||
# for leads with no team
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(False, 'no_team_%s' % str(10), country_ids[1], state_ids[1], state_values[2], state_values[0], source_ids[1], stage_ids[2]))
|
||||
self._prepare_test_lead_values(False, 'no_team_%s' % str(10), country_ids[1], state_ids[1], state_values[2], state_values[0], source_ids[1], stage_ids[2]))
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(False, 'no_team_%s' % str(11), country_ids[0], state_ids[1], state_values[1], state_values[1], source_ids[0], stage_ids[0]))
|
||||
self._prepare_test_lead_values(False, 'no_team_%s' % str(11), country_ids[0], state_ids[1], state_values[1], state_values[1], source_ids[0], stage_ids[0]))
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(False, 'no_team_%s' % str(12), country_ids[1], state_ids[2], state_values[0], state_values[1], source_ids[2], stage_ids[0]))
|
||||
self._prepare_test_lead_values(False, 'no_team_%s' % str(12), country_ids[1], state_ids[2], state_values[0], state_values[1], source_ids[2], stage_ids[0]))
|
||||
leads_to_create.append(
|
||||
self._get_lead_values(False, 'no_team_%s' % str(13), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[2], stage_ids[1]))
|
||||
self._prepare_test_lead_values(False, 'no_team_%s' % str(13), country_ids[0], state_ids[1], state_values[2], state_values[0], source_ids[2], stage_ids[1]))
|
||||
|
||||
leads = Lead.create(leads_to_create)
|
||||
|
||||
|
|
@ -283,7 +305,8 @@ class TestCRMPLS(TransactionCase):
|
|||
self.assertEqual(leads[8].is_automated_probability, True)
|
||||
|
||||
# Restore -> Should decrease lost
|
||||
leads[4].toggle_active()
|
||||
leads[4].action_unarchive()
|
||||
self.assertEqual(leads[4].won_status, 'pending')
|
||||
self.assertEqual(lead_4_stage_0_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_won_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_country_freq.won_count, 0.1) # unchanged
|
||||
|
|
@ -304,6 +327,7 @@ class TestCRMPLS(TransactionCase):
|
|||
|
||||
# set to won stage -> Should increase won
|
||||
leads[4].stage_id = won_stage_id
|
||||
self.assertEqual(leads[4].won_status, 'won')
|
||||
self.assertEqual(lead_4_stage_0_freq.won_count, 2.1) # + 1
|
||||
self.assertEqual(lead_4_stage_won_freq.won_count, 2.1) # + 1
|
||||
self.assertEqual(lead_4_country_freq.won_count, 1.1) # + 1
|
||||
|
|
@ -313,36 +337,52 @@ class TestCRMPLS(TransactionCase):
|
|||
self.assertEqual(lead_4_country_freq.lost_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_email_state_freq.lost_count, 2.1) # unchanged
|
||||
|
||||
# Archive (was won, now lost) -> Should decrease won and increase lost
|
||||
leads[4].toggle_active()
|
||||
self.assertEqual(lead_4_stage_0_freq.won_count, 1.1) # - 1
|
||||
self.assertEqual(lead_4_stage_won_freq.won_count, 1.1) # - 1
|
||||
self.assertEqual(lead_4_country_freq.won_count, 0.1) # - 1
|
||||
self.assertEqual(lead_4_email_state_freq.won_count, 1.1) # - 1
|
||||
self.assertEqual(lead_4_stage_0_freq.lost_count, 3.1) # + 1
|
||||
self.assertEqual(lead_4_stage_won_freq.lost_count, 1.1) # consider stages with <= sequence when lostand as stage is won.. even won_stage lost_count is increased by 1
|
||||
self.assertEqual(lead_4_country_freq.lost_count, 2.1) # + 1
|
||||
self.assertEqual(lead_4_email_state_freq.lost_count, 3.1) # + 1
|
||||
# Archive in won stage -> Should NOT decrease won NOR increase lost
|
||||
# as lost = archived + 0% and WON = won_stage (+ 100%)
|
||||
leads[4].action_archive()
|
||||
self.assertEqual(leads[4].won_status, 'won')
|
||||
self.assertEqual(lead_4_stage_0_freq.won_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_won_freq.won_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_4_country_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_email_state_freq.won_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_0_freq.lost_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_won_freq.lost_count, 0.1) # unchanged
|
||||
self.assertEqual(lead_4_country_freq.lost_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_email_state_freq.lost_count, 2.1) # unchanged
|
||||
|
||||
# Move to original stage -> Should do nothing (as lead is still lost)
|
||||
# Move to original stage -> lead is not won anymore but not lost as probability != 0
|
||||
leads[4].stage_id = stage_ids[0]
|
||||
self.assertEqual(leads[4].won_status, 'pending')
|
||||
self.assertEqual(lead_4_stage_0_freq.won_count, 1.1) # -1
|
||||
self.assertEqual(lead_4_stage_won_freq.won_count, 1.1) # -1
|
||||
self.assertEqual(lead_4_country_freq.won_count, 0.1) # -1
|
||||
self.assertEqual(lead_4_email_state_freq.won_count, 1.1) # -1
|
||||
self.assertEqual(lead_4_stage_0_freq.lost_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_won_freq.lost_count, 0.1) # unchanged
|
||||
self.assertEqual(lead_4_country_freq.lost_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_email_state_freq.lost_count, 2.1) # unchanged
|
||||
|
||||
# force proba to 0% -> as already archived, will be lost (lost = archived AND 0%)
|
||||
leads[4].probability = 0
|
||||
self.assertEqual(leads[4].won_status, 'lost')
|
||||
self.assertEqual(lead_4_stage_0_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_won_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_country_freq.won_count, 0.1) # unchanged
|
||||
self.assertEqual(lead_4_email_state_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_0_freq.lost_count, 3.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_won_freq.lost_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_country_freq.lost_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_4_email_state_freq.lost_count, 3.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_0_freq.lost_count, 3.1) # +1
|
||||
self.assertEqual(lead_4_stage_won_freq.lost_count, 0.1) # unchanged - should not increase lost frequency of won stage.
|
||||
self.assertEqual(lead_4_country_freq.lost_count, 2.1) # +1
|
||||
self.assertEqual(lead_4_email_state_freq.lost_count, 3.1) # +1
|
||||
|
||||
# Restore -> Should decrease lost - at the end, frequencies should be like first frequencyes tests (except for 0.0 -> 0.1)
|
||||
leads[4].toggle_active()
|
||||
leads[4].action_unarchive()
|
||||
self.assertEqual(leads[4].won_status, 'pending')
|
||||
self.assertEqual(lead_4_stage_0_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_won_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_country_freq.won_count, 0.1) # unchanged
|
||||
self.assertEqual(lead_4_email_state_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_0_freq.lost_count, 2.1) # - 1
|
||||
self.assertEqual(lead_4_stage_won_freq.lost_count, 1.1) # unchanged - consider stages with <= sequence when lost
|
||||
self.assertEqual(lead_4_stage_won_freq.lost_count, 0.1) # unchanged - consider stages with <= sequence when lost
|
||||
self.assertEqual(lead_4_country_freq.lost_count, 1.1) # - 1
|
||||
self.assertEqual(lead_4_email_state_freq.lost_count, 2.1) # - 1
|
||||
|
||||
|
|
@ -356,7 +396,7 @@ class TestCRMPLS(TransactionCase):
|
|||
self.assertEqual(lead_4_country_freq.won_count, 0.1) # unchanged
|
||||
self.assertEqual(lead_4_email_state_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_0_freq.lost_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_won_freq.lost_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_stage_won_freq.lost_count, 0.1) # unchanged
|
||||
self.assertEqual(lead_4_country_freq.lost_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_4_email_state_freq.lost_count, 2.1) # unchanged
|
||||
|
||||
|
|
@ -398,7 +438,7 @@ class TestCRMPLS(TransactionCase):
|
|||
{'name': "Tag_test_2"},
|
||||
]).ids
|
||||
# tag_ids = self.env['crm.tag'].search([], limit=2).ids
|
||||
leads_with_tags = self.generate_leads_with_tags(tag_ids)
|
||||
leads_with_tags = self._generate_leads_with_tags(tag_ids)
|
||||
|
||||
leads_with_tags[:30].action_set_lost() # 60% lost on tag 1
|
||||
leads_with_tags[31:50].action_set_won() # 40% won on tag 1
|
||||
|
|
@ -500,9 +540,9 @@ class TestCRMPLS(TransactionCase):
|
|||
team_id = self.env['crm.team'].create({'name': 'Team Test 1'}).id
|
||||
# create two leads
|
||||
leads = Lead.create([
|
||||
self._get_lead_values(team_id, 'edge pending', country_id, False, False, False, False, stage_id),
|
||||
self._get_lead_values(team_id, 'edge lost', country_id, False, False, False, False, stage_id),
|
||||
self._get_lead_values(team_id, 'edge won', country_id, False, False, False, False, stage_id),
|
||||
self._prepare_test_lead_values(team_id, 'edge pending', country_id, False, False, False, False, stage_id),
|
||||
self._prepare_test_lead_values(team_id, 'edge lost', country_id, False, False, False, False, stage_id),
|
||||
self._prepare_test_lead_values(team_id, 'edge won', country_id, False, False, False, False, stage_id),
|
||||
])
|
||||
# set a new tag
|
||||
leads.tag_ids = self.env['crm.tag'].create({'name': 'lead scoring edge case'})
|
||||
|
|
@ -540,27 +580,6 @@ class TestCRMPLS(TransactionCase):
|
|||
self.assertEqual(tools.float_compare(leads[1].probability, 0, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[0].probability, 0.01, 2), 0)
|
||||
|
||||
def test_settings_pls_start_date(self):
|
||||
# We test here that settings never crash due to ill-configured config param 'crm.pls_start_date'
|
||||
set_param = self.env['ir.config_parameter'].sudo().set_param
|
||||
str_date_8_days_ago = Date.to_string(Date.today() - timedelta(days=8))
|
||||
resConfig = self.env['res.config.settings']
|
||||
|
||||
set_param("crm.pls_start_date", "2021-10-10")
|
||||
res_config_new = resConfig.new()
|
||||
self.assertEqual(Date.to_string(res_config_new.predictive_lead_scoring_start_date),
|
||||
"2021-10-10", "If config param is a valid date, date in settings should match with config param")
|
||||
|
||||
set_param("crm.pls_start_date", "")
|
||||
res_config_new = resConfig.new()
|
||||
self.assertEqual(Date.to_string(res_config_new.predictive_lead_scoring_start_date),
|
||||
str_date_8_days_ago, "If config param is empty, date in settings should be set to 8 days before today")
|
||||
|
||||
set_param("crm.pls_start_date", "One does not simply walk into system parameters to corrupt them")
|
||||
res_config_new = resConfig.new()
|
||||
self.assertEqual(Date.to_string(res_config_new.predictive_lead_scoring_start_date),
|
||||
str_date_8_days_ago, "If config param is not a valid date, date in settings should be set to 8 days before today")
|
||||
|
||||
def test_pls_no_share_stage(self):
|
||||
""" We test here the situation where all stages are team specific, as there is
|
||||
a current limitation (can be seen in _pls_get_won_lost_total_count) regarding
|
||||
|
|
@ -568,47 +587,254 @@ class TestCRMPLS(TransactionCase):
|
|||
to have no team assigned to it."""
|
||||
Lead = self.env['crm.lead']
|
||||
team_id = self.env['crm.team'].create([{'name': 'Team Test'}]).id
|
||||
self.env['crm.stage'].search([('team_id', '=', False)]).write({'team_id': team_id})
|
||||
self.env['crm.stage'].search([('team_ids', '=', False)]).write({'team_ids': [team_id]})
|
||||
lead = Lead.create({'name': 'team', 'team_id': team_id, 'probability': 41.23})
|
||||
Lead._cron_update_automated_probabilities()
|
||||
self.assertEqual(tools.float_compare(lead.probability, 41.23, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead.automated_probability, 0, 2), 0)
|
||||
|
||||
def test_pls_tooltip_data(self):
|
||||
""" Assert that the method preparing tooltip data correctly returns (field, couple)
|
||||
values, in order of importance, of TOP 3 and LOW 3 criterions in PLS computation.
|
||||
See Table in docstring below for more details and a practical situation."""
|
||||
Lead = self.env['crm.lead']
|
||||
self.env['ir.config_parameter'].sudo().set_param(
|
||||
"crm.pls_fields",
|
||||
"country_id,state_id,email_state,phone_state,source_id"
|
||||
)
|
||||
country_ids = self.env['res.country'].search([], limit=2).ids
|
||||
source_ids = self.env['utm.source'].search([], limit=2).ids
|
||||
stage_ids = self.env['crm.stage'].search([], limit=3).ids
|
||||
state_ids = self.env['res.country.state'].search([], limit=2).ids
|
||||
team_id = self.env['crm.team'].create([{'name': 'Team Tooltip'}]).id
|
||||
leads = Lead.create([
|
||||
self._prepare_test_lead_values(team_id, 'lead Won A', country_ids[0], state_ids[0], False, False, source_ids[1], stage_ids[0]),
|
||||
self._prepare_test_lead_values(team_id, 'lead Won B', country_ids[1], state_ids[0], False, False, False, stage_ids[0]),
|
||||
self._prepare_test_lead_values(team_id, 'lead Lost C', False, False, False, False, source_ids[0], stage_ids[0]),
|
||||
self._prepare_test_lead_values(team_id, 'lead Lost D', country_ids[0], False, False, False, source_ids[0], stage_ids[0]),
|
||||
self._prepare_test_lead_values(team_id, 'lead Lost E', False, state_ids[1], False, False, False, stage_ids[2]),
|
||||
self._prepare_test_lead_values(team_id, 'lead Tooltip', country_ids[0], state_ids[0], False, False, source_ids[0], stage_ids[1]),
|
||||
])
|
||||
|
||||
# On creation, as phone and email are not set, these two fields will be set to False
|
||||
leads.email_state = 'correct'
|
||||
(leads[0] | leads[1] | leads[4] | leads[5]).phone_state = 'correct'
|
||||
(leads[2] | leads[3]).phone_state = 'incorrect'
|
||||
|
||||
leads[:2].action_set_won()
|
||||
leads[2:5].action_set_lost()
|
||||
Lead._cron_update_automated_probabilities()
|
||||
self.env.invalidate_all()
|
||||
|
||||
# Values for leads[5]:
|
||||
# pW / pL is the probability that a won / lost lead has the lead value for a given field
|
||||
# [Score = pW / (pW + pL)] -> A score above .5 is a TOP, below .5 a LOW, equal to .5 ignored
|
||||
# Exception : for stage_id -> Score = 1 - P(current stage or lower for a lost lead)
|
||||
# ------------------------------------------------------------------------------------------
|
||||
# -- LOW 3 (lowest first, only 2 here)
|
||||
# source_id: pW = 0.1/1.2 pL = 2.1/2.2 -> Score = 0.08
|
||||
# country_id: pW = 1.1/2.2 pL = 1.1/1.2 -> Score = 0.353
|
||||
# -- Neither
|
||||
# email_state: pW = 2.1/2.1 pL = 3.1/3.1 -> Score = 0.5
|
||||
# -- TOP 3 (highest first)
|
||||
# state_id: pW = 2.1/2.2 pL = 0.1/1.2 -> Score = 0.92
|
||||
# phone_state: pW = 2.1/2.2 pL = 1.1/3.2 -> Score = 0.735
|
||||
# stage_id: pL = 1.1/3.1 -> Score = 0.645
|
||||
expected_low_3 = ['source_id', 'country_id']
|
||||
expected_top_3 = ['state_id', 'phone_state', 'stage_id']
|
||||
|
||||
tooltip_data = leads[5].prepare_pls_tooltip_data()
|
||||
self.assertEqual('Team Tooltip', tooltip_data['team_name'])
|
||||
self.assertEqual(tools.float_compare(tooltip_data['probability'], 74.30, 2), 0)
|
||||
|
||||
self.assertListEqual([top_entry.get('field') for top_entry in tooltip_data['top_3_data']], expected_top_3)
|
||||
self.assertListEqual([low_entry.get('field') for low_entry in tooltip_data['low_3_data']], expected_low_3)
|
||||
|
||||
# Assert scores for phone/email_state are excluded if absurd,
|
||||
# e.g. in top 3 when incorrect / not set or in low 3 if correct
|
||||
# Stage does not change and always has a score of 0.645
|
||||
self.env['ir.config_parameter'].sudo().set_param("crm.pls_fields", "email_state,phone_state")
|
||||
|
||||
leads[5].phone_state = False
|
||||
leads[5].email_state = 'incorrect'
|
||||
leads[:2].phone_state = False
|
||||
leads[:2].email_state = 'incorrect'
|
||||
leads[2:5].phone_state = 'correct'
|
||||
leads[2:5].email_state = 'correct'
|
||||
Lead._cron_update_automated_probabilities()
|
||||
self.env.invalidate_all()
|
||||
|
||||
# phone_state: pW = 2.1/2.2 pL = 0.1/3.2 -> Score = 0.968
|
||||
# email_state: pW = 2.1/2.2 pL = 0.1/3.2 -> Score = 0.968
|
||||
tooltip_data = leads[5].prepare_pls_tooltip_data()
|
||||
self.assertEqual(['stage_id'], [entry['field'] for entry in tooltip_data['top_3_data']])
|
||||
self.assertFalse(tooltip_data['low_3_data'])
|
||||
|
||||
leads[5].email_state = 'correct'
|
||||
leads[5].phone_state = 'incorrect'
|
||||
leads[:2].phone_state = 'incorrect'
|
||||
Lead._cron_update_automated_probabilities()
|
||||
self.env.invalidate_all()
|
||||
|
||||
# phone_state: pW = 2.1/2.2 pL = 0.1/3.2 -> Score = 0.968
|
||||
# email_state: pW = 0.1/2.2 pL = 3.1/3.2 -> Score = 0.045
|
||||
tooltip_data = leads[5].prepare_pls_tooltip_data()
|
||||
self.assertEqual(['stage_id'], [entry['field'] for entry in tooltip_data['top_3_data']])
|
||||
self.assertFalse(tooltip_data['low_3_data'])
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'crm_lead_pls')
|
||||
class TestCrmPlsSides(CrmPlsCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.team = cls.env['crm.team'].create([{'name': 'Team Test'}])
|
||||
cls.stage_new, cls.stage_in_progress, cls.stage_won = cls.env['crm.stage'].create([
|
||||
{
|
||||
'name': 'New Stage',
|
||||
'sequence': 1,
|
||||
'team_ids': [cls.team.id],
|
||||
}, {
|
||||
'name': 'In Progress Stage',
|
||||
'sequence': 2,
|
||||
'team_ids': [cls.team.id],
|
||||
}, {
|
||||
'is_won': True,
|
||||
'name': 'Won Stage',
|
||||
'sequence': 3,
|
||||
'team_ids': [cls.team.id],
|
||||
},
|
||||
])
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_stage_update(self):
|
||||
""" Test side effects of changing stages """
|
||||
team_id = self.team.with_user(self.env.user).id
|
||||
stage_new, _stage_in_progress, stage_won = (self.stage_new + self.stage_in_progress + self.stage_won).with_user(self.env.user)
|
||||
leads = self.env['crm.lead'].create([
|
||||
{
|
||||
'name': 'Test Lead 1',
|
||||
'probability': 50,
|
||||
'stage_id': stage_new.id,
|
||||
'team_id': team_id,
|
||||
}, {
|
||||
'name': 'Test Lead 2',
|
||||
'probability': 50,
|
||||
'stage_id': stage_new.id,
|
||||
'team_id': team_id,
|
||||
}
|
||||
])
|
||||
leads.action_set_lost()
|
||||
for lead in leads:
|
||||
self.assertFalse(lead.active)
|
||||
self.assertFalse(lead.probability)
|
||||
leads[0].active = True
|
||||
|
||||
# putting in won state should reactivate
|
||||
leads.write({'stage_id': stage_won.id})
|
||||
for lead in leads:
|
||||
self.assertTrue(lead.active)
|
||||
self.assertEqual(lead.probability, 100)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_won_lost_validity(self):
|
||||
team_id = self.team.with_user(self.env.user).id
|
||||
stage_new, stage_in_progress, stage_won = (self.stage_new + self.stage_in_progress + self.stage_won).with_user(self.env.user)
|
||||
lead = self.env['crm.lead'].create([
|
||||
{
|
||||
'name': 'Test Lead',
|
||||
'probability': 50,
|
||||
'stage_id': stage_new.id,
|
||||
'team_id': team_id,
|
||||
}
|
||||
])
|
||||
self.assertEqual(lead.won_status, 'pending')
|
||||
|
||||
# Probability 100 is not a sufficient condition to win the lead
|
||||
lead.write({'probability': 100})
|
||||
self.assertEqual(lead.won_status, 'pending')
|
||||
|
||||
# Test won validity
|
||||
lead.write({'probability': 90})
|
||||
self.assertEqual(lead.won_status, 'pending')
|
||||
lead.action_set_won()
|
||||
self.assertEqual(lead.probability, 100)
|
||||
self.assertTrue(lead.stage_id.is_won)
|
||||
self.assertEqual(lead.won_status, 'won')
|
||||
with self.assertRaises(exceptions.ValidationError, msg='A won lead cannot be set as lost.'):
|
||||
lead.action_set_lost()
|
||||
|
||||
# Won lead can be inactive
|
||||
lead.write({'active': False})
|
||||
self.assertEqual(lead.probability, 100)
|
||||
self.assertEqual(lead.won_status, 'won')
|
||||
with self.assertRaises(exceptions.ValidationError, msg='A won lead cannot have probability < 100'):
|
||||
lead.write({'probability': 75})
|
||||
|
||||
# Restore the lead in a non won stage. won_count = lost_count = 0.1 in frequency table. P = 50%
|
||||
lead.write({'stage_id': stage_in_progress.id, 'active': True})
|
||||
self.assertFalse(lead.probability == 100)
|
||||
self.assertEqual(lead.won_status, 'pending')
|
||||
|
||||
# Test lost validity
|
||||
lead.action_set_lost()
|
||||
self.assertFalse(lead.active)
|
||||
self.assertEqual(lead.probability, 0)
|
||||
self.assertEqual(lead.won_status, 'lost')
|
||||
|
||||
# Test won validity reaching won stage
|
||||
lead.write({'stage_id': stage_won.id})
|
||||
self.assertTrue(lead.active)
|
||||
self.assertEqual(lead.probability, 100)
|
||||
self.assertEqual(lead.won_status, 'won')
|
||||
|
||||
# Back to lost
|
||||
lead.write({'active': False, 'probability': 0, 'stage_id': stage_new.id})
|
||||
self.assertEqual(lead.won_status, 'lost')
|
||||
|
||||
# Once active again, lead is not lost anymore
|
||||
lead.write({'active': True})
|
||||
self.assertEqual(lead.won_status, 'pending', "An active lead cannot be lost")
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_team_unlink(self):
|
||||
""" Test that frequencies are sent to "no team" when unlinking a team
|
||||
in order to avoid losing too much informations. """
|
||||
pls_team = self.env["crm.team"].browse(self.pls_team.ids)
|
||||
|
||||
# clean existing data
|
||||
self.env["crm.lead.scoring.frequency"].sudo().search([('team_id', '=', False)]).unlink()
|
||||
|
||||
# existing no-team data
|
||||
no_team = [
|
||||
noteam_scoring_data = [
|
||||
('stage_id', '1', 20, 10),
|
||||
('stage_id', '2', 0.1, 0.1),
|
||||
('stage_id', '3', 10, 0),
|
||||
('country_id', '1', 10, 0.1),
|
||||
]
|
||||
self.env["crm.lead.scoring.frequency"].sudo().create([
|
||||
{'variable': variable, 'value': value,
|
||||
'won_count': won_count, 'lost_count': lost_count,
|
||||
'team_id': False,
|
||||
} for variable, value, won_count, lost_count in no_team
|
||||
{
|
||||
'lost_count': lost_count,
|
||||
'team_id': False,
|
||||
'value': value,
|
||||
'variable': variable,
|
||||
'won_count': won_count,
|
||||
} for variable, value, won_count, lost_count in noteam_scoring_data
|
||||
])
|
||||
|
||||
# add some frequencies to team to unlink
|
||||
team = [
|
||||
team_scoring_data = [
|
||||
('stage_id', '1', 20, 10), # existing noteam
|
||||
('country_id', '1', 0.1, 10), # existing noteam
|
||||
('country_id', '2', 0.1, 0), # new but void
|
||||
('country_id', '3', 30, 30), # new
|
||||
]
|
||||
existing_plsteam = self.env["crm.lead.scoring.frequency"].sudo().create([
|
||||
{'variable': variable, 'value': value,
|
||||
'won_count': won_count, 'lost_count': lost_count,
|
||||
'team_id': pls_team.id,
|
||||
} for variable, value, won_count, lost_count in team
|
||||
{
|
||||
'lost_count': lost_count,
|
||||
'team_id': pls_team.id,
|
||||
'value': value,
|
||||
'variable': variable,
|
||||
'won_count': won_count,
|
||||
} for variable, value, won_count, lost_count in team_scoring_data
|
||||
])
|
||||
|
||||
pls_team.unlink()
|
||||
|
|
@ -633,3 +859,147 @@ class TestCRMPLS(TransactionCase):
|
|||
self.assertEqual(frequency.won_count, stat[2])
|
||||
self.assertEqual(frequency.lost_count, stat[3])
|
||||
self.assertEqual(len(existing_noteam), len(final_noteam))
|
||||
|
||||
|
||||
@tagged('lead_manage', 'crm_lead_pls')
|
||||
class TestLeadLost(TestCrmCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.lost_reason = cls.env['crm.lost.reason'].create({
|
||||
'name': 'Test Reason'
|
||||
})
|
||||
|
||||
@users('user_sales_salesman')
|
||||
def test_lead_lost(self):
|
||||
""" Test setting a lead as lost using the wizard. Also check that an
|
||||
'html editor' void content used as feedback is not logged on the lead. """
|
||||
# Initial data
|
||||
self.assertEqual(len(self.lead_1.message_ids), 1, 'Should contain creation message')
|
||||
creation_message = self.lead_1.message_ids[0]
|
||||
self.assertEqual(creation_message.subtype_id, self.env.ref('crm.mt_lead_create'))
|
||||
self.assertEqual(
|
||||
self.lead_1.message_partner_ids, self.user_sales_leads.partner_id,
|
||||
'Responsible should be follower')
|
||||
|
||||
# Update responsible as ACLs is "own only" for user_sales_salesman
|
||||
with self.mock_mail_gateway():
|
||||
self.lead_1.with_user(self.user_sales_manager).write({
|
||||
'user_id': self.user_sales_salesman.id,
|
||||
'probability': 32,
|
||||
})
|
||||
self.flush_tracking()
|
||||
|
||||
lead = self.env['crm.lead'].browse(self.lead_1.ids)
|
||||
self.assertFalse(lead.lost_reason_id)
|
||||
self.assertEqual(
|
||||
self.lead_1.message_partner_ids, self.user_sales_leads.partner_id + self.user_sales_salesman.partner_id,
|
||||
'New responsible should be follower')
|
||||
self.assertEqual(lead.probability, 32)
|
||||
# tracking message
|
||||
self.assertEqual(len(lead.message_ids), 2, 'Should have tracked new responsible')
|
||||
update_message = lead.message_ids[0]
|
||||
self.assertMessageFields(
|
||||
update_message,
|
||||
{
|
||||
'notified_partner_ids': self.env['res.partner'],
|
||||
'partner_ids': self.env['res.partner'],
|
||||
'subtype_id': self.env.ref('mail.mt_note'),
|
||||
'tracking_field_names': ['user_id'],
|
||||
}
|
||||
)
|
||||
|
||||
# mark as lost using the wizard
|
||||
lost_wizard = self.env['crm.lead.lost'].create({
|
||||
'lead_ids': lead.ids,
|
||||
'lost_reason_id': self.lost_reason.id,
|
||||
'lost_feedback': '<p></p>', # void content
|
||||
})
|
||||
lost_wizard.action_lost_reason_apply()
|
||||
self.flush_tracking()
|
||||
|
||||
# check lead update
|
||||
self.assertFalse(lead.active)
|
||||
self.assertEqual(lead.automated_probability, 0)
|
||||
self.assertEqual(lead.lost_reason_id, self.lost_reason) # TDE FIXME: should be called lost_reason_id non didjou
|
||||
self.assertEqual(lead.probability, 0)
|
||||
# check messages
|
||||
self.assertEqual(len(lead.message_ids), 3, 'Should have logged a tracking message for lost lead with reason')
|
||||
lost_message = lead.message_ids[0]
|
||||
self.assertMessageFields(
|
||||
lost_message,
|
||||
{
|
||||
'notified_partner_ids': self.env['res.partner'],
|
||||
'partner_ids': self.env['res.partner'],
|
||||
'subtype_id': self.env.ref('crm.mt_lead_lost'),
|
||||
'tracking_field_names': ['active', 'lost_reason_id', 'won_status'],
|
||||
'tracking_values': [
|
||||
('active', 'boolean', True, False),
|
||||
('lost_reason_id', 'many2one', False, self.lost_reason),
|
||||
('won_status', 'char', 'Pending', 'Lost'),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
@users('user_sales_leads')
|
||||
def test_lead_lost_batch_wfeedback(self):
|
||||
""" Test setting leads as lost in batch using the wizard, including a log
|
||||
message. """
|
||||
leads = self._create_leads_batch(lead_type='lead', count=10, probabilities=[10, 20, 30])
|
||||
self.assertEqual(len(leads), 10)
|
||||
self.flush_tracking()
|
||||
|
||||
lost_wizard = self.env['crm.lead.lost'].create({
|
||||
'lead_ids': leads.ids,
|
||||
'lost_reason_id': self.lost_reason.id,
|
||||
'lost_feedback': '<p>I cannot find it. It was in my closet and pouf, disappeared.</p>',
|
||||
})
|
||||
lost_wizard.action_lost_reason_apply()
|
||||
self.flush_tracking()
|
||||
|
||||
for lead in leads:
|
||||
# check content
|
||||
self.assertFalse(lead.active)
|
||||
self.assertEqual(lead.automated_probability, 0)
|
||||
self.assertEqual(lead.probability, 0)
|
||||
self.assertEqual(lead.lost_reason_id, self.lost_reason)
|
||||
# check messages
|
||||
self.assertEqual(len(lead.message_ids), 2, 'Should have 2 messages: creation, lost with log')
|
||||
lost_message = lead.message_ids.filtered(lambda msg: msg.subtype_id == self.env.ref('crm.mt_lead_lost'))
|
||||
self.assertTrue(lost_message)
|
||||
self.assertTracking(
|
||||
lost_message,
|
||||
[('active', 'boolean', True, False),
|
||||
('lost_reason_id', 'many2one', False, self.lost_reason)
|
||||
]
|
||||
)
|
||||
self.assertIn('<p>I cannot find it. It was in my closet and pouf, disappeared.</p>', lost_message.body,
|
||||
'Feedback should be included directly within tracking message')
|
||||
|
||||
@users('user_sales_salesman')
|
||||
@mute_logger('odoo.addons.base.models')
|
||||
def test_lead_lost_crm_rights(self):
|
||||
""" Test ACLs of lost reasons management and usage """
|
||||
lead = self.lead_1.with_user(self.env.user)
|
||||
|
||||
# nice try little salesman but only managers can create lost reason to avoid bloating the DB
|
||||
with self.assertRaises(exceptions.AccessError):
|
||||
lost_reason = self.env['crm.lost.reason'].create({
|
||||
'name': 'Test Reason'
|
||||
})
|
||||
|
||||
with self.with_user('user_sales_manager'):
|
||||
lost_reason = self.env['crm.lost.reason'].create({
|
||||
'name': 'Test Reason'
|
||||
})
|
||||
|
||||
# nice try little salesman, you cannot invoke a wizard to update other people leads
|
||||
with self.assertRaises(exceptions.AccessError):
|
||||
# wizard needs to be here due to cache clearing in assertRaises
|
||||
# (ORM does not load m2m records unavailable to the user from database)
|
||||
lost_wizard = self.env['crm.lead.lost'].create({
|
||||
'lead_ids': lead.ids,
|
||||
'lost_reason_id': lost_reason.id
|
||||
})
|
||||
lost_wizard.action_lost_reason_apply()
|
||||
|
|
|
|||
469
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_rainbowman.py
Normal file
469
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_rainbowman.py
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
from datetime import datetime
|
||||
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.tests import tagged, users
|
||||
|
||||
|
||||
@tagged('lead_internals')
|
||||
class TestCrmLeadRainbowmanMessages(TestCrmCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# unlink all leads from sales_team_1
|
||||
cls.env['crm.lead'].search([
|
||||
('team_id', '=', cls.sales_team_1.id),
|
||||
]).unlink()
|
||||
|
||||
cls.company_casey = cls.env['res.company'].create({
|
||||
'name': 'company_casey',
|
||||
})
|
||||
cls.sales_manager_casey = mail_new_test_user(
|
||||
cls.env,
|
||||
login='sales_manager_casey',
|
||||
name='sales_manager_casey',
|
||||
groups='sales_team.group_sale_manager,base.group_partner_manager',
|
||||
company_id=cls.company_casey.id,
|
||||
company_ids=[(4, cls.company_casey.id)],
|
||||
)
|
||||
|
||||
# cls.env['crm.team.member'].create([
|
||||
# {'user_id': cls.user_sales_manager.id, 'crm_team_id': cls.sales_team_1.id},
|
||||
# {'user_id': cls.user_sales_salesman.id, 'crm_team_id': cls.sales_team_1.id},
|
||||
# ])
|
||||
|
||||
def _update_create_date(self, lead, date):
|
||||
self.env.cr.execute("""
|
||||
UPDATE crm_lead
|
||||
SET create_date = %(date)s
|
||||
WHERE id = %(lead_id)s
|
||||
""", {
|
||||
'lead_id': lead.id,
|
||||
'date': date,
|
||||
})
|
||||
lead.invalidate_recordset(['create_date'])
|
||||
|
||||
def _set_won_get_rainbowman_message(self, lead, user, reset_team=False):
|
||||
"""
|
||||
Assign the passed user and set the lead as won.
|
||||
Then, if there's a message, return that message.
|
||||
Otherwise, as the result for action_set_won_rainbowman() if there's no message is True,
|
||||
return False to make testing code more readable.
|
||||
"""
|
||||
|
||||
# lead.user_id = user
|
||||
# # If reset_team is passed, reset the team to False, as otherwise assigning a user will automatically assign a team
|
||||
# if reset_team:
|
||||
# lead.team_id = False
|
||||
lead.update({
|
||||
'user_id': user.id,
|
||||
'team_id': False if reset_team else lead.team_id.id,
|
||||
})
|
||||
|
||||
rainbowman_action_result = lead.with_user(user).action_set_won_rainbowman()
|
||||
if rainbowman_action_result and not isinstance(rainbowman_action_result, bool):
|
||||
return rainbowman_action_result['effect']['message']
|
||||
return False
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_leads_rainbowman(self):
|
||||
"""
|
||||
This test ensures that all rainbowman messages can trigger, and that they do so in correct order of priority.
|
||||
"""
|
||||
|
||||
# setup timestamps:
|
||||
past = datetime(2024, 12, 15, 12, 0)
|
||||
jan1_10am = datetime(2025, 1, 1, 10, 0)
|
||||
jan1_12pm = datetime(2025, 1, 1, 12, 0)
|
||||
jan2 = datetime(2025, 1, 2, 12, 0)
|
||||
jan3_12pm = datetime(2025, 1, 3, 12, 0)
|
||||
jan3_1pm = datetime(2025, 1, 3, 13, 0)
|
||||
jan4 = datetime(2025, 1, 4, 12, 0)
|
||||
jan12 = datetime(2025, 1, 12, 12, 0)
|
||||
march1 = datetime(2025, 3, 1, 12, 0)
|
||||
|
||||
# setup main batch of leads
|
||||
with self.mock_datetime_and_now(past):
|
||||
leads_norevenue = self._create_leads_batch(
|
||||
count=15,
|
||||
partner_count=5,
|
||||
user_ids=[self.user_sales_manager.id, self.user_sales_salesman.id],
|
||||
lead_type='opportunity',
|
||||
additional_lead_values={
|
||||
'stage_id': self.stage_team1_1.id,
|
||||
},
|
||||
)
|
||||
leads_revenue = self._create_leads_batch(
|
||||
count=18,
|
||||
partner_count=3,
|
||||
user_ids=[self.user_sales_manager.id, self.user_sales_salesman.id],
|
||||
lead_type='opportunity',
|
||||
additional_lead_values={
|
||||
'expected_revenue': 500,
|
||||
'stage_id': self.stage_team1_1.id,
|
||||
},
|
||||
)
|
||||
iter_leads_norevenue = iter(leads_norevenue)
|
||||
iter_leads_revenue = iter(leads_revenue)
|
||||
all_leads = leads_norevenue | leads_revenue
|
||||
# initialize tracking
|
||||
self.flush_tracking()
|
||||
|
||||
# test lead rainbowman messages (leads without expected revenues)
|
||||
|
||||
with self.mock_datetime_and_now(jan1_10am):
|
||||
self.flush_tracking()
|
||||
all_leads.invalidate_recordset(['duration_tracking'])
|
||||
|
||||
# switch the stage to avoid having the "first to last stage" message show up all the time
|
||||
all_leads.write({'stage_id': self.stage_team1_2.id})
|
||||
# flush tracking to make sure it's taken into account
|
||||
self.flush_tracking()
|
||||
all_leads.invalidate_recordset(['duration_tracking'])
|
||||
|
||||
msg_firstdeal = self._set_won_get_rainbowman_message(next(iter_leads_norevenue), self.user_sales_manager)
|
||||
self.assertEqual(
|
||||
msg_firstdeal,
|
||||
'Go, go, go! Congrats for your first deal.',
|
||||
'First deal',
|
||||
)
|
||||
|
||||
lead_25messages = next(iter_leads_norevenue)
|
||||
self.env['mail.message'].create([
|
||||
{
|
||||
'model': 'crm.lead',
|
||||
'res_id': lead_25messages.id,
|
||||
'body': 'Message',
|
||||
'message_type': 'comment',
|
||||
} for x in range(25)
|
||||
])
|
||||
msg_25messages = self._set_won_get_rainbowman_message(lead_25messages, self.user_sales_manager)
|
||||
self.assertEqual(
|
||||
msg_25messages,
|
||||
'Phew, that took some effort — but you nailed it. Good job!',
|
||||
'Win with 25 messages on the counter',
|
||||
)
|
||||
|
||||
with self.mock_datetime_and_now(jan1_12pm):
|
||||
self.flush_tracking()
|
||||
all_leads.invalidate_recordset(['duration_tracking'])
|
||||
|
||||
lead_other_first_with_revenue = next(iter_leads_norevenue)
|
||||
lead_other_first_with_revenue.expected_revenue = 100
|
||||
msg_other_first_with_revenue = self._set_won_get_rainbowman_message(lead_other_first_with_revenue, self.user_sales_salesman)
|
||||
self.assertEqual(
|
||||
msg_other_first_with_revenue,
|
||||
'Go, go, go! Congrats for your first deal.',
|
||||
'First deal (another user), even with record revenue',
|
||||
)
|
||||
|
||||
lead_first_country = next(iter_leads_norevenue)
|
||||
lead_first_country.country_id = self.env.ref('base.au')
|
||||
msg_first_country = self._set_won_get_rainbowman_message(lead_first_country, self.user_sales_manager)
|
||||
self.assertEqual(
|
||||
msg_first_country,
|
||||
'You just expanded the map! First win in Australia.',
|
||||
'First win in a country (all team)',
|
||||
)
|
||||
|
||||
lead_second_country = next(iter_leads_norevenue)
|
||||
lead_second_country.country_id = self.env.ref('base.au')
|
||||
msg_second_country = self._set_won_get_rainbowman_message(lead_second_country, self.user_sales_salesman)
|
||||
self.assertFalse(
|
||||
msg_second_country,
|
||||
'Second deal from the same country (all team)',
|
||||
)
|
||||
|
||||
source_facebook_ad = self.env['utm.source'].create({'name': 'Facebook Ad'})
|
||||
lead_first_source = next(iter_leads_norevenue)
|
||||
lead_first_source.source_id = source_facebook_ad
|
||||
msg_first_source = self._set_won_get_rainbowman_message(lead_first_source, self.user_sales_manager)
|
||||
self.assertEqual(
|
||||
msg_first_source,
|
||||
'Yay, your first win from Facebook Ad!',
|
||||
'First win from a UTM source (all team)',
|
||||
)
|
||||
|
||||
lead_second_source = next(iter_leads_norevenue)
|
||||
lead_second_source.source_id = source_facebook_ad.id
|
||||
msg_second_source = self._set_won_get_rainbowman_message(lead_second_source, self.user_sales_salesman)
|
||||
self.assertFalse(
|
||||
msg_second_source,
|
||||
'Second deal from the same source (all team)',
|
||||
)
|
||||
|
||||
lead_combo5 = next(iter_leads_norevenue)
|
||||
msg_combo5 = self._set_won_get_rainbowman_message(lead_combo5, self.user_sales_manager)
|
||||
self.assertEqual(
|
||||
msg_combo5,
|
||||
'You\'re on fire! Fifth deal won today 🔥',
|
||||
'Fifth deal won today (user)',
|
||||
)
|
||||
|
||||
with self.mock_datetime_and_now(jan2):
|
||||
self.flush_tracking()
|
||||
all_leads.invalidate_recordset(['duration_tracking'])
|
||||
|
||||
# fast closes:
|
||||
# 10 days ago
|
||||
lead_fastclose_10 = next(iter_leads_norevenue)
|
||||
self._update_create_date(lead_fastclose_10, datetime(2024, 12, 22))
|
||||
msg_fastclose_10 = self._set_won_get_rainbowman_message(lead_fastclose_10, self.user_sales_manager)
|
||||
self.assertEqual(
|
||||
msg_fastclose_10,
|
||||
'Wow, that was fast. That deal didn’t stand a chance!',
|
||||
'Fastest close in 30 days',
|
||||
)
|
||||
|
||||
# 15 days ago
|
||||
lead_fastclose_15 = next(iter_leads_norevenue)
|
||||
self._update_create_date(lead_fastclose_15, datetime(2024, 12, 17))
|
||||
msg_fastclose_15 = self._set_won_get_rainbowman_message(lead_fastclose_15, self.user_sales_manager)
|
||||
self.assertFalse(
|
||||
msg_fastclose_15,
|
||||
'Not the fastest close in 30 days',
|
||||
)
|
||||
|
||||
# Today
|
||||
lead_fastclose_0 = next(iter_leads_norevenue)
|
||||
self._update_create_date(lead_fastclose_0, jan1_12pm)
|
||||
msg_fastclose_0 = self._set_won_get_rainbowman_message(lead_fastclose_0, self.user_sales_manager)
|
||||
self.assertEqual(
|
||||
msg_fastclose_0,
|
||||
'Wow, that was fast. That deal didn’t stand a chance!',
|
||||
'Fastest close in 30 days',
|
||||
)
|
||||
|
||||
self.assertFalse(
|
||||
self._set_won_get_rainbowman_message(next(iter_leads_norevenue), self.user_sales_salesman),
|
||||
'No achievment reached',
|
||||
)
|
||||
|
||||
with self.mock_datetime_and_now(jan3_12pm):
|
||||
self.flush_tracking()
|
||||
all_leads.invalidate_recordset(['duration_tracking'])
|
||||
|
||||
lead_3daystreak = next(iter_leads_norevenue)
|
||||
msg_3daystreak = self._set_won_get_rainbowman_message(lead_3daystreak, self.user_sales_manager)
|
||||
self.assertEqual(
|
||||
msg_3daystreak,
|
||||
'You\'re on a winning streak. 3 deals in 3 days, congrats!',
|
||||
'Three-day streak',
|
||||
)
|
||||
|
||||
with self.mock_datetime_and_now(jan3_1pm):
|
||||
self.flush_tracking()
|
||||
all_leads.invalidate_recordset(['duration_tracking'])
|
||||
|
||||
# Create new lead with no changed stage to get 'straight to the win' message
|
||||
|
||||
lead_first_to_last = self.env['crm.lead'].create({
|
||||
'name': 'lead',
|
||||
'type': 'opportunity',
|
||||
'stage_id': self.stage_team1_1.id,
|
||||
'user_id': self.user_sales_manager.id,
|
||||
})
|
||||
self._update_create_date(lead_first_to_last, jan1_12pm)
|
||||
self.flush_tracking()
|
||||
all_leads.invalidate_recordset(['duration_tracking'])
|
||||
msg_first_to_last = self._set_won_get_rainbowman_message(lead_first_to_last, self.user_sales_manager)
|
||||
self.assertEqual(
|
||||
msg_first_to_last,
|
||||
'No detours, no delays - from New straight to the win! 🚀',
|
||||
'First stage to last stage',
|
||||
)
|
||||
|
||||
self.assertFalse(
|
||||
self._set_won_get_rainbowman_message(next(iter_leads_norevenue), self.user_sales_manager),
|
||||
'Check that no message is returned if no "achievement" is reached',
|
||||
)
|
||||
|
||||
with self.mock_datetime_and_now(jan4):
|
||||
# test lead rainbowman messages (leads with expected revenues)
|
||||
last_30_days_cases = [
|
||||
(self.user_sales_manager, 650, 'Boom! Team record for the past 30 days.'),
|
||||
(self.user_sales_manager, 550, False),
|
||||
(self.user_sales_manager, 700, 'Boom! Team record for the past 30 days.'),
|
||||
(self.user_sales_manager, 700, False),
|
||||
(self.user_sales_salesman, 600, 'You just beat your personal record for the past 30 days.'),
|
||||
(self.user_sales_salesman, 600, False),
|
||||
(self.user_sales_salesman, 550, False),
|
||||
(self.user_sales_salesman, 1000, 'Boom! Team record for the past 30 days.'),
|
||||
(self.user_sales_manager, 950, 'You just beat your personal record for the past 30 days.'),
|
||||
]
|
||||
for user, expected_revenue, expected_message in last_30_days_cases:
|
||||
with self.subTest(user=user, revenue=expected_revenue):
|
||||
lead_revenue = next(iter_leads_revenue)
|
||||
lead_revenue.expected_revenue = expected_revenue
|
||||
msg_revenue = self._set_won_get_rainbowman_message(lead_revenue, user)
|
||||
self.assertEqual(msg_revenue, expected_message)
|
||||
|
||||
with self.mock_datetime_and_now(jan12):
|
||||
last_7_days_cases = [
|
||||
(self.user_sales_manager, 650, 'Yeah! Best deal out of the last 7 days for the team.'),
|
||||
(self.user_sales_manager, 500, False),
|
||||
(self.user_sales_manager, 650, False),
|
||||
(self.user_sales_manager, 800, 'Yeah! Best deal out of the last 7 days for the team.'),
|
||||
(self.user_sales_salesman, 700, 'You just beat your personal record for the past 7 days.'),
|
||||
(self.user_sales_salesman, 650, False),
|
||||
(self.user_sales_salesman, 750, 'You just beat your personal record for the past 7 days.'),
|
||||
(self.user_sales_salesman, 850, 'Yeah! Best deal out of the last 7 days for the team.'),
|
||||
]
|
||||
for user, expected_revenue, expected_message in last_7_days_cases:
|
||||
with self.subTest(user=user, revenue=expected_revenue):
|
||||
lead_revenue = next(iter_leads_revenue)
|
||||
lead_revenue.expected_revenue = expected_revenue
|
||||
msg_revenue = self._set_won_get_rainbowman_message(lead_revenue, user)
|
||||
self.assertEqual(msg_revenue, expected_message)
|
||||
|
||||
with self.mock_datetime_and_now(march1):
|
||||
lead_later_record = next(iter_leads_revenue)
|
||||
lead_later_record.expected_revenue = 750
|
||||
msg_later_record = self._set_won_get_rainbowman_message(lead_later_record, self.user_sales_manager)
|
||||
self.assertEqual(msg_later_record, 'Boom! Team record for the past 30 days.', 'Once a month has passed, \
|
||||
monthly team records may be set even if the amount was lower than the alltime max.')
|
||||
|
||||
# cross-year case
|
||||
current_dt = datetime(2026, 1, 5, 12, 0)
|
||||
past_dt = datetime(2025, 12, 1, 12, 0)
|
||||
|
||||
with self.mock_datetime_and_now(past_dt):
|
||||
lead_cross_year = self.env['crm.lead'].create({
|
||||
'name': 'lead_future_create',
|
||||
'type': 'opportunity',
|
||||
'stage_id': self.stage_team1_won.id,
|
||||
'user_id': self.user_sales_manager.id,
|
||||
'expected_revenue': 500.0,
|
||||
})
|
||||
with self.mock_datetime_and_now(current_dt):
|
||||
msg = self._set_won_get_rainbowman_message(lead_cross_year, self.sales_manager_casey)
|
||||
self.assertFalse(msg)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_leads_rainbowman_timezones(self):
|
||||
"""
|
||||
Users in differing timezones need to get appropriate time-based messages.
|
||||
This test verifies that users in distant timezones still get rainbowman messages
|
||||
when it makes sense from their own point of view.
|
||||
"""
|
||||
sales_m10 = mail_new_test_user( # UTC-10
|
||||
self.env(su=True),
|
||||
login='polynesia_-10',
|
||||
tz='Pacific/Honolulu',
|
||||
name='polynesia_-10',
|
||||
groups='sales_team.group_sale_manager',
|
||||
)
|
||||
sales_p530 = mail_new_test_user( # UTC+5:30
|
||||
self.env(su=True),
|
||||
login='india_+5:30',
|
||||
tz='Asia/Kolkata',
|
||||
name='india_+5:30',
|
||||
groups='sales_team.group_sale_manager',
|
||||
)
|
||||
sales_p13 = mail_new_test_user( # UTC+13
|
||||
self.env(su=True),
|
||||
login='samoa_+13',
|
||||
tz='Pacific/Apia',
|
||||
name='samoa_+13',
|
||||
groups='sales_team.group_sale_manager',
|
||||
)
|
||||
sales_users = [sales_m10, sales_p530, sales_p13]
|
||||
|
||||
# All datetimes stored in-DB are in UTC
|
||||
jan9_10_45am = datetime(2025, 1, 9, 10, 45, 0) # first deal
|
||||
jan9_11_30am = datetime(2025, 1, 9, 11, 30, 0)
|
||||
jan9_4pm = datetime(2025, 1, 9, 16, 0)
|
||||
jan9_6_45pm = datetime(2025, 1, 9, 18, 45)
|
||||
jan9_11pm = datetime(2025, 1, 9, 23, 0) # polynesia_m10: fifth deal in a day
|
||||
jan10_midnight = datetime(2025, 1, 10, 0, 0) # samoa_p13: fifth deal in a day
|
||||
jan10_3am = datetime(2025, 1, 10, 3, 0)
|
||||
jan10_8am = datetime(2025, 1, 10, 8, 0) # india_p530: fifth deal in a day
|
||||
jan10_11am = datetime(2025, 1, 10, 11, 0) # samoa_p13: three-day streak
|
||||
|
||||
first_deal = 'Go, go, go! Congrats for your first deal.'
|
||||
fifth_deal_day = 'You\'re on fire! Fifth deal won today 🔥'
|
||||
three_day_streak = 'You\'re on a winning streak. 3 deals in 3 days, congrats!'
|
||||
cases = [
|
||||
(jan9_10_45am, {user: first_deal for user in sales_users}),
|
||||
(jan9_11_30am, {}),
|
||||
(jan9_4pm, {}),
|
||||
(jan9_6_45pm, {}),
|
||||
(jan9_11pm, {sales_m10: fifth_deal_day}),
|
||||
(jan10_midnight, {sales_p13: fifth_deal_day}),
|
||||
(jan10_3am, {}),
|
||||
(jan10_8am, {sales_p530: fifth_deal_day}),
|
||||
(jan10_11am, {sales_p13: three_day_streak}),
|
||||
]
|
||||
leads = self._create_leads_batch(
|
||||
count=27,
|
||||
lead_type='opportunity',
|
||||
additional_lead_values={
|
||||
'stage_id': self.stage_team1_1.id,
|
||||
},
|
||||
)
|
||||
iter_leads = iter(leads)
|
||||
for deal_closing_time, expected_messages in cases:
|
||||
with self.mock_datetime_and_now(deal_closing_time):
|
||||
for sales_user in sales_users:
|
||||
with self.subTest(username=sales_user.name, time=deal_closing_time):
|
||||
msg = self._set_won_get_rainbowman_message(next(iter_leads), sales_user)
|
||||
self.assertEqual(msg, expected_messages.get(sales_user, False))
|
||||
|
||||
@users('sales_manager_casey')
|
||||
def test_leads_rainbowman_no_team(self):
|
||||
past = datetime(2025, 1, 2, 12, 0)
|
||||
past_1pm = datetime(2025, 1, 2, 13, 0)
|
||||
now = datetime(2025, 1, 5, 12, 0)
|
||||
|
||||
with self.mock_datetime_and_now(past):
|
||||
leads = self._create_leads_batch(
|
||||
count=6,
|
||||
user_ids=[self.sales_manager_casey.id, self.user_sales_salesman.id],
|
||||
lead_type='opportunity',
|
||||
additional_lead_values={
|
||||
'stage_id': self.stage_team1_1.id,
|
||||
},
|
||||
)
|
||||
iter_leads = iter(leads)
|
||||
|
||||
with self.mock_datetime_and_now(past_1pm):
|
||||
# prime the users and leads (to skip first deal closed, fastest close, from first to last...)
|
||||
self.flush_tracking()
|
||||
leads.stage_id = self.stage_gen_1
|
||||
self.flush_tracking()
|
||||
lead_prime_casey = next(iter_leads)
|
||||
lead_prime_benoit = next(iter_leads)
|
||||
self._set_won_get_rainbowman_message(lead_prime_casey, self.sales_manager_casey, reset_team=True)
|
||||
self._set_won_get_rainbowman_message(lead_prime_benoit, self.user_sales_salesman)
|
||||
|
||||
with self.mock_datetime_and_now(now):
|
||||
source_xitter_post = self.env['utm.source'].create({'name': 'Xitter Post'})
|
||||
lead_noteam = next(iter_leads)
|
||||
lead_noteam.source_id = source_xitter_post
|
||||
msg_lead_noteam = self._set_won_get_rainbowman_message(lead_noteam, self.sales_manager_casey, reset_team=True)
|
||||
self.assertEqual(
|
||||
msg_lead_noteam,
|
||||
'Yay, your first win from Xitter Post!',
|
||||
'First win from a UTM source (lead has no team)',
|
||||
)
|
||||
|
||||
# (complete an empty lead to skip the fifth row in a day message)
|
||||
self._set_won_get_rainbowman_message(next(iter_leads), self.sales_manager_casey)
|
||||
|
||||
lead_noteam_samesource = next(iter_leads)
|
||||
lead_noteam_samesource.source_id = source_xitter_post
|
||||
msg_lead_noteam_samesource = self._set_won_get_rainbowman_message(lead_noteam_samesource, self.sales_manager_casey, reset_team=True)
|
||||
self.assertFalse(
|
||||
msg_lead_noteam_samesource,
|
||||
'Second deal from the same source (no team) triggers no message if the source has already been won once for the user',
|
||||
)
|
||||
|
||||
lead_inteam_samesource = next(iter_leads)
|
||||
lead_inteam_samesource.source_id = source_xitter_post
|
||||
msg_lead_inteam_samesource = self._set_won_get_rainbowman_message(lead_inteam_samesource, self.user_sales_salesman)
|
||||
self.assertEqual(
|
||||
msg_lead_inteam_samesource,
|
||||
'Yay, your first win from Xitter Post!',
|
||||
'Benoit can still receive the message as neither he nor his team have a recorded win for this source',
|
||||
)
|
||||
|
|
@ -1,30 +1,20 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon
|
||||
from odoo.tests import HttpCase
|
||||
from odoo.tests.common import tagged, users
|
||||
from odoo.tests import Form, HttpCase, TransactionCase
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestUi(HttpCase):
|
||||
|
||||
class TestUi(HttpCase, TestCrmCommon):
|
||||
def test_01_crm_tour(self):
|
||||
# TODO: The tour is raising a JS error when selecting Brandon Freeman
|
||||
# but with the demo data it succeeds to continue if there is already another lead
|
||||
# in the pipe
|
||||
brandon = self.env["res.partner"].create({
|
||||
self.env["res.partner"].create({
|
||||
'name': 'Brandon Freeman',
|
||||
'email': 'brandon.freeman55@example.com',
|
||||
'phone': '(355)-687-3262',
|
||||
'is_company': True,
|
||||
})
|
||||
self.env['crm.lead'].create({
|
||||
'name': "Zizizbroken",
|
||||
'type': 'opportunity',
|
||||
'partner_id': brandon.id,
|
||||
'stage_id': self.env.ref('crm.stage_lead1').id,
|
||||
'user_id': self.env.ref('base.user_admin').id,
|
||||
})
|
||||
self.start_tour("/web", 'crm_tour', login="admin")
|
||||
self.start_tour("/odoo", 'crm_tour', login="admin")
|
||||
|
||||
def test_02_crm_tour_rainbowman(self):
|
||||
# we create a new user to make sure they get the 'Congrats on your first deal!'
|
||||
|
|
@ -33,15 +23,15 @@ class TestUi(HttpCase):
|
|||
'name': 'Temporary CRM User',
|
||||
'login': 'temp_crm_user',
|
||||
'password': 'temp_crm_user',
|
||||
'groups_id': [(6, 0, [
|
||||
'group_ids': [(6, 0, [
|
||||
self.ref('base.group_user'),
|
||||
self.ref('sales_team.group_sale_salesman')
|
||||
])]
|
||||
})
|
||||
self.start_tour("/web", 'crm_rainbowman', login="temp_crm_user")
|
||||
self.start_tour("/odoo", 'crm_rainbowman', login="temp_crm_user")
|
||||
|
||||
def test_03_crm_tour_forecast(self):
|
||||
self.start_tour("/web", 'crm_forecast', login="admin")
|
||||
self.start_tour("/odoo", 'crm_forecast', login="admin")
|
||||
|
||||
def test_email_and_phone_propagation_edit_save(self):
|
||||
"""Test the propagation of the email / phone on the partner.
|
||||
|
|
@ -73,45 +63,145 @@ class TestUi(HttpCase):
|
|||
self.assertTrue(lead.partner_email_update)
|
||||
self.assertTrue(lead.partner_phone_update)
|
||||
|
||||
self.start_tour('/web', 'crm_email_and_phone_propagation_edit_save', login='admin')
|
||||
self.start_tour('/odoo', 'crm_email_and_phone_propagation_edit_save', login='admin')
|
||||
|
||||
self.assertEqual(lead.email_from, 'test@example.com', 'Should not have changed the lead email')
|
||||
self.assertEqual(lead.phone, '+32 494 44 44 44', 'Should not have changed the lead phone')
|
||||
self.assertEqual(partner.email, 'test@example.com', 'Should have propagated the lead email on the partner')
|
||||
self.assertEqual(partner.phone, '+32 494 44 44 44', 'Should have propagated the lead phone on the partner')
|
||||
|
||||
def test_email_and_phone_propagation_remove_email_and_phone(self):
|
||||
"""Test the propagation of the email / phone on the partner.
|
||||
|
||||
If we remove the email and phone on the lead, it should be removed on the
|
||||
partner. This test check that we correctly detect field values changes in JS
|
||||
(aka undefined VS falsy).
|
||||
class TestCrmKanbanUI(TransactionCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.child_contact_1, cls.child_contact_2, cls.orphan_contact = cls.env['res.partner'].create([
|
||||
{'name': 'Child Contact 1'}, {'name': 'Child Contact 2'}, {'name': 'Orphan Contact'},
|
||||
])
|
||||
cls.parent_company, cls.childless_company = cls.env['res.partner'].create([
|
||||
{'name': 'Parent Company', 'is_company': True},
|
||||
{'name': 'Childless Company', 'is_company': True},
|
||||
])
|
||||
(cls.child_contact_1 + cls.child_contact_2).parent_id = cls.parent_company
|
||||
cls.quick_create_form_view = cls.env.ref('crm.quick_create_opportunity_form', raise_if_not_found=False)
|
||||
|
||||
def test_kanban_quick_create_form(self):
|
||||
"""Check major state transitions when picking a company or a partner from the quick_create form."""
|
||||
lead_form = Form(self.env['crm.lead'], self.quick_create_form_view)
|
||||
self.assertFalse(lead_form._get_context('partner_id')['default_parent_id'])
|
||||
|
||||
lead_form.partner_id = self.orphan_contact
|
||||
self.assertFalse(lead_form.commercial_partner_id)
|
||||
self.assertFalse(lead_form._get_context('partner_id')['default_parent_id'])
|
||||
|
||||
# set contact, updates commercial partner
|
||||
lead_form.partner_id = self.child_contact_1
|
||||
self.assertEqual(lead_form.commercial_partner_id, self.parent_company)
|
||||
self.assertEqual(lead_form._get_context('partner_id')['default_parent_id'], self.parent_company.id)
|
||||
lead_form.partner_id = self.child_contact_2
|
||||
self.assertEqual(lead_form.commercial_partner_id, self.parent_company)
|
||||
self.assertEqual(lead_form.partner_id, self.child_contact_2)
|
||||
|
||||
# set company, resets partner
|
||||
lead_form.commercial_partner_id = self.childless_company
|
||||
self.assertEqual(lead_form.commercial_partner_id, self.childless_company)
|
||||
self.assertFalse(lead_form.partner_id)
|
||||
self.assertEqual(lead_form._get_context('partner_id')['default_parent_id'], self.childless_company.id)
|
||||
|
||||
lead_form.commercial_partner_id = self.parent_company
|
||||
self.assertEqual(lead_form.commercial_partner_id, self.parent_company)
|
||||
self.assertFalse(lead_form.partner_id)
|
||||
self.assertEqual(lead_form._get_context('partner_id')['default_parent_id'], self.parent_company.id)
|
||||
|
||||
def test_kanban_quick_create_partner_inherited_details(self):
|
||||
"""Check behavior of setting the quick create "company" field at create time.
|
||||
|
||||
It should link the company as the partner if there's no contact
|
||||
and the contact details are either empty or match the company's.
|
||||
Otherwise it will simply be added as the partner_name, if there is none.
|
||||
"""
|
||||
self.env['crm.lead'].search([]).unlink()
|
||||
user_admin = self.env['res.users'].search([('login', '=', 'admin')])
|
||||
|
||||
partner = self.env['res.partner'].create({'name': 'Test Partner'})
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'Test Lead Propagation',
|
||||
'type': 'opportunity',
|
||||
'user_id': user_admin.id,
|
||||
'partner_id': partner.id,
|
||||
'email_from': 'test@example.com',
|
||||
'phone': '+32 494 44 44 44',
|
||||
no_partner = self.env['res.partner']
|
||||
company = self.childless_company
|
||||
company.write({
|
||||
'email': 'childless@test.lan',
|
||||
'phone': '+32 499 00 00 00'
|
||||
})
|
||||
|
||||
# Check initial state
|
||||
self.assertEqual(partner.email, 'test@example.com')
|
||||
self.assertEqual(lead.phone, '+32 494 44 44 44')
|
||||
self.assertEqual(lead.email_from, 'test@example.com')
|
||||
self.assertEqual(lead.phone, '+32 494 44 44 44')
|
||||
test_cases = [
|
||||
({'email_from': False, 'phone': False}, {'partner_id': company, 'email_from': company.email, 'phone': company.phone}),
|
||||
({'email_from': company.email, 'phone': False}, {'partner_id': no_partner, 'email_from': company.email, 'phone': False}),
|
||||
(
|
||||
{'email_from': company.email, 'phone': company.phone[:-1] + '1'},
|
||||
{'partner_id': no_partner, 'email_from': company.email, 'phone': company.phone[:-1] + '1'},
|
||||
),
|
||||
(
|
||||
{'email_from': company.email, 'phone': company.phone},
|
||||
{'partner_id': company, 'email_from': company.email, 'phone': company.phone}
|
||||
),
|
||||
(
|
||||
{'email_from': company.email + 'n', 'phone': company.phone},
|
||||
{'partner_id': no_partner, 'email_from': company.email + 'n', 'phone': company.phone}
|
||||
),
|
||||
(
|
||||
{'partner_id': self.child_contact_1, 'email_from': company.email, 'phone': company.phone},
|
||||
{'partner_name': self.parent_company.name, 'partner_id': self.child_contact_1, 'email_from': company.email, 'phone': company.phone}
|
||||
),
|
||||
]
|
||||
for form_values, expected_lead_values in test_cases:
|
||||
lead_form = Form(self.env['crm.lead'], self.quick_create_form_view)
|
||||
lead_form.commercial_partner_id = self.childless_company
|
||||
self.assertFalse(lead_form.phone)
|
||||
self.assertFalse(lead_form.email_from)
|
||||
expected_lead_values = {'partner_name': company.name} | expected_lead_values
|
||||
with self.subTest(form_values=form_values):
|
||||
for field_name, input_value in form_values.items():
|
||||
lead_form[field_name] = input_value
|
||||
lead = lead_form.save()
|
||||
for field_name, expected_value in expected_lead_values.items():
|
||||
self.assertEqual(lead[field_name], expected_value)
|
||||
|
||||
self.assertFalse(lead.partner_email_update)
|
||||
self.assertFalse(lead.partner_phone_update)
|
||||
# sanity check, nothing was synced
|
||||
self.assertEqual(company.email, 'childless@test.lan')
|
||||
self.assertEqual(company.phone, '+32 499 00 00 00')
|
||||
|
||||
self.start_tour('/web', 'crm_email_and_phone_propagation_remove_email_and_phone', login='admin')
|
||||
# check that it behaves reasonably if used without form too
|
||||
lead = self.env['crm.lead'].create({
|
||||
'commercial_partner_id': self.childless_company.id,
|
||||
'name': "Childless Company's lead",
|
||||
})
|
||||
self.assertEqual(lead.partner_id, self.childless_company)
|
||||
|
||||
self.assertFalse(lead.email_from, 'Should have removed the email')
|
||||
self.assertFalse(lead.phone, 'Should have removed the phone')
|
||||
self.assertFalse(partner.email, 'Should have removed the email')
|
||||
self.assertFalse(partner.phone, 'Should have removed the phone')
|
||||
lead = self.env['crm.lead'].with_context(default_partner_id=self.parent_company).create({
|
||||
'commercial_partner_id': self.childless_company.id,
|
||||
'name': "Childless Company's lead",
|
||||
})
|
||||
self.assertEqual(lead.partner_id, self.parent_company, 'Default partner should take precedence over commercial_partner_id')
|
||||
|
||||
# check default_partner_id behaviors
|
||||
orphan = self.orphan_contact
|
||||
orphan.write({'email': 'orphan_individual@example.com', 'phone': '+32 488 00 00 00'})
|
||||
child_contact = self.child_contact_1
|
||||
child_contact.write({'email': 'child_contact@example.com', 'phone': '+32 477 00 00 00'})
|
||||
|
||||
test_cases_default = {
|
||||
child_contact: {
|
||||
'partner_id': child_contact, 'email_from': child_contact.email, 'phone': child_contact.phone,
|
||||
'partner_name': self.parent_company.name, 'commercial_partner_id': self.parent_company
|
||||
},
|
||||
company: {
|
||||
'partner_id': company, 'email_from': company.email, 'phone': company.phone,
|
||||
'partner_name': company.name, 'commercial_partner_id': self.env['res.partner']
|
||||
},
|
||||
orphan: {
|
||||
'partner_id': orphan, 'email_from': orphan.email, 'phone': orphan.phone,
|
||||
'partner_name': False, 'commercial_partner_id': self.env['res.partner']
|
||||
}
|
||||
}
|
||||
|
||||
for default_partner, expected_lead_values in test_cases_default.items():
|
||||
# check default partner in both the form and quick create form views
|
||||
for view in [None, self.quick_create_form_view]:
|
||||
with self.subTest(default_partner=default_partner, view=view):
|
||||
lead_form = Form(self.env['crm.lead'].with_context(default_partner_id=default_partner), view)
|
||||
for field_name, expected_value in expected_lead_values.items():
|
||||
self.assertEqual(lead_form[field_name], expected_value)
|
||||
|
|
|
|||
55
odoo-bringout-oca-ocb-crm/crm/tests/test_digest.py
Normal file
55
odoo-bringout-oca-ocb-crm/crm/tests/test_digest.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo.addons.digest.tests.common import TestDigestCommon
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestCrmDigest(TestDigestCommon):
|
||||
@classmethod
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.env['crm.lead'].search([]).unlink()
|
||||
|
||||
cls.env['crm.lead'].create([{
|
||||
'name': 'Lead 1',
|
||||
'company_id': cls.company_1.id,
|
||||
'probability': 100,
|
||||
'type': 'opportunity',
|
||||
'date_closed': datetime.now(),
|
||||
}, {
|
||||
'name': 'Lead 2',
|
||||
'company_id': cls.company_1.id,
|
||||
'probability': 90,
|
||||
'type': 'opportunity',
|
||||
'date_closed': datetime.now(),
|
||||
}, {
|
||||
'name': 'Lead 3',
|
||||
'company_id': False,
|
||||
'probability': 100,
|
||||
'type': 'opportunity',
|
||||
'date_closed': datetime.now(),
|
||||
}, {
|
||||
'name': 'Lead 4',
|
||||
'company_id': cls.company_1.id,
|
||||
'probability': 100,
|
||||
'type': 'opportunity',
|
||||
'date_closed': datetime.now() - timedelta(days=700),
|
||||
}])
|
||||
|
||||
def test_kpi_crm_lead_created_value(self):
|
||||
self.assertEqual(self.digest_1.kpi_crm_lead_created_value, 3)
|
||||
self.assertEqual(self.digest_2.kpi_crm_lead_created_value, 0,
|
||||
msg='This digest is in a different company')
|
||||
self.assertEqual(self.digest_3.kpi_crm_lead_created_value, 3,
|
||||
msg='This digest has no company, should take the current one')
|
||||
|
||||
self.digest_3.invalidate_recordset()
|
||||
self.assertEqual(
|
||||
self.digest_3.with_company(self.company_2).kpi_crm_lead_created_value,
|
||||
0,
|
||||
)
|
||||
|
|
@ -21,6 +21,13 @@ class TestLeadAssignPerf(TestLeadAssignCommon):
|
|||
of random in tests.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# patch registry to simulate a ready environment
|
||||
self.patch(self.env.registry, 'ready', True)
|
||||
# we don't use mock_mail_gateway thus want to mock smtp to test the stack
|
||||
self._mock_smtplib_connection()
|
||||
|
||||
@mute_logger('odoo.models.unlink', 'odoo.addons.crm.models.crm_team', 'odoo.addons.crm.models.crm_team_member')
|
||||
def test_assign_perf_duplicates(self):
|
||||
""" Test assign process with duplicates on partner. Allow to ensure notably
|
||||
|
|
@ -47,11 +54,11 @@ class TestLeadAssignPerf(TestLeadAssignCommon):
|
|||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
|
||||
# randomness: at least 1 query
|
||||
# randomness: at least 1 query, +3 for demo -> 957 + 5
|
||||
with self.with_user('user_sales_manager'):
|
||||
self.env['res.users'].has_group('base.group_user') # warmup the cache to avoid inconsistency between community an enterprise
|
||||
with self.assertQueryCount(user_sales_manager=1266): # crm 1187
|
||||
self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads(work_days=2)
|
||||
self.env.user._is_internal() # warmup the cache to avoid inconsistency between community an enterprise
|
||||
with self.assertQueryCount(user_sales_manager=962):
|
||||
self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads()
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
|
|
@ -62,12 +69,12 @@ class TestLeadAssignPerf(TestLeadAssignCommon):
|
|||
self.assertEqual(len(leads_st1) + len(leads_stc), len(leads)) # Make sure all lead are assigned
|
||||
|
||||
# salespersons assign
|
||||
self.members.invalidate_model(['lead_month_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 11) # 45 max on 2 days (3) + compensation (8.4)
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 4) # 15 max on 2 days (1) + compensation (2.8)
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 4) # 15 max on 2 days (1) + compensation (2.8)
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 8) # 30 max on 15 (2) + compensation (5.6)
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 15) # 60 max on 15 (4) + compsantion (11.2)
|
||||
self.members.invalidate_model(['lead_month_count', 'lead_day_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 2) # 45 max on one month -> 2 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 1) # 30 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 2) # 60 max on one month -> 2 daily
|
||||
|
||||
@mute_logger('odoo.models.unlink', 'odoo.addons.crm.models.crm_team', 'odoo.addons.crm.models.crm_team_member')
|
||||
def test_assign_perf_no_duplicates(self):
|
||||
|
|
@ -93,10 +100,10 @@ class TestLeadAssignPerf(TestLeadAssignCommon):
|
|||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
|
||||
# randomness: at least 1 query
|
||||
# randomness: at least 1 query, +1 for demo
|
||||
with self.with_user('user_sales_manager'):
|
||||
with self.assertQueryCount(user_sales_manager=585): # crm 584
|
||||
self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads(work_days=2)
|
||||
with self.assertQueryCount(user_sales_manager=552):
|
||||
self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads()
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
|
|
@ -105,12 +112,12 @@ class TestLeadAssignPerf(TestLeadAssignCommon):
|
|||
self.assertEqual(len(leads_st1) + len(leads_stc), 100)
|
||||
|
||||
# salespersons assign
|
||||
self.members.invalidate_model(['lead_month_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 11) # 45 max on 2 days (3) + compensation (8.4)
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 4) # 15 max on 2 days (1) + compensation (2.8)
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 4) # 15 max on 2 days (1) + compensation (2.8)
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 8) # 30 max on 15 (2) + compensation (5.6)
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 15) # 60 max on 15 (4) + compensation (11.2)
|
||||
self.members.invalidate_model(['lead_month_count', 'lead_day_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 2) # 45 max on one month -> 2 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 1) # 30 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 2) # 60 max on one month -> 2 daily
|
||||
|
||||
@mute_logger('odoo.models.unlink', 'odoo.addons.crm.models.crm_team', 'odoo.addons.crm.models.crm_team_member')
|
||||
def test_assign_perf_populated(self):
|
||||
|
|
@ -176,10 +183,10 @@ class TestLeadAssignPerf(TestLeadAssignCommon):
|
|||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
|
||||
# randomness
|
||||
# randomness: add 2 queries
|
||||
with self.with_user('user_sales_manager'):
|
||||
with self.assertQueryCount(user_sales_manager=6280): # crm 6226 / com 6276 / ent 6278
|
||||
self.env['crm.team'].browse(sales_teams.ids)._action_assign_leads(work_days=30)
|
||||
with self.assertQueryCount(user_sales_manager=5173):
|
||||
self.env['crm.team'].browse(sales_teams.ids)._action_assign_leads()
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)])
|
||||
|
|
@ -187,12 +194,12 @@ class TestLeadAssignPerf(TestLeadAssignCommon):
|
|||
self.assertEqual(leads.user_id, sales_teams.member_ids)
|
||||
|
||||
# salespersons assign
|
||||
self.members.invalidate_model(['lead_month_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 45) # 45 max on one month
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 15) # 15 max on one month
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 15) # 15 max on one month
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 30) # 30 max on one month
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 60) # 60 max on one month
|
||||
self.assertMemberAssign(sales_team_3_m1, 60) # 60 max on one month
|
||||
self.assertMemberAssign(sales_team_3_m2, 60) # 60 max on one month
|
||||
self.assertMemberAssign(sales_team_3_m3, 15) # 15 max on one month
|
||||
self.members.invalidate_model(['lead_month_count', 'lead_day_count'])
|
||||
self.assertMemberAssign(self.sales_team_1_m1, 2) # 45 max on one month -> 2 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m2, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_1_m3, 1) # 15 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m1, 1) # 30 max on one month -> 1 daily
|
||||
self.assertMemberAssign(self.sales_team_convert_m2, 2) # 60 max on one month -> 2 daily
|
||||
self.assertMemberAssign(sales_team_3_m1, 2) # 60 max on one month -> 2 daily
|
||||
self.assertMemberAssign(sales_team_3_m2, 2) # 60 max on one month -> 2 daily
|
||||
self.assertMemberAssign(sales_team_3_m3, 1) # 15 max on one month -> 1 daily
|
||||
|
|
|
|||
|
|
@ -1,71 +1,73 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon
|
||||
from odoo.tests.common import Form
|
||||
from odoo.tests import tagged, users
|
||||
|
||||
|
||||
@tagged('res_partner')
|
||||
class TestPartner(TestCrmCommon):
|
||||
# This is explicit: we want CRM only check, to test base method
|
||||
@tagged('res_partner', '-post_install', 'at_install')
|
||||
class TestResPartner(TestCrmCommon):
|
||||
|
||||
@users('user_sales_leads')
|
||||
def test_parent_sync_sales_rep(self):
|
||||
""" Test team_id / user_id sync from parent to children if the contact
|
||||
is a person. Company children are not updated. """
|
||||
contact_company = self.contact_company.with_env(self.env)
|
||||
contact_company_1 = self.contact_company_1.with_env(self.env)
|
||||
self.assertFalse(contact_company.team_id)
|
||||
self.assertFalse(contact_company.user_id)
|
||||
self.assertFalse(contact_company_1.team_id)
|
||||
self.assertFalse(contact_company_1.user_id)
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.contact_1_1, cls.contact_1_2 = cls.env['res.partner'].create([
|
||||
{
|
||||
'name': 'Philip J Fry Bouffe-tête',
|
||||
'email': 'bouffe.tete@test.example.com',
|
||||
'function': 'Bouffe-Tête',
|
||||
'lang': cls.lang_en.code,
|
||||
'phone': False,
|
||||
'parent_id': cls.contact_1.id,
|
||||
'is_company': False,
|
||||
'street': 'Same as Fry',
|
||||
'city': 'New York',
|
||||
'country_id': cls.env.ref('base.us').id,
|
||||
'zip': '54321',
|
||||
}, {
|
||||
'name': 'Philip J Fry Banjo',
|
||||
'email': 'banjo@test.example.com',
|
||||
'function': 'Being a banjo',
|
||||
'lang': cls.lang_en.code,
|
||||
'phone': False,
|
||||
'parent_id': cls.contact_1.id,
|
||||
'is_company': False,
|
||||
'street': 'Same as Fry',
|
||||
'city': 'New York',
|
||||
'country_id': cls.env.ref('base.us').id,
|
||||
'zip': '54321',
|
||||
}
|
||||
])
|
||||
|
||||
child = self.contact_1.with_env(self.env)
|
||||
self.assertEqual(child.parent_id, self.contact_company_1)
|
||||
self.assertFalse(child.team_id)
|
||||
self.assertFalse(child.user_id)
|
||||
cls.test_leads = cls.env['crm.lead'].create([
|
||||
{
|
||||
'name': 'CompanyLead',
|
||||
'type': 'lead',
|
||||
'partner_id': cls.contact_company_1.id,
|
||||
}, {
|
||||
'name': 'ChildLead',
|
||||
'type': 'lead',
|
||||
'partner_id': cls.contact_1.id,
|
||||
}, {
|
||||
'name': 'GrandChildLead',
|
||||
'type': 'lead',
|
||||
'partner_id': cls.contact_1_1.id,
|
||||
}, {
|
||||
'name': 'GrandChildOpp',
|
||||
'type': 'opportunity',
|
||||
'partner_id': cls.contact_1_1.id,
|
||||
}, {
|
||||
'name': 'Nobody',
|
||||
'type': 'opportunity',
|
||||
},
|
||||
])
|
||||
|
||||
# update comppany sales rep info
|
||||
contact_company.user_id = self.env.uid
|
||||
contact_company.team_id = self.sales_team_1.id
|
||||
|
||||
# change child parent: shold update sales rep info
|
||||
child.parent_id = contact_company.id
|
||||
self.assertEqual(child.user_id, self.env.user)
|
||||
|
||||
# test form tool
|
||||
# <field name="team_id" groups="base.group_no_one"/>
|
||||
with self.debug_mode():
|
||||
partner_form = Form(self.env['res.partner'], 'base.view_partner_form')
|
||||
partner_form.parent_id = contact_company
|
||||
partner_form.company_type = 'person'
|
||||
partner_form.name = 'Hermes Conrad'
|
||||
self.assertEqual(partner_form.team_id, self.sales_team_1)
|
||||
self.assertEqual(partner_form.user_id, self.env.user)
|
||||
partner_form.parent_id = contact_company_1
|
||||
self.assertEqual(partner_form.team_id, self.sales_team_1)
|
||||
self.assertEqual(partner_form.user_id, self.env.user)
|
||||
|
||||
# test form tool
|
||||
# <field name="team_id" groups="base.group_no_one"/>
|
||||
with self.debug_mode():
|
||||
partner_form = Form(self.env['res.partner'], 'base.view_partner_form')
|
||||
# `parent_id` is invisible when `is_company` is True (`company_type == 'company'`)
|
||||
# and parent_id is not set
|
||||
# So, set a temporary `parent_id` before setting the contact as company
|
||||
# to make `parent_id` visible in the interface while being a company
|
||||
# <field name="parent_id"
|
||||
# attrs="{
|
||||
# 'invisible': [
|
||||
# '|',
|
||||
# '&', ('is_company','=', True),('parent_id', '=', False),
|
||||
# ('company_name', '!=', False),('company_name', '!=', '')
|
||||
# ]
|
||||
# }"
|
||||
# />
|
||||
partner_form.parent_id = contact_company_1
|
||||
partner_form.company_type = 'company'
|
||||
partner_form.parent_id = contact_company
|
||||
partner_form.name = 'Mom Corp'
|
||||
self.assertFalse(partner_form.team_id)
|
||||
self.assertFalse(partner_form.user_id)
|
||||
@users('user_sales_manager')
|
||||
def test_fields_opportunity_count(self):
|
||||
(
|
||||
contact_company_1, contact_1, contact_1_1, contact_1_2
|
||||
) = (
|
||||
self.contact_company_1 + self.contact_1 + self.contact_1_1 + self.contact_1_2
|
||||
).with_env(self.env)
|
||||
self.assertEqual(contact_company_1.opportunity_count, 4, 'Should contain own + children leads')
|
||||
self.assertEqual(contact_1.opportunity_count, 3, 'Should contain own + child leads')
|
||||
self.assertEqual(contact_1_1.opportunity_count, 2, 'Should contain own, aka 2')
|
||||
self.assertEqual(contact_1_2.opportunity_count, 0, 'Should contain own, aka none')
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class TestUi(HttpCase, SalesTeamCommon):
|
|||
@users('salesmanager')
|
||||
def test_crm_team_members_mono_company(self):
|
||||
""" Make sure you can create crm.team records with members in a mono-company scenario """
|
||||
self.sale_manager.sudo().groups_id -= self.env.ref("base.group_multi_company")
|
||||
self.sale_manager.sudo().group_ids -= self.env.ref("base.group_multi_company")
|
||||
self.env['ir.config_parameter'].sudo().set_param('sales_team.membership_multi', True)
|
||||
|
||||
self.start_tour("/", "create_crm_team_tour", login="salesmanager")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue