Also impacts mass_mailing_sms, test_mail_sms, test_mass_mailing This PR adds support to receive sms delivery reports. Before this PR, an SMS was considered 'sent' when successfully handled by the third party. The user couldn't know if/when an SMS was actually sent for delivery or delivered to the recipient's device. This was similar to the behavior for emails as delivery reports are not commonly used (and not supported in Odoo). With this work, the SMS `pending` state is introduced in mail, mass_mailing, sms and mass_mailing_sms contexts although only fully used in the latter two modules (+tests of course). Because of the huge cost related to upgrading very large existing databases, the following compromises were made: 1. An email and sms notification/trace SENT means DELIVERED. Those that are sent but NOT DELIVERED are PENDING. The difference between email and sms traces reinforced with this PR is that an email sent will be counted as "sent" ~ "delivered" unless an error is returned for emails while for SMS it can only be reached if a delivery report is received. 2. The Link between an SMS uuid (shared with trusted parties) and the tracking records (notifications or traces) is done via an explicit relationship table (sms_tracker) instead of via a new field. This however allowed to nicely concentrate the state update logic. A `process` state is added to represent an intermediate step in the sending process, such as held at IAP for SMS. A few adjustments are also included to update for IAP api v3. Also, adapts and includes new tests. Task-2560666 Part-of: odoo/odoo#133392
83 lines
3.8 KiB
Python
83 lines
3.8 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import fields, models
|
|
|
|
|
|
class SmsTracker(models.Model):
|
|
"""Relationship between a sent SMS and tracking records such as notifications and traces.
|
|
|
|
This model acts as an extension of a `mail.notification` or a `mailing.trace` and allows to
|
|
update those based on the SMS provider responses both at sending and when later receiving
|
|
sent/delivery reports (see `SmsController`).
|
|
SMS trackers are supposed to be created manually when necessary, and tied to their related
|
|
SMS through the SMS UUID field. (They are not tied to the SMS records directly as those can
|
|
be deleted when sent).
|
|
|
|
Note: Only admins/system user should need to access (a fortiori modify) these technical
|
|
records so no "sudo" is used nor should be required here.
|
|
"""
|
|
_name = 'sms.tracker'
|
|
_description = "Link SMS to mailing/sms tracking models"
|
|
|
|
SMS_STATE_TO_NOTIFICATION_STATUS = {
|
|
'canceled': 'canceled',
|
|
'process': 'process',
|
|
'error': 'exception',
|
|
'outgoing': 'ready',
|
|
'sent': 'sent',
|
|
'pending': 'pending',
|
|
}
|
|
|
|
sms_uuid = fields.Char('SMS uuid', required=True)
|
|
mail_notification_id = fields.Many2one('mail.notification', ondelete='cascade')
|
|
|
|
_sql_constraints = [
|
|
('sms_uuid_unique', 'unique(sms_uuid)', 'A record for this UUID already exists'),
|
|
]
|
|
|
|
def _action_update_from_provider_error(self, provider_error):
|
|
"""
|
|
:param str provider_error: value returned by SMS service provider (IAP) or any string.
|
|
If provided, notification values will be derived from it.
|
|
(see ``_get_tracker_values_from_provider_error``)
|
|
"""
|
|
failure_reason = False
|
|
failure_type = f'sms_{provider_error}'
|
|
error_status = None
|
|
if failure_type not in self.env['sms.sms'].DELIVERY_ERRORS:
|
|
failure_type = 'unknown'
|
|
failure_reason = provider_error
|
|
elif failure_type in self.env['sms.sms'].BOUNCE_DELIVERY_ERRORS:
|
|
error_status = "bounce"
|
|
|
|
self._update_sms_notifications(error_status or 'exception', failure_type=failure_type, failure_reason=failure_reason)
|
|
return error_status, failure_type, failure_reason
|
|
|
|
def _action_update_from_sms_state(self, sms_state, failure_type=False, failure_reason=False):
|
|
notification_status = self.SMS_STATE_TO_NOTIFICATION_STATUS[sms_state]
|
|
self._update_sms_notifications(notification_status, failure_type=failure_type, failure_reason=failure_reason)
|
|
|
|
def _update_sms_notifications(self, notification_status, failure_type=False, failure_reason=False):
|
|
# canceled is a state which means that the SMS sending order should not be sent to the SMS service.
|
|
# `process`, `pending` are sent to IAP which is not revertible (as `sent` which means "delivered").
|
|
notifications_statuses_to_ignore = {
|
|
'canceled': ['canceled', 'process', 'pending', 'sent'],
|
|
'ready': ['ready', 'process', 'pending', 'sent'],
|
|
'process': ['process', 'pending', 'sent'],
|
|
'pending': ['pending', 'sent'],
|
|
'bounce': ['bounce', 'sent'],
|
|
'sent': ['sent'],
|
|
'exception': ['exception'],
|
|
}[notification_status]
|
|
notifications = self.mail_notification_id.filtered(
|
|
lambda n: n.notification_status not in notifications_statuses_to_ignore
|
|
)
|
|
if notifications:
|
|
notifications.write({
|
|
'notification_status': notification_status,
|
|
'failure_type': failure_type,
|
|
'failure_reason': failure_reason,
|
|
})
|
|
if not self.env.context.get('sms_skip_msg_notification'):
|
|
notifications.mail_message_id._notify_message_notification_update()
|