From 45555bdd1fddb988bfa54ff54f1bc1887d0dc1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 27 May 2019 15:10:54 +0000 Subject: [PATCH] [IMP] sms: allow to resend and cancel SMS notifications Purpose of this commit is to allow SMS notification management. It is based on what has been done in mail for notification: resend and cancel. In this commit we add wizards allowing users to resend and cancel failed SMS notifications. Resend allow to change the contact number, as well as resending only a subset of notifications. Future commits will improve the UX itself. Related to task 1922163 Linked to PR #33510 Co-Authored-By: Thibault Delavallee Co-Authored-By: Pierre Rousseau --- addons/sms/__manifest__.py | 2 + addons/sms/models/mail_message.py | 79 ++++++++- addons/sms/static/img/sms_failure.png | Bin 0 -> 5385 bytes addons/sms/wizard/__init__.py | 2 + addons/sms/wizard/sms_cancel.py | 40 +++++ addons/sms/wizard/sms_cancel_views.xml | 27 ++++ addons/sms/wizard/sms_resend.py | 116 +++++++++++++ addons/sms/wizard/sms_resend_views.xml | 41 +++++ addons/test_mail/tests/test_performance.py | 4 +- addons/test_mail_full/tests/__init__.py | 1 + .../tests/test_sms_management.py | 153 ++++++++++++++++++ 11 files changed, 462 insertions(+), 3 deletions(-) create mode 100644 addons/sms/static/img/sms_failure.png create mode 100644 addons/sms/wizard/sms_cancel.py create mode 100644 addons/sms/wizard/sms_cancel_views.xml create mode 100644 addons/sms/wizard/sms_resend.py create mode 100644 addons/sms/wizard/sms_resend_views.xml create mode 100644 addons/test_mail_full/tests/test_sms_management.py 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 0000000000000000000000000000000000000000..6c40062f92cf7e6cb2d30ae97d38be104a432588 GIT binary patch literal 5385 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 XdgW&Jdb + + + sms.cancel.form + sms.cancel + + +
+ + +

If you want to re-send them, click Cancel now, then click on the notification and review them one by one by clicking on the red icon next to each message.

