mirror of
https://github.com/bringout/oca-ocb-crm.git
synced 2026-04-23 23:32:08 +02:00
Initial commit: Crm packages
This commit is contained in:
commit
21a345b5b9
654 changed files with 418312 additions and 0 deletions
19
odoo-bringout-oca-ocb-crm/crm/tests/__init__.py
Normal file
19
odoo-bringout-oca-ocb-crm/crm/tests/__init__.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_crm_activity
|
||||
from . import test_crm_lead
|
||||
from . import test_crm_lead_assignment
|
||||
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_performances
|
||||
from . import test_res_partner
|
||||
from . import test_sales_team_ui
|
||||
727
odoo-bringout-oca-ocb-crm/crm/tests/common.py
Normal file
727
odoo-bringout-oca-ocb-crm/crm/tests/common.py
Normal file
|
|
@ -0,0 +1,727 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from ast import literal_eval
|
||||
from contextlib import contextmanager
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.crm.models.crm_lead import PARTNER_ADDRESS_FIELDS_TO_SYNC
|
||||
from odoo.addons.mail.tests.common import MailCase, mail_new_test_user
|
||||
from odoo.addons.phone_validation.tools import phone_validation
|
||||
from odoo.addons.sales_team.tests.common import TestSalesCommon
|
||||
from odoo.fields import Datetime
|
||||
from odoo import models, tools
|
||||
|
||||
INCOMING_EMAIL = """Return-Path: {return_path}
|
||||
X-Original-To: {to}
|
||||
Delivered-To: {to}
|
||||
Received: by mail.my.com (Postfix, from userid xxx)
|
||||
id 822ECBFB67; Mon, 24 Oct 2011 07:36:51 +0200 (CEST)
|
||||
X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.my.com
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-1.0 required=5.0 tests=ALL_TRUSTED autolearn=ham
|
||||
version=3.3.1
|
||||
Received: from [192.168.1.146]
|
||||
(Authenticated sender: {email_from})
|
||||
by mail.customer.com (Postfix) with ESMTPSA id 07A30BFAB4
|
||||
for <{to}>; Mon, 24 Oct 2011 07:36:50 +0200 (CEST)
|
||||
Message-ID: {msg_id}
|
||||
Date: Mon, 24 Oct 2011 11:06:29 +0530
|
||||
From: {email_from}
|
||||
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.14) Gecko/20110223 Lightning/1.0b2 Thunderbird/3.1.8
|
||||
MIME-Version: 1.0
|
||||
To: {to}
|
||||
Subject: {subject}
|
||||
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
This is an example email. All sensitive content has been stripped out.
|
||||
|
||||
ALL GLORY TO THE HYPNOTOAD !
|
||||
|
||||
Cheers,
|
||||
|
||||
Somebody."""
|
||||
|
||||
|
||||
class TestCrmCommon(TestSalesCommon, MailCase):
|
||||
|
||||
FIELDS_FIRST_SET = [
|
||||
'name', 'partner_id', 'campaign_id', 'company_id', 'country_id',
|
||||
'team_id', 'state_id', 'stage_id', 'medium_id', 'source_id', 'user_id',
|
||||
'title', 'city', 'contact_name', 'mobile', 'partner_name',
|
||||
'phone', 'probability', 'expected_revenue', 'street', 'street2', 'zip',
|
||||
'create_date', 'date_action_last', 'email_from', 'email_cc', 'website'
|
||||
]
|
||||
merge_fields = ['description', 'type', 'priority']
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCrmCommon, cls).setUpClass()
|
||||
cls._init_mail_gateway()
|
||||
|
||||
# Salesmen organization
|
||||
# ------------------------------------------------------------
|
||||
# Role: M (team member) R (team manager)
|
||||
# SALESMAN---------------sales_team_1
|
||||
# admin------------------M-----------
|
||||
# user_sales_manager-----R-----------
|
||||
# user_sales_leads-------M-----------
|
||||
# user_sales_salesman----/-----------
|
||||
|
||||
# Sales teams organization
|
||||
# ------------------------------------------------------------
|
||||
# SALESTEAM-----------SEQU-----COMPANY
|
||||
# sales_team_1--------5--------False
|
||||
# data----------------9999-----??
|
||||
|
||||
cls.sales_team_1.write({
|
||||
'alias_name': 'sales.test',
|
||||
'use_leads': True,
|
||||
'use_opportunities': True,
|
||||
'assignment_domain': False,
|
||||
})
|
||||
cls.sales_team_1_m1.write({
|
||||
'assignment_max': 45,
|
||||
'assignment_domain': False,
|
||||
})
|
||||
cls.sales_team_1_m2.write({
|
||||
'assignment_max': 15,
|
||||
'assignment_domain': False,
|
||||
})
|
||||
|
||||
(cls.user_sales_manager + cls.user_sales_leads + cls.user_sales_salesman).write({
|
||||
'groups_id': [(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,
|
||||
})
|
||||
cls.stage_team1_2 = cls.env['crm.stage'].create({
|
||||
'name': 'Proposition',
|
||||
'sequence': 5,
|
||||
'team_id': 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,
|
||||
'is_won': True,
|
||||
})
|
||||
cls.stage_gen_1 = cls.env['crm.stage'].create({
|
||||
'name': 'Generic stage',
|
||||
'sequence': 3,
|
||||
'team_id': False,
|
||||
})
|
||||
cls.stage_gen_won = cls.env['crm.stage'].create({
|
||||
'name': 'Generic Won',
|
||||
'sequence': 30,
|
||||
'team_id': False,
|
||||
'is_won': True,
|
||||
})
|
||||
|
||||
# countries and langs
|
||||
base_us = cls.env.ref('base.us')
|
||||
cls.env['res.lang']._activate_lang('fr_FR')
|
||||
cls.env['res.lang']._activate_lang('en_US')
|
||||
cls.lang_en = cls.env['res.lang']._lang_get('en_US')
|
||||
cls.lang_fr = cls.env['res.lang']._lang_get('fr_FR')
|
||||
|
||||
# leads
|
||||
cls.lead_1 = cls.env['crm.lead'].create({
|
||||
'name': 'Nibbler Spacecraft Request',
|
||||
'type': 'lead',
|
||||
'user_id': cls.user_sales_leads.id,
|
||||
'team_id': cls.sales_team_1.id,
|
||||
'partner_id': False,
|
||||
'contact_name': 'Amy Wong',
|
||||
'email_from': 'amy.wong@test.example.com',
|
||||
'lang_id': cls.lang_fr.id,
|
||||
'phone': '+1 202 555 9999',
|
||||
'country_id': cls.env.ref('base.us').id,
|
||||
'probability': 20,
|
||||
})
|
||||
# update lead_1: stage_id is not computed anymore by default for leads
|
||||
cls.lead_1.write({
|
||||
'stage_id': cls.stage_team1_1.id,
|
||||
})
|
||||
|
||||
# create an history for new team
|
||||
cls.lead_team_1_won = cls.env['crm.lead'].create({
|
||||
'name': 'Already Won',
|
||||
'type': 'lead',
|
||||
'user_id': cls.user_sales_leads.id,
|
||||
'team_id': cls.sales_team_1.id,
|
||||
})
|
||||
cls.lead_team_1_won.action_set_won()
|
||||
cls.lead_team_1_lost = cls.env['crm.lead'].create({
|
||||
'name': 'Already Won',
|
||||
'type': 'lead',
|
||||
'user_id': cls.user_sales_leads.id,
|
||||
'team_id': cls.sales_team_1.id,
|
||||
})
|
||||
cls.lead_team_1_lost.action_set_lost()
|
||||
(cls.lead_team_1_won + cls.lead_team_1_lost).flush_recordset()
|
||||
|
||||
# email / phone data
|
||||
cls.test_email_data = [
|
||||
'"Planet Express" <planet.express@test.example.com>',
|
||||
'"Philip, J. Fry" <philip.j.fry@test.example.com>',
|
||||
'"Turanga Leela" <turanga.leela@test.example.com>',
|
||||
]
|
||||
cls.test_email_data_normalized = [
|
||||
'planet.express@test.example.com',
|
||||
'philip.j.fry@test.example.com',
|
||||
'turanga.leela@test.example.com',
|
||||
]
|
||||
cls.test_phone_data = [
|
||||
'+1 202 555 0122', # formatted US number
|
||||
'202 555 0999', # local US number
|
||||
'202 555 0888', # local US number
|
||||
]
|
||||
cls.test_phone_data_sanitized = [
|
||||
'+12025550122',
|
||||
'+12025550999',
|
||||
'+12025550888',
|
||||
]
|
||||
|
||||
# create some test contact and companies
|
||||
cls.contact_company_1 = cls.env['res.partner'].create({
|
||||
'name': 'Planet Express',
|
||||
'email': cls.test_email_data[0],
|
||||
'is_company': True,
|
||||
'street': '57th Street',
|
||||
'city': 'New New York',
|
||||
'country_id': cls.env.ref('base.us').id,
|
||||
'zip': '12345',
|
||||
})
|
||||
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,
|
||||
'parent_id': cls.contact_company_1.id,
|
||||
'is_company': False,
|
||||
'street': 'Actually the sewers',
|
||||
'city': 'New York',
|
||||
'country_id': cls.env.ref('base.us').id,
|
||||
'zip': '54321',
|
||||
})
|
||||
cls.contact_2 = cls.env['res.partner'].create({
|
||||
'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,
|
||||
'street': 'Cookieville Minimum-Security Orphanarium',
|
||||
'city': 'New New York',
|
||||
'country_id': cls.env.ref('base.us').id,
|
||||
'zip': '97648',
|
||||
})
|
||||
cls.contact_company = cls.env['res.partner'].create({
|
||||
'name': 'Mom',
|
||||
'company_name': 'MomCorp',
|
||||
'is_company': True,
|
||||
'street': 'Mom Friendly Robot Street',
|
||||
'city': 'New new York',
|
||||
'country_id': base_us.id,
|
||||
'lang': cls.lang_en.code,
|
||||
'mobile': '+1 202 555 0888',
|
||||
'zip': '87654',
|
||||
})
|
||||
|
||||
# test activities
|
||||
cls.activity_type_1 = cls.env['mail.activity.type'].create({
|
||||
'name': 'Lead Test Activity 1',
|
||||
'summary': 'ACT 1 : Presentation, barbecue, ... ',
|
||||
'res_model': 'crm.lead',
|
||||
'category': 'meeting',
|
||||
'delay_count': 5,
|
||||
})
|
||||
cls.env['ir.model.data'].create({
|
||||
'name': cls.activity_type_1.name.lower().replace(' ', '_'),
|
||||
'module': 'crm',
|
||||
'model': cls.activity_type_1._name,
|
||||
'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({
|
||||
'country_id': cls.env.ref('base.au').id,
|
||||
'currency_id': cls.env.ref('base.AUD').id,
|
||||
'email': 'company.2@test.example.com',
|
||||
'name': 'New Test Company',
|
||||
})
|
||||
|
||||
cls.user_sales_manager_mc = mail_new_test_user(
|
||||
cls.env,
|
||||
company_id=cls.company_2.id,
|
||||
company_ids=[(4, cls.company_main.id), (4, cls.company_2.id)],
|
||||
email='user.sales.manager.mc@test.example.com',
|
||||
login='user_sales_manager_mc',
|
||||
groups='sales_team.group_sale_manager,base.group_partner_manager',
|
||||
name='Myrddin Sales Manager',
|
||||
notification_type='inbox',
|
||||
)
|
||||
cls.team_company2 = cls.env['crm.team'].create({
|
||||
'company_id': cls.company_2.id,
|
||||
'name': 'C2 Team',
|
||||
'sequence': 10,
|
||||
'user_id': False,
|
||||
})
|
||||
cls.team_company2_m1 = cls.env['crm.team.member'].create({
|
||||
'crm_team_id': cls.team_company2.id,
|
||||
'user_id': cls.user_sales_manager_mc.id,
|
||||
'assignment_max': 30,
|
||||
'assignment_domain': False,
|
||||
})
|
||||
|
||||
cls.team_company1 = cls.env['crm.team'].create({
|
||||
'company_id': cls.company_main.id,
|
||||
'name': 'MainCompany Team',
|
||||
'sequence': 50,
|
||||
'user_id': cls.user_sales_manager.id,
|
||||
})
|
||||
|
||||
cls.partner_c2 = cls.env['res.partner'].create({
|
||||
'company_id': cls.company_2.id,
|
||||
'email': '"Partner C2" <partner_c2@multicompany.example.com>',
|
||||
'name': 'Customer for C2',
|
||||
'phone': '+32455001122',
|
||||
})
|
||||
|
||||
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=''):
|
||||
""" Helper tool method creating a batch of leads, useful when dealing
|
||||
with batch processes. Please update me.
|
||||
|
||||
:param string type: 'lead', 'opportunity', 'mixed' (lead then opp),
|
||||
None (depends on configuration);
|
||||
:param partner_count: if not partner_ids is given, generate partner count
|
||||
customers; other leads will have no customer;
|
||||
:param partner_ids: a set of partner ids to cycle when creating leads;
|
||||
:param user_ids: a set of user ids to cycle when creating leads;
|
||||
|
||||
:return: create leads
|
||||
"""
|
||||
types = ['lead', 'opportunity']
|
||||
leads_data = [{
|
||||
'name': f'TestLead{suffix}_{x:04d}',
|
||||
'type': lead_type if lead_type else types[x % 2],
|
||||
'priority': '%s' % (x % 3),
|
||||
} for x in range(count)]
|
||||
|
||||
# generate customer information
|
||||
partners = []
|
||||
if partner_count:
|
||||
partners = self.env['res.partner'].create([{
|
||||
'name': 'AutoPartner_%04d' % (x),
|
||||
'email': tools.formataddr((
|
||||
'AutoPartner_%04d' % (x),
|
||||
'partner_email_%04d@example.com' % (x),
|
||||
)),
|
||||
} for x in range(partner_count)])
|
||||
|
||||
# customer information
|
||||
if partner_ids:
|
||||
for idx, lead_data in enumerate(leads_data):
|
||||
lead_data['partner_id'] = partner_ids[idx % len(partner_ids)]
|
||||
else:
|
||||
for idx, lead_data in enumerate(leads_data):
|
||||
if partner_count and idx < partner_count:
|
||||
lead_data['partner_id'] = partners[idx].id
|
||||
else:
|
||||
lead_data['email_from'] = tools.formataddr((
|
||||
'TestCustomer_%02d' % (idx),
|
||||
'customer_email_%04d@example.com' % (idx)
|
||||
))
|
||||
|
||||
# country + phone information
|
||||
if country_ids:
|
||||
cid_to_country = dict(
|
||||
(country.id, country)
|
||||
for country in self.env['res.country'].browse([cid for cid in country_ids if cid])
|
||||
)
|
||||
for idx, lead_data in enumerate(leads_data):
|
||||
country_id = country_ids[idx % len(country_ids)]
|
||||
country = cid_to_country.get(country_id, self.env['res.country'])
|
||||
lead_data['country_id'] = country.id
|
||||
if lead_data['country_id']:
|
||||
lead_data['phone'] = phone_validation.phone_format(
|
||||
'0456%04d99' % (idx),
|
||||
country.code, country.phone_code,
|
||||
force_format='E164')
|
||||
else:
|
||||
lead_data['phone'] = '+32456%04d99' % (idx)
|
||||
|
||||
# salesteam information
|
||||
if user_ids:
|
||||
for idx, lead_data in enumerate(leads_data):
|
||||
lead_data['user_id'] = user_ids[idx % len(user_ids)]
|
||||
|
||||
# probabilities
|
||||
if probabilities:
|
||||
for idx, lead_data in enumerate(leads_data):
|
||||
lead_data['probability'] = probabilities[idx % len(probabilities)]
|
||||
|
||||
# duplicates (currently only with email)
|
||||
dups_data = []
|
||||
if email_dup_count and not partner_ids:
|
||||
for idx, lead_data in enumerate(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']
|
||||
dups_data.append(dup_data)
|
||||
if len(dups_data) >= email_dup_count:
|
||||
break
|
||||
|
||||
return self.env['crm.lead'].create(leads_data + dups_data)
|
||||
|
||||
def _create_duplicates(self, lead, create_opp=True):
|
||||
""" Helper tool method creating, based on a given lead
|
||||
|
||||
* a customer (res.partner) based on lead email (to test partner finding)
|
||||
-> FIXME: using same normalized email does not work currently, only exact email works
|
||||
* a lead with same email_from
|
||||
* a lead with same email_normalized (other email_from)
|
||||
* a lead with customer but another email
|
||||
* a lost opportunity with same email_from
|
||||
"""
|
||||
customer = self.env['res.partner'].create({
|
||||
'name': 'Lead1 Email Customer',
|
||||
'email': lead.email_from,
|
||||
})
|
||||
lead_email_from = self.env['crm.lead'].create({
|
||||
'name': 'Duplicate: same email_from',
|
||||
'type': 'lead',
|
||||
'team_id': lead.team_id.id,
|
||||
'email_from': lead.email_from,
|
||||
'probability': lead.probability,
|
||||
})
|
||||
lead_email_normalized = self.env['crm.lead'].create({
|
||||
'name': 'Duplicate: email_normalize comparison',
|
||||
'type': 'lead',
|
||||
'team_id': lead.team_id.id,
|
||||
'stage_id': lead.stage_id.id,
|
||||
'email_from': 'CUSTOMER WITH NAME <%s>' % lead.email_normalized.upper(),
|
||||
'probability': lead.probability,
|
||||
})
|
||||
lead_partner = self.env['crm.lead'].create({
|
||||
'name': 'Duplicate: customer ID',
|
||||
'type': 'lead',
|
||||
'team_id': lead.team_id.id,
|
||||
'partner_id': customer.id,
|
||||
'probability': lead.probability,
|
||||
})
|
||||
if create_opp:
|
||||
opp_lost = self.env['crm.lead'].create({
|
||||
'name': 'Duplicate: lost opportunity',
|
||||
'type': 'opportunity',
|
||||
'team_id': lead.team_id.id,
|
||||
'stage_id': lead.stage_id.id,
|
||||
'email_from': lead.email_from,
|
||||
'probability': lead.probability,
|
||||
})
|
||||
opp_lost.action_set_lost()
|
||||
else:
|
||||
opp_lost = self.env['crm.lead']
|
||||
|
||||
new_leads = lead_email_from + lead_email_normalized + lead_partner + opp_lost
|
||||
new_leads.flush_recordset() # compute notably probability
|
||||
return customer, new_leads
|
||||
|
||||
@contextmanager
|
||||
def assertLeadMerged(self, opportunity, leads, **expected):
|
||||
""" Assert result of lead _merge_opportunity process. This is done using
|
||||
a context manager in order to save original opportunity (master lead)
|
||||
values. Indeed those will be modified during merge process. We have to
|
||||
ensure final values are correct taking into account all leads values
|
||||
before merging them.
|
||||
|
||||
:param opportunity: final opportunity
|
||||
:param leads: merged leads (including opportunity)
|
||||
"""
|
||||
self.assertIn(opportunity, leads)
|
||||
|
||||
# save opportunity value before being modified by merge process
|
||||
fields_all = self.FIELDS_FIRST_SET + self.merge_fields
|
||||
original_opp_values = dict(
|
||||
(fname, opportunity[fname])
|
||||
for fname in fields_all
|
||||
if fname in opportunity
|
||||
)
|
||||
|
||||
def _find_value(lead, fname):
|
||||
if lead == opportunity:
|
||||
return original_opp_values[fname]
|
||||
return lead[fname]
|
||||
|
||||
def _first_set(fname):
|
||||
values = [_find_value(lead, fname) for lead in leads]
|
||||
return next((value for value in values if value), False)
|
||||
|
||||
def _get_type():
|
||||
values = [_find_value(lead, 'type') for lead in leads]
|
||||
return 'opportunity' if 'opportunity' in values else 'lead'
|
||||
|
||||
def _get_description():
|
||||
values = [_find_value(lead, 'description') for lead in leads]
|
||||
return '<br><br>'.join(value for value in values if value)
|
||||
|
||||
def _get_priority():
|
||||
values = [_find_value(lead, 'priority') for lead in leads]
|
||||
return max(values)
|
||||
|
||||
def _aggregate(fname):
|
||||
if isinstance(self.env['crm.lead'][fname], models.BaseModel):
|
||||
values = leads.mapped(fname)
|
||||
else:
|
||||
values = [_find_value(lead, fname) for lead in leads]
|
||||
return values
|
||||
|
||||
try:
|
||||
# merge process will modify opportunity
|
||||
yield
|
||||
finally:
|
||||
# support specific values caller may want to check in addition to generic tests
|
||||
for fname, expected in expected.items():
|
||||
if expected 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))
|
||||
|
||||
# classic fields: first not void wins or specific computation
|
||||
for fname in fields_all:
|
||||
if fname not in opportunity: # not all fields available when doing -u
|
||||
continue
|
||||
opp_value = opportunity[fname]
|
||||
if fname == 'description':
|
||||
self.assertEqual(opp_value, _get_description())
|
||||
elif fname == 'type':
|
||||
self.assertEqual(opp_value, _get_type())
|
||||
elif fname == 'priority':
|
||||
self.assertEqual(opp_value, _get_priority())
|
||||
elif fname in ('order_ids', 'visitor_ids'):
|
||||
self.assertEqual(opp_value, _aggregate(fname))
|
||||
elif fname in PARTNER_ADDRESS_FIELDS_TO_SYNC:
|
||||
# Specific computation, has its own test
|
||||
continue
|
||||
else:
|
||||
self.assertEqual(
|
||||
opp_value if opp_value or not isinstance(opp_value, models.BaseModel) else False,
|
||||
_first_set(fname)
|
||||
)
|
||||
|
||||
|
||||
class TestLeadConvertCommon(TestCrmCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestLeadConvertCommon, cls).setUpClass()
|
||||
# Sales Team organization
|
||||
# Role: M (team member) R (team manager)
|
||||
# SALESMAN---------------sales_team_1-----sales_team_convert
|
||||
# admin------------------M----------------/ (sales_team_1_m2)
|
||||
# user_sales_manager-----R----------------R
|
||||
# user_sales_leads-------M----------------/ (sales_team_1_m1)
|
||||
# user_sales_salesman----/----------------M (sales_team_convert_m1)
|
||||
|
||||
# Stages Team organization
|
||||
# Name-------------------ST-------------------Sequ
|
||||
# stage_team1_1----------sales_team_1---------1
|
||||
# stage_team1_2----------sales_team_1---------5
|
||||
# stage_team1_won--------sales_team_1---------70
|
||||
# stage_gen_1------------/--------------------3
|
||||
# stage_gen_won----------/--------------------30
|
||||
# stage_team_convert_1---sales_team_convert---1
|
||||
|
||||
cls.sales_team_convert = cls.env['crm.team'].create({
|
||||
'name': 'Convert Sales Team',
|
||||
'sequence': 10,
|
||||
'alias_name': False,
|
||||
'use_leads': True,
|
||||
'use_opportunities': True,
|
||||
'company_id': False,
|
||||
'user_id': cls.user_sales_manager.id,
|
||||
'assignment_domain': [('priority', 'in', ['1', '2', '3'])],
|
||||
})
|
||||
cls.sales_team_convert_m1 = cls.env['crm.team.member'].create({
|
||||
'user_id': cls.user_sales_salesman.id,
|
||||
'crm_team_id': cls.sales_team_convert.id,
|
||||
'assignment_max': 30,
|
||||
'assignment_domain': False,
|
||||
})
|
||||
cls.stage_team_convert_1 = cls.env['crm.stage'].create({
|
||||
'name': 'New',
|
||||
'sequence': 1,
|
||||
'team_id': cls.sales_team_convert.id,
|
||||
})
|
||||
|
||||
cls.lead_1.write({'date_open': Datetime.from_string('2020-01-15 11:30:00')})
|
||||
|
||||
cls.crm_lead_dt_patcher = patch('odoo.addons.crm.models.crm_lead.fields.Datetime', wraps=Datetime)
|
||||
cls.crm_lead_dt_mock = cls.startClassPatcher(cls.crm_lead_dt_patcher)
|
||||
|
||||
@classmethod
|
||||
def _switch_to_multi_membership(cls):
|
||||
# Sales Team organization
|
||||
# Role: M (team member) R (team manager)
|
||||
# SALESMAN---------------sales_team_1-----sales_team_convert
|
||||
# admin------------------M----------------/ (sales_team_1_m2)
|
||||
# user_sales_manager-----R----------------R+M <-- NEW (sales_team_convert_m2)
|
||||
# user_sales_leads-------M----------------/ (sales_team_1_m1)
|
||||
# user_sales_salesman----M----------------M <-- NEW (sales_team_1_m3 / sales_team_convert_m1)
|
||||
|
||||
# SALESMAN--------------sales_team----------assign_max
|
||||
# admin-----------------sales_team_1--------15 (tot: 0.5/day)
|
||||
# user_sales_manager----sales_team_convert--60 (tot: 2/day)
|
||||
# user_sales_leads------sales_team_1--------45 (tot: 1.5/day)
|
||||
# user_sales_salesman---sales_team_1--------15 (tot: 1.5/day)
|
||||
# user_sales_salesman---sales_team_convert--30
|
||||
|
||||
cls.sales_team_1_m1.write({
|
||||
'assignment_max': 45,
|
||||
'assignment_domain': False,
|
||||
})
|
||||
cls.sales_team_1_m2.write({
|
||||
'assignment_max': 15,
|
||||
'assignment_domain': [('probability', '>=', 10)],
|
||||
})
|
||||
|
||||
cls.env['ir.config_parameter'].set_param('sales_team.membership_multi', True)
|
||||
cls.sales_team_1_m3 = cls.env['crm.team.member'].create({
|
||||
'user_id': cls.user_sales_salesman.id,
|
||||
'crm_team_id': cls.sales_team_1.id,
|
||||
'assignment_max': 15,
|
||||
'assignment_domain': [('probability', '>=', 20)],
|
||||
})
|
||||
cls.sales_team_convert_m1.write({
|
||||
'assignment_max': 30,
|
||||
'assignment_domain': [('probability', '>=', 20)]
|
||||
})
|
||||
cls.sales_team_convert_m2 = cls.env['crm.team.member'].create({
|
||||
'user_id': cls.user_sales_manager.id,
|
||||
'crm_team_id': cls.sales_team_convert.id,
|
||||
'assignment_max': 60,
|
||||
'assignment_domain': False,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def _switch_to_auto_assign(cls):
|
||||
cls.env['ir.config_parameter'].set_param('crm.lead.auto.assignment', True)
|
||||
cls.assign_cron = cls.env.ref('crm.ir_cron_crm_lead_assign')
|
||||
cls.assign_cron.update({
|
||||
'active': True,
|
||||
'interval_type': 'days',
|
||||
'interval_number': 1,
|
||||
})
|
||||
|
||||
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(len(member_leads), count)
|
||||
if member.assignment_domain:
|
||||
self.assertEqual(
|
||||
member_leads.filtered_domain(literal_eval(member.assignment_domain)),
|
||||
member_leads
|
||||
)
|
||||
# TODO this condition is not fulfilled in case of merge, need to change merge/assignment process
|
||||
# if member.crm_team_id.assignment_domain:
|
||||
# self.assertEqual(
|
||||
# member_leads.filtered_domain(literal_eval(member.crm_team_id.assignment_domain)),
|
||||
# member_leads,
|
||||
# 'Assign domain not matching: %s' % member.crm_team_id.assignment_domain
|
||||
# )
|
||||
|
||||
class TestLeadConvertMassCommon(TestLeadConvertCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestLeadConvertMassCommon, cls).setUpClass()
|
||||
# Sales Team organization
|
||||
# Role: M (team member) R (team manager)
|
||||
# SALESMAN-------------------sales_team_1-----sales_team_convert
|
||||
# admin----------------------M----------------/ (sales_team_1_m2)
|
||||
# user_sales_manager---------R----------------R (sales_team_1_m1)
|
||||
# user_sales_leads-----------M----------------/
|
||||
# user_sales_leads_convert---/----------------M <-- NEW (sales_team_convert_m2)
|
||||
# user_sales_salesman--------/----------------M (sales_team_convert_m1)
|
||||
|
||||
cls.user_sales_leads_convert = mail_new_test_user(
|
||||
cls.env, login='user_sales_leads_convert',
|
||||
name='Lucien Sales Leads Convert', email='crm_leads_2@test.example.com',
|
||||
company_id=cls.env.ref("base.main_company").id,
|
||||
notification_type='inbox',
|
||||
groups='sales_team.group_sale_salesman_all_leads,base.group_partner_manager,crm.group_use_lead',
|
||||
)
|
||||
cls.sales_team_convert_m2 = cls.env['crm.team.member'].create({
|
||||
'user_id': cls.user_sales_leads_convert.id,
|
||||
'crm_team_id': cls.sales_team_convert.id,
|
||||
})
|
||||
|
||||
cls.lead_w_partner = cls.env['crm.lead'].create({
|
||||
'name': 'New1',
|
||||
'type': 'lead',
|
||||
'priority': '0',
|
||||
'probability': 10,
|
||||
'user_id': cls.user_sales_manager.id,
|
||||
'stage_id': False,
|
||||
'partner_id': cls.contact_1.id,
|
||||
})
|
||||
cls.lead_w_partner.write({'stage_id': False})
|
||||
|
||||
cls.tags = cls.env['crm.tag'].create([{'name': 'Tag %i' % i} for i in range(4)])
|
||||
cls.lead_1.tag_ids = cls.tags[:3]
|
||||
cls.lead_w_partner_company = cls.env['crm.lead'].create({
|
||||
'name': 'New1',
|
||||
'type': 'lead',
|
||||
'probability': 50,
|
||||
'user_id': cls.user_sales_manager.id,
|
||||
'stage_id': cls.stage_team1_1.id,
|
||||
'partner_id': cls.contact_company_1.id,
|
||||
'contact_name': 'Hermes Conrad',
|
||||
'email_from': 'hermes.conrad@test.example.com',
|
||||
'tag_ids': (cls.tags[:2] | cls.tags[3]),
|
||||
})
|
||||
cls.lead_w_contact = cls.env['crm.lead'].create({
|
||||
'name': 'LeadContact',
|
||||
'type': 'lead',
|
||||
'probability': 25,
|
||||
'contact_name': 'TestContact',
|
||||
'user_id': cls.user_sales_salesman.id,
|
||||
'stage_id': cls.stage_gen_1.id,
|
||||
})
|
||||
cls.lead_w_email = cls.env['crm.lead'].create({
|
||||
'name': 'LeadEmailAsContact',
|
||||
'type': 'lead',
|
||||
'priority': '2',
|
||||
'probability': 15,
|
||||
'email_from': 'contact.email@test.example.com',
|
||||
'user_id': cls.user_sales_salesman.id,
|
||||
'stage_id': cls.stage_gen_1.id,
|
||||
})
|
||||
cls.lead_w_email_lost = cls.env['crm.lead'].create({
|
||||
'name': 'Lost',
|
||||
'type': 'lead',
|
||||
'probability': 15,
|
||||
'email_from': 'strange.from@test.example.com',
|
||||
'user_id': cls.user_sales_leads.id,
|
||||
'stage_id': cls.stage_team1_2.id,
|
||||
'active': False,
|
||||
})
|
||||
cls.env.flush_all()
|
||||
177
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_activity.py
Normal file
177
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_activity.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import date, timedelta
|
||||
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
class TestCrmMailActivity(TestCrmCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCrmMailActivity, cls).setUpClass()
|
||||
|
||||
cls.activity_type_1 = cls.env['mail.activity.type'].create({
|
||||
'name': 'Initial Contact',
|
||||
'delay_count': 5,
|
||||
'summary': 'ACT 1 : Presentation, barbecue, ... ',
|
||||
'res_model': 'crm.lead',
|
||||
})
|
||||
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 !',
|
||||
'res_model': 'crm.lead',
|
||||
})
|
||||
for activity_type in cls.activity_type_1 + cls.activity_type_2:
|
||||
cls.env['ir.model.data'].create({
|
||||
'name': activity_type.name.lower().replace(' ', '_'),
|
||||
'module': 'crm',
|
||||
'model': activity_type._name,
|
||||
'res_id': activity_type.id,
|
||||
})
|
||||
|
||||
@users('user_sales_leads')
|
||||
def test_crm_activity_ordering(self):
|
||||
""" Test ordering on "my activities", linked to a hack introduced for a b2b.
|
||||
Purpose is to be able to order on "my activities", which is a filtered o2m.
|
||||
In this test we will check search, limit, order and offset linked to the
|
||||
override of search for this non stored computed field. """
|
||||
# Initialize some batch data
|
||||
default_order = self.env['crm.lead']._order
|
||||
self.assertEqual(default_order, "priority desc, id desc") # force updating this test is order changes
|
||||
test_leads = self._create_leads_batch(count=10, partner_ids=[self.contact_1.id, self.contact_2.id, False]).sorted('id')
|
||||
|
||||
# assert initial data, ensure we did not break base behavior
|
||||
for lead in test_leads:
|
||||
self.assertFalse(lead.my_activity_date_deadline)
|
||||
search_res = self.env['crm.lead'].search([('id', 'in', test_leads.ids)], limit=5, offset=0, order='id ASC')
|
||||
self.assertEqual(search_res.ids, test_leads[:5].ids)
|
||||
search_res = self.env['crm.lead'].search([('id', 'in', test_leads.ids)], limit=5, offset=5, order='id ASC')
|
||||
self.assertEqual(search_res.ids, test_leads[5:10].ids)
|
||||
|
||||
# Let's schedule some activities for "myself" and "my bro"and check those are correctly computed
|
||||
# LEAD NUMBER DEADLINE (MY) PRIORITY LATE DEADLINE (MY)
|
||||
# 0 +2D 0 +2D
|
||||
# 1 -1D 1 +2D
|
||||
# 2 -2D 2 +2D
|
||||
# 3 -1D 0 -1D
|
||||
# 4 -2D 1 -2D
|
||||
# 5 +2D 2 +2D
|
||||
# 6+ / 0/1/2/0 /
|
||||
today = date.today()
|
||||
deadline_in2d, deadline_in1d = today + timedelta(days=2), today + timedelta(days=1)
|
||||
deadline_was2d, deadline_was1d = today + timedelta(days=-2), today + timedelta(days=-1)
|
||||
deadlines_my = [deadline_in2d, deadline_was1d, deadline_was2d, deadline_was1d, deadline_was2d,
|
||||
deadline_in2d, False, False, False, False]
|
||||
deadlines_gl = [deadline_in1d, deadline_was1d, deadline_was2d, deadline_was1d, deadline_was2d,
|
||||
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.invalidate_recordset()
|
||||
|
||||
expected_ids_asc = [2, 4, 1, 3, 5, 0, 8, 7, 9, 6]
|
||||
expected_leads_asc = self.env['crm.lead'].browse([test_leads[lid].id for lid in expected_ids_asc])
|
||||
expected_ids_desc = [5, 0, 1, 3, 2, 4, 8, 7, 9, 6]
|
||||
expected_leads_desc = self.env['crm.lead'].browse([test_leads[lid].id for lid in expected_ids_desc])
|
||||
|
||||
for idx, lead in enumerate(test_leads):
|
||||
self.assertEqual(lead.my_activity_date_deadline, deadlines_my[idx])
|
||||
self.assertEqual(lead.activity_date_deadline, deadlines_gl[idx], 'Fail at %s' % idx)
|
||||
|
||||
# Let's go for a first batch of search
|
||||
_order = 'my_activity_date_deadline ASC, %s' % default_order
|
||||
_domain = [('id', 'in', test_leads.ids)]
|
||||
|
||||
search_res = self.env['crm.lead'].search(_domain, limit=None, offset=0, order=_order)
|
||||
self.assertEqual(expected_leads_asc.ids, search_res.ids)
|
||||
search_res = self.env['crm.lead'].search(_domain, limit=4, offset=0, order=_order)
|
||||
self.assertEqual(expected_leads_asc[:4].ids, search_res.ids)
|
||||
search_res = self.env['crm.lead'].search(_domain, limit=4, offset=3, order=_order)
|
||||
self.assertEqual(expected_leads_asc[3:7].ids, search_res.ids)
|
||||
search_res = self.env['crm.lead'].search(_domain, limit=None, offset=3, order=_order)
|
||||
self.assertEqual(expected_leads_asc[3:].ids, search_res.ids)
|
||||
|
||||
_order = 'my_activity_date_deadline DESC, %s' % default_order
|
||||
search_res = self.env['crm.lead'].search(_domain, limit=None, offset=0, order=_order)
|
||||
self.assertEqual(expected_leads_desc.ids, search_res.ids)
|
||||
search_res = self.env['crm.lead'].search(_domain, limit=4, offset=0, order=_order)
|
||||
self.assertEqual(expected_leads_desc[:4].ids, search_res.ids)
|
||||
search_res = self.env['crm.lead'].search(_domain, limit=4, offset=3, order=_order)
|
||||
self.assertEqual(expected_leads_desc[3:7].ids, search_res.ids)
|
||||
search_res = self.env['crm.lead'].search(_domain, limit=None, offset=3, order=_order)
|
||||
self.assertEqual(expected_leads_desc[3:].ids, search_res.ids)
|
||||
|
||||
def test_crm_activity_recipients(self):
|
||||
""" This test case checks
|
||||
- no internal subtype followed by client
|
||||
- activity subtype are not default ones
|
||||
- only activity followers are recipients when this kind of activity is logged
|
||||
"""
|
||||
# Add explicitly a the client as follower
|
||||
self.lead_1.message_subscribe(partner_ids=[self.contact_1.id])
|
||||
|
||||
# Check the client is not follower of any internal subtype
|
||||
internal_subtypes = self.lead_1.message_follower_ids.filtered(lambda fol: fol.partner_id == self.contact_1).mapped('subtype_ids').filtered(lambda subtype: subtype.internal)
|
||||
self.assertFalse(internal_subtypes)
|
||||
|
||||
# Add sale manager as follower of default subtypes
|
||||
self.lead_1.message_subscribe(partner_ids=[self.user_sales_manager.partner_id.id], subtype_ids=[self.env.ref('mail.mt_activities').id, self.env.ref('mail.mt_comment').id])
|
||||
|
||||
activity = self.env['mail.activity'].with_user(self.user_sales_leads).create({
|
||||
'activity_type_id': self.activity_type_1.id,
|
||||
'note': 'Content of the activity to log',
|
||||
'res_id': self.lead_1.id,
|
||||
'res_model_id': self.env.ref('crm.model_crm_lead').id,
|
||||
})
|
||||
activity._onchange_activity_type_id()
|
||||
self.assertEqual(self.lead_1.activity_type_id, self.activity_type_1)
|
||||
self.assertEqual(self.lead_1.activity_summary, self.activity_type_1.summary)
|
||||
# self.assertEqual(self.lead.activity_date_deadline, self.activity_type_1.summary)
|
||||
|
||||
# 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)
|
||||
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'))
|
||||
|
||||
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)
|
||||
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_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(fields.Datetime.from_string(self.lead.activity_date_deadline), datetime.now() + timedelta(days=activity.activity_type_id.days))
|
||||
|
||||
activity.write({
|
||||
'activity_type_id': self.activity_type_2.id,
|
||||
'summary': '',
|
||||
'note': 'Content of the activity to log',
|
||||
})
|
||||
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(fields.Datetime.from_string(self.lead.activity_date_deadline), datetime.now() + timedelta(days=activity.activity_type_id.days))
|
||||
|
||||
activity.action_done()
|
||||
|
||||
# Check the next activity on the lead has been removed
|
||||
self.assertFalse(self.lead_1.activity_type_id)
|
||||
986
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead.py
Normal file
986
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead.py
Normal file
|
|
@ -0,0 +1,986 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from freezegun import freeze_time
|
||||
from unittest.mock import patch
|
||||
|
||||
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.phone_validation.tools.phone_validation import phone_format
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import Form, tagged, users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('lead_internals')
|
||||
class TestCRMLead(TestCrmCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCRMLead, cls).setUpClass()
|
||||
cls.country_ref = cls.env.ref('base.be')
|
||||
cls.test_email = '"Test Email" <test.email@example.com>'
|
||||
cls.test_phone = '0485112233'
|
||||
|
||||
def assertLeadAddress(self, lead, street, street2, city, lead_zip, state, country):
|
||||
self.assertEqual(lead.street, street)
|
||||
self.assertEqual(lead.street2, street2)
|
||||
self.assertEqual(lead.city, city)
|
||||
self.assertEqual(lead.zip, lead_zip)
|
||||
self.assertEqual(lead.state_id, state)
|
||||
self.assertEqual(lead.country_id, country)
|
||||
|
||||
@users('user_sales_leads')
|
||||
def test_crm_lead_contact_fields_mixed(self):
|
||||
""" Test mixed configuration from partner: both user input and coming
|
||||
from partner, in order to ensure we do not loose information or make
|
||||
it incoherent. """
|
||||
lead_data = {
|
||||
'name': 'TestMixed',
|
||||
'partner_id': self.contact_1.id,
|
||||
# address
|
||||
'country_id': self.country_ref.id,
|
||||
# other contact fields
|
||||
'function': 'Parmesan Rappeur',
|
||||
'lang_id': self.lang_fr.id,
|
||||
# specific contact fields
|
||||
'email_from': self.test_email,
|
||||
'phone': self.test_phone,
|
||||
}
|
||||
lead = self.env['crm.lead'].create(lead_data)
|
||||
# classic
|
||||
self.assertEqual(lead.name, "TestMixed")
|
||||
# address
|
||||
self.assertLeadAddress(lead, False, False, False, False, self.env['res.country.state'], self.country_ref)
|
||||
# other contact fields
|
||||
for fname in set(PARTNER_FIELDS_TO_SYNC) - set(['function', 'lang']):
|
||||
self.assertEqual(lead[fname], self.contact_1[fname], 'No user input -> take from contact for field %s' % fname)
|
||||
self.assertEqual(lead.function, 'Parmesan Rappeur', 'User input should take over partner value')
|
||||
self.assertEqual(lead.lang_id, self.lang_fr)
|
||||
# specific contact fields
|
||||
self.assertEqual(lead.partner_name, self.contact_company_1.name)
|
||||
self.assertEqual(lead.contact_name, self.contact_1.name)
|
||||
self.assertEqual(lead.email_from, self.test_email)
|
||||
self.assertEqual(lead.phone, self.test_phone)
|
||||
|
||||
# update a single address fields -> only those are updated
|
||||
lead.write({'street': 'Super Street', 'city': 'Super City'})
|
||||
self.assertLeadAddress(lead, 'Super Street', False, 'Super City', False, self.env['res.country.state'], self.country_ref)
|
||||
|
||||
# change partner -> whole address updated
|
||||
lead.write({'partner_id': self.contact_company_1.id})
|
||||
for fname in PARTNER_ADDRESS_FIELDS_TO_SYNC:
|
||||
self.assertEqual(lead[fname], self.contact_company_1[fname])
|
||||
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_creation_no_partner(self):
|
||||
lead_data = {
|
||||
'name': 'Test',
|
||||
'country_id': self.country_ref.id,
|
||||
'email_from': self.test_email,
|
||||
'phone': self.test_phone,
|
||||
}
|
||||
lead = self.env['crm.lead'].new(lead_data)
|
||||
# get the street should not trigger cache miss
|
||||
lead.street
|
||||
# Create the lead and the write partner_id = False: country should remain
|
||||
lead = self.env['crm.lead'].create(lead_data)
|
||||
self.assertEqual(lead.country_id, self.country_ref, "Country should be set on the lead")
|
||||
self.assertEqual(lead.email_from, self.test_email)
|
||||
self.assertEqual(lead.phone, self.test_phone)
|
||||
lead.partner_id = False
|
||||
self.assertEqual(lead.country_id, self.country_ref, "Country should still be set on the lead")
|
||||
self.assertEqual(lead.email_from, self.test_email)
|
||||
self.assertEqual(lead.phone, self.test_phone)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_creation_partner(self):
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'TestLead',
|
||||
'contact_name': 'Raoulette TestContact',
|
||||
'email_from': '"Raoulette TestContact" <raoulette@test.example.com>',
|
||||
})
|
||||
self.assertEqual(lead.type, 'lead')
|
||||
self.assertEqual(lead.user_id, self.user_sales_manager)
|
||||
self.assertEqual(lead.team_id, self.sales_team_1)
|
||||
self.assertEqual(lead.stage_id, self.stage_team1_1)
|
||||
self.assertEqual(lead.contact_name, 'Raoulette TestContact')
|
||||
self.assertEqual(lead.email_from, '"Raoulette TestContact" <raoulette@test.example.com>')
|
||||
self.assertEqual(self.contact_1.lang, self.env.user.lang)
|
||||
|
||||
# update to a partner, should udpate address
|
||||
lead.write({'partner_id': self.contact_1.id})
|
||||
self.assertEqual(lead.partner_name, self.contact_company_1.name)
|
||||
self.assertEqual(lead.contact_name, self.contact_1.name)
|
||||
self.assertEqual(lead.email_from, self.contact_1.email)
|
||||
self.assertEqual(lead.street, self.contact_1.street)
|
||||
self.assertEqual(lead.city, self.contact_1.city)
|
||||
self.assertEqual(lead.zip, self.contact_1.zip)
|
||||
self.assertEqual(lead.country_id, self.contact_1.country_id)
|
||||
self.assertEqual(lead.lang_id.code, self.contact_1.lang)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_creation_partner_address(self):
|
||||
""" Test that an address erases all lead address fields (avoid mixed addresses) """
|
||||
other_country = self.env.ref('base.fr')
|
||||
empty_partner = self.env['res.partner'].create({
|
||||
'name': 'Empty partner',
|
||||
'country_id': other_country.id,
|
||||
})
|
||||
lead_data = {
|
||||
'name': 'Test',
|
||||
'street': 'My street',
|
||||
'street2': 'My street',
|
||||
'city': 'My city',
|
||||
'zip': 'test@odoo.com',
|
||||
'state_id': self.env['res.country.state'].create({
|
||||
'name': 'My state',
|
||||
'country_id': self.country_ref.id,
|
||||
'code': 'MST',
|
||||
}).id,
|
||||
'country_id': self.country_ref.id,
|
||||
}
|
||||
lead = self.env['crm.lead'].create(lead_data)
|
||||
lead.partner_id = empty_partner
|
||||
# PARTNER_ADDRESS_FIELDS_TO_SYNC
|
||||
self.assertEqual(lead.street, empty_partner.street, "Street should be sync from the Partner")
|
||||
self.assertEqual(lead.street2, empty_partner.street2, "Street 2 should be sync from the Partner")
|
||||
self.assertEqual(lead.city, empty_partner.city, "City should be sync from the Partner")
|
||||
self.assertEqual(lead.zip, empty_partner.zip, "Zip should be sync from the Partner")
|
||||
self.assertEqual(lead.state_id, empty_partner.state_id, "State should be sync from the Partner")
|
||||
self.assertEqual(lead.country_id, empty_partner.country_id, "Country should be sync from the Partner")
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_creation_partner_company(self):
|
||||
""" Test lead / partner synchronization involving company details """
|
||||
# Test that partner_name (company name) is the partner name if partner is company
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'TestLead',
|
||||
'partner_id': self.contact_company.id,
|
||||
})
|
||||
self.assertEqual(lead.contact_name, False,
|
||||
"Lead contact name should be Falsy when dealing with companies")
|
||||
self.assertEqual(lead.partner_name, self.contact_company.name,
|
||||
"Lead company name should be set to partner name if partner is a company")
|
||||
# Test that partner_name (company name) is the partner company name if partner is an individual
|
||||
self.contact_company.write({'is_company': False})
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'TestLead',
|
||||
'partner_id': self.contact_company.id,
|
||||
})
|
||||
self.assertEqual(lead.contact_name, self.contact_company.name,
|
||||
"Lead contact name should be set to partner name if partner is not a company")
|
||||
self.assertEqual(lead.partner_name, self.contact_company.company_name,
|
||||
"Lead company name should be set to company name if partner is not a company")
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_creation_partner_no_address(self):
|
||||
""" Test that an empty address on partner does not void its lead values """
|
||||
empty_partner = self.env['res.partner'].create({
|
||||
'name': 'Empty partner',
|
||||
'is_company': True,
|
||||
'lang': 'en_US',
|
||||
'mobile': '123456789',
|
||||
'title': self.env.ref('base.res_partner_title_mister').id,
|
||||
'function': 'My function',
|
||||
})
|
||||
lead_data = {
|
||||
'name': 'Test',
|
||||
'contact_name': 'Test',
|
||||
'street': 'My street',
|
||||
'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)
|
||||
lead.partner_id = empty_partner
|
||||
# SPECIFIC FIELDS
|
||||
self.assertEqual(lead.contact_name, lead_data['contact_name'], "Contact should remain")
|
||||
self.assertEqual(lead.email_from, lead_data['email_from'], "Email From should keep its initial value")
|
||||
self.assertEqual(lead.partner_name, empty_partner.name, "Partner name should be set as contact is a company")
|
||||
# PARTNER_ADDRESS_FIELDS_TO_SYNC
|
||||
self.assertEqual(lead.street, lead_data['street'], "Street should remain since partner has no address field set")
|
||||
self.assertEqual(lead.street2, False, "Street2 should remain since partner has no address field set")
|
||||
self.assertEqual(lead.country_id, self.country_ref, "Country should remain since partner has no address field set")
|
||||
self.assertEqual(lead.city, False, "City should remain since partner has no address field set")
|
||||
self.assertEqual(lead.zip, False, "Zip should remain since partner has no address field set")
|
||||
self.assertEqual(lead.state_id, self.env['res.country.state'], "State should remain since partner has no address field set")
|
||||
# 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")
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_create_pipe_data(self):
|
||||
""" Test creation pipe data: user, team, stage, depending on some default
|
||||
configuration. """
|
||||
# gateway-like creation: no user, no team, generic stage
|
||||
lead = self.env['crm.lead'].with_context(default_user_id=False).create({
|
||||
'name': 'Test',
|
||||
'contact_name': 'Test Contact',
|
||||
'email_from': self.test_email,
|
||||
'phone': self.test_phone,
|
||||
})
|
||||
self.assertEqual(lead.user_id, self.env['res.users'])
|
||||
self.assertEqual(lead.team_id, self.env['crm.team'])
|
||||
self.assertEqual(lead.stage_id, self.stage_gen_1)
|
||||
|
||||
# pipe creation: current user's best team and default stage
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'Test',
|
||||
'contact_name': 'Test Contact',
|
||||
'email_from': self.test_email,
|
||||
'phone': self.test_phone,
|
||||
})
|
||||
self.assertEqual(lead.user_id, self.user_sales_manager)
|
||||
self.assertEqual(lead.team_id, self.sales_team_1)
|
||||
self.assertEqual(lead.stage_id, self.stage_team1_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_currency_sync(self):
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'Lead 1',
|
||||
'company_id': self.company_main.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})
|
||||
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):
|
||||
# Test for one won lead
|
||||
stage_team1_won2 = self.env['crm.stage'].create({
|
||||
'name': 'Won2',
|
||||
'sequence': 75,
|
||||
'team_id': self.sales_team_1.id,
|
||||
'is_won': True,
|
||||
})
|
||||
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
|
||||
self.assertTrue(won_lead.date_closed)
|
||||
self.assertFalse(other_lead.date_closed)
|
||||
|
||||
# multi update
|
||||
leads = won_lead + other_lead
|
||||
with freeze_time('2020-02-02 18:00'):
|
||||
leads.stage_id = stage_team1_won2
|
||||
self.assertEqual(won_lead.date_closed, old_date_closed, 'Should not change date')
|
||||
self.assertEqual(other_lead.date_closed, datetime(2020, 2, 2, 18, 0, 0))
|
||||
|
||||
# back to open stage
|
||||
leads.write({'stage_id': self.stage_team1_2.id})
|
||||
self.assertFalse(won_lead.date_closed)
|
||||
self.assertFalse(other_lead.date_closed)
|
||||
|
||||
# close with lost
|
||||
with freeze_time('2020-02-02 18:00'):
|
||||
leads.action_set_lost()
|
||||
self.assertEqual(won_lead.date_closed, datetime(2020, 2, 2, 18, 0, 0))
|
||||
self.assertEqual(other_lead.date_closed, datetime(2020, 2, 2, 18, 0, 0))
|
||||
|
||||
@users('user_sales_leads')
|
||||
@freeze_time("2012-01-14")
|
||||
def test_crm_lead_lost_date_closed(self):
|
||||
lead = self.lead_1.with_env(self.env)
|
||||
self.assertFalse(lead.date_closed, "Initially, closed date is not set")
|
||||
# Mark the lead as lost
|
||||
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_partner_sync(self):
|
||||
lead, partner = self.lead_1.with_user(self.env.user), self.contact_2
|
||||
partner_email, partner_phone = self.contact_2.email, self.contact_2.phone
|
||||
lead.partner_id = partner
|
||||
|
||||
# email & phone must be automatically set on the lead
|
||||
lead.partner_id = partner
|
||||
self.assertEqual(lead.email_from, partner_email)
|
||||
self.assertEqual(lead.phone, partner_phone)
|
||||
|
||||
# writing on the lead field must change the partner field
|
||||
lead.email_from = '"John Zoidberg" <john.zoidberg@test.example.com>'
|
||||
lead.phone = '+1 202 555 7799'
|
||||
self.assertEqual(partner.email, '"John Zoidberg" <john.zoidberg@test.example.com>')
|
||||
self.assertEqual(partner.email_normalized, 'john.zoidberg@test.example.com')
|
||||
self.assertEqual(partner.phone, '+1 202 555 7799')
|
||||
|
||||
# writing on the partner must change the lead values
|
||||
partner.email = partner_email
|
||||
partner.phone = '+1 202 555 6666'
|
||||
self.assertEqual(lead.email_from, partner_email)
|
||||
self.assertEqual(lead.phone, '+1 202 555 6666')
|
||||
|
||||
# resetting lead values also resets partner
|
||||
lead.email_from, lead.phone = False, False
|
||||
self.assertFalse(partner.email)
|
||||
self.assertFalse(partner.email_normalized)
|
||||
self.assertFalse(partner.phone)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_partner_sync_email_phone(self):
|
||||
""" Specifically test synchronize between a lead and its partner about
|
||||
phone and email fields. Phone especially has some corner cases due to
|
||||
automatic formatting (notably with onchange in form view). """
|
||||
lead, partner = self.lead_1.with_user(self.env.user), self.contact_2
|
||||
# This is a type == 'lead', not a type == 'opportunity'
|
||||
# {'invisible': ['|', ('type', '=', 'opportunity'), ('is_partner_visible', '=', False)]}
|
||||
# lead.is_partner_visible = bool(lead.type == 'opportunity' or lead.partner_id or is_debug_mode)
|
||||
# Hence, debug mode required for `partner_id` to be visible
|
||||
with self.debug_mode():
|
||||
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_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
|
||||
# as well as mobile (who does not trigger the reverse sync)
|
||||
lead_form.partner_id = partner
|
||||
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)
|
||||
|
||||
lead_form.save()
|
||||
self.assertEqual(partner.phone, partner_phone,
|
||||
'Lead / Partner: partner values sent to lead')
|
||||
self.assertEqual(lead.email_from, partner_email,
|
||||
'Lead / Partner: partner values sent to lead')
|
||||
self.assertEqual(lead.email_normalized, partner_email_normalized,
|
||||
'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,
|
||||
'Lead: phone_sanitized computed field on mobile')
|
||||
|
||||
# for email_from, if only formatting differs, warning should not appear and
|
||||
# email on partner should not be updated
|
||||
lead_form.email_from = '"Hermes Conrad" <%s>' % partner_email_normalized
|
||||
self.assertFalse(lead_form.partner_email_update)
|
||||
lead_form.save()
|
||||
self.assertEqual(partner.email, partner_email)
|
||||
|
||||
# for phone, if only formatting differs, warning should not appear and
|
||||
# phone on partner should not be updated
|
||||
lead_form.phone = partner_phone_sanitized
|
||||
self.assertFalse(lead_form.partner_phone_update)
|
||||
lead_form.save()
|
||||
self.assertEqual(partner.phone, partner_phone)
|
||||
|
||||
# LEAD/PARTNER SYNC: lead updates partner
|
||||
new_email = '"John Zoidberg" <john.zoidberg@test.example.com>'
|
||||
new_email_normalized = 'john.zoidberg@test.example.com'
|
||||
lead_form.email_from = new_email
|
||||
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")
|
||||
lead_form.phone = new_phone
|
||||
self.assertEqual(lead_form.phone, new_phone_formatted)
|
||||
self.assertTrue(lead_form.partner_email_update)
|
||||
self.assertTrue(lead_form.partner_phone_update)
|
||||
|
||||
lead_form.save()
|
||||
self.assertEqual(partner.email, new_email)
|
||||
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_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.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,
|
||||
'Partner sanitized should be computed on mobile')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_partner_sync_email_phone_corner_cases(self):
|
||||
""" Test corner cases of email and phone sync (False versus '', formatting
|
||||
differences, wrong input, ...) """
|
||||
test_email = 'amy.wong@test.example.com'
|
||||
lead = self.lead_1.with_user(self.env.user)
|
||||
lead.write({'phone': False}) # reset phone to start with all Falsy values
|
||||
contact = self.env['res.partner'].create({
|
||||
'name': 'NoContact Partner',
|
||||
'phone': '',
|
||||
'email': '',
|
||||
'mobile': '',
|
||||
})
|
||||
|
||||
# This is a type == 'lead', not a type == 'opportunity'
|
||||
# {'invisible': ['|', ('type', '=', 'opportunity'), ('is_partner_visible', '=', False)]}
|
||||
# lead.is_partner_visible = bool(lead.type == 'opportunity' or lead.partner_id or is_debug_mode)
|
||||
# Hence, debug mode required for `partner_id` to be visible
|
||||
with self.debug_mode():
|
||||
lead_form = Form(lead)
|
||||
self.assertEqual(lead_form.email_from, test_email)
|
||||
self.assertFalse(lead_form.partner_email_update)
|
||||
self.assertFalse(lead_form.partner_phone_update)
|
||||
|
||||
# email: False versus empty string
|
||||
lead_form.partner_id = contact
|
||||
self.assertTrue(lead_form.partner_email_update)
|
||||
self.assertFalse(lead_form.partner_phone_update)
|
||||
lead_form.email_from = ''
|
||||
self.assertFalse(lead_form.partner_email_update)
|
||||
lead_form.email_from = False
|
||||
self.assertFalse(lead_form.partner_email_update)
|
||||
|
||||
# phone: False versus empty string
|
||||
lead_form.phone = '+1 202-555-0888'
|
||||
self.assertFalse(lead_form.partner_email_update)
|
||||
self.assertTrue(lead_form.partner_phone_update)
|
||||
lead_form.phone = ''
|
||||
self.assertFalse(lead_form.partner_phone_update)
|
||||
lead_form.phone = False
|
||||
self.assertFalse(lead_form.partner_phone_update)
|
||||
|
||||
# email/phone: formatting should not trigger ribbon
|
||||
lead.write({
|
||||
'email_from': '"My Name" <%s>' % test_email,
|
||||
'phone': '+1 202-555-0888',
|
||||
})
|
||||
contact.write({
|
||||
'email': '"My Name" <%s>' % test_email,
|
||||
'phone': '+1 202-555-0888',
|
||||
})
|
||||
|
||||
lead_form = Form(lead)
|
||||
self.assertFalse(lead_form.partner_email_update)
|
||||
self.assertFalse(lead_form.partner_phone_update)
|
||||
lead_form.partner_id = contact
|
||||
self.assertFalse(lead_form.partner_email_update)
|
||||
self.assertFalse(lead_form.partner_phone_update)
|
||||
lead_form.email_from = '"Another Name" <%s>' % test_email # same email normalized
|
||||
self.assertFalse(lead_form.partner_email_update, 'Formatting-only change should not trigger write')
|
||||
self.assertFalse(lead_form.partner_phone_update, 'Formatting-only change should not trigger write')
|
||||
lead_form.phone = '2025550888' # same number but another format
|
||||
self.assertFalse(lead_form.partner_email_update, 'Formatting-only change should not trigger write')
|
||||
self.assertFalse(lead_form.partner_phone_update, 'Formatting-only change should not trigger write')
|
||||
|
||||
# wrong value are also propagated
|
||||
lead_form.phone = '666 789456789456789456'
|
||||
self.assertTrue(lead_form.partner_phone_update)
|
||||
|
||||
# test country propagation allowing to correctly compute sanitized numbers
|
||||
# by adding missing relevant information from contact
|
||||
be_country = self.env.ref('base.be')
|
||||
contact.write({
|
||||
'country_id': be_country.id,
|
||||
'phone': '+32456001122',
|
||||
})
|
||||
lead.write({'country_id': False})
|
||||
lead_form = Form(lead)
|
||||
lead_form.partner_id = contact
|
||||
lead_form.phone = '0456 00 11 22'
|
||||
self.assertFalse(lead_form.partner_phone_update)
|
||||
self.assertEqual(lead_form.country_id, be_country)
|
||||
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_stages(self):
|
||||
first_now = datetime(2023, 11, 6, 8, 0, 0)
|
||||
with patch.object(self.env.cr, 'now', lambda: first_now), \
|
||||
freeze_time(first_now):
|
||||
self.lead_1.write({'date_open': first_now})
|
||||
|
||||
lead = self.lead_1.with_user(self.env.user)
|
||||
self.assertEqual(lead.date_open, first_now)
|
||||
self.assertEqual(lead.team_id, self.sales_team_1)
|
||||
self.assertEqual(lead.user_id, self.user_sales_leads)
|
||||
|
||||
second_now = datetime(2023, 11, 8, 8, 0, 0)
|
||||
with patch.object(self.env.cr, 'now', lambda: second_now), \
|
||||
freeze_time(second_now):
|
||||
lead.convert_opportunity(self.contact_1)
|
||||
self.assertEqual(lead.date_open, first_now)
|
||||
self.assertEqual(lead.team_id, self.sales_team_1)
|
||||
self.assertEqual(lead.user_id, self.user_sales_leads)
|
||||
|
||||
lead.action_set_won()
|
||||
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
|
||||
|
||||
@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
|
||||
event form view) when lead is unlinked """
|
||||
lead = self.env['crm.lead'].create({'name': 'Lead With Meetings'})
|
||||
meetings = self.env['calendar.event'].create([
|
||||
{
|
||||
'name': 'Meeting 1 of Lead',
|
||||
'res_id': lead.id,
|
||||
'res_model_id': self.env['ir.model']._get_id(lead._name),
|
||||
'start': '2022-07-12 08:00:00',
|
||||
'stop': '2022-07-12 10:00:00',
|
||||
}, {
|
||||
'name': 'Meeting 2 of Lead',
|
||||
'opportunity_id': lead.id,
|
||||
'res_id': lead.id,
|
||||
'res_model_id': self.env['ir.model']._get_id(lead._name),
|
||||
'start': '2022-07-13 08:00:00',
|
||||
'stop': '2022-07-13 10:00:00',
|
||||
}
|
||||
])
|
||||
self.assertEqual(lead.calendar_event_count, 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'])
|
||||
lead.unlink()
|
||||
self.assertEqual(meetings.exists(), meetings)
|
||||
self.assertFalse(meetings.opportunity_id)
|
||||
self.assertEqual(set(meetings.mapped('res_id')), set([0]))
|
||||
self.assertEqual(set(meetings.mapped('res_model')), set([False]))
|
||||
|
||||
@users('user_sales_leads')
|
||||
def test_crm_lead_update_contact(self):
|
||||
# ensure initial data, especially for corner cases
|
||||
self.assertFalse(self.contact_company_1.phone)
|
||||
self.assertEqual(self.contact_company_1.country_id.code, "US")
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'Test',
|
||||
'country_id': self.country_ref.id,
|
||||
'email_from': self.test_email,
|
||||
'phone': self.test_phone,
|
||||
})
|
||||
self.assertEqual(lead.country_id, self.country_ref, "Country should be set on the lead")
|
||||
lead.partner_id = False
|
||||
self.assertEqual(lead.country_id, self.country_ref, "Country should still be set on the lead")
|
||||
self.assertEqual(lead.email_from, self.test_email)
|
||||
self.assertEqual(lead.phone, self.test_phone)
|
||||
self.assertEqual(lead.email_state, 'correct')
|
||||
self.assertEqual(lead.phone_state, 'correct')
|
||||
|
||||
lead.partner_id = self.contact_company_1
|
||||
self.assertEqual(lead.country_id, self.contact_company_1.country_id, "Country should still be the one set on partner")
|
||||
self.assertEqual(lead.email_from, self.contact_company_1.email)
|
||||
self.assertEqual(lead.phone, self.test_phone)
|
||||
self.assertEqual(lead.email_state, 'correct')
|
||||
# currently we keep phone as partner as a void one -> may lead to inconsistencies
|
||||
self.assertEqual(lead.phone_state, 'incorrect', "Belgian phone with US country -> considered as incorrect")
|
||||
|
||||
lead.email_from = 'broken'
|
||||
lead.phone = 'alsobroken'
|
||||
self.assertEqual(lead.email_state, 'incorrect')
|
||||
self.assertEqual(lead.phone_state, 'incorrect')
|
||||
self.assertEqual(self.contact_company_1.email, 'broken')
|
||||
self.assertEqual(self.contact_company_1.phone, 'alsobroken')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_lead_update_dates(self):
|
||||
""" Test date_open / date_last_stage_update update, check those dates
|
||||
are not erased too often """
|
||||
first_now = datetime(2023, 11, 6, 8, 0, 0)
|
||||
with patch.object(self.env.cr, 'now', lambda: first_now), \
|
||||
freeze_time(first_now):
|
||||
leads = self.env['crm.lead'].create([
|
||||
{
|
||||
'email_from': 'testlead@customer.company.com',
|
||||
'name': 'Lead_1',
|
||||
'team_id': self.sales_team_1.id,
|
||||
'type': 'lead',
|
||||
'user_id': False,
|
||||
}, {
|
||||
'email_from': 'testopp@customer.company.com',
|
||||
'name': 'Opp_1',
|
||||
'type': 'opportunity',
|
||||
'user_id': self.user_sales_salesman.id,
|
||||
},
|
||||
])
|
||||
leads.flush_recordset()
|
||||
for lead in leads:
|
||||
self.assertEqual(lead.date_last_stage_update, first_now,
|
||||
"Stage updated at create time with default value")
|
||||
self.assertEqual(lead.stage_id, self.stage_team1_1)
|
||||
self.assertEqual(lead.team_id, self.sales_team_1)
|
||||
self.assertFalse(leads[0].date_open, "No user -> no assign date")
|
||||
self.assertFalse(leads[0].user_id)
|
||||
self.assertEqual(leads[1].date_open, first_now, "Default user assigned")
|
||||
self.assertEqual(leads[1].user_id, self.user_sales_salesman, "Default user assigned")
|
||||
|
||||
# changing user_id may change team_id / stage_id; update date_open and
|
||||
# maybe date_last_stage_update
|
||||
updated_time = datetime(2023, 11, 23, 8, 0, 0)
|
||||
with patch.object(self.env.cr, 'now', lambda: updated_time), \
|
||||
freeze_time(updated_time):
|
||||
leads.write({"user_id": self.user_sales_salesman.id})
|
||||
leads.flush_recordset()
|
||||
for lead in leads:
|
||||
self.assertEqual(lead.stage_id, self.stage_team1_1)
|
||||
self.assertEqual(lead.team_id, self.sales_team_1)
|
||||
self.assertEqual(
|
||||
leads[0].date_last_stage_update, first_now,
|
||||
'Setting same stage when changing user_id, should not update')
|
||||
self.assertEqual(
|
||||
leads[0].date_open, updated_time,
|
||||
'User assigned -> assign date updated')
|
||||
self.assertEqual(
|
||||
leads[1].date_last_stage_update, first_now,
|
||||
'Setting same stage when changing user_id, should not update')
|
||||
self.assertEqual(
|
||||
leads[1].date_open, updated_time,
|
||||
'Should not update date_open, was already the same user_id, but done in batch so ...')
|
||||
|
||||
# set won changes stage -> update date_last_stage_update
|
||||
newer_time = datetime(2023, 11, 26, 8, 0, 0)
|
||||
with patch.object(self.env.cr, 'now', lambda: newer_time), \
|
||||
freeze_time(newer_time):
|
||||
leads[1].action_set_won()
|
||||
leads[1].flush_recordset()
|
||||
self.assertEqual(
|
||||
leads[1].date_last_stage_update, newer_time,
|
||||
'Mark as won updates stage hence stage update date')
|
||||
self.assertEqual(leads[1].stage_id, self.stage_gen_won)
|
||||
|
||||
# merge may change user_id and then may change team_id / stage_id; in this
|
||||
# case no real value change is happening
|
||||
last_time = datetime(2023, 11, 29, 8, 0, 0)
|
||||
with patch.object(self.env.cr, 'now', lambda: last_time), \
|
||||
freeze_time(last_time):
|
||||
leads.merge_opportunity(
|
||||
user_id=self.user_sales_salesman.id,
|
||||
auto_unlink=False,
|
||||
)
|
||||
leads.flush_recordset()
|
||||
self.assertEqual(leads[0].date_last_stage_update, first_now)
|
||||
self.assertEqual(leads[0].date_open, updated_time)
|
||||
self.assertEqual(leads[0].stage_id, self.stage_team1_1)
|
||||
self.assertEqual(leads[0].team_id, self.sales_team_1)
|
||||
self.assertEqual(
|
||||
leads[1].date_last_stage_update, newer_time,
|
||||
'Should not rewrite when setting same stage')
|
||||
self.assertEqual(
|
||||
leads[1].date_open, updated_time,
|
||||
'Should not rewrite when setting same user_id')
|
||||
self.assertEqual(leads[1].stage_id, self.stage_gen_won)
|
||||
self.assertEqual(leads[1].team_id, self.sales_team_1)
|
||||
self.assertEqual(leads[1].user_id, self.user_sales_salesman)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_team_alias(self):
|
||||
new_team = self.env['crm.team'].create({
|
||||
'name': 'TestAlias',
|
||||
'use_leads': True,
|
||||
'use_opportunities': True,
|
||||
'alias_name': 'test.alias'
|
||||
})
|
||||
self.assertEqual(new_team.alias_id.alias_name, 'test.alias')
|
||||
self.assertEqual(new_team.alias_name, 'test.alias')
|
||||
|
||||
new_team.write({
|
||||
'use_leads': False,
|
||||
'use_opportunities': False,
|
||||
})
|
||||
# self.assertFalse(new_team.alias_id.alias_name)
|
||||
# self.assertFalse(new_team.alias_name)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_crm_team_alias_helper(self):
|
||||
"""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._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',
|
||||
'use_leads': True,
|
||||
'member_ids': [(6, 0, [self.env.user.id])],
|
||||
}, {
|
||||
'name': 'TeamLeads',
|
||||
'use_leads': True,
|
||||
'member_ids': [],
|
||||
}, {
|
||||
'name': 'UserTeamOpportunities',
|
||||
'use_leads': False,
|
||||
'member_ids': [(6, 0, [self.env.user.id])],
|
||||
}, {
|
||||
'name': 'TeamOpportunities',
|
||||
'use_leads': False,
|
||||
'member_ids': [],
|
||||
}])
|
||||
|
||||
self.env['crm.lead'].create([{
|
||||
'name': 'LeadOurTeam',
|
||||
'team_id': user_team_leads.id,
|
||||
'type': 'lead',
|
||||
}, {
|
||||
'name': 'LeadTeam',
|
||||
'team_id': team_leads.id,
|
||||
'type': 'lead',
|
||||
}, {
|
||||
'name': 'OpportunityOurTeam',
|
||||
'team_id': user_team_opport.id,
|
||||
'type': 'opportunity',
|
||||
}, {
|
||||
'name': 'OpportunityTeam',
|
||||
'team_id': team_opport.id,
|
||||
'type': 'opportunity',
|
||||
}])
|
||||
self.env['crm.lead'].with_user(self.user_sales_manager_mc).create({
|
||||
'name': 'LeadOtherComp',
|
||||
'team_id': team_other_comp.id,
|
||||
'type': 'lead',
|
||||
'company_id': self.company_2.id,
|
||||
})
|
||||
|
||||
# archive our team one by one and check that we have the correct help message
|
||||
teams = [user_team_leads, team_leads, user_team_opport, team_opport, team_other_comp]
|
||||
for team in teams:
|
||||
team.alias_id.sudo().write({'alias_name': team.name})
|
||||
|
||||
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(""))
|
||||
else:
|
||||
self.assertNotIn(f"<a href='mailto:{team_mail}'>{team_mail}</a>", self.env['crm.lead'].sudo().get_empty_list_help(""))
|
||||
team.active = False
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def test_mailgateway(self):
|
||||
new_lead = self.format_and_process(
|
||||
INCOMING_EMAIL,
|
||||
'unknown.sender@test.example.com',
|
||||
'%s@%s' % (self.sales_team_1.alias_name, self.alias_domain),
|
||||
subject='Delivery cost inquiry',
|
||||
target_model='crm.lead',
|
||||
)
|
||||
self.assertEqual(new_lead.email_from, 'unknown.sender@test.example.com')
|
||||
self.assertFalse(new_lead.partner_id)
|
||||
self.assertEqual(new_lead.name, 'Delivery cost inquiry')
|
||||
|
||||
message = new_lead.with_user(self.user_sales_manager).message_post(
|
||||
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):
|
||||
lead_1 = self.env['crm.lead'].create({
|
||||
'name': 'Lead 1',
|
||||
'country_id': self.env.ref('base.be').id,
|
||||
'phone': '+32485001122',
|
||||
})
|
||||
lead_2 = self.env['crm.lead'].create({
|
||||
'name': 'Lead 2',
|
||||
'country_id': self.env.ref('base.be').id,
|
||||
'phone': '0032485001122',
|
||||
})
|
||||
lead_3 = self.env['crm.lead'].create({
|
||||
'name': 'Lead 3',
|
||||
'country_id': self.env.ref('base.be').id,
|
||||
'phone': 'hello',
|
||||
})
|
||||
lead_4 = self.env['crm.lead'].create({
|
||||
'name': 'Lead 3',
|
||||
'country_id': self.env.ref('base.be').id,
|
||||
'phone': '+32485112233',
|
||||
})
|
||||
|
||||
# search term containing less than 3 characters should throw an error (some currently not working)
|
||||
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 ')])
|
||||
with self.assertRaises(UserError):
|
||||
self.env['crm.lead'].search([('phone_mobile_search', 'like', 'c')])
|
||||
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', '5')])
|
||||
with self.assertRaises(UserError):
|
||||
self.env['crm.lead'].search([('phone_mobile_search', 'like', '42')])
|
||||
|
||||
# + / 00 prefixes do not block search
|
||||
self.assertEqual(lead_1 + lead_2, self.env['crm.lead'].search([
|
||||
('phone_mobile_search', 'like', '+32485001122')
|
||||
]))
|
||||
self.assertEqual(lead_1 + lead_2, self.env['crm.lead'].search([
|
||||
('phone_mobile_search', 'like', '0032485001122')
|
||||
]))
|
||||
self.assertEqual(lead_1 + lead_2, self.env['crm.lead'].search([
|
||||
('phone_mobile_search', 'like', '485001122')
|
||||
]))
|
||||
self.assertEqual(lead_1 + lead_2 + lead_4, self.env['crm.lead'].search([
|
||||
('phone_mobile_search', 'like', '1122')
|
||||
]))
|
||||
|
||||
# textual input still possible
|
||||
self.assertEqual(
|
||||
self.env['crm.lead'].search([('phone_mobile_search', 'like', 'hello')]),
|
||||
lead_3,
|
||||
'Should behave like a text field'
|
||||
)
|
||||
self.assertFalse(
|
||||
self.env['crm.lead'].search([('phone_mobile_search', 'like', 'Hello')]),
|
||||
'Should behave like a text field (case sensitive)'
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env['crm.lead'].search([('phone_mobile_search', 'ilike', 'Hello')]),
|
||||
lead_3,
|
||||
'Should behave like a text field (case insensitive)'
|
||||
)
|
||||
self.assertEqual(
|
||||
self.env['crm.lead'].search([('phone_mobile_search', 'like', 'hello123')]),
|
||||
self.env['crm.lead'],
|
||||
'Should behave like a text field'
|
||||
)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_phone_mobile_search_format(self):
|
||||
numbers = [
|
||||
# standard
|
||||
'0499223311',
|
||||
# separators
|
||||
'0499/223311', '0499/22.33.11', '0499/22 33 11', '0499/223 311',
|
||||
# international format -> currently not working
|
||||
# '+32499223311', '0032499223311',
|
||||
]
|
||||
leads = self.env['crm.lead'].create([
|
||||
{'name': 'Lead %s' % index,
|
||||
'country_id': self.env.ref('base.be').id,
|
||||
'phone': number,
|
||||
}
|
||||
for index, number in enumerate(numbers)
|
||||
])
|
||||
|
||||
self.assertEqual(leads, self.env['crm.lead'].search([
|
||||
('phone_mobile_search', 'like', '0499223311')
|
||||
]))
|
||||
self.assertEqual(leads, self.env['crm.lead'].search([
|
||||
('phone_mobile_search', 'like', '0499/223311')
|
||||
]))
|
||||
self.assertEqual(leads, self.env['crm.lead'].search([
|
||||
('phone_mobile_search', 'like', '0499/22.33.11')
|
||||
]))
|
||||
self.assertEqual(leads, self.env['crm.lead'].search([
|
||||
('phone_mobile_search', 'like', '0499/22 33 11')
|
||||
]))
|
||||
self.assertEqual(leads, self.env['crm.lead'].search([
|
||||
('phone_mobile_search', 'like', '0499/223 311')
|
||||
]))
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_phone_mobile_update(self):
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'Lead 1',
|
||||
'country_id': self.env.ref('base.us').id,
|
||||
'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]})
|
||||
self.assertFalse(lead.phone)
|
||||
self.assertEqual(lead.mobile, self.test_phone_data[1])
|
||||
self.assertEqual(lead.phone_sanitized, self.test_phone_data_sanitized[1])
|
||||
|
||||
lead.write({'phone': self.test_phone_data[1], 'mobile': self.test_phone_data[2]})
|
||||
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])
|
||||
|
||||
# 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):
|
||||
def test_address_view(self):
|
||||
self.env.company.country_id = self.env.ref('base.us')
|
||||
self.assertAddressView('crm.lead')
|
||||
565
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead_assignment.py
Normal file
565
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead_assignment.py
Normal file
|
|
@ -0,0 +1,565 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import random
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import fields
|
||||
from odoo.addons.crm.tests.common import TestLeadConvertCommon
|
||||
from odoo.tests.common import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestLeadAssignCommon(TestLeadConvertCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestLeadAssignCommon, cls).setUpClass()
|
||||
cls._switch_to_multi_membership()
|
||||
cls._switch_to_auto_assign()
|
||||
|
||||
# don't mess with existing teams, deactivate them to make tests repeatable
|
||||
cls.sales_teams = cls.sales_team_1 + cls.sales_team_convert
|
||||
cls.members = cls.sales_team_1_m1 + cls.sales_team_1_m2 + cls.sales_team_1_m3 + cls.sales_team_convert_m1 + cls.sales_team_convert_m2
|
||||
cls.env['crm.team'].search([('id', 'not in', cls.sales_teams.ids)]).write({'active': False})
|
||||
|
||||
# don't mess with existing leads, unlink those assigned to users used here to make tests
|
||||
# repeatable (archive is not sufficient because of lost leads)
|
||||
|
||||
with mute_logger('odoo.models.unlink'):
|
||||
cls.env['crm.lead'].with_context(active_test=False).search(['|', ('team_id', '=', False), ('user_id', 'in', cls.sales_teams.member_ids.ids)]).unlink()
|
||||
cls.bundle_size = 50
|
||||
cls.env['ir.config_parameter'].set_param('crm.assignment.commit.bundle', '%s' % cls.bundle_size)
|
||||
cls.env['ir.config_parameter'].set_param('crm.assignment.delay', '0')
|
||||
|
||||
def assertInitialData(self):
|
||||
self.assertEqual(self.sales_team_1.assignment_max, 75)
|
||||
self.assertEqual(self.sales_team_convert.assignment_max, 90)
|
||||
|
||||
# ensure domains
|
||||
self.assertEqual(self.sales_team_1.assignment_domain, False)
|
||||
self.assertEqual(self.sales_team_1_m1.assignment_domain, False)
|
||||
self.assertEqual(self.sales_team_1_m2.assignment_domain, "[('probability', '>=', 10)]")
|
||||
self.assertEqual(self.sales_team_1_m3.assignment_domain, "[('probability', '>=', 20)]")
|
||||
|
||||
self.assertEqual(self.sales_team_convert.assignment_domain, "[('priority', 'in', ['1', '2', '3'])]")
|
||||
self.assertEqual(self.sales_team_convert_m1.assignment_domain, "[('probability', '>=', 20)]")
|
||||
self.assertEqual(self.sales_team_convert_m2.assignment_domain, False)
|
||||
|
||||
# start afresh
|
||||
self.assertEqual(self.sales_team_1_m1.lead_month_count, 0)
|
||||
self.assertEqual(self.sales_team_1_m2.lead_month_count, 0)
|
||||
self.assertEqual(self.sales_team_1_m3.lead_month_count, 0)
|
||||
self.assertEqual(self.sales_team_convert_m1.lead_month_count, 0)
|
||||
self.assertEqual(self.sales_team_convert_m2.lead_month_count, 0)
|
||||
|
||||
|
||||
@tagged('lead_assign')
|
||||
class TestLeadAssign(TestLeadAssignCommon):
|
||||
""" Test lead assignment feature added in saas-14.2 """
|
||||
|
||||
def test_assign_configuration(self):
|
||||
now_patch = datetime(2020, 11, 2, 10, 0, 0)
|
||||
|
||||
with patch.object(fields.Datetime, 'now', return_value=now_patch):
|
||||
config = self.env['res.config.settings'].create({
|
||||
'crm_use_auto_assignment': True,
|
||||
'crm_auto_assignment_action': 'auto',
|
||||
'crm_auto_assignment_interval_number': 19,
|
||||
'crm_auto_assignment_interval_type': 'hours'
|
||||
})
|
||||
config._onchange_crm_auto_assignment_run_datetime()
|
||||
config.execute()
|
||||
self.assertTrue(self.assign_cron.active)
|
||||
self.assertEqual(self.assign_cron.nextcall, datetime(2020, 11, 2, 10, 0, 0) + relativedelta(hours=19))
|
||||
|
||||
config.write({
|
||||
'crm_auto_assignment_interval_number': 2,
|
||||
'crm_auto_assignment_interval_type': 'days'
|
||||
})
|
||||
config._onchange_crm_auto_assignment_run_datetime()
|
||||
config.execute()
|
||||
self.assertTrue(self.assign_cron.active)
|
||||
self.assertEqual(self.assign_cron.nextcall, datetime(2020, 11, 2, 10, 0, 0) + relativedelta(days=2))
|
||||
|
||||
config.write({
|
||||
'crm_auto_assignment_run_datetime': fields.Datetime.to_string(datetime(2020, 11, 1, 10, 0, 0)),
|
||||
})
|
||||
config.execute()
|
||||
self.assertTrue(self.assign_cron.active)
|
||||
self.assertEqual(self.assign_cron.nextcall, datetime(2020, 11, 1, 10, 0, 0))
|
||||
|
||||
config.write({
|
||||
'crm_auto_assignment_action': 'manual',
|
||||
})
|
||||
config.execute()
|
||||
self.assertFalse(self.assign_cron.active)
|
||||
self.assertEqual(self.assign_cron.nextcall, datetime(2020, 11, 1, 10, 0, 0))
|
||||
|
||||
config.write({
|
||||
'crm_use_auto_assignment': False,
|
||||
'crm_auto_assignment_action': 'auto',
|
||||
})
|
||||
config.execute()
|
||||
self.assertFalse(self.assign_cron.active)
|
||||
self.assertEqual(self.assign_cron.nextcall, datetime(2020, 11, 1, 10, 0, 0))
|
||||
|
||||
def test_assign_count(self):
|
||||
""" Test number of assigned leads when dealing with some existing data (leads
|
||||
or opportunities) as well as with opt-out management. """
|
||||
leads = self._create_leads_batch(
|
||||
lead_type='lead',
|
||||
user_ids=[False],
|
||||
partner_ids=[False, False, False, self.contact_1.id],
|
||||
probabilities=[30],
|
||||
count=8,
|
||||
suffix='Initial',
|
||||
)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
self.assertInitialData()
|
||||
|
||||
# archived members should not be taken into account
|
||||
self.sales_team_1_m1.action_archive()
|
||||
# assignment_max = 0 means opt_out
|
||||
self.sales_team_1_m2.assignment_max = 0
|
||||
|
||||
# assign probability to leads (bypass auto probability as purpose is not to test pls)
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
for idx, lead in enumerate(leads):
|
||||
lead.probability = idx * 10
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
self.assertEqual(leads[0].probability, 0)
|
||||
|
||||
# create exiting leads for user_sales_salesman (sales_team_1_m3, sales_team_convert_m1)
|
||||
existing_leads = self._create_leads_batch(
|
||||
lead_type='lead', user_ids=[self.user_sales_salesman.id],
|
||||
probabilities=[10],
|
||||
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.flush_recordset()
|
||||
|
||||
self.members.invalidate_model(['lead_month_count'])
|
||||
self.assertEqual(self.sales_team_1_m3.lead_month_count, 14)
|
||||
self.assertEqual(self.sales_team_convert_m1.lead_month_count, 0)
|
||||
|
||||
# re-assign existing leads, check monthly count is updated
|
||||
existing_leads[-2:]._handle_salesmen_assignment(user_ids=self.user_sales_manager.ids)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
self.members.invalidate_model(['lead_month_count'])
|
||||
self.assertEqual(self.sales_team_1_m3.lead_month_count, 12)
|
||||
|
||||
# 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})
|
||||
with self.with_user('user_sales_manager'):
|
||||
teams_data, members_data = self.sales_team_1._action_assign_leads(work_days=4)
|
||||
|
||||
Leads = self.env['crm.lead']
|
||||
|
||||
self.assertEqual(
|
||||
sorted(Leads.browse(teams_data[self.sales_team_1]['assigned']).mapped('name')),
|
||||
['TestLeadInitial_0000', 'TestLeadInitial_0001', 'TestLeadInitial_0002',
|
||||
'TestLeadInitial_0004', 'TestLeadInitial_0005', 'TestLeadInitial_0006']
|
||||
)
|
||||
self.assertEqual(
|
||||
Leads.browse(teams_data[self.sales_team_1]['merged']).mapped('name'),
|
||||
['TestLeadInitial_0003']
|
||||
)
|
||||
|
||||
self.assertEqual(len(teams_data[self.sales_team_1]['duplicates']), 1)
|
||||
|
||||
self.assertEqual(
|
||||
sorted(members_data[self.sales_team_1_m3]['assigned'].mapped('name')),
|
||||
['TestLeadInitial_0000', 'TestLeadInitial_0005']
|
||||
)
|
||||
|
||||
# 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, 14) # 15 max on 4 days (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)
|
||||
|
||||
# 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
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_assign_duplicates(self):
|
||||
""" Test assign process with duplicates on partner. Allow to ensure notably
|
||||
that de duplication is effectively performed. """
|
||||
# fix the seed and avoid randomness
|
||||
random.seed(1940)
|
||||
|
||||
leads = self._create_leads_batch(
|
||||
lead_type='lead',
|
||||
user_ids=[False],
|
||||
partner_ids=[self.contact_1.id, self.contact_2.id, False, False, False],
|
||||
count=200
|
||||
)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
self.assertInitialData()
|
||||
|
||||
# assign probability to leads (bypass auto probability as purpose is not to test pls)
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
for idx in range(0, 5):
|
||||
sliced_leads = leads[idx:len(leads):5]
|
||||
for lead in sliced_leads:
|
||||
lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2)
|
||||
# commit probability and related fields
|
||||
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)
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
self.assertEqual(len(leads), 122)
|
||||
leads_st1 = leads.filtered_domain([('team_id', '=', self.sales_team_1.id)])
|
||||
leads_stc = leads.filtered_domain([('team_id', '=', self.sales_team_convert.id)])
|
||||
|
||||
# check random globally assigned enough leads to team
|
||||
self.assertEqual(len(leads_st1), 76)
|
||||
self.assertEqual(len(leads_stc), 46)
|
||||
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)
|
||||
|
||||
# teams assign: everything should be done due to duplicates
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
self.assertEqual(len(leads.filtered_domain([('team_id', '=', False)])), 0)
|
||||
|
||||
# deduplicate should have removed all duplicated linked to contact_1 and contact_2
|
||||
new_assigned_leads_wpartner = self.env['crm.lead'].search([
|
||||
('partner_id', 'in', (self.contact_1 | self.contact_2).ids),
|
||||
('id', 'in', leads.ids)
|
||||
])
|
||||
self.assertEqual(len(new_assigned_leads_wpartner), 2)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_assign_no_duplicates(self):
|
||||
# fix the seed and avoid randomness
|
||||
random.seed(1945)
|
||||
|
||||
leads = self._create_leads_batch(
|
||||
lead_type='lead',
|
||||
user_ids=[False],
|
||||
partner_ids=[False],
|
||||
count=150
|
||||
)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
self.assertInitialData()
|
||||
|
||||
# assign probability to leads (bypass auto probability as purpose is not to test pls)
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
for idx in range(0, 5):
|
||||
sliced_leads = leads[idx:len(leads):5]
|
||||
for lead in sliced_leads:
|
||||
lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2)
|
||||
# commit probability and related fields
|
||||
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)
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
leads_st1 = leads.filtered_domain([('team_id', '=', self.sales_team_1.id)])
|
||||
leads_stc = leads.filtered_domain([('team_id', '=', self.sales_team_convert.id)])
|
||||
self.assertEqual(len(leads), 150)
|
||||
|
||||
# check random globally assigned enough leads to team
|
||||
self.assertEqual(len(leads_st1), 104)
|
||||
self.assertEqual(len(leads_stc), 46)
|
||||
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)
|
||||
|
||||
@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)
|
||||
random.seed(1871)
|
||||
|
||||
# create leads enough to assign one month of work
|
||||
_lead_count, _email_dup_count, _partner_count = 600, 50, 150
|
||||
leads = self._create_leads_batch(
|
||||
lead_type='lead',
|
||||
user_ids=[False],
|
||||
partner_count=_partner_count,
|
||||
country_ids=[self.env.ref('base.be').id, self.env.ref('base.fr').id, False],
|
||||
count=_lead_count,
|
||||
email_dup_count=_email_dup_count)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
self.assertInitialData()
|
||||
|
||||
# assign for one month, aka a lot
|
||||
self.env.ref('crm.ir_cron_crm_lead_assign').write({'interval_type': 'days', 'interval_number': 30})
|
||||
# create a third team
|
||||
sales_team_3 = self.env['crm.team'].create({
|
||||
'name': 'Sales Team 3',
|
||||
'sequence': 15,
|
||||
'alias_name': False,
|
||||
'use_leads': True,
|
||||
'use_opportunities': True,
|
||||
'company_id': False,
|
||||
'user_id': False,
|
||||
'assignment_domain': [('country_id', '!=', False)],
|
||||
})
|
||||
sales_team_3_m1 = self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_manager.id,
|
||||
'crm_team_id': sales_team_3.id,
|
||||
'assignment_max': 60,
|
||||
'assignment_domain': False,
|
||||
})
|
||||
sales_team_3_m2 = self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_leads.id,
|
||||
'crm_team_id': sales_team_3.id,
|
||||
'assignment_max': 60,
|
||||
'assignment_domain': False,
|
||||
})
|
||||
sales_team_3_m3 = self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_salesman.id,
|
||||
'crm_team_id': sales_team_3.id,
|
||||
'assignment_max': 15,
|
||||
'assignment_domain': [('probability', '>=', 10)],
|
||||
})
|
||||
sales_teams = self.sales_teams + sales_team_3
|
||||
self.assertEqual(sum(team.assignment_max for team in sales_teams), 300)
|
||||
self.assertEqual(len(leads), 650)
|
||||
|
||||
# assign probability to leads (bypass auto probability as purpose is not to test pls)
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
for idx in range(0, 5):
|
||||
sliced_leads = leads[idx:len(leads):5]
|
||||
for lead in sliced_leads:
|
||||
lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
|
||||
with self.with_user('user_sales_manager'):
|
||||
self.env['crm.team'].browse(sales_teams.ids)._action_assign_leads(work_days=30)
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)])
|
||||
self.assertEqual(leads.team_id, sales_teams)
|
||||
self.assertEqual(leads.user_id, sales_teams.member_ids)
|
||||
self.assertEqual(len(leads), 600)
|
||||
|
||||
# check random globally assigned enough leads to team
|
||||
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)
|
||||
|
||||
# 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
|
||||
|
||||
def test_assign_quota(self):
|
||||
""" Test quota computation """
|
||||
self.assertInitialData()
|
||||
|
||||
# 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)"
|
||||
)
|
||||
|
||||
# create exiting leads for user_sales_leads (sales_team_1_m1)
|
||||
existing_leads = self._create_leads_batch(
|
||||
lead_type='lead', user_ids=[self.user_sales_leads.id],
|
||||
probabilities=[10],
|
||||
count=30)
|
||||
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.assertEqual(self.sales_team_1_m1.lead_month_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.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)"
|
||||
)
|
||||
|
||||
def test_assign_specific_won_lost(self):
|
||||
""" Test leads taken into account in assign process: won, lost, stage
|
||||
configuration. """
|
||||
leads = self._create_leads_batch(
|
||||
lead_type='lead',
|
||||
user_ids=[False],
|
||||
partner_ids=[False, False, False, self.contact_1.id],
|
||||
probabilities=[30],
|
||||
count=6
|
||||
)
|
||||
leads[0].stage_id = self.stage_gen_won.id # is won -> should not be taken into account
|
||||
leads[1].stage_id = False
|
||||
leads[2].update({'stage_id': False, 'probability': 0})
|
||||
leads[3].update({'stage_id': False, 'probability': False})
|
||||
leads[4].active = False # is lost -> should not be taken into account
|
||||
leads[5].update({'team_id': self.sales_team_convert.id, 'user_id': self.user_sales_manager.id}) # assigned lead should not be re-assigned
|
||||
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
|
||||
with self.with_user('user_sales_manager'):
|
||||
self.env['crm.team'].browse(self.sales_team_1.ids)._action_assign_leads(work_days=4)
|
||||
|
||||
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')
|
||||
for lead in leads[1:4]:
|
||||
self.assertIn(lead.user_id, self.sales_team_1.member_ids)
|
||||
self.assertEqual(lead.team_id, self.sales_team_1)
|
||||
self.assertEqual(leads[4].team_id, self.env['crm.team'], 'Lost lead should not be assigned')
|
||||
self.assertEqual(leads[4].user_id, self.env['res.users'], 'Lost lead should not be assigned')
|
||||
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')
|
||||
|
||||
@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 """
|
||||
sales_team_dupe = self.env['crm.team'].create({
|
||||
'name': 'Sales Team Dupe',
|
||||
'sequence': 15,
|
||||
'alias_name': False,
|
||||
'use_leads': True,
|
||||
'use_opportunities': True,
|
||||
'company_id': False,
|
||||
'user_id': False,
|
||||
'assignment_domain': "[]",
|
||||
})
|
||||
self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_salesman.id,
|
||||
'crm_team_id': sales_team_dupe.id,
|
||||
'assignment_max': 10,
|
||||
'assignment_domain': "[]",
|
||||
})
|
||||
|
||||
master_opp = self.env['crm.lead'].create({
|
||||
'name': 'Master',
|
||||
'type': 'opportunity',
|
||||
'probability': 50,
|
||||
'partner_id': self.contact_1.id,
|
||||
'team_id': self.sales_team_1.id,
|
||||
'user_id': self.user_sales_manager.id,
|
||||
})
|
||||
dupe_lead = self.env['crm.lead'].create({
|
||||
'name': 'Dupe',
|
||||
'type': 'lead',
|
||||
'email_from': 'Duplicate Email <%s>' % master_opp.email_normalized,
|
||||
'probability': 10,
|
||||
'team_id': False,
|
||||
'user_id': False,
|
||||
})
|
||||
|
||||
sales_team_dupe._action_assign_leads(work_days=2)
|
||||
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')
|
||||
|
||||
def test_no_assign_if_exceed_max_assign(self):
|
||||
""" Test no leads being assigned to any team member if weights list sums to 0"""
|
||||
|
||||
leads = self._create_leads_batch(
|
||||
lead_type='lead',
|
||||
user_ids=[False],
|
||||
count=1
|
||||
)
|
||||
|
||||
sales_team_4 = self.env['crm.team'].create({
|
||||
'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
|
||||
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,
|
||||
"If team member has lead count greater than max assign,then do not assign any more")
|
||||
668
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead_convert.py
Normal file
668
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead_convert.py
Normal file
|
|
@ -0,0 +1,668 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import SUPERUSER_ID
|
||||
from odoo.addons.crm.tests import common as crm_common
|
||||
from odoo.fields import Datetime
|
||||
from odoo.tests.common import tagged, users
|
||||
from odoo.tests.common import Form
|
||||
|
||||
@tagged('lead_manage')
|
||||
class TestLeadConvertForm(crm_common.TestLeadConvertCommon):
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_form_action_default(self):
|
||||
""" Test Lead._find_matching_partner() """
|
||||
lead = self.env['crm.lead'].browse(self.lead_1.ids)
|
||||
customer = self.env['res.partner'].create({
|
||||
"name": "Amy Wong",
|
||||
"email": '"Amy, PhD Student, Wong" Tiny <AMY.WONG@test.example.com>'
|
||||
})
|
||||
|
||||
wizard = Form(self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': lead.id,
|
||||
'active_ids': lead.ids,
|
||||
}))
|
||||
|
||||
self.assertEqual(wizard.name, 'convert')
|
||||
self.assertEqual(wizard.action, 'exist')
|
||||
self.assertEqual(wizard.partner_id, customer)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_form_name_onchange(self):
|
||||
""" Test Lead._find_matching_partner() """
|
||||
lead = self.env['crm.lead'].browse(self.lead_1.ids)
|
||||
lead_dup = lead.copy({'name': 'Duplicate'})
|
||||
customer = self.env['res.partner'].create({
|
||||
"name": "Amy Wong",
|
||||
"email": '"Amy, PhD Student, Wong" Tiny <AMY.WONG@test.example.com>'
|
||||
})
|
||||
|
||||
wizard = Form(self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': lead.id,
|
||||
'active_ids': lead.ids,
|
||||
}))
|
||||
|
||||
self.assertEqual(wizard.name, 'merge')
|
||||
self.assertEqual(wizard.action, 'exist')
|
||||
self.assertEqual(wizard.partner_id, customer)
|
||||
self.assertEqual(wizard.duplicated_lead_ids[:], lead + lead_dup)
|
||||
|
||||
wizard.name = 'convert'
|
||||
wizard.action = 'create'
|
||||
self.assertEqual(wizard.action, 'create', 'Should keep user input')
|
||||
self.assertEqual(wizard.name, 'convert', 'Should keep user input')
|
||||
|
||||
|
||||
@tagged('lead_manage')
|
||||
class TestLeadConvert(crm_common.TestLeadConvertCommon):
|
||||
"""
|
||||
TODO: created partner (handle assignation) has team of lead
|
||||
TODO: create partner has user_id coming from wizard
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestLeadConvert, cls).setUpClass()
|
||||
date = Datetime.from_string('2020-01-20 16:00:00')
|
||||
cls.crm_lead_dt_mock.now.return_value = date
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_duplicates_computation(self):
|
||||
""" Test Lead._get_lead_duplicates() and check won / probability usage """
|
||||
test_lead = self.env['crm.lead'].browse(self.lead_1.ids)
|
||||
customer, dup_leads = self._create_duplicates(test_lead)
|
||||
dup_leads += self.env['crm.lead'].create([
|
||||
{'name': 'Duplicate lead: same email_from, lost',
|
||||
'type': 'lead',
|
||||
'email_from': test_lead.email_from,
|
||||
'probability': 0, 'active': False,
|
||||
},
|
||||
{'name': 'Duplicate lead: same email_from, proba 0 but not lost',
|
||||
'type': 'lead',
|
||||
'email_from': test_lead.email_from,
|
||||
'probability': 0, 'active': True,
|
||||
},
|
||||
{'name': 'Duplicate opp: same email_from, won',
|
||||
'type': 'opportunity',
|
||||
'email_from': test_lead.email_from,
|
||||
'probability': 100, 'stage_id': self.stage_team1_won.id,
|
||||
},
|
||||
{'name': 'Duplicate opp: same email_from, proba 100 but not won',
|
||||
'type': 'opportunity',
|
||||
'email_from': test_lead.email_from,
|
||||
'probability': 100, 'stage_id': self.stage_team1_2.id,
|
||||
}
|
||||
])
|
||||
lead_lost = dup_leads.filtered(lambda lead: lead.name == 'Duplicate lead: same email_from, lost')
|
||||
_opp_proba100 = dup_leads.filtered(lambda lead: lead.name == 'Duplicate opp: same email_from, proba 100 but not won')
|
||||
opp_won = dup_leads.filtered(lambda lead: lead.name == 'Duplicate opp: same email_from, won')
|
||||
opp_lost = dup_leads.filtered(lambda lead: lead.name == 'Duplicate: lost opportunity')
|
||||
|
||||
test_lead.write({'partner_id': customer.id})
|
||||
|
||||
# not include_lost = remove archived leads as well as 'won' opportunities
|
||||
result = test_lead._get_lead_duplicates(
|
||||
partner=test_lead.partner_id,
|
||||
email=test_lead.email_from,
|
||||
include_lost=False
|
||||
)
|
||||
self.assertEqual(result, test_lead + dup_leads - (lead_lost + opp_won + opp_lost))
|
||||
|
||||
# include_lost = remove archived opp only
|
||||
result = test_lead._get_lead_duplicates(
|
||||
partner=test_lead.partner_id,
|
||||
email=test_lead.email_from,
|
||||
include_lost=True
|
||||
)
|
||||
self.assertEqual(result, test_lead + dup_leads - (lead_lost))
|
||||
|
||||
def test_initial_data(self):
|
||||
""" Ensure initial data to avoid spaghetti test update afterwards """
|
||||
self.assertFalse(self.lead_1.date_conversion)
|
||||
self.assertEqual(self.lead_1.date_open, Datetime.from_string('2020-01-15 11:30:00'))
|
||||
self.assertEqual(self.lead_1.lang_id, self.lang_fr)
|
||||
self.assertFalse(self.lead_1.mobile)
|
||||
self.assertEqual(self.lead_1.phone, '+1 202 555 9999')
|
||||
self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
|
||||
self.assertEqual(self.lead_1.team_id, self.sales_team_1)
|
||||
self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_base(self):
|
||||
""" Test base method ``convert_opportunity`` or crm.lead model """
|
||||
self.contact_2.phone = False # force Falsy to compare with mobile
|
||||
self.assertFalse(self.contact_2.phone)
|
||||
lead = self.lead_1.with_user(self.env.user)
|
||||
lead.write({
|
||||
'phone': '123456789',
|
||||
})
|
||||
self.assertEqual(lead.team_id, self.sales_team_1)
|
||||
self.assertEqual(lead.stage_id, self.stage_team1_1)
|
||||
self.assertEqual(lead.email_from, 'amy.wong@test.example.com')
|
||||
self.assertEqual(lead.lang_id, self.lang_fr)
|
||||
lead.convert_opportunity(self.contact_2)
|
||||
|
||||
self.assertEqual(lead.type, 'opportunity')
|
||||
self.assertEqual(lead.partner_id, self.contact_2)
|
||||
self.assertEqual(lead.email_from, self.contact_2.email)
|
||||
self.assertEqual(lead.lang_id, self.lang_en)
|
||||
self.assertEqual(lead.mobile, self.contact_2.mobile)
|
||||
self.assertEqual(lead.phone, '123456789')
|
||||
self.assertEqual(lead.team_id, self.sales_team_1)
|
||||
self.assertEqual(lead.stage_id, self.stage_team1_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_base_corner_cases(self):
|
||||
""" Test base method ``convert_opportunity`` or crm.lead model with corner
|
||||
cases: inactive, won, stage update, ... """
|
||||
# inactive leads are not converted
|
||||
lead = self.lead_1.with_user(self.env.user)
|
||||
lead.action_archive()
|
||||
self.assertFalse(lead.active)
|
||||
lead.convert_opportunity(self.contact_2)
|
||||
|
||||
self.assertEqual(lead.type, 'lead')
|
||||
self.assertEqual(lead.partner_id, self.env['res.partner'])
|
||||
|
||||
lead.action_unarchive()
|
||||
self.assertTrue(lead.active)
|
||||
|
||||
# won leads are not converted
|
||||
lead.action_set_won()
|
||||
# TDE FIXME: set won does not take into account sales team when fetching a won stage
|
||||
# self.assertEqual(lead.stage_id, self.stage_team1_won)
|
||||
self.assertEqual(lead.stage_id, self.stage_gen_won)
|
||||
self.assertEqual(lead.probability, 100)
|
||||
|
||||
lead.convert_opportunity(self.contact_2)
|
||||
self.assertEqual(lead.type, 'lead')
|
||||
self.assertEqual(lead.partner_id, self.env['res.partner'])
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_base_w_salesmen(self):
|
||||
""" Test base method ``convert_opportunity`` while forcing salesmen, as it
|
||||
should also force sales team """
|
||||
lead = self.lead_1.with_user(self.env.user)
|
||||
self.assertEqual(lead.team_id, self.sales_team_1)
|
||||
lead.convert_opportunity(False, user_ids=self.user_sales_salesman.ids)
|
||||
self.assertEqual(lead.user_id, self.user_sales_salesman)
|
||||
self.assertEqual(lead.team_id, self.sales_team_convert)
|
||||
# TDE FIXME: convert does not recompute stage based on updated team of assigned user
|
||||
# self.assertEqual(lead.stage_id, self.stage_team_convert_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_base_w_team(self):
|
||||
""" Test base method ``convert_opportunity`` while forcing team """
|
||||
lead = self.lead_1.with_user(self.env.user)
|
||||
lead.convert_opportunity(False, team_id=self.sales_team_convert.id)
|
||||
self.assertEqual(lead.team_id, self.sales_team_convert)
|
||||
self.assertEqual(lead.user_id, self.user_sales_leads)
|
||||
# TDE FIXME: convert does not recompute stage based on team
|
||||
# self.assertEqual(lead.stage_id, self.stage_team_convert_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_corner_cases_crud(self):
|
||||
""" Test Lead._find_matching_partner() """
|
||||
# email formatting
|
||||
other_lead = self.lead_1.copy()
|
||||
other_lead.write({'partner_id': self.contact_1.id})
|
||||
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'default_lead_id': other_lead.id,
|
||||
}).create({})
|
||||
self.assertEqual(convert.lead_id, other_lead)
|
||||
self.assertEqual(convert.partner_id, self.contact_1)
|
||||
self.assertEqual(convert.action, 'exist')
|
||||
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'default_lead_id': other_lead.id,
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': self.lead_1.id,
|
||||
}).create({})
|
||||
self.assertEqual(convert.lead_id, other_lead)
|
||||
self.assertEqual(convert.partner_id, self.contact_1)
|
||||
self.assertEqual(convert.action, 'exist')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_corner_cases_matching(self):
|
||||
""" Test Lead._find_matching_partner() """
|
||||
# email formatting
|
||||
self.lead_1.write({
|
||||
'email_from': 'Amy Wong <amy.wong@test.example.com>'
|
||||
})
|
||||
customer = self.env['res.partner'].create({
|
||||
'name': 'Different Name',
|
||||
'email': 'Wong AMY <AMY.WONG@test.example.com>'
|
||||
})
|
||||
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': self.lead_1.id,
|
||||
'active_ids': self.lead_1.ids,
|
||||
}).create({})
|
||||
# TDE FIXME: should take into account normalized email version, not encoded one
|
||||
# self.assertEqual(convert.partner_id, customer)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_no_lang(self):
|
||||
""" Ensure converting a lead with an archived language correctly falls back on the default partner language. """
|
||||
inactive_lang = self.env["res.lang"].sudo().create({
|
||||
'code': 'en_ZZ',
|
||||
'name': 'Inactive Lang',
|
||||
'active': False,
|
||||
})
|
||||
|
||||
lead = self.lead_1.with_user(self.env.user)
|
||||
lead.lang_id = inactive_lang
|
||||
|
||||
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({'action': 'create'})
|
||||
convert.action_apply()
|
||||
self.assertTrue(lead.partner_id)
|
||||
self.assertEqual(lead.partner_id.lang, 'en_US')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_internals(self):
|
||||
""" Test internals of convert wizard """
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': self.lead_1.id,
|
||||
'active_ids': self.lead_1.ids,
|
||||
}).create({})
|
||||
|
||||
# test internals of convert wizard
|
||||
self.assertEqual(convert.lead_id, self.lead_1)
|
||||
self.assertEqual(convert.user_id, self.lead_1.user_id)
|
||||
self.assertEqual(convert.team_id, self.lead_1.team_id)
|
||||
self.assertFalse(convert.partner_id)
|
||||
self.assertEqual(convert.name, 'convert')
|
||||
self.assertEqual(convert.action, 'create')
|
||||
|
||||
convert.write({'user_id': self.user_sales_salesman.id})
|
||||
self.assertEqual(convert.user_id, self.user_sales_salesman)
|
||||
self.assertEqual(convert.team_id, self.sales_team_convert)
|
||||
|
||||
convert.action_apply()
|
||||
# convert test
|
||||
self.assertEqual(self.lead_1.type, 'opportunity')
|
||||
self.assertEqual(self.lead_1.user_id, self.user_sales_salesman)
|
||||
self.assertEqual(self.lead_1.team_id, self.sales_team_convert)
|
||||
# TDE FIXME: stage is linked to the old sales team and is not updated when converting, could be improved
|
||||
# self.assertEqual(self.lead_1.stage_id, self.stage_gen_1)
|
||||
# partner creation test
|
||||
new_partner = self.lead_1.partner_id
|
||||
self.assertEqual(new_partner.email, 'amy.wong@test.example.com')
|
||||
self.assertEqual(new_partner.lang, self.lang_fr.code)
|
||||
self.assertEqual(new_partner.phone, '+1 202 555 9999')
|
||||
self.assertEqual(new_partner.name, 'Amy Wong')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_action_exist(self):
|
||||
""" Test specific use case of 'exist' action in conver wizard """
|
||||
self.lead_1.write({'partner_id': self.contact_1.id})
|
||||
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': self.lead_1.id,
|
||||
'active_ids': self.lead_1.ids,
|
||||
}).create({})
|
||||
self.assertEqual(convert.action, 'exist')
|
||||
convert.action_apply()
|
||||
self.assertEqual(self.lead_1.type, 'opportunity')
|
||||
self.assertEqual(self.lead_1.partner_id, self.contact_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_action_nothing(self):
|
||||
""" Test specific use case of 'nothing' action in conver wizard """
|
||||
self.lead_1.write({'contact_name': False})
|
||||
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': self.lead_1.id,
|
||||
'active_ids': self.lead_1.ids,
|
||||
}).create({})
|
||||
self.assertEqual(convert.action, 'nothing')
|
||||
convert.action_apply()
|
||||
self.assertEqual(self.lead_1.type, 'opportunity')
|
||||
self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
|
||||
self.assertEqual(self.lead_1.team_id, self.sales_team_1)
|
||||
self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
|
||||
self.assertEqual(self.lead_1.partner_id, self.env['res.partner'])
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_contact_mutlicompany(self):
|
||||
""" Check the wizard convert to opp don't find contact
|
||||
You are not able to see because they belong to another company """
|
||||
# Use superuser_id because creating a company with a user add directly
|
||||
# the company in company_ids of the user.
|
||||
company_2 = self.env['res.company'].with_user(SUPERUSER_ID).create({'name': 'Company 2'})
|
||||
partner_company_2 = self.env['res.partner'].with_user(SUPERUSER_ID).create({
|
||||
'name': 'Contact in other company',
|
||||
'email': 'test@company2.com',
|
||||
'company_id': company_2.id,
|
||||
})
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'LEAD',
|
||||
'type': 'lead',
|
||||
'email_from': 'test@company2.com',
|
||||
})
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': lead.id,
|
||||
'active_ids': lead.ids,
|
||||
}).create({'name': 'convert', 'action': 'exist'})
|
||||
self.assertNotEqual(convert.partner_id, partner_company_2,
|
||||
"Conversion wizard should not be able to find the partner from another company")
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_same_partner(self):
|
||||
""" Check that we don't erase lead information
|
||||
with existing partner info if the partner is already set
|
||||
"""
|
||||
partner = self.env['res.partner'].create({
|
||||
'name': 'Empty partner',
|
||||
})
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'LEAD',
|
||||
'partner_id': partner.id,
|
||||
'type': 'lead',
|
||||
'email_from': 'demo@test.com',
|
||||
'lang_id': self.lang_fr.id,
|
||||
'street': 'my street',
|
||||
'city': 'my city',
|
||||
})
|
||||
lead.convert_opportunity(partner)
|
||||
self.assertEqual(lead.email_from, 'demo@test.com', 'Email From should be preserved during conversion')
|
||||
self.assertEqual(lead.lang_id, self.lang_fr, 'Lang should be preserved during conversion')
|
||||
self.assertEqual(lead.street, 'my street', 'Street should be preserved during conversion')
|
||||
self.assertEqual(lead.city, 'my city', 'City should be preserved during conversion')
|
||||
self.assertEqual(partner.lang, 'en_US')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_properties_preserve(self):
|
||||
"""Verify that the properties are preserved when converting."""
|
||||
initial_team = self.lead_1.with_env(self.env).team_id
|
||||
self.lead_1.lead_properties = [{
|
||||
'name': 'test',
|
||||
'type': 'char',
|
||||
'value': 'test value',
|
||||
'definition_changed': True,
|
||||
}]
|
||||
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',
|
||||
}])
|
||||
|
||||
# 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',
|
||||
}])
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_properties_reset(self):
|
||||
"""Verify that the properties are reset when converting if the team changed."""
|
||||
initial_team = self.lead_1.with_env(self.env).team_id
|
||||
self.lead_1.lead_properties = [{
|
||||
'name': 'test',
|
||||
'type': 'char',
|
||||
'value': 'test value',
|
||||
'definition_changed': True,
|
||||
}]
|
||||
self.lead_1.convert_opportunity(False, user_ids=self.user_sales_salesman.ids)
|
||||
self.assertNotEqual(self.lead_1.team_id, initial_team)
|
||||
self.assertFalse(self.lead_1.lead_properties)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_merge(self):
|
||||
""" Test convert wizard working in merge mode """
|
||||
date = Datetime.from_string('2020-01-20 16:00:00')
|
||||
self.crm_lead_dt_mock.now.return_value = date
|
||||
|
||||
leads = self.env['crm.lead']
|
||||
for x in range(2):
|
||||
leads |= self.env['crm.lead'].create({
|
||||
'name': 'Dup-%02d-%s' % (x+1, self.lead_1.name),
|
||||
'type': 'lead', 'user_id': False, 'team_id': self.lead_1.team_id.id,
|
||||
'contact_name': 'Duplicate %02d of %s' % (x+1, self.lead_1.contact_name),
|
||||
'email_from': self.lead_1.email_from,
|
||||
'probability': 10,
|
||||
})
|
||||
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': self.lead_1.id,
|
||||
'active_ids': self.lead_1.ids,
|
||||
}).create({})
|
||||
|
||||
# test internals of convert wizard
|
||||
self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | leads)
|
||||
self.assertEqual(convert.user_id, self.lead_1.user_id)
|
||||
self.assertEqual(convert.team_id, self.lead_1.team_id)
|
||||
self.assertFalse(convert.partner_id)
|
||||
self.assertEqual(convert.name, 'merge')
|
||||
self.assertEqual(convert.action, 'create')
|
||||
|
||||
convert.write({'user_id': self.user_sales_salesman.id})
|
||||
self.assertEqual(convert.user_id, self.user_sales_salesman)
|
||||
self.assertEqual(convert.team_id, self.sales_team_convert)
|
||||
|
||||
convert.action_apply()
|
||||
self.assertEqual(self.lead_1.type, 'opportunity')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_merge_last_created(self):
|
||||
"""
|
||||
Test convert wizard is not deleted in merge mode when the original assigned lead is deleted
|
||||
"""
|
||||
date = Datetime.from_string('2020-01-20 16:00:00')
|
||||
self.crm_lead_dt_mock.now.return_value = date
|
||||
|
||||
last_lead = self.env['crm.lead'].create({
|
||||
'name': f'Duplicate of {self.lead_1.contact_name}',
|
||||
'type': 'lead', 'user_id': False, 'team_id': self.lead_1.team_id.id,
|
||||
'contact_name': f'Duplicate of {self.lead_1.contact_name}',
|
||||
'email_from': self.lead_1.email_from,
|
||||
'probability': 10,
|
||||
})
|
||||
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': last_lead.id,
|
||||
'active_ids': last_lead.ids,
|
||||
}).create({})
|
||||
|
||||
# test main lead on wizard
|
||||
self.assertEqual(convert.lead_id, last_lead)
|
||||
convert.action_apply()
|
||||
self.assertTrue(convert.exists(), 'Wizard cannot be deleted via cascade!')
|
||||
self.assertEqual(convert.lead_id, self.lead_1, "Lead must be the result opportunity!")
|
||||
self.assertEqual(self.lead_1.type, 'opportunity')
|
||||
self.assertFalse(last_lead.exists(), 'The last lead must be merged with the first one!')
|
||||
|
||||
@users('user_sales_salesman')
|
||||
def test_lead_merge_user(self):
|
||||
""" Test convert wizard working in merge mode with sales user """
|
||||
date = Datetime.from_string('2020-01-20 16:00:00')
|
||||
self.crm_lead_dt_mock.now.return_value = date
|
||||
|
||||
leads = self.env['crm.lead']
|
||||
for x in range(2):
|
||||
leads |= self.env['crm.lead'].create({
|
||||
'name': 'Dup-%02d-%s' % (x+1, self.lead_1.name),
|
||||
'type': 'lead', 'user_id': False, 'team_id': self.lead_1.team_id.id,
|
||||
'contact_name': 'Duplicate %02d of %s' % (x+1, self.lead_1.contact_name),
|
||||
'email_from': self.lead_1.email_from,
|
||||
'probability': 10,
|
||||
})
|
||||
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': leads[0].id,
|
||||
'active_ids': leads[0].ids,
|
||||
}).create({})
|
||||
|
||||
# test internals of convert wizard
|
||||
self.assertEqual(convert.duplicated_lead_ids, leads)
|
||||
self.assertEqual(convert.name, 'merge')
|
||||
self.assertEqual(convert.action, 'create')
|
||||
|
||||
convert.write({'user_id': self.user_sales_salesman.id})
|
||||
self.assertEqual(convert.user_id, self.user_sales_salesman)
|
||||
self.assertEqual(convert.team_id, self.sales_team_convert)
|
||||
|
||||
convert.action_apply()
|
||||
self.assertEqual(leads[0].type, 'opportunity')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_merge_duplicates(self):
|
||||
""" Test Lead._get_lead_duplicates() and check: partner / email fallbacks """
|
||||
customer, dup_leads = self._create_duplicates(self.lead_1)
|
||||
lead_partner = dup_leads.filtered(lambda lead: lead.name == 'Duplicate: customer ID')
|
||||
self.assertTrue(bool(lead_partner))
|
||||
|
||||
self.lead_1.write({
|
||||
'partner_id': customer.id,
|
||||
})
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': self.lead_1.id,
|
||||
'active_ids': self.lead_1.ids,
|
||||
}).create({})
|
||||
self.assertEqual(convert.partner_id, customer)
|
||||
self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | dup_leads)
|
||||
|
||||
# Check: partner fallbacks
|
||||
self.lead_1.write({
|
||||
'email_from': False,
|
||||
'partner_id': customer.id,
|
||||
})
|
||||
customer.write({'email': False})
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': self.lead_1.id,
|
||||
'active_ids': self.lead_1.ids,
|
||||
}).create({})
|
||||
self.assertEqual(convert.partner_id, customer)
|
||||
self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | lead_partner)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_merge_duplicates_flow(self):
|
||||
""" Test Lead._get_lead_duplicates() + merge with active_test """
|
||||
|
||||
# Check: email formatting
|
||||
self.lead_1.write({
|
||||
'email_from': 'Amy Wong <amy.wong@test.example.com>'
|
||||
})
|
||||
customer, dup_leads = self._create_duplicates(self.lead_1)
|
||||
opp_lost = dup_leads.filtered(lambda lead: lead.name == 'Duplicate: lost opportunity')
|
||||
self.assertTrue(bool(opp_lost))
|
||||
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': self.lead_1.id,
|
||||
'active_ids': self.lead_1.ids,
|
||||
}).create({})
|
||||
self.assertEqual(convert.partner_id, customer)
|
||||
self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | dup_leads)
|
||||
|
||||
convert.action_apply()
|
||||
self.assertEqual(
|
||||
(self.lead_1 | dup_leads).exists(),
|
||||
opp_lost)
|
||||
|
||||
|
||||
@tagged('lead_manage')
|
||||
class TestLeadConvertBatch(crm_common.TestLeadConvertMassCommon):
|
||||
|
||||
def test_initial_data(self):
|
||||
""" Ensure initial data to avoid spaghetti test update afterwards """
|
||||
self.assertFalse(self.lead_1.date_conversion)
|
||||
self.assertEqual(self.lead_1.date_open, Datetime.from_string('2020-01-15 11:30:00'))
|
||||
self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
|
||||
self.assertEqual(self.lead_1.team_id, self.sales_team_1)
|
||||
self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
|
||||
|
||||
self.assertEqual(self.lead_w_partner.stage_id, self.env['crm.stage'])
|
||||
self.assertEqual(self.lead_w_partner.user_id, self.user_sales_manager)
|
||||
self.assertEqual(self.lead_w_partner.team_id, self.sales_team_1)
|
||||
|
||||
self.assertEqual(self.lead_w_partner_company.stage_id, self.stage_team1_1)
|
||||
self.assertEqual(self.lead_w_partner_company.user_id, self.user_sales_manager)
|
||||
self.assertEqual(self.lead_w_partner_company.team_id, self.sales_team_1)
|
||||
|
||||
self.assertEqual(self.lead_w_contact.stage_id, self.stage_gen_1)
|
||||
self.assertEqual(self.lead_w_contact.user_id, self.user_sales_salesman)
|
||||
self.assertEqual(self.lead_w_contact.team_id, self.sales_team_convert)
|
||||
|
||||
self.assertEqual(self.lead_w_email.stage_id, self.stage_gen_1)
|
||||
self.assertEqual(self.lead_w_email.user_id, self.user_sales_salesman)
|
||||
self.assertEqual(self.lead_w_email.team_id, self.sales_team_convert)
|
||||
|
||||
self.assertEqual(self.lead_w_email_lost.stage_id, self.stage_team1_2)
|
||||
self.assertEqual(self.lead_w_email_lost.user_id, self.user_sales_leads)
|
||||
self.assertEqual(self.lead_w_email_lost.team_id, self.sales_team_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_convert_batch_internals(self):
|
||||
""" Test internals of convert wizard, working in batch mode """
|
||||
date = self.env.cr.now()
|
||||
|
||||
lead_w_partner = self.lead_w_partner
|
||||
lead_w_contact = self.lead_w_contact
|
||||
lead_w_email_lost = self.lead_w_email_lost
|
||||
lead_w_email_lost.action_set_lost()
|
||||
self.assertEqual(lead_w_email_lost.active, False)
|
||||
|
||||
convert = self.env['crm.lead2opportunity.partner'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_id': self.lead_1.id,
|
||||
'active_ids': (self.lead_1 | lead_w_partner | lead_w_contact | lead_w_email_lost).ids,
|
||||
}).create({})
|
||||
|
||||
# test internals of convert wizard
|
||||
# self.assertEqual(convert.lead_id, self.lead_1)
|
||||
self.assertEqual(convert.user_id, self.lead_1.user_id)
|
||||
self.assertEqual(convert.team_id, self.lead_1.team_id)
|
||||
self.assertFalse(convert.partner_id)
|
||||
self.assertEqual(convert.name, 'convert')
|
||||
self.assertEqual(convert.action, 'create')
|
||||
|
||||
convert.action_apply()
|
||||
self.assertEqual(convert.user_id, self.user_sales_leads)
|
||||
self.assertEqual(convert.team_id, self.sales_team_1)
|
||||
# lost leads are not converted (see crm_lead.convert_opportunity())
|
||||
self.assertFalse(lead_w_email_lost.active)
|
||||
self.assertFalse(lead_w_email_lost.date_conversion)
|
||||
self.assertEqual(lead_w_email_lost.partner_id, self.env['res.partner'])
|
||||
self.assertEqual(lead_w_email_lost.stage_id, self.stage_team1_2) # did not change
|
||||
# other leads are converted into opportunities
|
||||
for opp in (self.lead_1 | lead_w_partner | lead_w_contact):
|
||||
# team management update: opportunity linked to chosen wizard values
|
||||
self.assertEqual(opp.type, 'opportunity')
|
||||
self.assertTrue(opp.active)
|
||||
self.assertEqual(opp.user_id, convert.user_id)
|
||||
self.assertEqual(opp.team_id, convert.team_id)
|
||||
# dates update: convert set them to now
|
||||
self.assertEqual(opp.date_open, date)
|
||||
self.assertEqual(opp.date_conversion, date)
|
||||
# stage update (depends on previous value)
|
||||
if opp == self.lead_1:
|
||||
self.assertEqual(opp.stage_id, self.stage_team1_1) # did not change
|
||||
elif opp == lead_w_partner:
|
||||
self.assertEqual(opp.stage_id, self.stage_team1_1) # is set to default stage of sales_team_1
|
||||
elif opp == lead_w_contact:
|
||||
self.assertEqual(opp.stage_id, self.stage_gen_1) # did not change
|
||||
else:
|
||||
self.assertFalse(True)
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
# -*- 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.tests.common import tagged, users
|
||||
|
||||
|
||||
@tagged('lead_manage', 'crm_performance', 'post_install', '-at_install')
|
||||
class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestLeadConvertMass, cls).setUpClass()
|
||||
|
||||
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
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_assignment_salesmen(self):
|
||||
test_leads = self._create_leads_batch(count=50, user_ids=[False])
|
||||
user_ids = self.assign_users.ids
|
||||
self.assertEqual(test_leads.user_id, self.env['res.users'])
|
||||
|
||||
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
|
||||
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)
|
||||
self.assertEqual(test_leads[0::3].user_id, self.user_sales_manager)
|
||||
self.assertEqual(test_leads[1::3].user_id, self.user_sales_leads_convert)
|
||||
self.assertEqual(test_leads[2::3].user_id, self.user_sales_salesman)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_assignment_salesmen_wteam(self):
|
||||
test_leads = self._create_leads_batch(count=50, user_ids=[False])
|
||||
user_ids = self.assign_users.ids
|
||||
team_id = self.sales_team_convert.id
|
||||
self.assertEqual(test_leads.user_id, self.env['res.users'])
|
||||
|
||||
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
|
||||
test_leads._handle_salesmen_assignment(user_ids=user_ids, team_id=team_id)
|
||||
|
||||
self.assertEqual(test_leads.team_id, self.sales_team_convert)
|
||||
self.assertEqual(test_leads[0::3].user_id, self.user_sales_manager)
|
||||
self.assertEqual(test_leads[1::3].user_id, self.user_sales_leads_convert)
|
||||
self.assertEqual(test_leads[2::3].user_id, self.user_sales_salesman)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_mass_convert_internals(self):
|
||||
""" Test internals mass converted in convert mode, without duplicate management """
|
||||
# reset some assigned users to test salesmen assign
|
||||
(self.lead_w_partner | self.lead_w_email_lost).write({
|
||||
'user_id': False
|
||||
})
|
||||
|
||||
mass_convert = self.env['crm.lead2opportunity.partner.mass'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_ids': self.leads.ids,
|
||||
'active_id': self.leads.ids[0]
|
||||
}).create({
|
||||
'deduplicate': False,
|
||||
'user_id': self.user_sales_salesman.id,
|
||||
'force_assignment': False,
|
||||
})
|
||||
|
||||
# default values
|
||||
self.assertEqual(mass_convert.name, 'convert')
|
||||
self.assertEqual(mass_convert.action, 'each_exist_or_create')
|
||||
# depending on options
|
||||
self.assertEqual(mass_convert.partner_id, self.env['res.partner'])
|
||||
self.assertEqual(mass_convert.deduplicate, False)
|
||||
self.assertEqual(mass_convert.user_id, self.user_sales_salesman)
|
||||
self.assertEqual(mass_convert.team_id, self.sales_team_convert)
|
||||
|
||||
mass_convert.action_mass_convert()
|
||||
for lead in self.lead_1 | self.lead_w_partner:
|
||||
self.assertEqual(lead.type, 'opportunity')
|
||||
if lead == self.lead_w_partner:
|
||||
self.assertEqual(lead.user_id, self.env['res.users']) # user_id is bypassed
|
||||
self.assertEqual(lead.partner_id, self.contact_1)
|
||||
elif lead == self.lead_1:
|
||||
self.assertEqual(lead.user_id, self.user_sales_leads) # existing value not forced
|
||||
new_partner = lead.partner_id
|
||||
self.assertEqual(new_partner.name, 'Amy Wong')
|
||||
self.assertEqual(new_partner.email, 'amy.wong@test.example.com')
|
||||
|
||||
# test unforced assignation
|
||||
mass_convert.write({
|
||||
'user_ids': self.user_sales_salesman.ids,
|
||||
})
|
||||
mass_convert.action_mass_convert()
|
||||
self.assertEqual(self.lead_w_partner.user_id, self.user_sales_salesman)
|
||||
self.assertEqual(self.lead_1.user_id, self.user_sales_leads) # existing value not forced
|
||||
|
||||
# lost leads are untouched
|
||||
self.assertEqual(self.lead_w_email_lost.type, 'lead')
|
||||
self.assertFalse(self.lead_w_email_lost.active)
|
||||
self.assertFalse(self.lead_w_email_lost.date_conversion)
|
||||
# TDE FIXME: partner creation is done even on lost leads because not checked in wizard
|
||||
# self.assertEqual(self.lead_w_email_lost.partner_id, self.env['res.partner'])
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_mass_convert_deduplicate(self):
|
||||
""" Test duplicated_lead_ids fields having another behavior in mass convert
|
||||
because why not. Its use is: among leads under convert, store those with
|
||||
duplicates if deduplicate is set to True. """
|
||||
_customer, lead_1_dups = self._create_duplicates(self.lead_1, create_opp=False)
|
||||
lead_1_final = self.lead_1 # after merge: same but with lower ID
|
||||
|
||||
_customer2, lead_w_partner_dups = self._create_duplicates(self.lead_w_partner, create_opp=False)
|
||||
lead_w_partner_final = lead_w_partner_dups[0] # lead_w_partner has no stage -> lower in sort by confidence
|
||||
lead_w_partner_dups_partner = lead_w_partner_dups[1] # copy with a partner_id (with the same email)
|
||||
|
||||
mass_convert = self.env['crm.lead2opportunity.partner.mass'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_ids': self.leads.ids,
|
||||
}).create({
|
||||
'deduplicate': True,
|
||||
})
|
||||
self.assertEqual(mass_convert.action, 'each_exist_or_create')
|
||||
self.assertEqual(mass_convert.name, 'convert')
|
||||
self.assertEqual(mass_convert.lead_tomerge_ids, self.leads)
|
||||
self.assertEqual(mass_convert.duplicated_lead_ids, self.lead_1 | self.lead_w_partner)
|
||||
|
||||
mass_convert.action_mass_convert()
|
||||
|
||||
self.assertEqual(
|
||||
(lead_1_dups | lead_w_partner_dups | lead_w_partner_dups_partner).exists(),
|
||||
lead_w_partner_final
|
||||
)
|
||||
for lead in lead_1_final | lead_w_partner_final:
|
||||
self.assertTrue(lead.active)
|
||||
self.assertEqual(lead.type, 'opportunity')
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_mass_convert_find_existing(self):
|
||||
""" Check that we don't find a wrong partner
|
||||
that have similar name during mass conversion
|
||||
"""
|
||||
wrong_partner = self.env['res.partner'].create({
|
||||
'name': 'casa depapel',
|
||||
'street': "wrong street"
|
||||
})
|
||||
|
||||
lead = self.env['crm.lead'].create({'name': 'Asa Depape'})
|
||||
mass_convert = self.env['crm.lead2opportunity.partner.mass'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_ids': lead.ids,
|
||||
'active_id': lead.ids[0]
|
||||
}).create({
|
||||
'deduplicate': False,
|
||||
'action': 'each_exist_or_create',
|
||||
'name': 'convert',
|
||||
})
|
||||
mass_convert.action_mass_convert()
|
||||
|
||||
self.assertNotEqual(lead.partner_id, wrong_partner, "Partner Id should not match the wrong contact")
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_mass_convert_performances(self):
|
||||
test_leads = self._create_leads_batch(count=50, user_ids=[False])
|
||||
user_ids = self.assign_users.ids
|
||||
|
||||
# randomness: at least 1 query
|
||||
with self.assertQueryCount(user_sales_manager=1704): # crm 1410 / com 1697
|
||||
mass_convert = self.env['crm.lead2opportunity.partner.mass'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_ids': test_leads.ids,
|
||||
}).create({
|
||||
'deduplicate': True,
|
||||
'user_ids': user_ids,
|
||||
'force_assignment': True,
|
||||
})
|
||||
mass_convert.action_mass_convert()
|
||||
|
||||
self.assertEqual(set(test_leads.mapped('type')), set(['opportunity']))
|
||||
self.assertEqual(len(test_leads.partner_id), len(test_leads))
|
||||
# TDE FIXME: strange
|
||||
# self.assertEqual(test_leads.team_id, self.sales_team_convert | self.sales_team_1)
|
||||
self.assertEqual(test_leads.team_id, self.sales_team_1)
|
||||
self.assertEqual(test_leads[0::3].user_id, self.user_sales_manager)
|
||||
self.assertEqual(test_leads[1::3].user_id, self.user_sales_leads_convert)
|
||||
self.assertEqual(test_leads[2::3].user_id, self.user_sales_salesman)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_mass_convert_w_salesmen(self):
|
||||
# reset some assigned users to test salesmen assign
|
||||
(self.lead_w_partner | self.lead_w_email_lost).write({
|
||||
'user_id': False
|
||||
})
|
||||
|
||||
mass_convert = self.env['crm.lead2opportunity.partner.mass'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_ids': self.leads.ids,
|
||||
'active_id': self.leads.ids[0]
|
||||
}).create({
|
||||
'deduplicate': False,
|
||||
'user_ids': self.assign_users.ids,
|
||||
'force_assignment': True,
|
||||
})
|
||||
|
||||
# TDE FIXME: what happens if we mix people from different sales team ? currently nothing, to check
|
||||
mass_convert.action_mass_convert()
|
||||
|
||||
for idx, lead in enumerate(self.leads - self.lead_w_email_lost):
|
||||
self.assertEqual(lead.type, 'opportunity')
|
||||
assigned_user = self.assign_users[idx % len(self.assign_users)]
|
||||
self.assertEqual(lead.user_id, assigned_user)
|
||||
228
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead_duplicates.py
Normal file
228
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead_duplicates.py
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
# -*- 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 tagged, users
|
||||
|
||||
|
||||
@tagged('lead_manage')
|
||||
class TestLeadConvert(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',
|
||||
'city': 'New New York',
|
||||
'country_id': self.env.ref('base.us').id,
|
||||
'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',
|
||||
'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,
|
||||
'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)
|
||||
|
||||
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',
|
||||
})
|
||||
|
||||
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'))
|
||||
)
|
||||
|
||||
@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'
|
||||
})
|
||||
|
||||
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)
|
||||
132
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead_lost.py
Normal file
132
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead_lost.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# -*- 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()
|
||||
574
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead_merge.py
Normal file
574
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_lead_merge.py
Normal file
|
|
@ -0,0 +1,574 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.addons.crm.tests.common import TestLeadConvertMassCommon
|
||||
from odoo.fields import Datetime
|
||||
from odoo.tests.common import tagged, users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestLeadMergeCommon(TestLeadConvertMassCommon):
|
||||
""" During a mixed merge (involving leads and opps), data should be handled a certain way following their type
|
||||
(m2o, m2m, text, ...). """
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestLeadMergeCommon, cls).setUpClass()
|
||||
|
||||
cls.leads = cls.lead_1 + cls.lead_w_partner + cls.lead_w_contact + cls.lead_w_email + cls.lead_w_partner_company + cls.lead_w_email_lost
|
||||
# reset some assigned users to test salesmen assign
|
||||
(cls.lead_w_partner | cls.lead_w_email_lost).write({
|
||||
'user_id': False,
|
||||
})
|
||||
cls.lead_w_partner.write({'stage_id': False})
|
||||
|
||||
cls.lead_w_contact.write({'description': 'lead_w_contact'})
|
||||
cls.lead_w_email.write({'description': 'lead_w_email'})
|
||||
cls.lead_1.write({'description': 'lead_1'})
|
||||
cls.lead_w_partner.write({'description': 'lead_w_partner'})
|
||||
|
||||
cls.assign_users = cls.user_sales_manager + cls.user_sales_leads_convert + cls.user_sales_salesman
|
||||
|
||||
|
||||
@tagged('lead_manage')
|
||||
class TestLeadMerge(TestLeadMergeCommon):
|
||||
|
||||
def _run_merge_wizard(self, leads):
|
||||
res = self.env['crm.merge.opportunity'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_ids': leads.ids,
|
||||
'active_id': False,
|
||||
}).create({
|
||||
'team_id': False,
|
||||
'user_id': False,
|
||||
}).action_merge()
|
||||
return self.env['crm.lead'].browse(res['res_id'])
|
||||
|
||||
def test_initial_data(self):
|
||||
""" Ensure initial data to avoid spaghetti test update afterwards
|
||||
|
||||
Original order:
|
||||
|
||||
lead_w_contact ----------lead---seq=3----proba=15
|
||||
lead_w_email ------------lead---seq=3----proba=15
|
||||
lead_1 ------------------lead---seq=1----proba=?
|
||||
lead_w_partner ----------lead---seq=False---proba=10
|
||||
lead_w_partner_company --lead---seq=False---proba=15
|
||||
"""
|
||||
self.assertFalse(self.lead_1.date_conversion)
|
||||
self.assertEqual(self.lead_1.date_open, Datetime.from_string('2020-01-15 11:30:00'))
|
||||
self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
|
||||
self.assertEqual(self.lead_1.team_id, self.sales_team_1)
|
||||
self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
|
||||
|
||||
self.assertEqual(self.lead_w_partner.stage_id, self.env['crm.stage'])
|
||||
self.assertEqual(self.lead_w_partner.user_id, self.env['res.users'])
|
||||
self.assertEqual(self.lead_w_partner.team_id, self.sales_team_1)
|
||||
|
||||
self.assertEqual(self.lead_w_partner_company.stage_id, self.stage_team1_1)
|
||||
self.assertEqual(self.lead_w_partner_company.user_id, self.user_sales_manager)
|
||||
self.assertEqual(self.lead_w_partner_company.team_id, self.sales_team_1)
|
||||
|
||||
self.assertEqual(self.lead_w_contact.stage_id, self.stage_gen_1)
|
||||
self.assertEqual(self.lead_w_contact.user_id, self.user_sales_salesman)
|
||||
self.assertEqual(self.lead_w_contact.team_id, self.sales_team_convert)
|
||||
|
||||
self.assertEqual(self.lead_w_email.stage_id, self.stage_gen_1)
|
||||
self.assertEqual(self.lead_w_email.user_id, self.user_sales_salesman)
|
||||
self.assertEqual(self.lead_w_email.team_id, self.sales_team_convert)
|
||||
|
||||
self.assertEqual(self.lead_w_email_lost.stage_id, self.stage_team1_2)
|
||||
self.assertEqual(self.lead_w_email_lost.user_id, self.env['res.users'])
|
||||
self.assertEqual(self.lead_w_email_lost.team_id, self.sales_team_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_lead_merge_address_not_propagated(self):
|
||||
"""All addresses have the same number of non-empty address fields, take the first one (lead_w_contact)
|
||||
because it's the lead that has the best confidence level after being sorted with '_sort_by_confidence_level'"""
|
||||
initial_address = {
|
||||
'street': 'Test street',
|
||||
'street2': 'Test street2',
|
||||
'city': 'Test City',
|
||||
'zip': '5000',
|
||||
'state_id': False,
|
||||
'country_id': self.env.ref('base.be'),
|
||||
}
|
||||
self.lead_w_contact.write(initial_address)
|
||||
|
||||
(self.leads - self.lead_w_contact).write({
|
||||
'street': 'Other street',
|
||||
'street2': 'Other street2',
|
||||
'city': 'Other City',
|
||||
'zip': '6666',
|
||||
'state_id': self.env.ref('base.state_us_1'),
|
||||
'country_id': False,
|
||||
})
|
||||
|
||||
leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
|
||||
with self.assertLeadMerged(self.lead_w_contact, leads, **initial_address):
|
||||
leads._merge_opportunity(auto_unlink=False, max_length=None)
|
||||
|
||||
@users('user_sales_manager')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_lead_merge_address_propagated(self):
|
||||
"""Test that the address with the most non-empty fields is propagated.
|
||||
|
||||
Should take the address of "lead_w_partner" (maximum number of non-empty address
|
||||
fields and with an highest rank than "lead_w_email_lost")
|
||||
"""
|
||||
self.leads.write({
|
||||
'street': 'Original street',
|
||||
'street2': False,
|
||||
'city': False,
|
||||
'zip': False,
|
||||
'state_id': False,
|
||||
'country_id': False,
|
||||
})
|
||||
new_address = {
|
||||
'street': 'New street',
|
||||
'street2': False,
|
||||
'city': 'New City',
|
||||
'zip': False,
|
||||
'state_id': False,
|
||||
'country_id': False,
|
||||
}
|
||||
self.lead_w_partner.write(new_address)
|
||||
self.lead_w_email_lost.write({
|
||||
'street': 'Other street',
|
||||
'street2': False,
|
||||
'city': 'Other City',
|
||||
'zip': False,
|
||||
'state_id': False,
|
||||
'country_id': False,
|
||||
})
|
||||
|
||||
leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
|
||||
|
||||
with self.assertLeadMerged(self.lead_w_contact, leads, **new_address):
|
||||
leads._merge_opportunity(auto_unlink=False, max_length=None)
|
||||
|
||||
@users('user_sales_manager')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_lead_merge_internals(self):
|
||||
""" Test internals of merge wizard. In this test leads are ordered as
|
||||
|
||||
lead_w_contact --lead---seq=3---probability=25
|
||||
lead_w_email ----lead---seq=3---probability=15
|
||||
lead_1 ----------lead---seq=1
|
||||
lead_w_partner --lead---seq=False
|
||||
"""
|
||||
# ensure initial data
|
||||
self.lead_w_partner_company.action_set_won() # won opps should be excluded
|
||||
|
||||
merge = self.env['crm.merge.opportunity'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_ids': self.leads.ids,
|
||||
'active_id': False,
|
||||
}).create({
|
||||
'user_id': self.user_sales_leads_convert.id,
|
||||
})
|
||||
self.assertEqual(merge.team_id, self.sales_team_convert)
|
||||
|
||||
# TDE FIXME: not sure the browse in default get of wizard intended to exlude lost, as it browse ids
|
||||
# and exclude inactive leads, but that's not written anywhere ... intended ??
|
||||
self.assertEqual(merge.opportunity_ids, self.leads - self.lead_w_partner_company - self.lead_w_email_lost)
|
||||
ordered_merge = self.lead_w_contact + self.lead_w_email + self.lead_1 + self.lead_w_partner
|
||||
ordered_merge_description = '<br><br>'.join(l.description for l in ordered_merge)
|
||||
|
||||
# merged opportunity: in this test, all input are leads. Confidence is based on stage
|
||||
# sequence -> lead_w_contact has a stage sequence of 3 and probability is greater
|
||||
result = merge.action_merge()
|
||||
merge_opportunity = self.env['crm.lead'].browse(result['res_id'])
|
||||
self.assertFalse((ordered_merge - merge_opportunity).exists())
|
||||
self.assertEqual(merge_opportunity, self.lead_w_contact)
|
||||
self.assertEqual(merge_opportunity.type, 'lead')
|
||||
self.assertEqual(merge_opportunity.description, ordered_merge_description)
|
||||
# merged opportunity has updated salesman / team / stage is ok as generic
|
||||
self.assertEqual(merge_opportunity.user_id, self.user_sales_leads_convert)
|
||||
self.assertEqual(merge_opportunity.team_id, self.sales_team_convert)
|
||||
self.assertEqual(merge_opportunity.stage_id, self.stage_gen_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_lead_merge_mixed(self):
|
||||
""" In case of mix, opportunities are on top, and result is an opportunity
|
||||
|
||||
lead_1 -------------------opp----seq=1---probability=60
|
||||
lead_w_partner_company ---opp----seq=1---probability=50
|
||||
lead_w_contact -----------lead---seq=3---probability=25
|
||||
lead_w_email -------------lead---seq=3---probability=15
|
||||
lead_w_partner -----------lead---seq=False
|
||||
"""
|
||||
# ensure initial data
|
||||
(self.lead_w_partner_company | self.lead_1).write({'type': 'opportunity'})
|
||||
self.lead_1.write({'probability': 60})
|
||||
|
||||
self.assertEqual(self.lead_w_partner_company.stage_id.sequence, 1)
|
||||
self.assertEqual(self.lead_1.stage_id.sequence, 1)
|
||||
|
||||
merge = self.env['crm.merge.opportunity'].with_context({
|
||||
'active_model': 'crm.lead',
|
||||
'active_ids': self.leads.ids,
|
||||
'active_id': False,
|
||||
}).create({
|
||||
'team_id': self.sales_team_convert.id,
|
||||
'user_id': False,
|
||||
})
|
||||
# TDE FIXME: see aa44700dccdc2618e0b8bc94252789264104047c -> no user, no team -> strange
|
||||
merge.write({'team_id': self.sales_team_convert.id})
|
||||
|
||||
# TDE FIXME: not sure the browse in default get of wizard intended to exlude lost, as it browse ids
|
||||
# and exclude inactive leads, but that's not written anywhere ... intended ??
|
||||
self.assertEqual(merge.opportunity_ids, self.leads - self.lead_w_email_lost)
|
||||
ordered_merge = self.lead_w_partner_company + self.lead_w_contact + self.lead_w_email + self.lead_w_partner
|
||||
|
||||
result = merge.action_merge()
|
||||
merge_opportunity = self.env['crm.lead'].browse(result['res_id'])
|
||||
self.assertFalse((ordered_merge - merge_opportunity).exists())
|
||||
self.assertEqual(merge_opportunity, self.lead_1)
|
||||
self.assertEqual(merge_opportunity.type, 'opportunity')
|
||||
|
||||
# merged opportunity has same salesman (not updated in wizard)
|
||||
self.assertEqual(merge_opportunity.user_id, self.user_sales_leads)
|
||||
# TDE FIXME: as same uer_id is enforced, team is updated through onchange and therefore stage
|
||||
self.assertEqual(merge_opportunity.team_id, self.sales_team_convert)
|
||||
# self.assertEqual(merge_opportunity.team_id, self.sales_team_1)
|
||||
# TDE FIXME: BUT team_id is computed after checking stage, based on wizard's team_id
|
||||
self.assertEqual(merge_opportunity.stage_id, self.stage_team_convert_1)
|
||||
|
||||
@users('user_sales_manager')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_lead_merge_probability_auto(self):
|
||||
""" Check master lead keeps its automated probability when merged. """
|
||||
self.lead_1.write({'type': 'opportunity', 'probability': self.lead_1.automated_probability})
|
||||
self.assertTrue(self.lead_1.is_automated_probability)
|
||||
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(merged_lead.is_automated_probability, "lead with Auto proba should remain with auto probability")
|
||||
|
||||
@users('user_sales_manager')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_lead_merge_probability_auto_empty(self):
|
||||
""" Check master lead keeps its automated probability when merged
|
||||
even if its probability is 0. """
|
||||
self.lead_1.write({'type': 'opportunity', 'probability': 0, 'automated_probability': 0})
|
||||
self.assertTrue(self.lead_1.is_automated_probability)
|
||||
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(merged_lead.is_automated_probability, "lead with Auto proba should remain with auto probability")
|
||||
|
||||
@users('user_sales_manager')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_lead_merge_probability_manual(self):
|
||||
""" Check master lead keeps its manual probability when merged. """
|
||||
self.lead_1.write({'probability': 60})
|
||||
self.assertFalse(self.lead_1.is_automated_probability)
|
||||
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.assertEqual(merged_lead.probability, 60, "Manual Probability should remain the same after the merge")
|
||||
self.assertFalse(merged_lead.is_automated_probability)
|
||||
|
||||
@users('user_sales_manager')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_lead_merge_probability_manual_empty(self):
|
||||
""" Check master lead keeps its manual probability when merged even if
|
||||
its probability is 0. """
|
||||
self.lead_1.write({'type': 'opportunity', 'probability': 0})
|
||||
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.assertEqual(merged_lead.probability, 0, "Manual Probability should remain the same after the merge")
|
||||
self.assertFalse(merged_lead.is_automated_probability)
|
||||
|
||||
@users('user_sales_manager')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_merge_method(self):
|
||||
""" In case of mix, opportunities are on top, and result is an opportunity
|
||||
|
||||
lead_1 -------------------opp----seq=1---probability=50
|
||||
lead_w_partner_company ---opp----seq=1---probability=50 (ID greater)
|
||||
lead_w_contact -----------lead---seq=3
|
||||
lead_w_email -------------lead---seq=3
|
||||
lead_w_partner -----------lead---seq=False
|
||||
"""
|
||||
# ensure initial data
|
||||
(self.lead_w_partner_company | self.lead_1).write({'type': 'opportunity', 'probability': 50})
|
||||
leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
|
||||
|
||||
# lead_w_partner is lost, check that the "lost_reason" is not propagated
|
||||
# because "lead_1" is not lost
|
||||
lost_reason = self.env['crm.lost.reason'].create({'name': 'Test Reason'})
|
||||
self.lead_w_partner.write({
|
||||
'lost_reason_id': lost_reason,
|
||||
'probability': 0,
|
||||
})
|
||||
|
||||
all_tags = self.leads.mapped('tag_ids')
|
||||
|
||||
with self.assertLeadMerged(self.lead_1, leads,
|
||||
name='Nibbler Spacecraft Request',
|
||||
partner_id=self.contact_company_1,
|
||||
priority='2',
|
||||
lost_reason_id=False,
|
||||
tag_ids=all_tags):
|
||||
leads._merge_opportunity(auto_unlink=False, max_length=None)
|
||||
|
||||
@users('user_sales_manager')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_merge_method_propagate_lost_reason(self):
|
||||
"""Check that the lost reason is propagated to the final lead if it's lost."""
|
||||
self.leads.write({
|
||||
'probability': 0,
|
||||
'automated_probability': 50, # Do not automatically update the probability
|
||||
})
|
||||
|
||||
lost_reason = self.env['crm.lost.reason'].create({'name': 'Test Reason'})
|
||||
self.lead_w_partner.lost_reason_id = lost_reason
|
||||
|
||||
leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
|
||||
|
||||
with self.assertLeadMerged(leads[0], leads, lost_reason_id=lost_reason):
|
||||
leads._merge_opportunity(auto_unlink=False, max_length=None)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_merge_properties_formatting(self):
|
||||
lead = self.lead_1
|
||||
partners = self.env['res.partner'].create([{'name': 'Alice'}, {'name': 'Bob'}])
|
||||
|
||||
lead.lead_properties = [{
|
||||
'type': 'many2one',
|
||||
'comodel': 'res.partner',
|
||||
'name': 'test_many2one',
|
||||
'string': 'My Partner',
|
||||
'value': partners[0].id,
|
||||
'definition_changed': True,
|
||||
}, {
|
||||
'type': 'many2many',
|
||||
'comodel': 'res.partner',
|
||||
'name': 'test_many2many',
|
||||
'string': 'My Partners',
|
||||
'value': partners.ids,
|
||||
}, {
|
||||
'type': 'selection',
|
||||
'selection': [['a', 'A'], ['b', 'B']],
|
||||
'name': 'test_selection',
|
||||
'string': 'My Selection',
|
||||
'value': 'a',
|
||||
}, {
|
||||
'type': 'tags',
|
||||
'tags': [['a', 'A', 1], ['b', 'B', 2], ['c', 'C', 3]],
|
||||
'name': 'test_tags',
|
||||
'string': 'My Tags',
|
||||
'value': ['a', 'c'],
|
||||
}, {
|
||||
'type': 'boolean',
|
||||
'name': 'test_boolean',
|
||||
'string': 'My Boolean',
|
||||
'value': True,
|
||||
}, {
|
||||
'type': 'integer',
|
||||
'name': 'test_integer',
|
||||
'string': 'My Integer',
|
||||
'value': 1337,
|
||||
}, {
|
||||
'type': 'datetime',
|
||||
'name': 'test_datetime',
|
||||
'string': 'My Datetime',
|
||||
'value': '2022-02-21 16:11:42',
|
||||
}]
|
||||
|
||||
expected = [{
|
||||
'label': 'My Partner',
|
||||
'value': 'Alice',
|
||||
}, {
|
||||
'label': 'My Partners',
|
||||
'values': [
|
||||
{'name': 'Alice'},
|
||||
{'name': 'Bob'},
|
||||
],
|
||||
}, {
|
||||
'label': 'My Selection',
|
||||
'value': 'A',
|
||||
}, {
|
||||
'label': 'My Tags',
|
||||
'values': [
|
||||
{'name': 'A', 'color': 1},
|
||||
{'name': 'C', 'color': 3},
|
||||
],
|
||||
}, {
|
||||
'label': 'My Boolean',
|
||||
'value': 'Yes',
|
||||
}, {
|
||||
'label': 'My Integer',
|
||||
'value': 1337,
|
||||
}, {
|
||||
'label': 'My Datetime',
|
||||
# datetime are stored as string because they are not JSONifiable
|
||||
'value': '2022-02-21 16:11:42',
|
||||
}]
|
||||
|
||||
self.assertEqual(expected, lead._format_properties())
|
||||
|
||||
# check the rendered template
|
||||
result = self.env['ir.qweb']._render(
|
||||
'crm.crm_lead_merge_summary',
|
||||
{'opportunities': lead, 'is_html_empty': lambda x: True})
|
||||
self.assertIn("o_tag_color_1", result)
|
||||
self.assertIn("Alice", result)
|
||||
self.assertIn("Bob", result)
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_merge_method_dependencies(self):
|
||||
""" Test if dependences for leads are not lost while merging leads. In
|
||||
this test leads are ordered as
|
||||
|
||||
lead_w_partner_company ---opp----seq=1 (ID greater)
|
||||
lead_w_contact -----------lead---seq=3
|
||||
lead_w_email -------------lead---seq=3----------------attachments
|
||||
lead_1 -------------------lead---seq=1----------------activity+meeting
|
||||
lead_w_partner -----------lead---seq=False
|
||||
"""
|
||||
self.env['crm.lead'].browse(self.lead_w_partner_company.ids).write({'type': 'opportunity'})
|
||||
|
||||
# create side documents
|
||||
attachments = self.env['ir.attachment'].create([
|
||||
{'name': '%02d.txt' % idx,
|
||||
'datas': base64.b64encode(b'Att%02d' % idx),
|
||||
'res_model': 'crm.lead',
|
||||
'res_id': self.lead_w_email.id,
|
||||
} 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')
|
||||
calendar_event = self.env['calendar.event'].create({
|
||||
'name': 'Meeting with partner',
|
||||
'activity_ids': [(4, activity.id)],
|
||||
'start': '2021-06-12 21:00:00',
|
||||
'stop': '2021-06-13 00:00:00',
|
||||
'res_model_id': self.env['ir.model']._get('crm.crm_lead').id,
|
||||
'res_id': lead_1.id,
|
||||
'opportunity_id': lead_1.id,
|
||||
})
|
||||
|
||||
# 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,
|
||||
'active_id': False,
|
||||
}).create({
|
||||
'team_id': self.sales_team_convert.id,
|
||||
'user_id': False,
|
||||
})
|
||||
result = merge.action_merge()
|
||||
master_lead = self.leads.filtered(lambda lead: lead.id == result['res_id'])
|
||||
|
||||
# check result of merge process
|
||||
self.assertEqual(master_lead, self.lead_w_partner_company)
|
||||
# records updated
|
||||
self.assertEqual(calendar_event.opportunity_id, master_lead)
|
||||
self.assertEqual(calendar_event.res_id, master_lead.id)
|
||||
self.assertTrue(all(att.res_id == master_lead.id for att in attachments))
|
||||
# 2many accessors updated
|
||||
self.assertEqual(master_lead.activity_ids, activity)
|
||||
self.assertEqual(master_lead.calendar_event_ids, calendar_event)
|
||||
|
||||
@users('user_sales_manager')
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_merge_method_followers(self):
|
||||
""" Test that the followers of the leads are added in the destination lead.
|
||||
|
||||
They should be added if:
|
||||
- The related partner was active on the lead (posted a message in the last 30 days)
|
||||
- The related partner is not already following the destination lead
|
||||
|
||||
Leads Followers Info
|
||||
---------------------------------------------------------------------------------
|
||||
lead_w_contact contact_1 OK (destination lead)
|
||||
lead_w_email contact_1 KO (already following the destination lead)
|
||||
contact_2 OK (active on lead_w_email)
|
||||
contact_company KO (most recent message on lead_w_email is 35 days ago, message
|
||||
on lead_w_partner is not counted as they don't follow it)
|
||||
lead_w_partner contact_2 KO (already added with lead_w_email)
|
||||
lead_w_partner_company
|
||||
"""
|
||||
self.leads.message_follower_ids.unlink()
|
||||
self.leads.message_ids.unlink()
|
||||
|
||||
self.lead_w_contact.message_subscribe([self.contact_1.id])
|
||||
self.lead_w_email.message_subscribe([self.contact_1.id, self.contact_2.id, self.contact_company.id])
|
||||
self.lead_w_partner.message_subscribe([self.contact_2.id])
|
||||
|
||||
self.env['mail.message'].create([{
|
||||
'author_id': self.contact_1.id,
|
||||
'model': 'crm.lead',
|
||||
'res_id': self.lead_w_contact.id,
|
||||
'date': Datetime.now() - timedelta(days=1),
|
||||
'subtype_id': self.ref('mail.mt_comment'),
|
||||
'reply_to': False,
|
||||
'body': 'Test follower',
|
||||
}, {
|
||||
'author_id': self.contact_1.id,
|
||||
'model': 'crm.lead',
|
||||
'res_id': self.lead_w_email.id,
|
||||
'date': Datetime.now() - timedelta(days=20),
|
||||
'subtype_id': self.ref('mail.mt_comment'),
|
||||
'reply_to': False,
|
||||
'body': 'Test follower',
|
||||
}, {
|
||||
'author_id': self.contact_2.id,
|
||||
'model': 'crm.lead',
|
||||
'res_id': self.lead_w_email.id,
|
||||
'date': Datetime.now() - timedelta(days=15),
|
||||
'subtype_id': self.ref('mail.mt_comment'),
|
||||
'reply_to': False,
|
||||
'body': 'Test follower',
|
||||
}, {
|
||||
'author_id': self.contact_2.id,
|
||||
'model': 'crm.lead',
|
||||
'res_id': self.lead_w_partner.id,
|
||||
'date': Datetime.now() - timedelta(days=29),
|
||||
'subtype_id': self.ref('mail.mt_comment'),
|
||||
'reply_to': False,
|
||||
'body': 'Test follower',
|
||||
}, {
|
||||
'author_id': self.contact_company.id,
|
||||
'model': 'crm.lead',
|
||||
'res_id': self.lead_w_email.id,
|
||||
'date': Datetime.now() - timedelta(days=35),
|
||||
'subtype_id': self.ref('mail.mt_comment'),
|
||||
'reply_to': False,
|
||||
'body': 'Test follower',
|
||||
}, {
|
||||
'author_id': self.contact_company.id,
|
||||
'model': 'crm.lead',
|
||||
'res_id': self.lead_w_partner.id,
|
||||
'date': Datetime.now(),
|
||||
'subtype_id': self.ref('mail.mt_comment'),
|
||||
'reply_to': False,
|
||||
'body': 'Test follower',
|
||||
}])
|
||||
initial_followers = self.lead_w_contact.message_follower_ids
|
||||
|
||||
leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
|
||||
master_lead = leads._merge_opportunity(max_length=None)
|
||||
|
||||
self.assertEqual(master_lead, self.lead_w_contact)
|
||||
|
||||
# Check followers of the destination lead
|
||||
new_partner_followers = (master_lead.message_follower_ids - initial_followers).partner_id
|
||||
self.assertIn(self.contact_2, new_partner_followers,
|
||||
'The partner must follow the destination lead')
|
||||
# "contact_company" posted a message 35 days ago on lead_2, so it's considered as inactive
|
||||
# "contact_company" posted a message now on lead_3, but they don't follow lead_3
|
||||
# so this message is just ignored
|
||||
self.assertNotIn(self.contact_company, new_partner_followers,
|
||||
'The partner was not active on the lead')
|
||||
self.assertIn(self.contact_1, master_lead.message_follower_ids.partner_id,
|
||||
'Should not have removed follower of the destination lead')
|
||||
|
|
@ -0,0 +1,322 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon, INCOMING_EMAIL
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.tests import Form, tagged
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
@tagged('multi_company')
|
||||
class TestCRMLeadMultiCompany(TestCrmCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCRMLeadMultiCompany, cls).setUpClass()
|
||||
cls._activate_multi_company()
|
||||
|
||||
def test_initial_data(self):
|
||||
""" Ensure global data for those tests to avoid unwanted side effects """
|
||||
self.assertFalse(self.sales_team_1.company_id)
|
||||
self.assertEqual(self.user_sales_manager_mc.company_id, self.company_2)
|
||||
|
||||
@users('user_sales_manager_mc')
|
||||
def test_lead_mc_company_computation(self):
|
||||
""" Test lead company computation depending on various parameters. Check
|
||||
the company is set from the team_id or from the env if there is no team.
|
||||
No responsible, no team, should not limit company. """
|
||||
# Lead with falsy values are kept
|
||||
lead_no_team = self.env['crm.lead'].create({
|
||||
'name': 'L1',
|
||||
'team_id': False,
|
||||
'user_id': False,
|
||||
})
|
||||
self.assertFalse(lead_no_team.company_id)
|
||||
self.assertFalse(lead_no_team.team_id)
|
||||
self.assertFalse(lead_no_team.user_id)
|
||||
|
||||
# Lead with team with company sets company
|
||||
lead_team_c2 = self.env['crm.lead'].create({
|
||||
'name': 'L2',
|
||||
'team_id': self.team_company2.id,
|
||||
'user_id': False,
|
||||
})
|
||||
self.assertEqual(lead_team_c2.company_id, self.company_2)
|
||||
self.assertFalse(lead_team_c2.user_id)
|
||||
|
||||
# Update team wo company: reset lead company also
|
||||
lead_team_c2.team_id = self.sales_team_1
|
||||
self.assertFalse(lead_team_c2.company_id)
|
||||
|
||||
# Lead with global team has no company
|
||||
lead_team_no_company = self.env['crm.lead'].create({
|
||||
'name': 'No company',
|
||||
'team_id': self.sales_team_1.id,
|
||||
'user_id': False,
|
||||
})
|
||||
self.assertFalse(lead_no_team.company_id)
|
||||
|
||||
# Update team w company updates company
|
||||
lead_team_no_company.team_id = self.team_company2
|
||||
self.assertEqual(lead_team_no_company.company_id, self.company_2)
|
||||
self.assertEqual(lead_team_no_company.team_id, self.team_company2)
|
||||
|
||||
@users('user_sales_manager_mc')
|
||||
def test_lead_mc_company_computation_env_team_norestrict(self):
|
||||
""" Check that the computed company is the one coming from the team even
|
||||
when it's not in self.env.companies. This may happen when running the
|
||||
Lead Assignment task. """
|
||||
LeadUnsyncCids = self.env['crm.lead'].with_context(allowed_company_ids=[self.company_main.id])
|
||||
self.assertEqual(LeadUnsyncCids.env.company, self.company_main)
|
||||
self.assertEqual(LeadUnsyncCids.env.companies, self.company_main)
|
||||
self.assertEqual(LeadUnsyncCids.env.user.company_id, self.company_2)
|
||||
|
||||
# multicompany raises if trying to create manually
|
||||
with self.assertRaises(AccessError):
|
||||
lead = LeadUnsyncCids.create({
|
||||
'name': 'My Lead MC',
|
||||
'team_id': self.team_company2.id
|
||||
})
|
||||
|
||||
# simulate auto-creation through sudo (assignment-like)
|
||||
lead = LeadUnsyncCids.sudo().create({
|
||||
'name': 'My Lead MC',
|
||||
'team_id': self.team_company2.id,
|
||||
})
|
||||
self.assertEqual(lead.company_id, self.company_2)
|
||||
self.assertEqual(lead.team_id, self.team_company2)
|
||||
self.assertEqual(lead.user_id, self.user_sales_manager_mc)
|
||||
|
||||
@users('user_sales_manager_mc')
|
||||
def test_lead_mc_company_computation_env_user_restrict(self):
|
||||
""" Check that the computed company is allowed (aka in self.env.companies).
|
||||
If the assigned team has a set company, the lead has the same one. Otherwise, use one
|
||||
allowed by user, preferentially choose env current company, user's company otherwise."""
|
||||
# User is logged in company_main even their default company is company_2
|
||||
LeadUnsyncCids = self.env['crm.lead'].with_context(allowed_company_ids=[self.company_main.id])
|
||||
self.assertEqual(LeadUnsyncCids.env.company, self.company_main)
|
||||
self.assertEqual(LeadUnsyncCids.env.companies, self.company_main)
|
||||
self.assertEqual(LeadUnsyncCids.env.user.company_id, self.company_2)
|
||||
|
||||
# simulate auto-creation through sudo (assignment-like)
|
||||
lead_1_auto = LeadUnsyncCids.sudo().create({
|
||||
'name': 'My Lead MC 1 Auto',
|
||||
})
|
||||
self.assertEqual(lead_1_auto.team_id, self.sales_team_1,
|
||||
'[Auto/1] First available team in current company should have been assigned (fallback as user in no team in Main Company).')
|
||||
self.assertEqual(lead_1_auto.company_id, self.company_main,
|
||||
'[Auto/1] Current company should be set on the lead as no company was assigned given by team and company is allowed for user.')
|
||||
self.assertEqual(lead_1_auto.user_id, self.user_sales_manager_mc, '[Auto/1] Current user should have been assigned.')
|
||||
# manual creation
|
||||
lead_1_manual = LeadUnsyncCids.create({
|
||||
'name': 'My Lead MC',
|
||||
})
|
||||
self.assertEqual(lead_1_manual.team_id, self.sales_team_1,
|
||||
'[Auto/1] First available team in current company should have been assigned (fallback as user in no team in Main Company).')
|
||||
self.assertEqual(lead_1_manual.company_id, self.company_main,
|
||||
'[Auto/1] Current company should be set on the lead as no company was given by team and company is allowed for user.')
|
||||
self.assertEqual(lead_1_manual.user_id, self.user_sales_manager_mc, '[Manual/1] Current user should have been assigned.')
|
||||
|
||||
# Logged on other company will use that one for the lead company with sales_team_2 as is assigned to company_2
|
||||
LeadUnsyncCids = self.env['crm.lead'].with_context(allowed_company_ids=[self.company_main.id, self.company_2.id])
|
||||
LeadUnsyncCids = LeadUnsyncCids.with_company(self.company_2)
|
||||
self.assertEqual(LeadUnsyncCids.env.company, self.company_2)
|
||||
|
||||
lead_2_auto = LeadUnsyncCids.sudo().create({
|
||||
'name': 'My Lead MC 2 Auto',
|
||||
})
|
||||
self.assertEqual(lead_2_auto.team_id, self.team_company2,
|
||||
'[Auto/2] First available team user is a member of, in current company, should have been assigned.')
|
||||
self.assertEqual(lead_2_auto.company_id, self.company_2,
|
||||
'[Auto/2] Current company should be set on the lead as company was assigned on team.')
|
||||
self.assertEqual(lead_2_auto.user_id, self.user_sales_manager_mc, '[Auto/2] Current user should have been assigned.')
|
||||
lead_2_manual = LeadUnsyncCids.create({
|
||||
'name': 'My Lead MC 2 Manual',
|
||||
})
|
||||
self.assertEqual(lead_2_manual.team_id, self.team_company2,
|
||||
'[Manual/2] First available team user is a member of, in current company, should have been assigned.')
|
||||
self.assertEqual(lead_2_manual.company_id, self.company_2,
|
||||
'[Manual/2] Current company should be set on the lead as company was assigned on team.')
|
||||
self.assertEqual(lead_2_manual.user_id, self.user_sales_manager_mc, '[Manual/2] Current user should have been assigned.')
|
||||
|
||||
# If assigned team has no company, use company
|
||||
self.team_company2.write({'company_id': False})
|
||||
lead_3_auto = LeadUnsyncCids.sudo().create({
|
||||
'name': 'My Lead MC 3 Auto',
|
||||
})
|
||||
self.assertEqual(lead_3_auto.team_id, self.team_company2,
|
||||
'[Auto/3] First available team user is a member of should have been assigned (fallback as no team with same company defined).')
|
||||
self.assertEqual(lead_3_auto.company_id, self.company_2,
|
||||
'[Auto/3] Current company should be set on the lead as no company was given by team and company is allowed for user.')
|
||||
self.assertEqual(lead_3_auto.user_id, self.user_sales_manager_mc, '[Auto/3] Current user should have been assigned.')
|
||||
lead_3_manual = LeadUnsyncCids.create({
|
||||
'name': 'My Lead MC 3 Manual',
|
||||
})
|
||||
self.assertEqual(lead_3_manual.company_id, self.company_2,
|
||||
'[Auto/3] First available team user is a member of should have been assigned (fallback as no team with same company defined).')
|
||||
self.assertEqual(lead_3_manual.team_id, self.team_company2,
|
||||
'[Auto/3] Current company should be set on the lead as no company was given by team and company is allowed for user.')
|
||||
self.assertEqual(lead_3_manual.user_id, self.user_sales_manager_mc, '[Manual/3] Current user should have been assigned.')
|
||||
|
||||
# If all teams have no company and don't have user as member, the first sales team is used.
|
||||
self.team_company2.write({'member_ids': [(3, self.user_sales_manager_mc.id)]})
|
||||
|
||||
lead_4_auto = LeadUnsyncCids.sudo().create({
|
||||
'name': 'My Lead MC 4 Auto',
|
||||
})
|
||||
self.assertEqual(lead_4_auto.team_id, self.sales_team_1,
|
||||
'[Auto/4] As no team has current user as member nor current company as company_id, first available team should have been assigned.')
|
||||
self.assertEqual(lead_4_auto.company_id, self.company_2,
|
||||
'[Auto/4] Current company should be set on the lead as no company was given by team and company is allowed for user.')
|
||||
self.assertEqual(lead_4_auto.user_id, self.user_sales_manager_mc, '[Auto/4] Current user should have been assigned.')
|
||||
lead_4_manual = LeadUnsyncCids.create({
|
||||
'name': 'My Lead MC 4 Manual',
|
||||
})
|
||||
self.assertEqual(lead_4_manual.company_id, self.company_2,
|
||||
'[Manual/4] As no team has current user as member nor current company as company_id, first available team should have been assigned.')
|
||||
self.assertEqual(lead_4_manual.team_id, self.sales_team_1,
|
||||
'[Manual/4] Current company should be set on the lead as no company was given by team and company is allowed for user.')
|
||||
self.assertEqual(lead_4_manual.user_id, self.user_sales_manager_mc,
|
||||
'[Manual/4] Current user should have been assigned.')
|
||||
|
||||
@users('user_sales_manager_mc')
|
||||
def test_lead_mc_company_computation_partner_restrict(self):
|
||||
""" Check company on partner limits the company on lead. As contacts may
|
||||
be separated by company, lead with a partner should be limited to that
|
||||
company. """
|
||||
partner_c2 = self.partner_c2.with_env(self.env)
|
||||
self.assertEqual(partner_c2.company_id, self.company_2)
|
||||
lead = self.env['crm.lead'].create({
|
||||
'partner_id': partner_c2.id,
|
||||
'name': 'MC Partner, no company lead',
|
||||
'user_id': False,
|
||||
'team_id': False,
|
||||
})
|
||||
self.assertEqual(lead.company_id, self.company_2)
|
||||
|
||||
partner_main = self.env['res.partner'].create({
|
||||
'company_id': self.company_main.id,
|
||||
'email': 'partner_main@multicompany.example.com',
|
||||
'name': 'Customer for Main',
|
||||
})
|
||||
lead.write({'partner_id': partner_main})
|
||||
self.assertEqual(lead.company_id, self.company_main)
|
||||
|
||||
# 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
|
||||
with self.assertRaises(UserError):
|
||||
lead.write({
|
||||
'user_id': self.env.user,
|
||||
})
|
||||
|
||||
@users('user_sales_manager_mc')
|
||||
def test_lead_mc_company_form(self):
|
||||
""" Test lead company computation using form view """
|
||||
crm_lead_form = Form(self.env['crm.lead'])
|
||||
crm_lead_form.name = "Test Lead"
|
||||
|
||||
# default values: current user, its team and therefore its company
|
||||
self.assertEqual(crm_lead_form.company_id, self.company_2)
|
||||
self.assertEqual(crm_lead_form.user_id, self.user_sales_manager_mc)
|
||||
self.assertEqual(crm_lead_form.team_id, self.team_company2)
|
||||
|
||||
# remove user, team only
|
||||
crm_lead_form.user_id = self.env['res.users']
|
||||
self.assertEqual(crm_lead_form.company_id, self.company_2)
|
||||
self.assertEqual(crm_lead_form.user_id, self.env['res.users'])
|
||||
self.assertEqual(crm_lead_form.team_id, self.team_company2)
|
||||
|
||||
# remove team, user only
|
||||
crm_lead_form.user_id = self.user_sales_manager_mc
|
||||
crm_lead_form.team_id = self.env['crm.team']
|
||||
self.assertEqual(crm_lead_form.company_id, self.company_2)
|
||||
self.assertEqual(crm_lead_form.user_id, self.user_sales_manager_mc)
|
||||
self.assertEqual(crm_lead_form.team_id, self.env['crm.team'])
|
||||
|
||||
# remove both: void company to ease assignment
|
||||
crm_lead_form.user_id = self.env['res.users']
|
||||
self.assertEqual(crm_lead_form.company_id, self.env['res.company'])
|
||||
self.assertEqual(crm_lead_form.user_id, self.env['res.users'])
|
||||
self.assertEqual(crm_lead_form.team_id, self.env['crm.team'])
|
||||
|
||||
# force company manually
|
||||
crm_lead_form.company_id = self.company_2
|
||||
lead = crm_lead_form.save()
|
||||
|
||||
# user_sales_manager cannot read it due to MC rules
|
||||
with self.assertRaises(AccessError):
|
||||
lead.with_user(self.user_sales_manager).read(['name'])
|
||||
|
||||
@users('user_sales_manager_mc')
|
||||
def test_lead_mc_company_form_progressives_setup(self):
|
||||
""" Specific bug reported at Task-2520276. Flow
|
||||
0) The sales team have no company set
|
||||
1) Create a lead without a user_id and a team_id
|
||||
2) Assign a team to the lead
|
||||
3) Assign a user_id
|
||||
|
||||
Goal: if no company is set on the sales team the lead at step 2 should
|
||||
not have any company_id set. Previous behavior
|
||||
1) set the company of the env.user
|
||||
2) Keep the company of the lead
|
||||
3) set the user company if the current company is not one of the allowed company of the user
|
||||
|
||||
Wanted behavior
|
||||
1) leave the company empty
|
||||
2) set the company of the team even if it's False (so erase the company if the team has no company set)
|
||||
3) set the user company if the current company is not one of the allowed company of the user
|
||||
"""
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'Test Progressive Setup',
|
||||
'user_id': False,
|
||||
'team_id': False,
|
||||
})
|
||||
crm_lead_form = Form(lead)
|
||||
self.assertEqual(crm_lead_form.company_id, self.env['res.company'])
|
||||
|
||||
crm_lead_form.team_id = self.sales_team_1
|
||||
self.assertEqual(crm_lead_form.company_id, self.env['res.company'])
|
||||
|
||||
crm_lead_form.user_id = self.env.user
|
||||
# self.assertEqual(crm_lead_form.company_id, self.env['res.company']) # FIXME
|
||||
self.assertEqual(crm_lead_form.company_id, self.company_2)
|
||||
|
||||
@users('user_sales_manager_mc')
|
||||
def test_lead_mc_company_form_w_partner_id(self):
|
||||
""" Test lead company computation with partner having a company. """
|
||||
partner_c2 = self.partner_c2.with_env(self.env)
|
||||
crm_lead_form = Form(self.env['crm.lead'])
|
||||
crm_lead_form.name = "Test Lead"
|
||||
|
||||
crm_lead_form.user_id = self.user_sales_manager_mc
|
||||
crm_lead_form.partner_id = partner_c2
|
||||
self.assertEqual(crm_lead_form.company_id, self.company_2, 'Crm: company comes from sales')
|
||||
self.assertEqual(crm_lead_form.team_id, self.team_company2, 'Crm: team comes from sales')
|
||||
|
||||
# reset sales: should not reset company, as partner constrains it
|
||||
crm_lead_form.team_id = self.env['crm.team']
|
||||
crm_lead_form.user_id = self.env['res.users']
|
||||
# ensuring that company_id is not overwritten when the salesperson becomes empty (w\o any team_id)
|
||||
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):
|
||||
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',
|
||||
'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),
|
||||
subject='Team having partner in company',
|
||||
target_model='crm.lead',
|
||||
)
|
||||
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)
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
# -*- 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 import tagged, users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mail_thread', 'mail_gateway')
|
||||
class NewLeadNotification(TestCrmCommon):
|
||||
|
||||
@users('user_sales_manager')
|
||||
def test_lead_message_get_suggested_recipient(self):
|
||||
""" Test '_message_get_suggested_recipients' and its override in lead. """
|
||||
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([
|
||||
{
|
||||
'email_from': '"New Customer" <new.customer.format@test.example.com>',
|
||||
'name': 'Test Suggestion (email_from with format)',
|
||||
'partner_name': 'Format Name',
|
||||
'user_id': self.user_sales_leads.id,
|
||||
}, {
|
||||
'email_from': 'new.customer.multi.1@test.example.com, new.customer.2@test.example.com',
|
||||
'name': 'Test Suggestion (email_from multi)',
|
||||
'partner_name': 'Multi Name',
|
||||
'user_id': self.user_sales_leads.id,
|
||||
}, {
|
||||
'email_from': 'new.customer.simple@test.example.com',
|
||||
'name': 'Test Suggestion (email_from)',
|
||||
'partner_name': 'Std Name',
|
||||
'user_id': self.user_sales_leads.id,
|
||||
}, {
|
||||
'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 with cc email)',
|
||||
'partner_id': partner_no_email.id,
|
||||
'email_cc': 'test_cc@odoo.com'
|
||||
}
|
||||
])
|
||||
for lead, expected_suggested in zip(
|
||||
lead_format + lead_multi + lead_from + lead_partner + lead_partner_no_email + lead_partner_no_email_with_cc,
|
||||
[
|
||||
[(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]
|
||||
self.assertEqual(len(res), len(expected_suggested))
|
||||
self.assertEqual(res, expected_suggested)
|
||||
|
||||
def test_new_lead_notification(self):
|
||||
""" Test newly create leads like from the website. People and channels
|
||||
subscribed to the Sales Team shoud be notified. """
|
||||
# subscribe a partner and a channel to the Sales Team with new lead subtype
|
||||
sales_team_1 = self.env['crm.team'].create({
|
||||
'name': 'Test Sales Team',
|
||||
'alias_name': 'test_sales_team',
|
||||
})
|
||||
|
||||
subtype = self.env.ref("crm.mt_salesteam_lead")
|
||||
sales_team_1.message_subscribe(partner_ids=[self.user_sales_manager.partner_id.id], subtype_ids=[subtype.id])
|
||||
|
||||
# Imitate what happens in the controller when somebody creates a new
|
||||
# lead from the website form
|
||||
lead = self.env["crm.lead"].with_context(mail_create_nosubscribe=True).sudo().create({
|
||||
"contact_name": "Somebody",
|
||||
"description": "Some question",
|
||||
"email_from": "somemail@example.com",
|
||||
"name": "Some subject",
|
||||
"partner_name": "Some company",
|
||||
"team_id": sales_team_1.id,
|
||||
"phone": "+0000000000"
|
||||
})
|
||||
# partner and channel should be auto subscribed
|
||||
self.assertIn(self.user_sales_manager.partner_id, lead.message_partner_ids)
|
||||
|
||||
msg = lead.message_ids[0]
|
||||
self.assertIn(self.user_sales_manager.partner_id, msg.notified_partner_ids)
|
||||
|
||||
# The user should have a new unread message
|
||||
lead_user = lead.with_user(self.user_sales_manager)
|
||||
self.assertTrue(lead_user.message_needaction)
|
||||
|
||||
@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'})
|
||||
|
||||
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_team0 = self.env['crm.team'].create({
|
||||
'name': 'crm team 0',
|
||||
'company_id': company0.id,
|
||||
})
|
||||
crm_team1 = self.env['crm.team'].create({
|
||||
'name': 'crm team 1',
|
||||
'company_id': company1.id,
|
||||
})
|
||||
|
||||
mail_alias0 = self.env['mail.alias'].create({
|
||||
'alias_name': 'sale_team_0',
|
||||
'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_name': 'sale_team_1',
|
||||
'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,
|
||||
})
|
||||
|
||||
crm_team0.write({'alias_id': mail_alias0.id})
|
||||
crm_team1.write({'alias_id': mail_alias1.id})
|
||||
|
||||
new_message0 = """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
|
||||
Content-Type: multipart/alternative; boundary="000000000000a47519057e029630"
|
||||
|
||||
--000000000000a47519057e029630
|
||||
Content-Type: text/plain; charset="UTF-8"
|
||||
|
||||
|
||||
--000000000000a47519057e029630
|
||||
Content-Type: text/html; charset="UTF-8"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<div>A good message</div>
|
||||
|
||||
--000000000000a47519057e029630--
|
||||
"""
|
||||
|
||||
new_message1 = """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
|
||||
Content-Type: multipart/alternative; boundary="000000000000a47519057e029630"
|
||||
|
||||
--000000000000a47519057e029630
|
||||
Content-Type: text/plain; charset="UTF-8"
|
||||
|
||||
|
||||
--000000000000a47519057e029630
|
||||
Content-Type: text/html; charset="UTF-8"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<div>A good message bis</div>
|
||||
|
||||
--000000000000a47519057e029630--
|
||||
"""
|
||||
crm_lead0_id = self.env['mail.thread'].message_process('crm.lead', new_message0)
|
||||
crm_lead1_id = self.env['mail.thread'].message_process('crm.lead', new_message1)
|
||||
|
||||
crm_lead0 = self.env['crm.lead'].browse(crm_lead0_id)
|
||||
crm_lead1 = self.env['crm.lead'].browse(crm_lead1_id)
|
||||
|
||||
self.assertEqual(crm_lead0.team_id, crm_team0)
|
||||
self.assertEqual(crm_lead1.team_id, crm_team1)
|
||||
|
||||
self.assertEqual(crm_lead0.company_id, company0)
|
||||
self.assertEqual(crm_lead1.company_id, company1)
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import date, datetime
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon
|
||||
from odoo.tests.common import tagged, users
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestCRMLeadSmartCalendar(TestCrmCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCRMLeadSmartCalendar, cls).setUpClass()
|
||||
# weekstart index : 7 (sunday), tz : UTC -4 / -5
|
||||
cls.user_NY_en_US = mail_new_test_user(
|
||||
cls.env, login='user_NY_en_US', lang='en_US', tz='America/New_York',
|
||||
name='user_NY_en_US User', email='user_NY_en_US@test.example.com',
|
||||
notification_type='inbox',
|
||||
groups='sales_team.group_sale_salesman_all_leads,base.group_partner_manager,crm.group_use_lead')
|
||||
# weekstart index : 1 (monday), tz : UTC+0
|
||||
cls.env['res.lang']._activate_lang('pt_PT')
|
||||
cls.user_UTC_pt_PT = mail_new_test_user(
|
||||
cls.env, login='user_UTC_pt_PT', lang='pt_PT', tz='Europe/Lisbon',
|
||||
name='user_UTC_pt_PT User', email='user_UTC_pt_PT@test.example.com',
|
||||
notification_type='inbox',
|
||||
groups='sales_team.group_sale_salesman_all_leads,base.group_partner_manager,crm.group_use_lead')
|
||||
|
||||
cls.next_year = datetime.now().year + 1
|
||||
cls.calendar_meeting_1 = cls.env['calendar.event'].create({
|
||||
'name': 'calendar_meeting_1',
|
||||
'start': datetime(2020, 12, 13, 17),
|
||||
'stop': datetime(2020, 12, 13, 22)})
|
||||
cls.calendar_meeting_2 = cls.env['calendar.event'].create({
|
||||
'name': 'calendar_meeting_2',
|
||||
'start': datetime(2020, 12, 13, 2),
|
||||
'stop': datetime(2020, 12, 13, 3)})
|
||||
cls.calendar_meeting_3 = cls.env['calendar.event'].create({
|
||||
'name': 'calendar_meeting_3',
|
||||
'start': datetime(cls.next_year, 5, 4, 12),
|
||||
'stop': datetime(cls.next_year, 5, 4, 13)})
|
||||
cls.calendar_meeting_4 = cls.env['calendar.event'].create({
|
||||
'name': 'calendar_meeting_4',
|
||||
'allday': True,
|
||||
'start': datetime(2020, 12, 6, 0, 0, 0),
|
||||
'stop': datetime(2020, 12, 6, 23, 59, 59)})
|
||||
cls.calendar_meeting_5 = cls.env['calendar.event'].create({
|
||||
'name': 'calendar_meeting_5',
|
||||
'start': datetime(2020, 12, 13, 8),
|
||||
'stop': datetime(2020, 12, 13, 18),
|
||||
'allday': True})
|
||||
cls.calendar_meeting_6 = cls.env['calendar.event'].create({
|
||||
'name': 'calendar_meeting_6',
|
||||
'start': datetime(2020, 12, 12, 0),
|
||||
'stop': datetime(2020, 12, 19, 0)})
|
||||
|
||||
@users('user_NY_en_US')
|
||||
def test_meeting_view_parameters_1(self):
|
||||
lead_smart_calendar_1 = self.env['crm.lead'].create({'name': 'Lead 1 - user_NY_en_US'})
|
||||
|
||||
mode, initial_date = lead_smart_calendar_1._get_opportunity_meeting_view_parameters()
|
||||
self.assertEqual(mode, 'week')
|
||||
self.assertEqual(initial_date, False)
|
||||
|
||||
self.calendar_meeting_1.write({'opportunity_id': lead_smart_calendar_1.id})
|
||||
mode, initial_date = lead_smart_calendar_1._get_opportunity_meeting_view_parameters()
|
||||
self.assertEqual(mode, 'week')
|
||||
self.assertEqual(initial_date, date(2020, 12, 13))
|
||||
|
||||
self.calendar_meeting_2.write({'opportunity_id': lead_smart_calendar_1.id})
|
||||
mode, initial_date = lead_smart_calendar_1._get_opportunity_meeting_view_parameters()
|
||||
self.assertEqual(mode, 'month')
|
||||
self.assertEqual(initial_date, date(2020, 12, 12))
|
||||
|
||||
self.calendar_meeting_3.write({'opportunity_id': lead_smart_calendar_1.id})
|
||||
mode, initial_date = lead_smart_calendar_1._get_opportunity_meeting_view_parameters()
|
||||
self.assertEqual(mode, 'week')
|
||||
self.assertEqual(initial_date, date(self.next_year, 5, 4))
|
||||
|
||||
lead_smart_calendar_2 = self.env['crm.lead'].create({'name': 'Lead 2 - user_NY_en_US'})
|
||||
|
||||
self.calendar_meeting_4.write({'opportunity_id': lead_smart_calendar_2.id})
|
||||
mode, initial_date = lead_smart_calendar_2._get_opportunity_meeting_view_parameters()
|
||||
self.assertEqual(mode, 'week')
|
||||
self.assertEqual(initial_date, date(2020, 12, 6))
|
||||
|
||||
self.calendar_meeting_2.write({'opportunity_id': lead_smart_calendar_2.id})
|
||||
mode, initial_date = lead_smart_calendar_2._get_opportunity_meeting_view_parameters()
|
||||
self.assertEqual(mode, 'week')
|
||||
self.assertEqual(initial_date, date(2020, 12, 6))
|
||||
|
||||
self.calendar_meeting_5.write({'opportunity_id': lead_smart_calendar_2.id})
|
||||
mode, initial_date = lead_smart_calendar_2._get_opportunity_meeting_view_parameters()
|
||||
self.assertEqual(mode, 'month')
|
||||
self.assertEqual(initial_date, date(2020, 12, 6))
|
||||
|
||||
@users('user_UTC_pt_PT')
|
||||
def test_meeting_view_parameters_2(self):
|
||||
|
||||
lead_smart_calendar_1 = self.env['crm.lead'].create({'name': 'Lead 1 - user_UTC_pt_PT'})
|
||||
|
||||
self.calendar_meeting_1.write({'opportunity_id': lead_smart_calendar_1.id})
|
||||
self.calendar_meeting_2.write({'opportunity_id': lead_smart_calendar_1.id})
|
||||
mode, initial_date = lead_smart_calendar_1._get_opportunity_meeting_view_parameters()
|
||||
self.assertEqual(mode, 'week')
|
||||
self.assertEqual(initial_date, date(2020, 12, 13))
|
||||
|
||||
self.calendar_meeting_3.write({'opportunity_id': lead_smart_calendar_1.id})
|
||||
mode, initial_date = lead_smart_calendar_1._get_opportunity_meeting_view_parameters()
|
||||
self.assertEqual(mode, 'week')
|
||||
self.assertEqual(initial_date, date(self.next_year, 5, 4))
|
||||
|
||||
lead_smart_calendar_2 = self.env['crm.lead'].create({'name': 'Lead 2 - user_UTC_pt_PT'})
|
||||
|
||||
self.calendar_meeting_6.write({'opportunity_id': lead_smart_calendar_2.id})
|
||||
mode, initial_date = lead_smart_calendar_2._get_opportunity_meeting_view_parameters()
|
||||
self.assertEqual(mode, 'week')
|
||||
self.assertEqual(initial_date, date(2020, 12, 12))
|
||||
|
||||
@users('user_sales_leads')
|
||||
def test_meeting_creation_from_lead_form(self):
|
||||
""" When going from a lead to the Calendar and adding a meeting, both salesman and customer
|
||||
should be attendees of the event """
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': 'SuperLead',
|
||||
'partner_id': self.contact_1.id,
|
||||
})
|
||||
calendar_action = lead.action_schedule_meeting()
|
||||
event = self.env['calendar.event'].with_context(calendar_action['context']).create({
|
||||
'start': datetime(2020, 12, 13, 17),
|
||||
'stop': datetime(2020, 12, 13, 22),
|
||||
})
|
||||
self.assertEqual(len(event.attendee_ids), 2)
|
||||
self.assertIn(self.user_sales_leads.partner_id, event.attendee_ids.partner_id)
|
||||
self.assertIn(self.contact_1, event.attendee_ids.partner_id)
|
||||
635
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_pls.py
Normal file
635
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_pls.py
Normal file
|
|
@ -0,0 +1,635 @@
|
|||
# -*- 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.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.common import TransactionCase
|
||||
|
||||
|
||||
@tagged('crm_lead_pls')
|
||||
class TestCRMPLS(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()
|
||||
|
||||
cls.company_main = cls.env.user.company_id
|
||||
cls.user_sales_manager = mail_new_test_user(
|
||||
cls.env, login='user_sales_manager',
|
||||
name='Martin PLS Sales Manager', email='crm_manager@test.example.com',
|
||||
company_id=cls.company_main.id,
|
||||
notification_type='inbox',
|
||||
groups='sales_team.group_sale_manager,base.group_partner_manager',
|
||||
)
|
||||
|
||||
cls.pls_team = cls.env['crm.team'].create({
|
||||
'name': 'PLS Team',
|
||||
})
|
||||
|
||||
# Ensure independance 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):
|
||||
return {
|
||||
'name': 'lead_' + name_suffix,
|
||||
'type': 'opportunity',
|
||||
'state_id': state_id,
|
||||
'email_state': email_state,
|
||||
'phone_state': phone_state,
|
||||
'source_id': source_id,
|
||||
'stage_id': stage_id,
|
||||
'country_id': country_id,
|
||||
'team_id': team_id
|
||||
}
|
||||
|
||||
def generate_leads_with_tags(self, tag_ids):
|
||||
Lead = self.env['crm.lead']
|
||||
team_id = self.env['crm.team'].create({
|
||||
'name': 'blup',
|
||||
}).id
|
||||
|
||||
leads_to_create = []
|
||||
for i in range(150):
|
||||
if i < 50: # tag 1
|
||||
leads_to_create.append({
|
||||
'name': 'lead_tag_%s' % str(i),
|
||||
'tag_ids': [(4, tag_ids[0])],
|
||||
'team_id': team_id
|
||||
})
|
||||
elif i < 100: # tag 2
|
||||
leads_to_create.append({
|
||||
'name': 'lead_tag_%s' % str(i),
|
||||
'tag_ids': [(4, tag_ids[1])],
|
||||
'team_id': team_id
|
||||
})
|
||||
else: # tag 1 and 2
|
||||
leads_to_create.append({
|
||||
'name': 'lead_tag_%s' % str(i),
|
||||
'tag_ids': [(6, 0, tag_ids)],
|
||||
'team_id': team_id
|
||||
})
|
||||
|
||||
leads_with_tags = Lead.create(leads_to_create)
|
||||
|
||||
return leads_with_tags
|
||||
|
||||
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
|
||||
"""
|
||||
# Set the PLS config
|
||||
frequency_fields = self.env['crm.lead.scoring.frequency.field'].search([])
|
||||
pls_fields_str = ','.join(frequency_fields.mapped('field_id.name'))
|
||||
pls_start_date_str = "2021-01-01"
|
||||
IrConfigSudo = self.env['ir.config_parameter'].sudo()
|
||||
IrConfigSudo.set_param("crm.pls_start_date", pls_start_date_str)
|
||||
IrConfigSudo.set_param("crm.pls_fields", pls_fields_str)
|
||||
|
||||
date_to_update = "2021-02-02"
|
||||
fields_to_remove = frequency_fields.filtered(lambda f: f.field_id.name in ['source_id', 'lang_id'])
|
||||
fields_after_updation_str = ','.join((frequency_fields - fields_to_remove).mapped('field_id.name'))
|
||||
|
||||
# Check that wizard to update lead probabilities has correct value set by default
|
||||
pls_update_wizard = Form(self.env['crm.lead.pls.update'])
|
||||
with pls_update_wizard:
|
||||
self.assertEqual(Date.to_string(pls_update_wizard.pls_start_date), pls_start_date_str, 'Correct date is taken from config')
|
||||
self.assertEqual(','.join([f.field_id.name for f in pls_update_wizard.pls_fields]), pls_fields_str, 'Correct fields are taken from config')
|
||||
# Update the wizard values and check that config values and probabilities are updated accordingly
|
||||
pls_update_wizard.pls_start_date = date_to_update
|
||||
for field in fields_to_remove:
|
||||
pls_update_wizard.pls_fields.remove(field.id)
|
||||
|
||||
pls_update_wizard0 = pls_update_wizard.save()
|
||||
pls_update_wizard0.action_update_crm_lead_probabilities()
|
||||
|
||||
# Config params should have been updated
|
||||
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_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:
|
||||
country_id : 1,2,3
|
||||
state_id: 1,2,3
|
||||
email_state: correct, incorrect, None
|
||||
phone_state: correct, incorrect, None
|
||||
source_id: 1,2,3
|
||||
stage_id: 1,2,3 + the won stage
|
||||
And we will compute all of this for 2 different team_id
|
||||
Note : We assume here that original bayes computation is correct
|
||||
as we don't compute manually the probabilities."""
|
||||
Lead = self.env['crm.lead']
|
||||
LeadScoringFrequency = self.env['crm.lead.scoring.frequency']
|
||||
state_values = ['correct', 'incorrect', None]
|
||||
source_ids = self.env['utm.source'].search([], limit=3).ids
|
||||
state_ids = self.env['res.country.state'].search([], limit=3).ids
|
||||
country_ids = self.env['res.country'].search([], limit=3).ids
|
||||
stage_ids = self.env['crm.stage'].search([], limit=3).ids
|
||||
won_stage_id = self.env['crm.stage'].search([('is_won', '=', True)], limit=1).id
|
||||
team_ids = self.env['crm.team'].create([{'name': 'Team Test 1'}, {'name': 'Team Test 2'}, {'name': 'Team Test 3'}]).ids
|
||||
# create bunch of lost and won crm_lead
|
||||
leads_to_create = []
|
||||
# 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]))
|
||||
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]))
|
||||
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]))
|
||||
# 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]))
|
||||
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]))
|
||||
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]))
|
||||
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]))
|
||||
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]))
|
||||
|
||||
# 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]))
|
||||
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]))
|
||||
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]))
|
||||
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]))
|
||||
|
||||
leads = Lead.create(leads_to_create)
|
||||
|
||||
# Assert lead data.
|
||||
existing_leads = Lead.with_context({'active_filter': False}).search([])
|
||||
self.assertEqual(existing_leads, leads)
|
||||
self.assertEqual(existing_leads.filtered(lambda lead: not lead.team_id), leads[-4::])
|
||||
|
||||
# Assign leads without team to team 3 to compare probability
|
||||
# as a separate team and the one with no team set. See below (*)
|
||||
leads[-4::].team_id = team_ids[2]
|
||||
|
||||
# Set the PLS config
|
||||
self.env['ir.config_parameter'].sudo().set_param("crm.pls_start_date", "2000-01-01")
|
||||
self.env['ir.config_parameter'].sudo().set_param("crm.pls_fields", "country_id,state_id,email_state,phone_state,source_id,tag_ids")
|
||||
|
||||
# set leads as won and lost
|
||||
# for Team 1
|
||||
leads[0].action_set_lost()
|
||||
leads[1].action_set_lost()
|
||||
leads[2].action_set_won()
|
||||
# for Team 2
|
||||
leads[5].action_set_lost()
|
||||
leads[6].action_set_lost()
|
||||
leads[7].action_set_won()
|
||||
# Leads with no team
|
||||
leads[10].action_set_won()
|
||||
leads[11].action_set_lost()
|
||||
leads[12].action_set_lost()
|
||||
|
||||
# A. Test Full Rebuild
|
||||
# rebuild frequencies table and recompute automated_probability for all leads.
|
||||
Lead._cron_update_automated_probabilities()
|
||||
|
||||
# As the cron is computing and writing in SQL queries, we need to invalidate the cache
|
||||
self.env.invalidate_all()
|
||||
|
||||
self.assertEqual(tools.float_compare(leads[3].automated_probability, 33.49, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[8].automated_probability, 7.74, 2), 0)
|
||||
lead_13_team_3_proba = leads[13].automated_probability
|
||||
self.assertEqual(tools.float_compare(lead_13_team_3_proba, 35.09, 2), 0)
|
||||
|
||||
# Probability for Lead with no teams should be based on all the leads no matter their team.
|
||||
# De-assign team 3 and rebuilt frequency table and recompute.
|
||||
# Proba should be different as "no team" is not considered as a separated team. (*)
|
||||
leads[-4::].write({'team_id': False})
|
||||
leads[-4::].flush_recordset()
|
||||
|
||||
Lead._cron_update_automated_probabilities()
|
||||
lead_13_no_team_proba = leads[13].automated_probability
|
||||
self.assertTrue(lead_13_team_3_proba != leads[13].automated_probability, "Probability for leads with no team should be different than if they where in their own team.")
|
||||
self.assertAlmostEqual(lead_13_no_team_proba, 35.19, places=2)
|
||||
|
||||
# Test frequencies
|
||||
lead_4_stage_0_freq = LeadScoringFrequency.search([('team_id', '=', leads[4].team_id.id), ('variable', '=', 'stage_id'), ('value', '=', stage_ids[0])])
|
||||
lead_4_stage_won_freq = LeadScoringFrequency.search([('team_id', '=', leads[4].team_id.id), ('variable', '=', 'stage_id'), ('value', '=', won_stage_id)])
|
||||
lead_4_country_freq = LeadScoringFrequency.search([('team_id', '=', leads[4].team_id.id), ('variable', '=', 'country_id'), ('value', '=', leads[4].country_id.id)])
|
||||
lead_4_email_state_freq = LeadScoringFrequency.search([('team_id', '=', leads[4].team_id.id), ('variable', '=', 'email_state'), ('value', '=', str(leads[4].email_state))])
|
||||
|
||||
lead_9_stage_0_freq = LeadScoringFrequency.search([('team_id', '=', leads[9].team_id.id), ('variable', '=', 'stage_id'), ('value', '=', stage_ids[0])])
|
||||
lead_9_stage_won_freq = LeadScoringFrequency.search([('team_id', '=', leads[9].team_id.id), ('variable', '=', 'stage_id'), ('value', '=', won_stage_id)])
|
||||
lead_9_country_freq = LeadScoringFrequency.search([('team_id', '=', leads[9].team_id.id), ('variable', '=', 'country_id'), ('value', '=', leads[9].country_id.id)])
|
||||
lead_9_email_state_freq = LeadScoringFrequency.search([('team_id', '=', leads[9].team_id.id), ('variable', '=', 'email_state'), ('value', '=', str(leads[9].email_state))])
|
||||
|
||||
self.assertEqual(lead_4_stage_0_freq.won_count, 1.1)
|
||||
self.assertEqual(lead_4_stage_won_freq.won_count, 1.1)
|
||||
self.assertEqual(lead_4_country_freq.won_count, 0.1)
|
||||
self.assertEqual(lead_4_email_state_freq.won_count, 1.1)
|
||||
self.assertEqual(lead_4_stage_0_freq.lost_count, 2.1)
|
||||
self.assertEqual(lead_4_stage_won_freq.lost_count, 0.1)
|
||||
self.assertEqual(lead_4_country_freq.lost_count, 1.1)
|
||||
self.assertEqual(lead_4_email_state_freq.lost_count, 2.1)
|
||||
|
||||
self.assertEqual(lead_9_stage_0_freq.won_count, 1.1)
|
||||
self.assertEqual(lead_9_stage_won_freq.won_count, 1.1)
|
||||
self.assertEqual(lead_9_country_freq.won_count, 0.0) # frequency does not exist
|
||||
self.assertEqual(lead_9_email_state_freq.won_count, 1.1)
|
||||
self.assertEqual(lead_9_stage_0_freq.lost_count, 2.1)
|
||||
self.assertEqual(lead_9_stage_won_freq.lost_count, 0.1)
|
||||
self.assertEqual(lead_9_country_freq.lost_count, 0.0) # frequency does not exist
|
||||
self.assertEqual(lead_9_email_state_freq.lost_count, 2.1)
|
||||
|
||||
# B. Test Live Increment
|
||||
leads[4].action_set_lost()
|
||||
leads[9].action_set_won()
|
||||
|
||||
# re-get frequencies that did not exists before
|
||||
lead_9_country_freq = LeadScoringFrequency.search([('team_id', '=', leads[9].team_id.id), ('variable', '=', 'country_id'), ('value', '=', leads[9].country_id.id)])
|
||||
|
||||
# B.1. Test frequencies - team 1 should not impact team 2
|
||||
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) # + 1
|
||||
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, 2.1) # + 1
|
||||
self.assertEqual(lead_4_email_state_freq.lost_count, 3.1) # + 1
|
||||
|
||||
self.assertEqual(lead_9_stage_0_freq.won_count, 2.1) # + 1
|
||||
self.assertEqual(lead_9_stage_won_freq.won_count, 2.1) # + 1 - consider every stages when won
|
||||
self.assertEqual(lead_9_country_freq.won_count, 1.1) # + 1
|
||||
self.assertEqual(lead_9_email_state_freq.won_count, 2.1) # + 1
|
||||
self.assertEqual(lead_9_stage_0_freq.lost_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_9_stage_won_freq.lost_count, 0.1) # unchanged
|
||||
self.assertEqual(lead_9_country_freq.lost_count, 0.1) # unchanged (did not exists before)
|
||||
self.assertEqual(lead_9_email_state_freq.lost_count, 2.1) # unchanged
|
||||
|
||||
# Propabilities of other leads should not be impacted as only modified lead are recomputed.
|
||||
self.assertEqual(tools.float_compare(leads[3].automated_probability, 33.49, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[8].automated_probability, 7.74, 2), 0)
|
||||
|
||||
self.assertEqual(leads[3].is_automated_probability, True)
|
||||
self.assertEqual(leads[8].is_automated_probability, True)
|
||||
|
||||
# Restore -> Should decrease lost
|
||||
leads[4].toggle_active()
|
||||
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, 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
|
||||
|
||||
self.assertEqual(lead_9_stage_0_freq.won_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_9_stage_won_freq.won_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_9_country_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_9_email_state_freq.won_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_9_stage_0_freq.lost_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_9_stage_won_freq.lost_count, 0.1) # unchanged
|
||||
self.assertEqual(lead_9_country_freq.lost_count, 0.1) # unchanged
|
||||
self.assertEqual(lead_9_email_state_freq.lost_count, 2.1) # unchanged
|
||||
|
||||
# set to won stage -> Should increase won
|
||||
leads[4].stage_id = won_stage_id
|
||||
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
|
||||
self.assertEqual(lead_4_email_state_freq.won_count, 2.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
|
||||
|
||||
# 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
|
||||
|
||||
# Move to original stage -> Should do nothing (as lead is still lost)
|
||||
leads[4].stage_id = stage_ids[0]
|
||||
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
|
||||
|
||||
# Restore -> Should decrease lost - at the end, frequencies should be like first frequencyes tests (except for 0.0 -> 0.1)
|
||||
leads[4].toggle_active()
|
||||
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_country_freq.lost_count, 1.1) # - 1
|
||||
self.assertEqual(lead_4_email_state_freq.lost_count, 2.1) # - 1
|
||||
|
||||
# Probabilities should only be recomputed after modifying the lead itself.
|
||||
leads[3].stage_id = stage_ids[0] # probability should only change a bit as frequencies are almost the same (except 0.0 -> 0.1)
|
||||
leads[8].stage_id = stage_ids[0] # probability should change quite a lot
|
||||
|
||||
# Test frequencies (should not have changed)
|
||||
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) # unchanged
|
||||
self.assertEqual(lead_4_stage_won_freq.lost_count, 1.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
|
||||
|
||||
self.assertEqual(lead_9_stage_0_freq.won_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_9_stage_won_freq.won_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_9_country_freq.won_count, 1.1) # unchanged
|
||||
self.assertEqual(lead_9_email_state_freq.won_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_9_stage_0_freq.lost_count, 2.1) # unchanged
|
||||
self.assertEqual(lead_9_stage_won_freq.lost_count, 0.1) # unchanged
|
||||
self.assertEqual(lead_9_country_freq.lost_count, 0.1) # unchanged
|
||||
self.assertEqual(lead_9_email_state_freq.lost_count, 2.1) # unchanged
|
||||
|
||||
# Continue to test probability computation
|
||||
leads[3].probability = 40
|
||||
|
||||
self.assertEqual(leads[3].is_automated_probability, False)
|
||||
self.assertEqual(leads[8].is_automated_probability, True)
|
||||
|
||||
self.assertEqual(tools.float_compare(leads[3].automated_probability, 20.87, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[8].automated_probability, 2.43, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[3].probability, 40, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[8].probability, 2.43, 2), 0)
|
||||
|
||||
# Test modify country_id
|
||||
leads[8].country_id = country_ids[1]
|
||||
self.assertEqual(tools.float_compare(leads[8].automated_probability, 34.38, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[8].probability, 34.38, 2), 0)
|
||||
|
||||
leads[8].country_id = country_ids[0]
|
||||
self.assertEqual(tools.float_compare(leads[8].automated_probability, 2.43, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[8].probability, 2.43, 2), 0)
|
||||
|
||||
# ----------------------------------------------
|
||||
# Test tag_id frequencies and probability impact
|
||||
# ----------------------------------------------
|
||||
|
||||
tag_ids = self.env['crm.tag'].create([
|
||||
{'name': "Tag_test_1"},
|
||||
{'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[:30].action_set_lost() # 60% lost on tag 1
|
||||
leads_with_tags[31:50].action_set_won() # 40% won on tag 1
|
||||
leads_with_tags[50:90].action_set_lost() # 80% lost on tag 2
|
||||
leads_with_tags[91:100].action_set_won() # 20% won on tag 2
|
||||
leads_with_tags[100:135].action_set_lost() # 70% lost on tag 1 and 2
|
||||
leads_with_tags[136:150].action_set_won() # 30% won on tag 1 and 2
|
||||
# tag 1 : won = 19+14 / lost = 30+35
|
||||
# tag 2 : won = 9+14 / lost = 40+35
|
||||
|
||||
tag_1_freq = LeadScoringFrequency.search([('variable', '=', 'tag_id'), ('value', '=', tag_ids[0])])
|
||||
tag_2_freq = LeadScoringFrequency.search([('variable', '=', 'tag_id'), ('value', '=', tag_ids[1])])
|
||||
self.assertEqual(tools.float_compare(tag_1_freq.won_count, 33.1, 1), 0)
|
||||
self.assertEqual(tools.float_compare(tag_1_freq.lost_count, 65.1, 1), 0)
|
||||
self.assertEqual(tools.float_compare(tag_2_freq.won_count, 23.1, 1), 0)
|
||||
self.assertEqual(tools.float_compare(tag_2_freq.lost_count, 75.1, 1), 0)
|
||||
|
||||
# Force recompute - A priori, no need to do this as, for each won / lost, we increment tag frequency.
|
||||
Lead._cron_update_automated_probabilities()
|
||||
self.env.invalidate_all()
|
||||
|
||||
lead_tag_1 = leads_with_tags[30]
|
||||
lead_tag_2 = leads_with_tags[90]
|
||||
lead_tag_1_2 = leads_with_tags[135]
|
||||
|
||||
self.assertEqual(tools.float_compare(lead_tag_1.automated_probability, 33.69, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead_tag_2.automated_probability, 23.51, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead_tag_1_2.automated_probability, 28.05, 2), 0)
|
||||
|
||||
lead_tag_1.tag_ids = [(5, 0, 0)] # remove all tags
|
||||
lead_tag_1_2.tag_ids = [(3, tag_ids[1], 0)] # remove tag 2
|
||||
|
||||
self.assertEqual(tools.float_compare(lead_tag_1.automated_probability, 28.6, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead_tag_2.automated_probability, 23.51, 2), 0) # no impact
|
||||
self.assertEqual(tools.float_compare(lead_tag_1_2.automated_probability, 33.69, 2), 0)
|
||||
|
||||
lead_tag_1.tag_ids = [(4, tag_ids[1])] # add tag 2
|
||||
lead_tag_2.tag_ids = [(4, tag_ids[0])] # add tag 1
|
||||
lead_tag_1_2.tag_ids = [(3, tag_ids[0]), (4, tag_ids[1])] # remove tag 1 / add tag 2
|
||||
|
||||
self.assertEqual(tools.float_compare(lead_tag_1.automated_probability, 23.51, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead_tag_2.automated_probability, 28.05, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead_tag_1_2.automated_probability, 23.51, 2), 0)
|
||||
|
||||
# go back to initial situation
|
||||
lead_tag_1.tag_ids = [(3, tag_ids[1]), (4, tag_ids[0])] # remove tag 2 / add tag 1
|
||||
lead_tag_2.tag_ids = [(3, tag_ids[0])] # remove tag 1
|
||||
lead_tag_1_2.tag_ids = [(4, tag_ids[0])] # add tag 1
|
||||
|
||||
self.assertEqual(tools.float_compare(lead_tag_1.automated_probability, 33.69, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead_tag_2.automated_probability, 23.51, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead_tag_1_2.automated_probability, 28.05, 2), 0)
|
||||
|
||||
# set email_state for each lead and update probabilities
|
||||
leads.filtered(lambda lead: lead.id % 2 == 0).email_state = 'correct'
|
||||
leads.filtered(lambda lead: lead.id % 2 == 1).email_state = 'incorrect'
|
||||
Lead._cron_update_automated_probabilities()
|
||||
self.env.invalidate_all()
|
||||
|
||||
self.assertEqual(tools.float_compare(leads[3].automated_probability, 4.21, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[8].automated_probability, 0.23, 2), 0)
|
||||
|
||||
# remove all pls fields
|
||||
self.env['ir.config_parameter'].sudo().set_param("crm.pls_fields", False)
|
||||
Lead._cron_update_automated_probabilities()
|
||||
self.env.invalidate_all()
|
||||
|
||||
self.assertEqual(tools.float_compare(leads[3].automated_probability, 34.38, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[8].automated_probability, 50.0, 2), 0)
|
||||
|
||||
# check if the probabilities are the same with the old param
|
||||
self.env['ir.config_parameter'].sudo().set_param("crm.pls_fields", "country_id,state_id,email_state,phone_state,source_id")
|
||||
Lead._cron_update_automated_probabilities()
|
||||
self.env.invalidate_all()
|
||||
|
||||
self.assertEqual(tools.float_compare(leads[3].automated_probability, 4.21, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[8].automated_probability, 0.23, 2), 0)
|
||||
|
||||
# remove tag_ids from the calculation
|
||||
self.assertEqual(tools.float_compare(lead_tag_1.automated_probability, 28.6, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead_tag_2.automated_probability, 28.6, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead_tag_1_2.automated_probability, 28.6, 2), 0)
|
||||
|
||||
lead_tag_1.tag_ids = [(5, 0, 0)] # remove all tags
|
||||
lead_tag_2.tag_ids = [(4, tag_ids[0])] # add tag 1
|
||||
lead_tag_1_2.tag_ids = [(3, tag_ids[1], 0)] # remove tag 2
|
||||
|
||||
self.assertEqual(tools.float_compare(lead_tag_1.automated_probability, 28.6, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead_tag_2.automated_probability, 28.6, 2), 0)
|
||||
self.assertEqual(tools.float_compare(lead_tag_1_2.automated_probability, 28.6, 2), 0)
|
||||
|
||||
def test_predictive_lead_scoring_always_won(self):
|
||||
""" The computation may lead scores close to 100% (or 0%), we check that pending
|
||||
leads are always in the ]0-100[ range."""
|
||||
Lead = self.env['crm.lead']
|
||||
LeadScoringFrequency = self.env['crm.lead.scoring.frequency']
|
||||
country_id = self.env['res.country'].search([], limit=1).id
|
||||
stage_id = self.env['crm.stage'].search([], limit=1).id
|
||||
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),
|
||||
])
|
||||
# set a new tag
|
||||
leads.tag_ids = self.env['crm.tag'].create({'name': 'lead scoring edge case'})
|
||||
|
||||
# Set the PLS config
|
||||
self.env['ir.config_parameter'].sudo().set_param("crm.pls_start_date", "2000-01-01")
|
||||
# tag_ids can be used in versions newer than v14
|
||||
self.env['ir.config_parameter'].sudo().set_param("crm.pls_fields", "country_id")
|
||||
|
||||
# set leads as won and lost
|
||||
leads[1].action_set_lost()
|
||||
leads[2].action_set_won()
|
||||
|
||||
# recompute
|
||||
Lead._cron_update_automated_probabilities()
|
||||
self.env.invalidate_all()
|
||||
|
||||
# adapt the probability frequency to have high values
|
||||
# this way we are nearly sure it's going to be won
|
||||
freq_stage = LeadScoringFrequency.search([('variable', '=', 'stage_id'), ('value', '=', str(stage_id))])
|
||||
freq_tag = LeadScoringFrequency.search([('variable', '=', 'tag_id'), ('value', '=', str(leads.tag_ids.id))])
|
||||
freqs = freq_stage + freq_tag
|
||||
|
||||
# check probabilities: won edge case
|
||||
freqs.write({'won_count': 10000000, 'lost_count': 1})
|
||||
leads._compute_probabilities()
|
||||
self.assertEqual(tools.float_compare(leads[2].probability, 100, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[1].probability, 0, 2), 0)
|
||||
self.assertEqual(tools.float_compare(leads[0].probability, 99.99, 2), 0)
|
||||
|
||||
# check probabilities: lost edge case
|
||||
freqs.write({'won_count': 1, 'lost_count': 10000000})
|
||||
leads._compute_probabilities()
|
||||
self.assertEqual(tools.float_compare(leads[2].probability, 100, 2), 0)
|
||||
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
|
||||
the first stage (used to know how many lost and won there is) that requires
|
||||
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})
|
||||
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)
|
||||
|
||||
@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 = [
|
||||
('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
|
||||
])
|
||||
|
||||
# add some frequencies to team to unlink
|
||||
team = [
|
||||
('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
|
||||
])
|
||||
|
||||
pls_team.unlink()
|
||||
|
||||
final_noteam = [
|
||||
('stage_id', '1', 40, 20),
|
||||
('stage_id', '2', 0.1, 0.1),
|
||||
('stage_id', '3', 10, 0),
|
||||
('country_id', '1', 10, 10),
|
||||
('country_id', '3', 30, 30),
|
||||
|
||||
]
|
||||
self.assertEqual(
|
||||
existing_plsteam.exists(), self.env["crm.lead.scoring.frequency"],
|
||||
'Frequencies of unlinked teams should be unlinked (cascade)')
|
||||
existing_noteam = self.env["crm.lead.scoring.frequency"].sudo().search([
|
||||
('team_id', '=', False),
|
||||
('variable', 'in', ['stage_id', 'country_id']),
|
||||
])
|
||||
for frequency in existing_noteam:
|
||||
stat = next(item for item in final_noteam if item[0] == frequency.variable and item[1] == frequency.value)
|
||||
self.assertEqual(frequency.won_count, stat[2])
|
||||
self.assertEqual(frequency.lost_count, stat[3])
|
||||
self.assertEqual(len(existing_noteam), len(final_noteam))
|
||||
117
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_ui.py
Normal file
117
odoo-bringout-oca-ocb-crm/crm/tests/test_crm_ui.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# 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
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestUi(HttpCase):
|
||||
|
||||
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({
|
||||
'name': 'Brandon Freeman',
|
||||
'email': 'brandon.freeman55@example.com',
|
||||
'phone': '(355)-687-3262',
|
||||
})
|
||||
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")
|
||||
|
||||
def test_02_crm_tour_rainbowman(self):
|
||||
# we create a new user to make sure they get the 'Congrats on your first deal!'
|
||||
# rainbowman message.
|
||||
self.env['res.users'].create({
|
||||
'name': 'Temporary CRM User',
|
||||
'login': 'temp_crm_user',
|
||||
'password': 'temp_crm_user',
|
||||
'groups_id': [(6, 0, [
|
||||
self.ref('base.group_user'),
|
||||
self.ref('sales_team.group_sale_salesman')
|
||||
])]
|
||||
})
|
||||
self.start_tour("/web", 'crm_rainbowman', login="temp_crm_user")
|
||||
|
||||
def test_03_crm_tour_forecast(self):
|
||||
self.start_tour("/web", 'crm_forecast', login="admin")
|
||||
|
||||
def test_email_and_phone_propagation_edit_save(self):
|
||||
"""Test the propagation of the email / phone on the partner.
|
||||
|
||||
If the partner has no email but the lead has one, it should be propagated
|
||||
if we edit and save the lead form.
|
||||
"""
|
||||
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',
|
||||
})
|
||||
partner.email = False
|
||||
partner.phone = False
|
||||
|
||||
# Check initial state
|
||||
self.assertFalse(partner.email)
|
||||
self.assertFalse(partner.phone)
|
||||
self.assertEqual(lead.email_from, 'test@example.com')
|
||||
self.assertEqual(lead.phone, '+32 494 44 44 44')
|
||||
|
||||
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.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).
|
||||
"""
|
||||
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',
|
||||
})
|
||||
|
||||
# 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')
|
||||
|
||||
self.assertFalse(lead.partner_email_update)
|
||||
self.assertFalse(lead.partner_phone_update)
|
||||
|
||||
self.start_tour('/web', 'crm_email_and_phone_propagation_remove_email_and_phone', login='admin')
|
||||
|
||||
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')
|
||||
198
odoo-bringout-oca-ocb-crm/crm/tests/test_performances.py
Normal file
198
odoo-bringout-oca-ocb-crm/crm/tests/test_performances.py
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import random
|
||||
|
||||
from odoo.addons.crm.tests.test_crm_lead_assignment import TestLeadAssignCommon
|
||||
from odoo.tests.common import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('lead_assign', 'crm_performance', 'post_install', '-at_install')
|
||||
class TestLeadAssignPerf(TestLeadAssignCommon):
|
||||
""" Test performances of lead assignment feature added in saas-14.2
|
||||
|
||||
Assign process is a random process: randomizing teams leads to searching,
|
||||
assigning and de-duplicating leads in various order. As a lot of search
|
||||
are implied during assign process query counters may vary from run to run.
|
||||
"Heavy" performance test included here ranged from 6K to 6.3K queries. Either
|
||||
we set high counters maximum which makes those tests less useful. Either we
|
||||
avoid random if possible which is what we decided to do by setting the seed
|
||||
of random in tests.
|
||||
"""
|
||||
|
||||
@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
|
||||
that de duplication is effectively performed. """
|
||||
# fix the seed and avoid randomness
|
||||
random.seed(1940)
|
||||
|
||||
leads = self._create_leads_batch(
|
||||
lead_type='lead',
|
||||
user_ids=[False],
|
||||
partner_ids=[self.contact_1.id, self.contact_2.id, False, False, False],
|
||||
count=200
|
||||
)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
self.assertInitialData()
|
||||
|
||||
# assign probability to leads (bypass auto probability as purpose is not to test pls)
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
for idx in range(0, 5):
|
||||
sliced_leads = leads[idx:len(leads):5]
|
||||
for lead in sliced_leads:
|
||||
lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
|
||||
# randomness: at least 1 query
|
||||
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)
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
leads_st1 = leads.filtered_domain([('team_id', '=', self.sales_team_1.id)])
|
||||
leads_stc = leads.filtered_domain([('team_id', '=', self.sales_team_convert.id)])
|
||||
self.assertLessEqual(len(leads_st1), 128)
|
||||
self.assertLessEqual(len(leads_stc), 96)
|
||||
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)
|
||||
|
||||
@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):
|
||||
# fix the seed and avoid randomness
|
||||
random.seed(1945)
|
||||
|
||||
leads = self._create_leads_batch(
|
||||
lead_type='lead',
|
||||
user_ids=[False],
|
||||
partner_ids=[False],
|
||||
count=100
|
||||
)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
self.assertInitialData()
|
||||
|
||||
# assign probability to leads (bypass auto probability as purpose is not to test pls)
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
for idx in range(0, 5):
|
||||
sliced_leads = leads[idx:len(leads):5]
|
||||
for lead in sliced_leads:
|
||||
lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
|
||||
# randomness: at least 1 query
|
||||
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)
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
leads_st1 = leads.filtered_domain([('team_id', '=', self.sales_team_1.id)])
|
||||
leads_stc = leads.filtered_domain([('team_id', '=', self.sales_team_convert.id)])
|
||||
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)
|
||||
|
||||
@mute_logger('odoo.models.unlink', 'odoo.addons.crm.models.crm_team', 'odoo.addons.crm.models.crm_team_member')
|
||||
def test_assign_perf_populated(self):
|
||||
""" Test assignment on a more high volume oriented test set in order to
|
||||
have more insights on query counts. """
|
||||
# fix the seed and avoid randomness
|
||||
random.seed(1871)
|
||||
|
||||
# create leads enough to have interesting counters
|
||||
_lead_count, _email_dup_count, _partner_count = 600, 50, 150
|
||||
leads = self._create_leads_batch(
|
||||
lead_type='lead',
|
||||
user_ids=[False],
|
||||
partner_count=_partner_count,
|
||||
country_ids=[self.env.ref('base.be').id, self.env.ref('base.fr').id, False],
|
||||
count=_lead_count,
|
||||
email_dup_count=_email_dup_count)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
self.assertInitialData()
|
||||
|
||||
# assign for one month, aka a lot
|
||||
self.env.ref('crm.ir_cron_crm_lead_assign').write({'interval_type': 'days', 'interval_number': 30})
|
||||
# create a third team
|
||||
sales_team_3 = self.env['crm.team'].create({
|
||||
'name': 'Sales Team 3',
|
||||
'sequence': 15,
|
||||
'alias_name': False,
|
||||
'use_leads': True,
|
||||
'use_opportunities': True,
|
||||
'company_id': False,
|
||||
'user_id': False,
|
||||
'assignment_domain': [('country_id', '!=', False)],
|
||||
})
|
||||
sales_team_3_m1 = self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_manager.id,
|
||||
'crm_team_id': sales_team_3.id,
|
||||
'assignment_max': 60,
|
||||
'assignment_domain': False,
|
||||
})
|
||||
sales_team_3_m2 = self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_leads.id,
|
||||
'crm_team_id': sales_team_3.id,
|
||||
'assignment_max': 60,
|
||||
'assignment_domain': False,
|
||||
})
|
||||
sales_team_3_m3 = self.env['crm.team.member'].create({
|
||||
'user_id': self.user_sales_salesman.id,
|
||||
'crm_team_id': sales_team_3.id,
|
||||
'assignment_max': 15,
|
||||
'assignment_domain': [('probability', '>=', 10)],
|
||||
})
|
||||
sales_teams = self.sales_teams | sales_team_3
|
||||
self.assertEqual(sum(team.assignment_max for team in sales_teams), 300)
|
||||
self.assertEqual(len(leads), 650)
|
||||
|
||||
# assign probability to leads (bypass auto probability as purpose is not to test pls)
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order
|
||||
for idx in range(0, 5):
|
||||
sliced_leads = leads[idx:len(leads):5]
|
||||
for lead in sliced_leads:
|
||||
lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2)
|
||||
# commit probability and related fields
|
||||
leads.flush_recordset()
|
||||
|
||||
# randomness
|
||||
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)
|
||||
|
||||
# teams assign
|
||||
leads = self.env['crm.lead'].search([('id', 'in', leads.ids)])
|
||||
self.assertEqual(leads.team_id, sales_teams)
|
||||
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
|
||||
71
odoo-bringout-oca-ocb-crm/crm/tests/test_res_partner.py
Normal file
71
odoo-bringout-oca-ocb-crm/crm/tests/test_res_partner.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# -*- 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):
|
||||
|
||||
@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)
|
||||
|
||||
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)
|
||||
|
||||
# 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)
|
||||
26
odoo-bringout-oca-ocb-crm/crm/tests/test_sales_team_ui.py
Normal file
26
odoo-bringout-oca-ocb-crm/crm/tests/test_sales_team_ui.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import tests
|
||||
from odoo.tests import HttpCase
|
||||
from odoo.tests.common import users
|
||||
from odoo.addons.sales_team.tests.common import SalesTeamCommon
|
||||
|
||||
|
||||
@tests.tagged('post_install', '-at_install')
|
||||
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.env['ir.config_parameter'].sudo().set_param('sales_team.membership_multi', True)
|
||||
|
||||
self.start_tour("/", "create_crm_team_tour", login="salesmanager")
|
||||
|
||||
created_team = self.env["crm.team"].search([("name", "=", "My CRM Team")])
|
||||
self.assertTrue(bool(created_team))
|
||||
self.assertEqual(
|
||||
created_team.member_ids,
|
||||
self.sale_user | self.sale_manager
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue