diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index bd99cfaa5e6..33bf3c25af9 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -111,6 +111,8 @@ class Message(models.Model): model = fields.Char('Related Document Model') res_id = fields.Many2oneReference('Related Document ID', model_field='model') record_name = fields.Char('Message Record Name') # display_name of the related document + record_alias_domain_id = fields.Many2one('mail.alias.domain', 'Alias Domain', ondelete='set null') + record_company_id = fields.Many2one('res.company', 'Company', ondelete='set null') # characteristics message_type = fields.Selection([ ('email', 'Email'), diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index e56c23f4578..2df2cc36a0e 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -2123,6 +2123,10 @@ class MailThread(models.AbstractModel): 'partner_ids': partner_ids, }) # add default-like values afterwards, to avoid useless queries + if 'record_alias_domain_id' not in msg_values: + msg_values['record_alias_domain_id'] = self.sudo()._mail_get_alias_domains(default_company=self.env.company)[self.id].id + if 'record_company_id' not in msg_values: + msg_values['record_company_id'] = self._mail_get_companies(default=self.env.company)[self.id].id if 'reply_to' not in msg_values: msg_values['reply_to'] = self._notify_get_reply_to(default=email_from)[self.id] @@ -2597,6 +2601,11 @@ class MailThread(models.AbstractModel): } msg_values.update(msg_kwargs) # add default-like values afterwards, to avoid useless queries + if self: + if 'record_alias_domain_id' not in msg_values: + msg_values['record_alias_domain_id'] = self._mail_get_alias_domains(default_company=self.env.company)[self.id].id + if 'record_company_id' not in msg_values: + msg_values['record_company_id'] = self._mail_get_companies(default=self.env.company)[self.id].id if 'reply_to' not in msg_values: msg_values['reply_to'] = self._notify_get_reply_to(default=email_from)[self.id if self else False] @@ -2689,6 +2698,8 @@ class MailThread(models.AbstractModel): 'email_from': email_from, # document 'model': self._name, + 'record_alias_domain_id': False, + 'record_company_id': False, 'record_name': False, # content 'attachment_ids': attachment_ids, @@ -2826,6 +2837,8 @@ class MailThread(models.AbstractModel): 'model', 'parent_id', 'partner_ids', + 'record_alias_domain_id', + 'record_company_id', 'record_name', 'reply_to', 'reply_to_force_new', diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 95798f552c1..4d5061c7551 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -121,6 +121,12 @@ class MailComposer(models.TransientModel): res_domain_user_id = fields.Many2one( 'res.users', string='Responsible', help='Used as context used to evaluate composer domain') + record_alias_domain_id = fields.Many2one( + 'mail.alias.domain', 'Alias Domain', + compute='_compute_record_environment', readonly=False, store=True) # useful only in monorecord comment mode + record_company_id = fields.Many2one( + 'res.company', 'Company', + compute='_compute_record_environment', readonly=False, store=True) # useful only in monorecord comment mode record_name = fields.Char( 'Record Name', compute='_compute_record_name', readonly=False, store=True) # useful only in monorecord comment mode @@ -387,6 +393,34 @@ class MailComposer(models.TransientModel): elif not active_res_ids and self.env.context.get('active_id'): composer.res_ids = f"{[self.env.context['active_id']]}" + @api.depends('composition_mode', 'model', 'res_domain', 'res_ids') + def _compute_record_environment(self): + """ In monorecord mode, fetch record company and the linked alias domain, + easing future processing notably at post and notification sending time. + + In batch mode it makes no sense to compute a single company, it will be + dynamically generated. """ + toreset = self.filtered( + lambda comp: (comp.record_company_id or comp.record_alias_domain_id) and comp.composition_batch + ) + if toreset: + toreset.record_alias_domain_id = False + toreset.record_company_id = False + + toupdate = self.filtered( + lambda comp: not comp.composition_batch + ) + for composer in toupdate: + res_ids = composer._evaluate_res_ids() + if composer.model and len(res_ids) == 1: + record = self.env[composer.model].browse(res_ids) + composer.record_company_id = record._mail_get_companies( + default=self.env.company + )[record.id] + composer.record_alias_domain_id = record._mail_get_alias_domains( + default_company=self.env.company + )[record.id] + @api.depends('composition_mode', 'model', 'parent_id', 'res_domain', 'res_ids') def _compute_record_name(self): """ Computation is coming either from parent message, either from the @@ -781,6 +815,8 @@ class MailComposer(models.TransientModel): STA - 'email_add_signature', STA - 'email_layout_xmlid', DYN - 'force_email_lang', # notify parameter + STA - 'record_alias_domain_id', # monorecord only + STA - 'record_company_id', # monorecord only BOTH DYN - 'attachment_ids', @@ -879,6 +915,8 @@ class MailComposer(models.TransientModel): force_send=self.force_send, mail_auto_delete=self.auto_delete, model_description=model_description, + record_alias_domain_id=self.record_alias_domain_id.id, + record_company_id=self.record_company_id.id, ) return values @@ -899,6 +937,10 @@ class MailComposer(models.TransientModel): RecordsModel = self.env[self.model].with_prefetch(res_ids) email_mode = self.composition_mode == 'mass_mail' + # records values + companies = RecordsModel.browse(res_ids)._mail_get_companies(default=self.env.company) + alias_domains = RecordsModel.browse(res_ids)._mail_get_alias_domains(default_company=self.env.company) + # langs, used currently only to propagate in comment mode for notification # layout translation langs = self._render_field('lang', res_ids) @@ -916,6 +958,9 @@ class MailComposer(models.TransientModel): 'email_from': emails_from[res_id], 'scheduled_date': False, 'subject': subjects[res_id], + # record-specific environment values (company, alias_domain) + 'record_alias_domain_id': alias_domains[res_id].id, + 'record_company_id': companies[res_id].id, # some fields are specific to mail **( { diff --git a/addons/mail/wizard/mail_compose_message_views.xml b/addons/mail/wizard/mail_compose_message_views.xml index 7d59093065f..3370d02acf7 100644 --- a/addons/mail/wizard/mail_compose_message_views.xml +++ b/addons/mail/wizard/mail_compose_message_views.xml @@ -21,6 +21,8 @@ + + diff --git a/addons/test_mail/tests/test_mail_composer.py b/addons/test_mail/tests/test_mail_composer.py index 88769e7f0c8..3becd823fad 100644 --- a/addons/test_mail/tests/test_mail_composer.py +++ b/addons/test_mail/tests/test_mail_composer.py @@ -171,6 +171,8 @@ class TestComposerForm(TestMailComposer): self.assertFalse(composer_form.mail_server_id) self.assertEqual(composer_form.model, self.test_record._name) self.assertFalse(composer_form.partner_ids) + self.assertEqual(composer_form.record_alias_domain_id, self.mail_alias_domain) + self.assertEqual(composer_form.record_company_id, self.env.company) self.assertEqual(composer_form.record_name, self.test_record.name, 'MailComposer: comment mode should compute record name') self.assertFalse(composer_form.reply_to) self.assertFalse(composer_form.reply_to_force_new) @@ -182,6 +184,32 @@ class TestComposerForm(TestMailComposer): self.assertEqual(composer_form.subtype_id, self.env.ref('mail.mt_comment')) self.assertFalse(composer_form.subtype_is_log) + @users('employee') + def test_mail_composer_comment_mc(self): + """ Test values specific to multi-company setup, in comment mode, using + the Form tool. """ + # add access to second company to avoid MC rules on ticket model + self.env.user.company_ids = [(4, self.company_2.id)] + test_record = self.test_record.with_env(self.env) + source_company = [ + self.company_admin, + self.company_2, + self.env['res.company'], + ] + expected = [ + (self.company_admin, self.mail_alias_domain), + (self.company_2, self.mail_alias_domain_c2), + (self.company_admin, self.mail_alias_domain), # env company + ] + for company, (exp_company, exp_alias_domain) in zip(source_company, expected): + with self.subTest(cmopany=company): + test_record.write({'company_id': company.id}) + composer_form = Form(self.env['mail.compose.message'].with_context( + self._get_web_context(self.test_record, add_web=True) + )) + self.assertEqual(composer_form.record_alias_domain_id, exp_alias_domain) + self.assertEqual(composer_form.record_company_id, exp_company) + @users('employee') def test_mail_composer_comment_attachments(self): """Tests that all attachments are added to the composer, static attachments @@ -251,6 +279,8 @@ class TestComposerForm(TestMailComposer): self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) self.assertEqual(composer_form.model, self.test_record._name) self.assertEqual(composer_form.partner_ids[:], self.partner_1) + self.assertEqual(composer_form.record_alias_domain_id, self.mail_alias_domain) + self.assertEqual(composer_form.record_company_id, self.env.company) self.assertEqual(composer_form.record_name, self.test_record.name, 'MailComposer: comment mode should compute record name') self.assertEqual(composer_form.reply_to, 'info@test.example.com') self.assertFalse(composer_form.reply_to_force_new) @@ -283,6 +313,8 @@ class TestComposerForm(TestMailComposer): self.assertFalse(composer_form.force_send, 'MailComposer: batch record post use email queue for notifications') self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) self.assertEqual(composer_form.model, self.test_record._name) + self.assertFalse(composer_form.record_alias_domain_id, 'MailComposer: comment in batch mode should have void alias domain') + self.assertFalse(composer_form.record_company_id, 'MailComposer: comment in batch mode should have void company') self.assertFalse(composer_form.record_name, 'MailComposer: comment in batch mode should have void record name') self.assertEqual(composer_form.reply_to, self.template.reply_to) self.assertFalse(composer_form.reply_to_force_new) @@ -315,6 +347,8 @@ class TestComposerForm(TestMailComposer): self.assertFalse(composer_form.force_send, 'MailComposer: batch record post use email queue for notifications') self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) self.assertEqual(composer_form.model, self.test_record._name) + self.assertFalse(composer_form.record_alias_domain_id, 'MailComposer: comment in batch mode should have void alias domain') + self.assertFalse(composer_form.record_company_id, 'MailComposer: comment in batch mode should have void company') self.assertFalse(composer_form.record_name, 'MailComposer: comment in batch mode should have void record name') self.assertEqual(composer_form.reply_to, self.template.reply_to) self.assertFalse(composer_form.reply_to_force_new) @@ -347,6 +381,8 @@ class TestComposerForm(TestMailComposer): self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) self.assertEqual(composer_form.model, self.test_record._name) self.assertFalse(composer_form.partner_ids[:]) + self.assertFalse(composer_form.record_alias_domain_id) + self.assertFalse(composer_form.record_company_id) self.assertFalse(composer_form.record_name) self.assertEqual(composer_form.reply_to, 'info@test.example.com') self.assertFalse(composer_form.reply_to_force_new) @@ -375,6 +411,8 @@ class TestComposerForm(TestMailComposer): self.assertTrue(composer_form.force_send, 'MailComposer: mass mode sends emails right away') self.assertFalse(composer_form.mail_server_id) self.assertEqual(composer_form.model, self.test_records._name) + self.assertFalse(composer_form.record_alias_domain_id, 'MailComposer: mass mode should have void alias domain') + self.assertFalse(composer_form.record_company_id, 'MailComposer: mass mode should have void company') self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name') self.assertFalse(composer_form.reply_to) self.assertFalse(composer_form.reply_to_force_new) @@ -404,6 +442,8 @@ class TestComposerForm(TestMailComposer): self.assertTrue(composer_form.force_send, 'MailComposer: mass mode sends emails right away') self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) self.assertEqual(composer_form.model, self.test_records._name) + self.assertFalse(composer_form.record_alias_domain_id, 'MailComposer: mass mode should have void alias domain') + self.assertFalse(composer_form.record_company_id, 'MailComposer: mass mode should have void company') self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name') self.assertEqual(composer_form.reply_to, self.template.reply_to) self.assertFalse(composer_form.reply_to_force_new) @@ -437,6 +477,8 @@ class TestComposerForm(TestMailComposer): self.assertTrue(composer_form.force_send, 'MailComposer: mass mode sends emails right away') self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) self.assertEqual(composer_form.model, self.test_records._name) + self.assertFalse(composer_form.record_alias_domain_id, 'MailComposer: mass mode should have void alias domain') + self.assertFalse(composer_form.record_company_id, 'MailComposer: mass mode should have void company') self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name') self.assertEqual(composer_form.reply_to, self.template.reply_to) self.assertFalse(composer_form.reply_to_force_new) @@ -470,6 +512,8 @@ class TestComposerForm(TestMailComposer): self.assertTrue(composer_form.force_send, 'MailComposer: mass mode sends emails right away') self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) self.assertEqual(composer_form.model, self.test_records._name) + self.assertFalse(composer_form.record_alias_domain_id, 'MailComposer: mass mode should have void alias domain') + self.assertFalse(composer_form.record_company_id, 'MailComposer: mass mode should have void company') self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name') self.assertEqual(composer_form.reply_to, self.template.reply_to) self.assertFalse(composer_form.reply_to_force_new) @@ -1262,6 +1306,12 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase): """ Ensure class initial data to ease understanding """ self.assertTrue(self.template.auto_delete) + self.assertEqual(len(self.test_record), 1) + self.assertEqual(self.test_record.user_id, self.user_employee_2) + self.assertEqual(self.test_record.message_partner_ids, self.partner_employee_2) + self.assertEqual(self.test_record[0].customer_id.lang, 'en_US') + self.assertEqual(self.test_record.company_id, self.company_admin) + self.assertEqual(len(self.test_records), 2) self.assertEqual(self.test_records.user_id, self.user_employee_2) self.assertEqual(self.test_records.message_partner_ids, self.partner_employee_2) @@ -1844,7 +1894,6 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase): self.assertEqual(set(message.attachment_ids.mapped('res_id')), set(test_record.ids)) self.assertTrue(all(attach not in message.attachment_ids for attach in attachs), 'Should have copied attachments') - @users('employee') @mute_logger('odoo.addons.mail.models.mail_mail') def test_mail_composer_wtpl_mc(self): @@ -1871,11 +1920,11 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase): attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])]) self.assertEqual(len(attachs), 2) - for batch, companies, expected_companies in [ + for batch, companies, expected_companies, expected_alias_domains in [ + (False, self.company_admin, self.company_admin, self.mail_alias_domain), ( - False, self.company_admin, self.company_admin - ), ( True, self.company_admin + self.company_2, self.company_admin + self.company_2, + self.mail_alias_domain + self.mail_alias_domain_c2, ), ]: with self.subTest(batch=batch, @@ -1902,8 +1951,8 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase): new_partner = self.env['res.partner'].search([('email_normalized', '=', 'test.to.1@test.example.com')]) self.assertEqual(len(new_partner), 1) # check output, company-specific values mainly for this test - for record, exp_company in zip( - test_records, expected_companies + for record, exp_company, exp_alias_domain in zip( + test_records, expected_companies, expected_alias_domains ): message = record.message_ids[0] for recipient in [self.partner_employee_2, new_partner, record.customer_id]: @@ -1924,6 +1973,8 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase): 'X-Odoo-Objects': f'{record._name}-{record.id}', }, 'mail_server_id': self.env['ir.mail_server'], + 'record_alias_domain_id': exp_alias_domain, + 'record_company_id': exp_company, 'subject': f'TemplateSubject {record.name}', }, )