[IMP] base, mail, crm, test_mail: prefill partner when creating them from chatter

Allows model to provide default values for auto creation of related res_partner
such as name, title, ... It avoids the user to enter the information twice.

Use the newly introduced mechanism for crm.lead to prepopulate auto created
res_partner:
- when selecting a suggested recipient, by prefilling the simplified form (job
position, phone, ...) along with non modifiable data that will also be used to
populate the res.partner like the address, website, ...
- when selecting a template for a message on a lead without a partner, the
partner is automatically created with values from the lead: email, phone,
address, ...

Task-3024050

closes odoo/odoo#105111

Related: odoo/enterprise#33687
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2023-02-23 14:53:58 +01:00
committed by GitHub
17 changed files with 356 additions and 61 deletions
+15
View File
@@ -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.
+75 -3
View File
@@ -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 = '<p>Top</p>'
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):
+13
View File
@@ -47,4 +47,17 @@
</field>
</record>
<record id="res_partner_view_form_simple_form" model="ir.ui.view">
<field name="name">res.partner.view.form.simple.form.crm</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_simple_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//form">
<field name="team_id" invisible="1"/>
</xpath>
</data>
</field>
</record>
</odoo>
+38 -31
View File
@@ -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
+21 -5
View File
@@ -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<partner_email> or partner_name, reason """
partner_id, partner_name<partner_email> 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 <email>
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
# ------------------------------------------------------------
+18 -4
View File
@@ -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')
]
@@ -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",
},
@@ -6,7 +6,7 @@
<input t-attf-id="{{ id }}_checkbox" class="form-check-input" type="checkbox" t-att-checked="composerSuggestedRecipientView.suggestedRecipientInfo.isSelected ? 'checked' : undefined" t-on-change="_onChangeCheckbox" t-ref="checkbox" />
<label class="form-check-label" t-attf-for="{{ id }}_checkbox">
<t t-if="composerSuggestedRecipientView.suggestedRecipientInfo.name" t-esc="composerSuggestedRecipientView.suggestedRecipientInfo.name"/>
<t t-if="composerSuggestedRecipientView.suggestedRecipientInfo.email">
<t t-if="composerSuggestedRecipientView.suggestedRecipientInfo.email and composerSuggestedRecipientView.suggestedRecipientInfo.email != composerSuggestedRecipientView.suggestedRecipientInfo.name">
(<t t-esc="composerSuggestedRecipientView.suggestedRecipientInfo.email"/>)
</t>
</label>
+1 -1
View File
@@ -173,7 +173,7 @@ function inline(node, transform_children) {
// Parses text to find email: Tagada <address@mail.fr> -> [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])];
}
@@ -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");
+2 -1
View File
@@ -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, {
+6 -1
View File
@@ -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):
@@ -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
@@ -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
@@ -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)
@@ -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 <cc3@example.com>'})
suggestions = record._message_get_suggested_recipients()[record.id]
self.assertEqual(sorted(suggestions), [
(False, '"cc3" <cc3@example.com>', 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 <cc3@example.com>',
})
suggestions = record._message_get_suggested_recipients()[record.id]
self.assertEqual(
sorted(suggestions),
[
(False, '"cc3" <cc3@example.com>', 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'})
+14 -2
View File
@@ -81,10 +81,22 @@
<field name="model">res.partner</field>
<field name="arch" type="xml">
<form string="Contact">
<field name="is_company" invisible="1"/>
<field name="type" invisible="1"/>
<!-- Invisible fields allows other modules to populate partner (ex.: creation from a lead) -->
<field name="avatar_128" invisible="1"/>
<field name="city" invisible="1"/>
<field name="comment" invisible="1"/>
<field name="country_id" invisible="1"/>
<field name="is_company" invisible="1"/>
<field name="name" invisible="1"/>
<field name="state_id" invisible="1"/>
<field name="street" invisible="1"/>
<field name="street2" invisible="1"/>
<field name="title" invisible="1"/>
<field name="type" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="website" invisible="1"/>
<field name="zip" invisible="1"/>
<field name="image_1920" widget='image' class="oe_avatar" options='{"preview_image": "avatar_128"}'/>
<div class="oe_title">
<field name="company_type" options="{'horizontal': true}" widget="radio" groups="base.group_no_one"/>