Files
odoo_source/addons/sms/models/sms_sms.py
T
Munaf Khan 160b5732ef [IMP] sms: allow users to resend SMS
Right now, for failed SMS, user have to go through each record one
by one to resend the SMS, which is not ideal and user should be
able to resend SMS in batch from the list view.

This commit improves the behavior with following changes:
 * Introduces an action 'Resend' which is availabe on list view of the SMS.
   It enables resending multiple SMS (that are in error state). It also
   displays appropriate toast notifcation to users so they know how it
   worked (if few/none SMS sent successfully or there was no SMS that could
   be resent etc).
 * On the SMS form view, a new 'Retry' button is introduced, visible on SMS
   whrich are cancelled or in error state. It pushes the SMS back in sendng
   queue (outgoing state).
 * 'Cancel' button on form view now also appears in error state.
 * Uses the state-decorations on the list view for better identifications
   of the records (info for outgoing, muted for canceled and danger for error
   state).

taskID-2465233
closes https://github.com/odoo/odoo/pull/67502
2021-05-19 12:51:06 +00:00

173 lines
6.9 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import threading
from odoo import api, fields, models, tools, _
_logger = logging.getLogger(__name__)
class SmsSms(models.Model):
_name = 'sms.sms'
_description = 'Outgoing SMS'
_rec_name = 'number'
_order = 'id DESC'
IAP_TO_SMS_STATE = {
'success': 'sent',
'insufficient_credit': 'sms_credit',
'wrong_number_format': 'sms_number_format',
'server_error': 'sms_server',
'unregistered': 'sms_acc'
}
number = fields.Char('Number')
body = fields.Text()
partner_id = fields.Many2one('res.partner', 'Customer')
mail_message_id = fields.Many2one('mail.message', index=True)
state = fields.Selection([
('outgoing', 'In Queue'),
('sent', 'Sent'),
('error', 'Error'),
('canceled', 'Canceled')
], 'SMS Status', readonly=True, copy=False, default='outgoing', required=True)
error_code = fields.Selection([
('sms_number_missing', 'Missing Number'),
('sms_number_format', 'Wrong Number Format'),
('sms_credit', 'Insufficient Credit'),
('sms_server', 'Server Error'),
('sms_acc', 'Unregistered Account'),
# mass mode specific codes
('sms_blacklist', 'Blacklisted'),
('sms_duplicate', 'Duplicate'),
], copy=False)
def send(self, delete_all=False, auto_commit=False, raise_exception=False):
""" Main API method to send SMS.
:param delete_all: delete all SMS (sent or not); otherwise delete only
sent SMS;
:param auto_commit: commit after each batch of SMS;
:param raise_exception: raise if there is an issue contacting IAP;
"""
for batch_ids in self._split_batch():
self.browse(batch_ids)._send(delete_all=delete_all, raise_exception=raise_exception)
# auto-commit if asked except in testing mode
if auto_commit is True and not getattr(threading.currentThread(), 'testing', False):
self._cr.commit()
def set_outgoing(self):
self.state = 'outgoing'
def resend_failed(self):
sms_to_send = self.filtered(lambda sms: sms.state == 'error')
notification_title = _('Warning')
notification_type = 'danger'
notification_message = ''
if sms_to_send:
sms_to_send.send()
success_sms = len(sms_to_send) - len(sms_to_send.exists())
if success_sms > 0:
notification_title = _('Success')
notification_type = 'success'
notification_message = _('%s out of the %s selected SMS Text Messages have successfully been resent.', success_sms, len(self))
else:
notification_message = _('The SMS Text Messages could not be resent.')
else:
notification_message = _('There are no SMS Text Messages to resend.')
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': notification_title,
'message': notification_message,
'type': notification_type,
}
}
def cancel(self):
self.state = 'canceled'
@api.model
def _process_queue(self, ids=None):
""" Send immediately queued messages, committing after each message is sent.
This is not transactional and should not be called during another transaction!
:param list ids: optional list of emails ids to send. If passed no search
is performed, and these ids are used instead.
"""
domain = [('state', '=', 'outgoing')]
filtered_ids = self.search(domain, limit=10000).ids # TDE note: arbitrary limit we might have to update
if ids:
ids = list(set(filtered_ids) & set(ids))
else:
ids = filtered_ids
ids.sort()
res = None
try:
# auto-commit except in testing mode
auto_commit = not getattr(threading.currentThread(), 'testing', False)
res = self.browse(ids).send(delete_all=False, auto_commit=auto_commit, raise_exception=False)
except Exception:
_logger.exception("Failed processing SMS queue")
return res
def _split_batch(self):
batch_size = int(self.env['ir.config_parameter'].sudo().get_param('sms.session.batch.size', 500))
for sms_batch in tools.split_every(batch_size, self.ids):
yield sms_batch
def _send(self, delete_all=False, raise_exception=False):
""" This method tries to send SMS after checking the number (presence and
formatting). """
iap_data = [{
'res_id': record.id,
'number': record.number,
'content': record.body,
} for record in self]
try:
iap_results = self.env['sms.api']._send_sms_batch(iap_data)
except Exception as e:
_logger.info('Sent batch %s SMS: %s: failed with exception %s', len(self.ids), self.ids, e)
if raise_exception:
raise
self._postprocess_iap_sent_sms([{'res_id': sms.id, 'state': 'server_error'} for sms in self], delete_all=delete_all)
else:
_logger.info('Send batch %s SMS: %s: gave %s', len(self.ids), self.ids, iap_results)
self._postprocess_iap_sent_sms(iap_results, delete_all=delete_all)
def _postprocess_iap_sent_sms(self, iap_results, failure_reason=None, delete_all=False):
if delete_all:
todelete_sms_ids = [item['res_id'] for item in iap_results]
else:
todelete_sms_ids = [item['res_id'] for item in iap_results if item['state'] == 'success']
for state in self.IAP_TO_SMS_STATE.keys():
sms_ids = [item['res_id'] for item in iap_results if item['state'] == state]
if sms_ids:
if state != 'success' and not delete_all:
self.env['sms.sms'].sudo().browse(sms_ids).write({
'state': 'error',
'error_code': self.IAP_TO_SMS_STATE[state],
})
notifications = self.env['mail.notification'].sudo().search([
('notification_type', '=', 'sms'),
('sms_id', 'in', sms_ids),
('notification_status', 'not in', ('sent', 'canceled'))]
)
if notifications:
notifications.write({
'notification_status': 'sent' if state == 'success' else 'exception',
'failure_type': self.IAP_TO_SMS_STATE[state] if state != 'success' else False,
'failure_reason': failure_reason if failure_reason else False,
})
self.mail_message_id._notify_message_notification_update()
if todelete_sms_ids:
self.browse(todelete_sms_ids).sudo().unlink()