[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:
@@ -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.
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -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')
|
||||
]
|
||||
|
||||
+3
@@ -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",
|
||||
},
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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'})
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user