diff --git a/addons/base_automation/models/base_automation.py b/addons/base_automation/models/base_automation.py
index 6c314147cd8..717b220273c 100644
--- a/addons/base_automation/models/base_automation.py
+++ b/addons/base_automation/models/base_automation.py
@@ -104,7 +104,7 @@ class BaseAutomation(models.Model):
raise exceptions.ValidationError(
_('Form Modification based actions can only be used with code action type.')
)
- if any(action.trigger == 'on_unlink' and action.state in ['email', 'followers', 'next_activity'] for action in self):
+ if any(action.trigger == 'on_unlink' and action.state in ['mail_post', 'followers', 'next_activity'] for action in self):
raise exceptions.ValidationError(
_('Email, followers or activities action types cannot be used when deleting records.')
)
diff --git a/addons/mail/models/ir_actions_server.py b/addons/mail/models/ir_actions_server.py
index 3d8be850312..87a7e24f651 100644
--- a/addons/mail/models/ir_actions_server.py
+++ b/addons/mail/models/ir_actions_server.py
@@ -8,27 +8,42 @@ from odoo.exceptions import ValidationError
class ServerActions(models.Model):
- """ Add email option in server actions. """
+ """ Add mail.thread related options in server actions. """
_name = 'ir.actions.server'
_description = 'Server Action'
_inherit = ['ir.actions.server']
- state = fields.Selection(selection_add=[
- ('email', 'Send Email'),
- ('followers', 'Add Followers'),
- ('next_activity', 'Create Next Activity'),
- ], ondelete={'email': 'cascade', 'followers': 'cascade', 'next_activity': 'cascade'})
+ state = fields.Selection(
+ selection_add=[('mail_post', 'Send Email'),
+ ('followers', 'Add Followers'),
+ ('next_activity', 'Create Next Activity'),
+ ],
+ ondelete={'mail_post': 'cascade',
+ 'followers': 'cascade',
+ 'next_activity': 'cascade',
+ }
+ )
# Followers
partner_ids = fields.Many2many(
'res.partner', string='Add Followers',
compute='_compute_partner_ids', readonly=False, store=True)
- # Template
+ # Message Post / Email
template_id = fields.Many2one(
'mail.template', 'Email Template',
domain="[('model_id', '=', model_id)]",
compute='_compute_template_id',
ondelete='set null', readonly=False, store=True,
)
+ # Message post
+ mail_post_autofollow = fields.Boolean(
+ 'Subscribe Recipients', compute='_compute_mail_post_autofollow',
+ readonly=False, store=True)
+ mail_post_method = fields.Selection(
+ selection=[('email', 'Email'), ('comment', 'Post as Message'), ('note', 'Post as Note')],
+ string='Send as',
+ compute='_compute_mail_post_method',
+ readonly=False, store=True,
+ help='Choose method for email sending:\nEMail: send directly emails\nPost as Message: post on document and notify followers\nPost as Note: log a note on document')
# Next Activity
activity_type_id = fields.Many2one(
'mail.activity.type', string='Activity',
@@ -65,19 +80,37 @@ class ServerActions(models.Model):
@api.depends('model_id', 'state')
def _compute_template_id(self):
to_reset = self.filtered(
- lambda act: act.state != 'email' or \
+ lambda act: act.state != 'mail_post' or \
(act.model_id != act.template_id.model_id)
)
if to_reset:
to_reset.template_id = False
+ @api.depends('state', 'mail_post_method')
+ def _compute_mail_post_autofollow(self):
+ to_reset = self.filtered(lambda act: act.state != 'mail_post' or act.mail_post_method == 'email')
+ if to_reset:
+ to_reset.mail_post_autofollow = False
+ other = self - to_reset
+ if other:
+ other.mail_post_autofollow = True
+
+ @api.depends('state')
+ def _compute_mail_post_method(self):
+ to_reset = self.filtered(lambda act: act.state != 'mail_post')
+ if to_reset:
+ to_reset.mail_post_method = False
+ other = self - to_reset
+ if other:
+ other.mail_post_method = 'email'
+
@api.depends('state')
def _compute_partner_ids(self):
to_reset = self.filtered(lambda act: act.state != 'followers')
if to_reset:
to_reset.partner_ids = False
- @api.depends('model_id')
+ @api.depends('model_id', 'state')
def _compute_activity_type_id(self):
to_reset = self.filtered(
lambda act: act.state != 'next_activity' or \
@@ -106,7 +139,6 @@ class ServerActions(models.Model):
if not activity.activity_user_field_name:
activity.activity_user_field_name = 'user_id'
-
@api.constrains('activity_date_deadline_range')
def _check_activity_date_deadline_range(self):
if any(action.activity_date_deadline_range < 0 for action in self):
@@ -155,17 +187,40 @@ class ServerActions(models.Model):
return True
return False
- def _run_action_email(self, eval_context=None):
+ def _run_action_mail_post_multi(self, eval_context=None):
# TDE CLEANME: when going to new api with server action, remove action
- if not self.template_id or not self._context.get('active_id') or self._is_recompute():
+ if not self.template_id or (not self._context.get('active_ids') and not self._context.get('active_id')) or self._is_recompute():
return False
+ res_ids = self._context.get('active_ids', [self._context.get('active_id')])
+
# Clean context from default_type to avoid making attachment
# with wrong values in subsequent operations
cleaned_ctx = dict(self.env.context)
cleaned_ctx.pop('default_type', None)
cleaned_ctx.pop('default_parent_id', None)
- self.template_id.with_context(cleaned_ctx).send_mail(self._context.get('active_id'), force_send=False,
- raise_exception=False)
+ cleaned_ctx['mail_create_nosubscribe'] = True # do not subscribe random people to records
+ cleaned_ctx['mail_post_autofollow'] = self.mail_post_autofollow
+
+ if self.mail_post_method in ('comment', 'note'):
+ records = self.env[self.model_name].with_context(cleaned_ctx).browse(res_ids)
+ if self.mail_post_method == 'comment':
+ subtype_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment')
+ else:
+ subtype_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note')
+ for record in records:
+ record.message_post_with_template(
+ self.template_id.id,
+ composition_mode='comment',
+ subtype_id=subtype_id,
+ )
+ else:
+ template = self.template_id.with_context(cleaned_ctx)
+ for res_id in res_ids:
+ template.send_mail(
+ res_id,
+ force_send=False,
+ raise_exception=False
+ )
return False
def _run_action_next_activity(self, eval_context=None):
diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py
index a649235c51a..d53c33c8dfb 100644
--- a/addons/mail/models/mail_template.py
+++ b/addons/mail/models/mail_template.py
@@ -272,7 +272,7 @@ class MailTemplate(models.Model):
Attachment = self.env['ir.attachment'] # TDE FIXME: should remove default_type from context
# create a mail_mail based on values, without attachments
- values = self.generate_email(res_id, ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'scheduled_date'])
+ values = self.generate_email(res_id, ['auto_delete', 'subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'scheduled_date'])
values['recipient_ids'] = [Command.link(pid) for pid in values.get('partner_ids', list())]
values['attachment_ids'] = [Command.link(aid) for aid in values.get('attachment_ids', list())]
values.update(email_values or {})
diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py
index b5d609d09ba..66dd23d8a80 100644
--- a/addons/mail/tests/common.py
+++ b/addons/mail/tests/common.py
@@ -819,6 +819,7 @@ class MailCase(MockEmail):
mail_groups['failure'].append(partner)
if ncheck_send:
email_groups[ngroup].append(partner)
+ # canceled: currently nothing checked
elif nstatus == 'canceled':
pass
else:
diff --git a/addons/mail/views/ir_actions_server_views.xml b/addons/mail/views/ir_actions_server_views.xml
index a3179a6d9a7..df2e7b082be 100644
--- a/addons/mail/views/ir_actions_server_views.xml
+++ b/addons/mail/views/ir_actions_server_views.xml
@@ -40,10 +40,14 @@
Hello TestingPartner
') + self.assertFalse(mail.is_notification) + with self.mock_mail_gateway(mail_unlink_sent=True): + mail.send() + + # no archive (message) + self.assertEqual(len(self.test_partner.message_ids), 1, + 'Contains Contact created message') + self.assertFalse(self.test_partner.message_partner_ids) def test_action_followers(self): self.test_partner.message_unsubscribe(self.test_partner.message_partner_ids.ids) @@ -27,6 +61,49 @@ class TestServerActionsEmail(TestMailCommon, TestServerActionsBase): self.action.with_context(self.context).run() self.assertEqual(self.test_partner.message_partner_ids, self.env.ref('base.partner_admin') | random_partner) + def test_action_message_post(self): + # initial state + self.assertEqual(len(self.test_partner.message_ids), 1, + 'Contains Contact created message') + self.assertFalse(self.test_partner.message_partner_ids) + + # test without autofollow and comment + self.action.write({ + 'mail_post_autofollow': False, + 'mail_post_method': 'comment', + 'state': 'mail_post', + 'template_id': self.template.id + }) + + with self.assertSinglePostNotifications( + [{'partner': self.test_partner, 'type': 'email', 'status': 'ready'}], + message_info={'content': 'Hello %s' % self.test_partner.name, + 'message_type': 'notification', + 'subtype': 'mail.mt_comment', + } + ): + self.action.with_context(self.context).run() + # NOTE: template using current user will have funny email_from + self.assertEqual(self.test_partner.message_ids[0].email_from, self.partner_root.email_formatted) + self.assertFalse(self.test_partner.message_partner_ids) + + # test with autofollow and note + self.action.write({ + 'mail_post_autofollow': True, + 'mail_post_method': 'note' + }) + with self.assertSinglePostNotifications( + [{'partner': self.test_partner, 'type': 'email', 'status': 'ready'}], + message_info={'content': 'Hello %s' % self.test_partner.name, + 'message_type': 'notification', + 'subtype': 'mail.mt_note', + } + ): + self.action.with_context(self.context).run() + self.assertEqual(len(self.test_partner.message_ids), 3, + '2 new messages produced') + self.assertEqual(self.test_partner.message_partner_ids, self.test_partner) + def test_action_next_activity(self): self.action.write({ 'state': 'next_activity', diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index 3b97c978865..7d6c9de7e6b 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -415,12 +415,13 @@ class IrActionsServer(models.Model): default='object_write', required=True, copy=True, help="Type of server action. The following values are available:\n" "- 'Execute Python Code': a block of python code that will be executed\n" - "- 'Create': create a new record with new values\n" + "- 'Create a new Record': create a new record with new values\n" "- 'Update a Record': update the values of a record\n" "- 'Execute several actions': define an action that triggers several other server actions\n" - "- 'Send Email': automatically send an email (Discuss)\n" + "- 'Send Email': post a message, a note or send an email (Discuss)\n" "- 'Add Followers': add followers to a record (Discuss)\n" - "- 'Create Next Activity': create an activity (Discuss)") + "- 'Create Next Activity': create an activity (Discuss)\n" + "- 'Send SMS Text Message': send SMS, log them on documents (SMS)") # Generic sequence = fields.Integer(default=5, help="When dealing with multiple actions, the execution order is " diff --git a/odoo/addons/base/tests/test_ir_actions.py b/odoo/addons/base/tests/test_ir_actions.py index 67215edaba2..8646a341278 100644 --- a/odoo/addons/base/tests/test_ir_actions.py +++ b/odoo/addons/base/tests/test_ir_actions.py @@ -23,9 +23,10 @@ class TestServerActionsBase(common.TransactionCase): 'address_format': 'SuperFormat', }) self.test_partner = self.env['res.partner'].create({ - 'name': 'TestingPartner', 'city': 'OrigCity', 'country_id': self.test_country.id, + 'email': 'test.partner@test.example.com', + 'name': 'TestingPartner', }) self.context = { 'active_model': 'res.partner',