+
+
+ +
+
+ + + Discard SMS delivery failures + sms.cancel + ir.actions.act_window + form + new + +
diff --git a/addons/sms/wizard/sms_resend.py b/addons/sms/wizard/sms_resend.py new file mode 100644 index 00000000000..10ae893fab8 --- /dev/null +++ b/addons/sms/wizard/sms_resend.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import _, api, exceptions, fields, models + + +class SMSRecipient(models.TransientModel): + _name = 'sms.resend.recipient' + _description = 'Resend Notification' + _rec_name = 'sms_resend_id' + + sms_resend_id = fields.Many2one('sms.resend', required=True) + notification_id = fields.Many2one('mail.notification', required=True, ondelete='cascade') + resend = fields.Boolean(string="Resend", default=True) + failure_type = fields.Selection([ + ('sms_number_missing', 'Missing Number'), + ('sms_number_format', 'Wrong Number Format'), + ('sms_credit', 'Insufficient Credit'), + ('sms_server', 'Server Error')], related='notification_id.failure_type', related_sudo=True, readonly=True) + partner_id = fields.Many2one('res.partner', 'Partner', related='notification_id.res_partner_id', readonly=True) + partner_name = fields.Char('Recipient', readonly='True') + sms_number = fields.Char('Number') + + +class SMSResend(models.TransientModel): + _name = 'sms.resend' + _description = 'SMS Resend' + _rec_name = 'mail_message_id' + + @api.model + def default_get(self, fields): + result = super(SMSResend, self).default_get(fields) + if result.get('mail_message_id'): + mail_message_id = self.env['mail.message'].browse(result['mail_message_id']) + result['recipient_ids'] = [(0, 0, { + 'notification_id': notif.id, + 'resend': True, + 'failure_type': notif.failure_type, + 'partner_name': notif.res_partner_id.display_name or mail_message_id.record_name, + 'sms_number': notif.sms_number, + }) for notif in mail_message_id.notification_ids if notif.notification_type == 'sms' and notif.notification_status in ('exception', 'bounce')] + return result + + mail_message_id = fields.Many2one('mail.message', 'Message', readonly=True, required=True) + recipient_ids = fields.One2many('sms.resend.recipient', 'sms_resend_id', string='Recipients') + has_cancel = fields.Boolean(compute='_compute_has_cancel') + has_insufficient_credit = fields.Boolean(compute='_compute_has_insufficient_credit') + + @api.depends("recipient_ids.failure_type") + def _compute_has_insufficient_credit(self): + self.has_insufficient_credit = self.recipient_ids.filtered(lambda p: p.failure_type == 'sms_credit') + + @api.depends("recipient_ids.resend") + def _compute_has_cancel(self): + self.has_cancel = self.recipient_ids.filtered(lambda p: not p.resend) + + def _check_access(self): + if not self.mail_message_id or not self.mail_message_id.model or not self.mail_message_id.res_id: + raise exceptions.UserError(_('You do not have access to the message and/or related document.')) + record = self.env[self.mail_message_id.model].browse(self.mail_message_id.res_id) + record.check_access_rights('read') + record.check_access_rule('read') + + @api.multi + def action_resend(self): + self._check_access() + + all_notifications = self.env['mail.notification'].sudo().search([ + ('mail_message_id', '=', self.mail_message_id.id), + ('notification_type', '=', 'sms'), + ('notification_status', 'in', ('exception', 'bounce')) + ]) + sudo_self = self.sudo() + to_cancel_ids = [r.notification_id.id for r in sudo_self.recipient_ids if not r.resend] + to_resend_ids = [r.notification_id.id for r in sudo_self.recipient_ids if r.resend] + + if to_cancel_ids: + all_notifications.filtered(lambda n: n.id in to_cancel_ids).write({'notification_status': 'canceled'}) + + if to_resend_ids: + record = self.env[self.mail_message_id.model].browse(self.mail_message_id.res_id) + + sms_pid_to_number = dict((r.partner_id.id, r.sms_number) for r in self.recipient_ids if r.resend and r.partner_id) + pids = list(sms_pid_to_number.keys()) + numbers = [r.sms_number for r in self.recipient_ids if r.resend and not r.partner_id] + + rdata = [] + for pid, cid, active, pshare, ctype, notif, groups in self.env['mail.followers']._get_recipient_data(record, 'sms', False, pids=pids): + if pid and notif == 'sms': + rdata.append({'id': pid, 'share': pshare, 'active': active, 'notif': notif, 'groups': groups or [], 'type': 'customer' if pshare else 'user'}) + if rdata or numbers: + record._notify_record_by_sms( + self.mail_message_id, {'partners': rdata}, check_existing=True, + sms_numbers=numbers, sms_pid_to_number=sms_pid_to_number, + put_in_queue=False + ) + + self.mail_message_id._notify_sms_update() + return {'type': 'ir.actions.act_window_close'} + + @api.multi + def action_cancel(self): + self._check_access() + + sudo_self = self.sudo() + sudo_self.mapped('recipient_ids.notification_id').write({'notification_status': 'canceled'}) + self.mail_message_id._notify_sms_update() + return {'type': 'ir.actions.act_window_close'} + + @api.multi + def action_buy_credits(self): + url = self.env['iap.account'].get_credits_url(service_name='sms') + return { + 'type': 'ir.actions.act_url', + 'url': url, + } diff --git a/addons/sms/wizard/sms_resend_views.xml b/addons/sms/wizard/sms_resend_views.xml new file mode 100644 index 00000000000..ba090626840 --- /dev/null +++ b/addons/sms/wizard/sms_resend_views.xml @@ -0,0 +1,41 @@ + + + + sms.resend.form + sms.resend + + +
+ + + + + + + + + + + + +
+
+ +
+
+ + + Sending Failures + sms.resend + ir.actions.act_window + form + new + +
diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index 2099e0ccc1b..cf01fc1850c 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -209,7 +209,7 @@ class TestAdvMailPerformance(BaseMailPerformance): @warmup def test_message_assignation_inbox(self): record = self.env['mail.test.track'].create({'name': 'Test'}) - with self.assertQueryCount(__system__=31, emp=36): # test_mail only: 31 - 36 + with self.assertQueryCount(__system__=32, emp=37): # test_mail only: 32 - 37 record.write({ 'user_id': self.user_test.id, }) @@ -265,7 +265,7 @@ class TestAdvMailPerformance(BaseMailPerformance): def test_message_post_one_inbox_notification(self): record = self.env['mail.test.simple'].create({'name': 'Test'}) - with self.assertQueryCount(__system__=28, emp=33): # com runbot 28 - 33 // test_mail only: 28 - 33 + with self.assertQueryCount(__system__=29, emp=34): # com runbot 29 - 34 // test_mail only: 29 - 34 record.message_post( body='

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]} + }]}] + )