diff --git a/addons/sms/__manifest__.py b/addons/sms/__manifest__.py
index 0de865810e7..820b8b5a711 100644
--- a/addons/sms/__manifest__.py
+++ b/addons/sms/__manifest__.py
@@ -13,7 +13,9 @@ The service is provided by the In App Purchase Odoo platform.
'depends': ['base', 'iap', 'mail', 'phone_validation'],
'data': [
'data/ir_cron_data.xml',
+ 'wizard/sms_cancel_views.xml',
'wizard/sms_composer_views.xml',
+ 'wizard/sms_resend_views.xml',
'views/res_config_settings_views.xml',
'views/res_partner_views.xml',
'views/assets.xml',
diff --git a/addons/sms/models/mail_message.py b/addons/sms/models/mail_message.py
index fbd0465f746..0581b9248c6 100644
--- a/addons/sms/models/mail_message.py
+++ b/addons/sms/models/mail_message.py
@@ -1,7 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, fields, models
+from collections import defaultdict
+from operator import itemgetter
+
+from odoo import api, exceptions, fields, models
+from odoo.tools import groupby
class MailMessage(models.Model):
@@ -11,3 +15,76 @@ class MailMessage(models.Model):
_inherit = 'mail.message'
message_type = fields.Selection(selection_add=[('sms', 'SMS')])
+
+ @api.multi
+ def _format_mail_failures(self):
+ """ A shorter message to notify a SMS delivery failure update
+
+ TDE FIXME: should be cleaned
+ """
+ res = super(MailMessage, self)._format_mail_failures()
+
+ # prepare notifications computation in batch
+ all_notifications = self.env['mail.notification'].sudo().search([
+ ('mail_message_id', 'in', self.ids)
+ ])
+ msgid_to_notif = defaultdict(lambda: self.env['mail.notification'].sudo())
+ for notif in all_notifications:
+ msgid_to_notif[notif.mail_message_id.id] += notif
+
+ for message in self:
+ notifications = msgid_to_notif[message.id]
+ if not any(notification.notification_type == 'sms' for notification in notifications):
+ continue
+ info = dict(message._get_mail_failure_dict(),
+ failure_type='sms',
+ notifications=dict((notif.res_partner_id.id, (notif.notification_status, notif.res_partner_id.name)) for notif in notifications if notif.notification_type == 'sms'),
+ module_icon='/sms/static/img/sms_failure.png'
+ )
+ res.append(info)
+ return res
+
+ @api.multi
+ def _notify_sms_update(self):
+ messages = self.env['mail.message']
+ for message in self:
+ # Check if user has access to the record before displaying a notification about it.
+ # In case the user switches from one company to another, it might happen that he doesn't
+ # have access to the record related to the notification. In this case, we skip it.
+ if message.model and message.res_id:
+ record = self.env[message.model].browse(message.res_id)
+ try:
+ record.check_access_rights('read')
+ record.check_access_rule('read')
+ except exceptions.AccessError:
+ continue
+ else:
+ messages |= message
+
+ """ Notify channels after update of SMS status """
+ updates = [[
+ (self._cr.dbname, 'res.partner', author.id),
+ {'type': 'sms_update', 'elements': self.env['mail.message'].concat(*author_messages)._format_mail_failures()}
+ ] for author, author_messages in groupby(messages, itemgetter('author_id'))]
+ self.env['bus.bus'].sendmany(updates)
+
+ @api.multi
+ def message_format(self):
+ """ Override in order to retrieves data about SMS (recipient name and
+ SMS status)
+
+ TDE FIXME: clean the overall message_format thingy
+ """
+ message_values = super(MailMessage, self).message_format()
+ all_sms_notifications = self.env['mail.notification'].sudo().search([
+ ('mail_message_id', 'in', [r['id'] for r in message_values]),
+ ('notification_type', '=', 'sms')
+ ])
+ msgid_to_notif = defaultdict(lambda: self.env['mail.notification'].sudo())
+ for notif in all_sms_notifications:
+ msgid_to_notif[notif.mail_message_id.id] += notif
+
+ for message in message_values:
+ customer_sms_data = [(notif.id, notif.res_partner_id.display_name, notif.notification_status) for notif in msgid_to_notif.get(message['id'], [])]
+ message['sms_ids'] = customer_sms_data
+ return message_values
diff --git a/addons/sms/static/img/sms_failure.png b/addons/sms/static/img/sms_failure.png
new file mode 100644
index 00000000000..6c40062f92c
Binary files /dev/null and b/addons/sms/static/img/sms_failure.png differ
diff --git a/addons/sms/wizard/__init__.py b/addons/sms/wizard/__init__.py
index 3b5c1a8895f..e6669ba5997 100644
--- a/addons/sms/wizard/__init__.py
+++ b/addons/sms/wizard/__init__.py
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
+from . import sms_cancel
from . import sms_composer
+from . import sms_resend
diff --git a/addons/sms/wizard/sms_cancel.py b/addons/sms/wizard/sms_cancel.py
new file mode 100644
index 00000000000..2b27b237920
--- /dev/null
+++ b/addons/sms/wizard/sms_cancel.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import _, api, fields, models
+
+
+class SMSCancel(models.TransientModel):
+ _name = 'sms.cancel'
+ _description = 'Dismiss notification for resend by model'
+
+ model = fields.Char(string='Model', required=True)
+ help_message = fields.Char(string='Help message', compute='_compute_help_message')
+
+ @api.multi
+ @api.depends('model')
+ def _compute_help_message(self):
+ for wizard in self:
+ wizard.help_message = _("Are you sure you want to discard %s SMS delivery failures. You won't be able to re-send these SMS later!") % (wizard._context.get('unread_counter'))
+
+ @api.multi
+ def action_cancel(self):
+ # TDE CHECK: delete pending SMS
+ author_id = self.env.user.partner_id.id
+ for wizard in self:
+ self._cr.execute("""
+SELECT notif.id, msg.id
+FROM mail_message_res_partner_needaction_rel notif
+JOIN mail_message msg
+ ON notif.mail_message_id = msg.id
+WHERE notif.notification_type = 'sms' IS TRUE AND notif.notification_status IN ('bounce', 'exception')
+ AND msg.model = %s
+ AND msg.author_id = %s """, (wizard.model, author_id))
+ res = self._cr.fetchall()
+ notif_ids = [row[0] for row in res]
+ message_ids = list(set([row[1] for row in res]))
+ if notif_ids:
+ self.env['mail.notification'].browse(notif_ids).sudo().write({'notification_status': 'canceled'})
+ if message_ids:
+ self.env['mail.message'].browse(message_ids)._notify_sms_update()
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/addons/sms/wizard/sms_cancel_views.xml b/addons/sms/wizard/sms_cancel_views.xml
new file mode 100644
index 00000000000..74208e5fd4c
--- /dev/null
+++ b/addons/sms/wizard/sms_cancel_views.xml
@@ -0,0 +1,27 @@
+
+
Test Post Performances with an inbox ping
', partner_ids=self.user_test.partner_id.ids, diff --git a/addons/test_mail_full/tests/__init__.py b/addons/test_mail_full/tests/__init__.py index 733d20548ce..8c7cd81f4ae 100644 --- a/addons/test_mail_full/tests/__init__.py +++ b/addons/test_mail_full/tests/__init__.py @@ -2,6 +2,7 @@ from . import common from . import test_sms_composer +from . import test_sms_management from . import test_sms_performance from . import test_sms_post from . import test_sms_sms diff --git a/addons/test_mail_full/tests/test_sms_management.py b/addons/test_mail_full/tests/test_sms_management.py new file mode 100644 index 00000000000..4463ce8b647 --- /dev/null +++ b/addons/test_mail_full/tests/test_sms_management.py @@ -0,0 +1,153 @@ +# -*- 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 TestSMSWizards(test_mail_full_common.BaseFunctionalTest, sms_common.MockSMS, test_mail_common.MockEmails, test_mail_common.TestRecipients): + + @classmethod + def setUpClass(cls): + super(TestSMSWizards, 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 = cls._reset_mail_context(cls.test_record) + cls.msg = cls.test_record.message_post(body='TEST BODY', author_id=cls.partner_employee.id) + cls.notif_p1 = cls.env['mail.notification'].create({ + 'mail_message_id': cls.msg.id, + 'res_partner_id': cls.partner_1.id, + 'sms_number': cls.partner_1.mobile, + 'notification_type': 'sms', + 'notification_status': 'exception', + 'failure_type': 'sms_number_format', + }) + cls.notif_p2 = cls.env['mail.notification'].create({ + 'mail_message_id': cls.msg.id, + 'res_partner_id': cls.partner_2.id, + 'sms_number': cls.partner_2.mobile, + 'notification_type': 'sms', + 'notification_status': 'exception', + 'failure_type': 'sms_credit', + }) + + def test_sms_resend(self): + self._clear_bus() + + with self.sudo('employee'): + wizard = self.env['sms.resend'].with_context(default_mail_message_id=self.msg.id).create({}) + wizard.write({'recipient_ids': [(1, r.id, {'resend': True}) for r in wizard.recipient_ids]}) + with self.mockSMSGateway(): + wizard.action_resend() + + self.assertSMSNotification([ + {'partner': self.partner_1, 'state': 'sent'}, + {'partner': self.partner_2, 'state': 'sent'} + ], 'TEST BODY', self.msg, check_sms=True) + self.assertBusNotification( + [(self.cr.dbname, 'res.partner', self.partner_employee.id)], + [{'type': 'sms_update', 'elements': [{ + 'message_id': self.msg.id, + 'failure_type': 'sms', + 'notifications': {'%s' % self.partner_1.id: ['sent', self.partner_1.name], '%s' % self.partner_2.id: ['sent', self.partner_2.name]} + }]}] + ) + + def test_sms_resend_update_number(self): + self._clear_bus() + + with self.sudo('employee'): + wizard = self.env['sms.resend'].with_context(default_mail_message_id=self.msg.id).create({}) + wizard.write({'recipient_ids': [(1, r.id, {'resend': True, 'sms_number': self.random_numbers[idx]}) for idx, r in enumerate(wizard.recipient_ids)]}) + with self.mockSMSGateway(): + wizard.action_resend() + + self.assertSMSNotification([ + {'partner': self.partner_1, 'state': 'sent', 'number': self.random_numbers_san[0]}, + {'partner': self.partner_2, 'state': 'sent', 'number': self.random_numbers_san[1]} + ], 'TEST BODY', self.msg, check_sms=True) + self.assertBusNotification( + [(self.cr.dbname, 'res.partner', self.partner_employee.id)], + [{'type': 'sms_update', 'elements': [{ + 'message_id': self.msg.id, + 'failure_type': 'sms', + 'notifications': {'%s' % self.partner_1.id: ['sent', self.partner_1.name], '%s' % self.partner_2.id: ['sent', self.partner_2.name]} + }]}] + ) + + def test_sms_resend_cancel(self): + self._clear_bus() + + with self.sudo('employee'): + wizard = self.env['sms.resend'].with_context(default_mail_message_id=self.msg.id).create({}) + with self.mockSMSGateway(): + wizard.action_cancel() + + self.assertSMSNotification([ + {'partner': self.partner_1, 'state': 'canceled', 'number': self.notif_p1.sms_number, 'failure_type': 'sms_number_format'}, + {'partner': self.partner_2, 'state': 'canceled', 'number': self.notif_p2.sms_number, 'failure_type': 'sms_credit'} + ], 'TEST BODY', self.msg, check_sms=False) + self.assertBusNotification( + [(self.cr.dbname, 'res.partner', self.partner_employee.id)], + [{'type': 'sms_update', 'elements': [{ + 'message_id': self.msg.id, + 'failure_type': 'sms', + 'notifications': {'%s' % self.partner_1.id: ['canceled', self.partner_1.name], '%s' % self.partner_2.id: ['canceled', self.partner_2.name]} + }]}] + ) + + def test_sms_resend_internals(self): + self._clear_bus() + self.assertSMSNotification([ + {'partner': self.partner_1, 'state': 'exception', 'number': self.notif_p1.sms_number, 'failure_type': 'sms_number_format'}, + {'partner': self.partner_2, 'state': 'exception', 'number': self.notif_p2.sms_number, 'failure_type': 'sms_credit'} + ], 'TEST BODY', self.msg, check_sms=False) + + with self.sudo('employee'): + wizard = self.env['sms.resend'].with_context(default_mail_message_id=self.msg.id).create({}) + self.assertTrue(wizard.has_insufficient_credit) + self.assertEqual(set(wizard.mapped('recipient_ids.partner_name')), set((self.partner_1 | self.partner_2).mapped('display_name'))) + wizard.write({'recipient_ids': [(1, r.id, {'resend': True}) for r in wizard.recipient_ids]}) + with self.mockSMSGateway(): + wizard.action_resend() + + def test_sms_resend_w_cancel(self): + self._clear_bus() + + with self.sudo('employee'): + wizard = self.env['sms.resend'].with_context(default_mail_message_id=self.msg.id).create({}) + wizard.write({'recipient_ids': [(1, r.id, {'resend': True if r.partner_id == self.partner_1 else False}) for r in wizard.recipient_ids]}) + with self.mockSMSGateway(): + wizard.action_resend() + + self.assertSMSNotification([{'partner': self.partner_1, 'state': 'sent'}], 'TEST BODY', self.msg, check_sms=True) + self.assertSMSNotification([{'partner': self.partner_2, 'state': 'canceled', 'number': self.notif_p2.sms_number, 'failure_type': 'sms_credit'}], 'TEST BODY', self.msg, check_sms=False) + self.assertBusNotification( + [(self.cr.dbname, 'res.partner', self.partner_employee.id)], + [{'type': 'sms_update', 'elements': [{ + 'message_id': self.msg.id, + 'failure_type': 'sms', + 'notifications': {'%s' % self.partner_1.id: ['sent', self.partner_1.name], '%s' % self.partner_2.id: ['canceled', self.partner_2.name]} + }]}] + ) + + def test_sms_cancel(self): + self._clear_bus() + + with self.mockSMSGateway(), self.sudo('employee'): + wizard = self.env['sms.cancel'].with_context(default_model=self.msg.model).create({}) + wizard.action_cancel() + + self.assertEqual((self.notif_p1 | self.notif_p2).mapped('notification_status'), ['canceled', 'canceled']) + + self.assertBusNotification( + [(self.cr.dbname, 'res.partner', self.partner_employee.id)], + [{'type': 'sms_update', 'elements': [{ + 'message_id': self.msg.id, + 'failure_type': 'sms', + 'notifications': {'%s' % self.partner_1.id: ['canceled', self.partner_1.name], '%s' % self.partner_2.id: ['canceled', self.partner_2.name]} + }]}] + )