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
- - + + + + + + + + + + + + + +