diff --git a/addons/sms/__manifest__.py b/addons/sms/__manifest__.py
index ccfae7f9049..9be1a884b13 100644
--- a/addons/sms/__manifest__.py
+++ b/addons/sms/__manifest__.py
@@ -16,6 +16,7 @@ The service is provided by the In App Purchase Odoo platform.
'wizard/sms_cancel_views.xml',
'wizard/sms_composer_views.xml',
'wizard/sms_resend_views.xml',
+ 'views/ir_actions_views.xml',
'views/res_config_settings_views.xml',
'views/res_partner_views.xml',
'views/assets.xml',
diff --git a/addons/sms/models/__init__.py b/addons/sms/models/__init__.py
index 4adcd67ab64..aab4ae27445 100644
--- a/addons/sms/models/__init__.py
+++ b/addons/sms/models/__init__.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from . import ir_actions
from . import mail_followers
from . import mail_message
from . import mail_notification
diff --git a/addons/sms/models/ir_actions.py b/addons/sms/models/ir_actions.py
new file mode 100644
index 00000000000..60abc9e0520
--- /dev/null
+++ b/addons/sms/models/ir_actions.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import _, api, fields, models
+from odoo.exceptions import ValidationError
+
+
+class ServerActions(models.Model):
+ """ Add SMS option in server actions. """
+ _name = 'ir.actions.server'
+ _inherit = ['ir.actions.server']
+
+ state = fields.Selection(selection_add=[
+ ('sms', 'Send SMS Text Message'),
+ ])
+ # SMS
+ sms_template_id = fields.Many2one(
+ 'sms.template', 'SMS Template', ondelete='set null',
+ domain="[('model_id', '=', model_id)]",
+ )
+ sms_mass_keep_log = fields.Boolean('Log a note', default=True)
+
+ @api.constrains('state', 'model_id')
+ def _check_sms_capability(self):
+ for action in self:
+ if action.state == 'sms' and not action.model_id.is_mail_thread:
+ raise ValidationError(_("Sending SMS can only be done on a mail.thread model"))
+
+ @api.model
+ def run_action_sms_multi(self, action, eval_context=None):
+ # TDE CLEANME: when going to new api with server action, remove action
+ if not action.sms_template_id or self._is_recompute(action):
+ return False
+
+ records = eval_context.get('records') or eval_context.get('record')
+ if not records:
+ return False
+
+ composer = self.env['sms.composer'].with_context(
+ default_res_model=records._name,
+ default_res_ids=records.ids,
+ default_composition_mode='mass',
+ default_template_id=action.sms_template_id.id,
+ default_mass_keep_log=action.sms_mass_keep_log,
+ ).create({})
+ composer.action_send_sms()
+ return False
diff --git a/addons/sms/views/ir_actions_views.xml b/addons/sms/views/ir_actions_views.xml
new file mode 100644
index 00000000000..8c6e3b72c5c
--- /dev/null
+++ b/addons/sms/views/ir_actions_views.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ ir.actions.server.view.form.inherit.sms
+ ir.actions.server
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/test_mail_full/tests/__init__.py b/addons/test_mail_full/tests/__init__.py
index 8c7cd81f4ae..b64df4304a9 100644
--- a/addons/test_mail_full/tests/__init__.py
+++ b/addons/test_mail_full/tests/__init__.py
@@ -5,5 +5,6 @@ from . import test_sms_composer
from . import test_sms_management
from . import test_sms_performance
from . import test_sms_post
+from . import test_sms_server_actions
from . import test_sms_sms
from . import test_sms_template
diff --git a/addons/test_mail_full/tests/test_sms_server_actions.py b/addons/test_mail_full/tests/test_sms_server_actions.py
new file mode 100644
index 00000000000..506513c524a
--- /dev/null
+++ b/addons/test_mail_full/tests/test_sms_server_actions.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.addons.sms.tests import common as sms_common
+from odoo.addons.test_mail.tests import common as test_mail_common
+from odoo.addons.test_mail_full.tests import common as test_mail_full_common
+
+
+class TestSmsTemplate(test_mail_full_common.BaseFunctionalTest, sms_common.MockSMS, test_mail_common.TestRecipients):
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestSmsTemplate, cls).setUpClass()
+ cls.test_record = cls.env['mail.test.sms'].with_context(**cls._test_context).create({
+ 'name': 'Test',
+ 'customer_id': cls.partner_1.id,
+ })
+ cls.test_record_2 = cls.env['mail.test.sms'].with_context(**cls._test_context).create({
+ 'name': 'Test Record 2',
+ 'customer_id': False,
+ 'phone_nbr': cls.test_numbers[0],
+ })
+
+ model_id = cls.env['ir.model']._get('mail.test.sms').id
+ cls.sms_template = cls.env['sms.template'].create({
+ 'name': 'Test Template',
+ 'model_id': model_id,
+ 'body': 'Dear ${object.display_name} this is an SMS.'
+ })
+
+ cls.action = cls.env['ir.actions.server'].create({
+ 'name': 'Test SMS Action',
+ 'model_id': model_id,
+ 'state': 'sms',
+ 'sms_template_id': cls.sms_template.id,
+ })
+
+ def test_action_sms(self):
+ context = {
+ 'active_model': 'mail.test.sms',
+ 'active_ids': (self.test_record | self.test_record_2).ids,
+ }
+
+ with self.sudo('employee'), self.mockSMSGateway():
+ self.action.with_user(self.env.user).with_context(**context).run()
+
+ self.assertSMSOutgoing(self.test_record.customer_id, None, 'Dear %s this is an SMS.' % self.test_record.display_name)
+ self.assertSMSOutgoing(self.env['res.partner'], self.test_numbers_san[0], 'Dear %s this is an SMS.' % self.test_record_2.display_name)
+
+ def test_action_sms_single(self):
+ context = {
+ 'active_model': 'mail.test.sms',
+ 'active_id': self.test_record.id,
+ }
+
+ with self.sudo('employee'), self.mockSMSGateway():
+ self.action.with_user(self.env.user).with_context(**context).run()
+ self.assertSMSOutgoing(self.test_record.customer_id, None, 'Dear %s this is an SMS.' % self.test_record.display_name)
+
+ def test_action_sms_w_log(self):
+ self.action.sms_mass_keep_log = True
+ context = {
+ 'active_model': 'mail.test.sms',
+ 'active_ids': (self.test_record | self.test_record_2).ids,
+ }
+
+ with self.sudo('employee'), self.mockSMSGateway():
+ self.action.with_user(self.env.user).with_context(**context).run()
+
+ self.assertSMSNotification([{'partner': self.test_record.customer_id, 'state': 'ready'}], 'Dear %s this is an SMS.' % self.test_record.display_name)
+ self.assertSMSNotification([{'partner': self.env['res.partner'], 'number': self.test_numbers_san[0], 'state': 'ready'}], 'Dear %s this is an SMS.' % self.test_record_2.display_name)
diff --git a/odoo/addons/base/views/ir_cron_views.xml b/odoo/addons/base/views/ir_cron_views.xml
index 562f15e02d6..97e801e2fd8 100644
--- a/odoo/addons/base/views/ir_cron_views.xml
+++ b/odoo/addons/base/views/ir_cron_views.xml
@@ -12,20 +12,18 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+