diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index 85468e3973d..22fb969c361 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -1822,6 +1822,21 @@ class Lead(models.Model):
return partner_company
return Partner.create(self._prepare_customer_values(self.name, is_company=False))
+ def _get_customer_information(self):
+ email_normalized_to_values = super()._get_customer_information()
+ Partner = self.env['res.partner']
+
+ for record in self.filtered('email_normalized'):
+ values = email_normalized_to_values.setdefault(record.email_normalized, {})
+ contact_name = record.contact_name or record.partner_name or (
+ Partner._parse_partner_name(record.email_from)[0] or record.email_from)
+ # Note that we don't attempt to create the parent company even if partner name is set
+ values.update(record._prepare_customer_values(contact_name, is_company=False))
+ values['company_name'] = record.partner_name
+ if contact_name == record.partner_name:
+ values['company_type'] = 'company'
+ return email_normalized_to_values
+
def _prepare_customer_values(self, partner_name, is_company=False, parent_id=False):
""" Extract data from lead to create a partner.
diff --git a/addons/crm/tests/test_crm_lead.py b/addons/crm/tests/test_crm_lead.py
index 049f77a9621..c3e245c22eb 100644
--- a/addons/crm/tests/test_crm_lead.py
+++ b/addons/crm/tests/test_crm_lead.py
@@ -10,7 +10,7 @@ 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
+from odoo.tools import formataddr, mute_logger
@tagged('lead_internals')
@@ -663,7 +663,13 @@ class TestCRMLead(TestCrmCommon):
'lang_id': lang_fr.id,
})
data = lead1._message_get_suggested_recipients()[lead1.id]
- self.assertEqual(data, [(False, self.test_email, None, 'Customer Email')])
+ self.assertEqual(len(data), 1)
+ suggested_partner_id, suggested_partner_email, suggested_lang, suggested_reason, create_vals = data[0]
+ self.assertFalse(suggested_partner_id)
+ self.assertEqual(suggested_partner_email, self.test_email)
+ self.assertEqual(suggested_lang, None)
+ self.assertEqual(suggested_reason, 'Customer Email')
+ self.assertEqual(create_vals['lang'], 'fr_FR')
# Create a lead with an active language -> should keep the preset language for recipients
lang_en = ResLang.search([('code', '=', 'en_US')])
@@ -678,7 +684,73 @@ class TestCRMLead(TestCrmCommon):
'lang_id': lang_en.id,
})
data = lead2._message_get_suggested_recipients()[lead2.id]
- self.assertEqual(data, [(False, self.test_email, "en_US", 'Customer Email')])
+ self.assertEqual(len(data), 1)
+ suggested_partner_id, suggested_partner_email, suggested_lang, suggested_reason, create_vals = data[0]
+ self.assertFalse(suggested_partner_id)
+ self.assertEqual(suggested_partner_email, self.test_email)
+ self.assertEqual(suggested_lang, 'en_US')
+ self.assertEqual(suggested_reason, 'Customer Email')
+ self.assertEqual(create_vals['lang'], lang_en.code)
+
+ @users('user_sales_manager')
+ def test_message_get_suggested_recipients_auto_creation(self):
+ """Check default creates value for auto creation of recipient
+ (customer)."""
+ lead_details_for_contact = {
+ 'title': self.env.ref('base.res_partner_title_mister').id,
+ 'street': '3rd Floor, Room 3-C',
+ 'street2': '123 Arlington Avenue',
+ 'zip': '13202',
+ 'city': 'New York',
+ 'country_id': self.env.ref('base.us').id,
+ 'state_id': self.env.ref('base.state_us_39').id,
+ 'website': 'https://www.arlington123.com/3f3c',
+ 'phone': '678-728-0949',
+ 'mobile': '661-606-0781',
+ 'function': 'Delivery Boy',
+ 'team_id': self.sales_team_1.id,
+ 'user_id': self.user_sales_manager.id,
+ }
+
+ for partner_name, name, email in [
+ (False, 'Test', 'test_default_create@example.com'),
+ ('Delivery Boy company', 'Test With Company', 'default_create_with_partner@example.com'),
+ ('Delivery Boy company', '', 'default_create_with_partner_no_name@example.com'),
+ ]:
+ formatted_email = formataddr((name, email))
+ with self.subTest(partner_name=partner_name):
+ lang = self.env['res.lang'].sudo().search([], limit=1)[0]
+ description = '
Top
'
+ lead1 = self.env['crm.lead'].create({
+ 'name': 'TestLead',
+ 'contact_name': name,
+ 'email_from': formatted_email,
+ 'lang_id': lang.id,
+ 'description': description,
+ 'partner_name': partner_name,
+ **lead_details_for_contact,
+ })
+ data = lead1._message_get_suggested_recipients()[lead1.id]
+ suggested_partner_id, suggested_partner_email, suggested_lang, suggested_reason, create_vals = data[0]
+ self.assertFalse(suggested_partner_id)
+ self.assertEqual(suggested_partner_email, formatted_email)
+ self.assertEqual(suggested_lang, lang.code)
+ self.assertEqual(suggested_reason, 'Customer Email')
+ self.assertEqual(create_vals, lead1._get_customer_information().get(email, {}))
+ for field, value in lead_details_for_contact.items():
+ self.assertEqual(create_vals.get(field), value)
+ expected_name = partner_name if partner_name and not name else name
+ self.assertEqual(create_vals['name'], expected_name)
+ self.assertEqual(create_vals['comment'], description) # description -> comment
+ # Parent company not created even if partner_name is set
+ self.assertFalse(create_vals.get('parent_id')) # not supported, even if partner_name set
+ self.assertEqual(create_vals['company_name'], partner_name) # partner_name -> company_name
+ expected_company_type = 'company' if partner_name and not name else 'person'
+ self.assertEqual(create_vals.get('company_type', 'person'), expected_company_type)
+
+ # Check that the creation of the contact won't fail
+ partner = self.env['res.partner'].create(create_vals)
+ partner.unlink()
@users('user_sales_manager')
def test_phone_mobile_search(self):
diff --git a/addons/crm/views/res_partner_views.xml b/addons/crm/views/res_partner_views.xml
index c3b02a18ef0..5f09112bc10 100644
--- a/addons/crm/views/res_partner_views.xml
+++ b/addons/crm/views/res_partner_views.xml
@@ -47,4 +47,17 @@
+
+ res.partner.view.form.simple.form.crm
+ res.partner
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py
index 9101525f99f..b14205a460b 100644
--- a/addons/mail/models/mail_template.py
+++ b/addons/mail/models/mail_template.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
+import itertools
import logging
from odoo import _, api, fields, models, tools, Command
@@ -283,6 +284,13 @@ class MailTemplate(models.Model):
(finding or creating partners). 'partner_to' field is transformed into
'partner_ids' field.
+ Note: for performance reason, information from records are transferred to
+ created partners no matter the company. For example, if we have a record of
+ company A and one of B with the same email and no related partner, a partner
+ will be created with company A or B but populated with information from the 2
+ records. So some info might be leaked from one company to the other through
+ the partner.
+
:param list res_ids: list of record IDs on which template is rendered;
:param list render_fields: list of fields to render on template which
are specific to recipients, e.g. email_cc, email_to, partner_to);
@@ -300,13 +308,12 @@ class MailTemplate(models.Model):
self.ensure_one()
if render_results is None:
render_results = {}
- records_sudo = None
+ ModelSudo = self.env[self.model].with_prefetch(res_ids).sudo()
# if using default recipients -> ``_message_get_default_recipients`` gives
# values for email_to, email_cc and partner_ids
if self.use_default_to and self.model:
- records_sudo = self.env[self.model].browse(res_ids).sudo()
- default_recipients = records_sudo._message_get_default_recipients()
+ default_recipients = ModelSudo.browse(res_ids)._message_get_default_recipients()
for res_id, recipients in default_recipients.items():
render_results.setdefault(res_id, {}).update(recipients)
# render fields dynamically which generates recipients
@@ -316,39 +323,39 @@ class MailTemplate(models.Model):
for res_id in res_ids:
render_results.setdefault(res_id, {})[field] = generated_field_values[res_id]
- # classify records per company for partner fetch/creation
- if find_or_create_partners and self.model and 'company_id' in self.env[self.model]._fields:
- if not records_sudo:
- records_sudo = self.env[self.model].browse(res_ids).sudo()
- records_per_company = {}
- for read_record in records_sudo.read(['company_id']):
- company_id = read_record['company_id'][0] if read_record['company_id'] else False
- records_per_company.setdefault(company_id, []).append(read_record['id'])
- else:
- records_per_company = {False: res_ids}
-
# create partners from emails if asked to
if find_or_create_partners:
- for company_id, record_ids in records_per_company.items():
- all_emails = []
- email_to_res_ids = {}
- for res_id in record_ids:
- record_values = render_results.setdefault(res_id, {})
- mails = tools.email_split(record_values.pop('email_to', '')) + \
- tools.email_split(record_values.pop('email_cc', ''))
- all_emails += mails
- for mail in mails:
- email_to_res_ids.setdefault(mail, []).append(res_id)
+ res_id_to_company = {}
+ if self.model and 'company_id' in ModelSudo._fields:
+ for read_record in ModelSudo.browse(res_ids).read(['company_id']):
+ company_id = read_record['company_id'][0] if read_record['company_id'] else False
+ res_id_to_company[read_record['id']] = company_id
- if not all_emails:
- continue
- additional_values = {}
- if company_id:
- additional_values['company_id'] = company_id
+ all_emails = []
+ email_to_res_ids = {}
+ email_to_company = {}
+ for res_id in res_ids:
+ record_values = render_results.setdefault(res_id, {})
+ mails = tools.email_split(record_values.pop('email_to', '')) + \
+ tools.email_split(record_values.pop('email_cc', ''))
+ all_emails += mails
+ record_company = res_id_to_company.get(res_id)
+ for mail in mails:
+ email_to_res_ids.setdefault(mail, []).append(res_id)
+ if record_company:
+ email_to_company[mail] = record_company
+
+ if all_emails:
+ customers_information = ModelSudo.browse(res_ids)._get_customer_information()
partners = self.env['res.partner']._find_or_create_from_emails(
all_emails,
- additional_values=additional_values
- )
+ additional_values={
+ email: {
+ 'company_id': email_to_company.get(email),
+ **customers_information.get(email, {}),
+ }
+ for email in itertools.chain(all_emails, [False])
+ })
for original_email, partner in zip(all_emails, partners):
if not partner:
continue
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index b8260ddddfa..18960992d0e 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -1609,7 +1609,8 @@ class MailThread(models.AbstractModel):
def _message_add_suggested_recipient(self, result, partner=None, email=None, lang=None, reason=''):
""" Called by _message_get_suggested_recipients, to add a suggested
recipient in the result dictionary. The form is :
- partner_id, partner_name or partner_name, reason """
+ partner_id, partner_name or partner_name, lang,
+ reason, create_values """
self.ensure_one()
if email and not partner:
# get partner info from email
@@ -1623,16 +1624,18 @@ class MailThread(models.AbstractModel):
if partner and partner.id in [val[0] for val in result[self.ids[0]]]: # already existing partner ID -> skip
return result
if partner and partner.email: # complete profile: id, name
- result[self.ids[0]].append((partner.id, partner.email_formatted, lang, reason))
+ result[self.ids[0]].append((partner.id, partner.email_formatted, lang, reason, {}))
elif partner: # incomplete profile: id, name
- result[self.ids[0]].append((partner.id, '%s' % (partner.name), lang, reason))
+ result[self.ids[0]].append((partner.id, '%s' % (partner.name), lang, reason, {}))
else: # unknown partner, we are probably managing an email address
- result[self.ids[0]].append((False, email, lang, reason))
+ _, parsed_email = self.env['res.partner']._parse_partner_name(email)
+ partner_create_values = self._get_customer_information().get(parsed_email, {})
+ result[self.ids[0]].append((False, email, lang, reason, partner_create_values))
return result
def _message_get_suggested_recipients(self):
""" Returns suggested recipients for ids. Those are a list of
- tuple (partner_id, partner_name, reason), to be managed by Chatter. """
+ tuple (partner_id, partner_name, reason, default_create_value), to be managed by Chatter. """
result = dict((res_id, []) for res_id in self.ids)
if 'user_id' in self._fields:
for obj in self.sudo(): # SUPERUSER because of a read on res.users that would crash otherwise
@@ -1793,6 +1796,19 @@ class MailThread(models.AbstractModel):
]).write({'author_id': partner.id})
return result
+
+ def _get_customer_information(self):
+ """ Get customer information that can be extracted from the records by
+ normalized email.
+
+ The goal of this method is to offer an extension point to subclasses
+ for retrieving initial values from a record to populate related
+ customers record (res_partner).
+
+ :return dict: normalized email -> dict of initial res_partner values
+ """
+ return {}
+
# ------------------------------------------------------------
# MESSAGE POST MAIN
# ------------------------------------------------------------
diff --git a/addons/mail/models/res_partner.py b/addons/mail/models/res_partner.py
index cf821a0dbcb..6edb1893dac 100644
--- a/addons/mail/models/res_partner.py
+++ b/addons/mail/models/res_partner.py
@@ -75,6 +75,11 @@ class Partner(models.Model):
# ------------------------------------------------------------
# ORM
# ------------------------------------------------------------
+ @api.model
+ def _get_view_cache_key(self, view_id=None, view_type='form', **options):
+ """Add context variable force_email in the key as _get_view depends on it."""
+ key = super()._get_view_cache_key(view_id, view_type, **options)
+ return key + (self._context.get('force_email'),)
@api.model
@api.returns('self', lambda value: value.id)
@@ -108,6 +113,11 @@ class Partner(models.Model):
can be given to newly created partners. If an email is not unique (e.g.
multi-email input), only the first found email is considered.
+ Additional values allow to customize the created partner when context
+ allows to give more information. It data is based on email normalized
+ as it is the main information used in this method to distinguish or
+ find partners.
+
If no valid email is found for a given item, the given value is used to
find partners with same invalid email or create a new one with the wrong
value. It allows updating it afterwards. Notably with notifications
@@ -116,12 +126,16 @@ class Partner(models.Model):
:param list emails: list of emails that may be formatted (each input
will be parsed and normalized);
- :param dict additional_values: additional values given to create if
- the partner is not found. Typically used to propagate a company_id;
+ :param dict additional_values: additional values per normalized email
+ given to create if the partner is not found. Typically used to
+ propagate a company_id and customer information from related record.
+ Values for key 'False' are used when creating partner for invalid
+ emails;
:return: res.partner records in a list, following order of emails. It
is not a recordset, to keep Falsy values.
"""
+ additional_values = additional_values if additional_values else {}
partners, tocreate_vals_list = self.env['res.partner'], []
name_emails = [self._parse_partner_name(email) for email in emails]
@@ -161,7 +175,7 @@ class Partner(models.Model):
{
self._rec_name: name or email_normalized,
'email': email_normalized,
- **(additional_values or {})
+ **additional_values.get(email_normalized, {}),
}
for name, email_normalized in notfound_name_emails
]
@@ -172,7 +186,7 @@ class Partner(models.Model):
{
self._rec_name: name,
'email': name,
- **(additional_values or {})
+ **additional_values.get(False, {}),
}
for name in names if name not in partners.mapped('email')
]
diff --git a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js
index 8bf6cba6e33..3f2533a2b2e 100644
--- a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js
+++ b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js
@@ -78,6 +78,9 @@ export class ComposerSuggestedRecipientView extends Component {
.name,
default_lang: this.composerSuggestedRecipientView.suggestedRecipientInfo
.lang,
+ ...(Object.fromEntries(Object.entries(
+ this.composerSuggestedRecipientView.suggestedRecipientInfo.defaultCreateValues)
+ .map(([k, v]) => ['default_' + k, v]))),
force_email: true,
ref: "compound_context",
},
diff --git a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml
index c27b6d74cdc..3c02468459a 100644
--- a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml
+++ b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml
@@ -6,7 +6,7 @@
diff --git a/addons/mail/static/src/js/utils.js b/addons/mail/static/src/js/utils.js
index 3049ecac935..8e94c665351 100644
--- a/addons/mail/static/src/js/utils.js
+++ b/addons/mail/static/src/js/utils.js
@@ -173,7 +173,7 @@ function inline(node, transform_children) {
// Parses text to find email: Tagada -> [Tagada, address@mail.fr] or False
function parseEmail(text) {
if (text) {
- var result = text.match(/(.*)<(.*@.*)>/);
+ var result = text.match(/"?(.*?)"? <(.*@.*)>/);
if (result) {
return [_.str.trim(result[1]), _.str.trim(result[2])];
}
diff --git a/addons/mail/static/src/models/suggested_recipient_info.js b/addons/mail/static/src/models/suggested_recipient_info.js
index 81ca18af343..b14d4162c2d 100644
--- a/addons/mail/static/src/models/suggested_recipient_info.js
+++ b/addons/mail/static/src/models/suggested_recipient_info.js
@@ -10,6 +10,13 @@ Model({
composerSuggestedRecipientViews: many("ComposerSuggestedRecipientView", {
inverse: "suggestedRecipientInfo",
}),
+ /**
+ * Default values for partner creation (empty object if partner is set).
+ */
+ defaultCreateValues: attr({
+ default: {},
+ readonly: true,
+ }),
dialogText: attr({
compute() {
return this.env._t("Please complete customer's information");
diff --git a/addons/mail/static/src/models/thread.js b/addons/mail/static/src/models/thread.js
index 657f643749a..f326bae7bb6 100644
--- a/addons/mail/static/src/models/thread.js
+++ b/addons/mail/static/src/models/thread.js
@@ -558,7 +558,7 @@ Model({
}
if (suggestedRecipientsData) {
const recipientInfoList = suggestedRecipientsData.map((recipientInfoData) => {
- const [partner_id, emailInfo, lang, reason] = recipientInfoData;
+ const [partner_id, emailInfo, lang, reason, defaultCreateValues] = recipientInfoData;
const [name, email] = emailInfo && mailUtils.parseEmail(emailInfo);
return {
email,
@@ -567,6 +567,7 @@ Model({
lang,
partner: partner_id ? insert({ id: partner_id }) : clear(),
reason,
+ defaultCreateValues: defaultCreateValues,
};
});
Object.assign(values, {
diff --git a/addons/mail/tests/test_res_partner.py b/addons/mail/tests/test_res_partner.py
index 061840cacb7..1ebfdb2f235 100644
--- a/addons/mail/tests/test_res_partner.py
+++ b/addons/mail/tests/test_res_partner.py
@@ -186,7 +186,12 @@ class TestPartner(MailCommon):
samples_emails = [item[0] for item in new_samples]
partners = self.env['res.partner'].with_context(lang='en_US')._find_or_create_from_emails(
samples_emails,
- additional_values={'company_id': self.env.company.id},
+ additional_values={
+ tools.email_normalize(email): {
+ 'company_id': self.env.company.id,
+ }
+ for email in samples_emails
+ },
)
self.assertEqual(len(partners), len(new_samples))
for (sample, exp_name, exp_email), partner in zip(new_samples, partners):
diff --git a/addons/test_mail/models/test_mail_models.py b/addons/test_mail/models/test_mail_models.py
index eb3c90bea26..62f2494e1c4 100644
--- a/addons/test_mail/models/test_mail_models.py
+++ b/addons/test_mail/models/test_mail_models.py
@@ -2,6 +2,8 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
+from odoo.tools import email_normalize
+from odoo.tools.translate import _
class MailTestSimple(models.Model):
@@ -127,6 +129,8 @@ class MailTestTicket(models.Model):
name = fields.Char()
email_from = fields.Char(tracking=True)
+ mobile_number = fields.Char()
+ phone_number = fields.Char()
count = fields.Integer(default=1)
datetime = fields.Datetime(default=fields.Datetime.now)
mail_template = fields.Many2one('mail.template', 'Template')
@@ -193,7 +197,39 @@ class MailTestTicket(models.Model):
return self.env.ref('test_mail.st_mail_test_ticket_container_upd')
return super(MailTestTicket, self)._track_subtype(init_values)
+ def _get_customer_information(self):
+ email_normalized_to_values = super()._get_customer_information()
+ for record in self.filtered('email_from'):
+ email_from_normalized = email_normalize(record.email_from)
+ if not email_from_normalized: # do not fill Falsy with random data
+ continue
+ values = email_normalized_to_values.setdefault(email_from_normalized, {})
+ if not values.get('mobile'):
+ values['mobile'] = record.mobile_number
+ if not values.get('phone'):
+ values['phone'] = record.phone_number
+ return email_normalized_to_values
+
+ def _message_get_suggested_recipients(self):
+ recipients = super()._message_get_suggested_recipients()
+ for ticket in self:
+ if ticket.customer_id:
+ ticket.customer_id._message_add_suggested_recipient(
+ recipients,
+ partner=ticket.customer_id,
+ lang=None,
+ reason=_('Customer'),
+ )
+ elif ticket.email_from:
+ ticket._message_add_suggested_recipient(
+ recipients,
+ partner=None,
+ email=self.email_from,
+ lang=None,
+ reason=_('Customer Email'),
+ )
+ return recipients
class MailTestTicketEL(models.Model):
""" Just mail.test.ticket, but exclusion-list enabled. Kept as different
diff --git a/addons/test_mail/tests/test_mail_composer.py b/addons/test_mail/tests/test_mail_composer.py
index 77d892f61a9..d71d9f769e0 100644
--- a/addons/test_mail/tests/test_mail_composer.py
+++ b/addons/test_mail/tests/test_mail_composer.py
@@ -1494,7 +1494,6 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase):
self.assertTrue(all(attach not in message.attachment_ids for attach in attachs), 'Should have copied attachments')
-
@tagged('mail_composer', 'mail_blacklist')
class TestComposerResultsCommentStatus(TestMailComposer):
""" Test cases involving blacklist, opt-out, state management, ... specific
diff --git a/addons/test_mail/tests/test_mail_multicompany.py b/addons/test_mail/tests/test_mail_multicompany.py
index 58309a7482f..657e2ecd740 100644
--- a/addons/test_mail/tests/test_mail_multicompany.py
+++ b/addons/test_mail/tests/test_mail_multicompany.py
@@ -3,10 +3,12 @@
import socket
+from itertools import product
+
from odoo.addons.test_mail.tests.common import TestMailCommon, TestRecipients
from odoo.tests import tagged
from odoo.tests.common import users
-from odoo.tools import formataddr
+from odoo.tools import formataddr, mute_logger
@tagged('multi_company')
@@ -47,6 +49,75 @@ class TestMultiCompanySetup(TestMailCommon, TestRecipients):
'message_id': '<123456-openerp-%s-mail.test.gateway@%s>' % (cls.test_record.id, socket.gethostname()),
})
+ @users('erp_manager')
+ @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
+ def test_mail_composer_wtpl_populate_new_recipient(self):
+ """ Test auto-populate of auto created partner with related record
+ values when sending a mail with a template.
+ """
+ self.user_erp_manager.write({
+ 'groups_id': [(4, self.env.ref('base.group_partner_manager').id)],
+ })
+ companies = self.company_admin + self.company_2
+ test_records = self.env['mail.test.ticket.mc'].create([
+ {
+ 'email_from': f'newpartner{idx}@example.com',
+ 'company_id': companies[idx].id,
+ 'customer_id': False,
+ 'mobile_number': f'+3319900{idx:02d}{idx:02d}',
+ 'name': f'TestRecord{idx}',
+ 'phone_number': False,
+ 'user_id': False,
+ } for idx in range(2)
+ ])
+ manual_recipients = test_records.mapped('email_from')
+ template = self.env['mail.template'].create({
+ 'email_to': '{{ object.email_from }}',
+ 'model_id': self.env['ir.model']._get_id(test_records._name),
+ 'partner_to': False,
+ })
+
+ for composition_mode, batch_mode in product(
+ ('comment', 'mass_mail'),
+ (True, False)
+ ):
+ with self.subTest(composition_mode=composition_mode, batch_mode=batch_mode):
+ test_records = test_records if batch_mode else test_records[0]
+
+ self.assertFalse(
+ self.env['res.partner'].search(
+ [('email_normalized', 'in', manual_recipients)]
+ )
+ )
+ ctx = {
+ 'default_composition_mode': composition_mode,
+ 'default_model': test_records._name,
+ 'default_res_ids': test_records.ids,
+ }
+ composer = self.env['mail.compose.message'].with_context(ctx).create({
+ 'template_id': template.id,
+ })
+
+ with self.mock_mail_gateway():
+ composer._action_send_mail()
+
+ new_partners = self.env['res.partner'].search([('email_normalized', 'in', manual_recipients)],
+ order='email')
+ try:
+ self.assertEqual(
+ len(new_partners), len(test_records)
+ )
+ self.assertEqual(
+ new_partners.mapped('company_id'),
+ test_records.mapped('company_id')
+ )
+ self.assertEqual(
+ new_partners.mapped('mobile'),
+ test_records.mapped('mobile_number')
+ )
+ finally:
+ new_partners.unlink()
+
@users('employee')
def test_notify_reply_to_computation(self):
test_record = self.env['mail.test.gateway'].browse(self.test_record.ids)
diff --git a/addons/test_mail/tests/test_mail_thread_internals.py b/addons/test_mail/tests/test_mail_thread_internals.py
index 36d254e3868..8148b4a1ad5 100644
--- a/addons/test_mail/tests/test_mail_thread_internals.py
+++ b/addons/test_mail/tests/test_mail_thread_internals.py
@@ -296,16 +296,6 @@ class TestDiscuss(TestMailCommon, TestRecipients):
self.assertFalse(msg.starred)
self.assertTrue(msg_emp.starred)
- @mute_logger('odoo.addons.mail.models.mail_mail')
- def test_mail_cc_recipient_suggestion(self):
- record = self.env['mail.test.cc'].create({'email_cc': 'cc1@example.com, cc2@example.com, cc3 '})
- suggestions = record._message_get_suggested_recipients()[record.id]
- self.assertEqual(sorted(suggestions), [
- (False, '"cc3" ', None, 'CC Email'),
- (False, 'cc1@example.com', None, 'CC Email'),
- (False, 'cc2@example.com', None, 'CC Email'),
- ], 'cc should be in suggestions')
-
def test_inbox_message_fetch_needaction(self):
user1 = self.env['res.users'].create({'login': 'user1', 'name': 'User 1'})
user1.notification_type = 'inbox'
@@ -356,6 +346,40 @@ class TestDiscuss(TestMailCommon, TestRecipients):
threads_admin = self.test_record.with_user(self.user_admin).search([('message_has_error', '=', True)])
self.assertEqual(len(threads_admin), 0)
+ @users("employee")
+ def test_suggested_recipients_default_create_value(self):
+ """ Test default creation values returned for suggested recipient. """
+ email = 'newpartner@example.com'
+ data_from_record_mobile = '+33199001015'
+ record = self.env['mail.test.ticket'].create({
+ 'email_from': email,
+ 'mobile_number': data_from_record_mobile,
+ })
+ suggestions = record._message_get_suggested_recipients()[record.id]
+ self.assertEqual(
+ suggestions,
+ [(False, email, None, 'Customer Email', {'mobile': '+33199001015', 'phone': False})]
+ )
+
+ @users("employee")
+ @mute_logger('odoo.addons.mail.models.mail_mail')
+ def test_suggested_recipients_mail_cc(self):
+ """ MailThreadCC mixin adds its own suggested recipients management
+ coming from CC (carbon copy) management. """
+ record = self.env['mail.test.cc'].create({
+ 'email_cc': 'cc1@example.com, cc2@example.com, cc3 ',
+ })
+ suggestions = record._message_get_suggested_recipients()[record.id]
+ self.assertEqual(
+ sorted(suggestions),
+ [
+ (False, '"cc3" ', None, 'CC Email', {}),
+ (False, 'cc1@example.com', None, 'CC Email', {}),
+ (False, 'cc2@example.com', None, 'CC Email', {}),
+ ],
+ 'cc should be in suggestions'
+ )
+
@users("employee")
def test_unlink_notification_message(self):
channel = self.env['mail.channel'].create({'name': 'testChannel'})
diff --git a/odoo/addons/base/views/res_partner_views.xml b/odoo/addons/base/views/res_partner_views.xml
index c69b9dbbb8c..49e5be43943 100644
--- a/odoo/addons/base/views/res_partner_views.xml
+++ b/odoo/addons/base/views/res_partner_views.xml
@@ -81,10 +81,22 @@
res.partner