174 lines
7.1 KiB
Python
174 lines
7.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
##############################################################################
|
|
#
|
|
# SMS Retry Module
|
|
# Copyright (C) 2024 Expert Co. Ltd. (<http://exp-sa.com>).
|
|
#
|
|
##############################################################################
|
|
from datetime import datetime, timedelta
|
|
from odoo import api, fields, models, _
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SmsSms(models.Model):
|
|
_inherit = 'sms.sms'
|
|
|
|
retry_count = fields.Integer(
|
|
string='Retry Count',
|
|
default=0,
|
|
help='Number of retry attempts made for this SMS'
|
|
)
|
|
max_retry_count = fields.Integer(
|
|
string='Max Retry Count',
|
|
default=3,
|
|
help='Maximum number of retry attempts allowed'
|
|
)
|
|
next_retry_time = fields.Datetime(
|
|
string='Next Retry Time',
|
|
index=True,
|
|
help='Scheduled time for the next retry attempt'
|
|
)
|
|
def _is_retry_enabled(self):
|
|
"""Check if SMS retry is enabled."""
|
|
company = self.env.user.company_id or self.env['res.company'].sudo().search([('sms_retry_enabled', '=', True)], limit=1)
|
|
if company and company.sms_retry_enabled:
|
|
return True
|
|
# Fallback to system parameter
|
|
return self.env['ir.config_parameter'].sudo().get_param('sms.retry.enabled', 'False') == 'True'
|
|
|
|
def _get_retry_interval(self):
|
|
"""Get retry interval in minutes from system parameters or company settings."""
|
|
company = self.env.user.company_id or self.env['res.company'].sudo().search([('sms_retry_enabled', '=', True)], limit=1)
|
|
if company and company.sms_retry_enabled:
|
|
return company.sms_retry_interval_minutes
|
|
# Fallback to system parameter
|
|
return int(self.env['ir.config_parameter'].sudo().get_param('sms.retry.interval_minutes', 60))
|
|
|
|
def _get_max_retry_count(self):
|
|
"""Get max retry count from company settings or system parameters."""
|
|
company = self.env.user.company_id or self.env['res.company'].sudo().search([('sms_retry_enabled', '=', True)], limit=1)
|
|
if company and company.sms_retry_enabled:
|
|
return company.sms_max_retry_count
|
|
# Fallback to system parameter
|
|
return int(self.env['ir.config_parameter'].sudo().get_param('sms.retry.max_count', 3))
|
|
|
|
def _should_retry(self, error_code=None):
|
|
"""Determine if SMS should be retried based on error code and retry settings."""
|
|
self.ensure_one()
|
|
if not self._is_retry_enabled():
|
|
return False
|
|
|
|
# Don't retry if max retry count reached
|
|
if self.retry_count >= self.max_retry_count:
|
|
return False
|
|
|
|
# Don't retry certain error codes that won't resolve with retry
|
|
non_retryable_errors = ['sms_number_missing', 'sms_number_format', 'sms_blacklist', 'sms_duplicate']
|
|
if error_code and error_code in non_retryable_errors:
|
|
return False
|
|
|
|
return True
|
|
|
|
def _schedule_retry(self, error_code=None):
|
|
"""Schedule a retry for failed SMS."""
|
|
self.ensure_one()
|
|
if not self._should_retry(error_code):
|
|
_logger.info("SMS %s will not be retried. Error: %s, Retry count: %s/%s",
|
|
self.id, error_code, self.retry_count, self.max_retry_count)
|
|
return False
|
|
|
|
interval_minutes = self._get_retry_interval()
|
|
next_retry = fields.Datetime.now() + timedelta(minutes=interval_minutes)
|
|
|
|
self.write({
|
|
'next_retry_time': next_retry,
|
|
'max_retry_count': self._get_max_retry_count(),
|
|
})
|
|
|
|
_logger.info("SMS %s scheduled for retry at %s (attempt %s/%s)",
|
|
self.id, next_retry, self.retry_count + 1, self.max_retry_count)
|
|
return True
|
|
|
|
def _retry_sms(self):
|
|
"""Retry sending failed SMS by resetting state to outgoing."""
|
|
self.ensure_one()
|
|
if self.state != 'error':
|
|
return False
|
|
|
|
if self.retry_count >= self.max_retry_count:
|
|
_logger.info("SMS %s has reached max retry count (%s)", self.id, self.max_retry_count)
|
|
return False
|
|
|
|
# Reset to outgoing state to trigger normal send process
|
|
self.write({
|
|
'state': 'outgoing',
|
|
'retry_count': self.retry_count + 1,
|
|
'next_retry_time': False,
|
|
})
|
|
_logger.info("SMS %s retry attempt %s initiated", self.id, self.retry_count)
|
|
|
|
# Trigger immediate send
|
|
try:
|
|
self.send(delete_all=False, auto_commit=True, raise_exception=False)
|
|
except Exception as e:
|
|
_logger.exception("Error during SMS %s retry: %s", self.id, e)
|
|
# State will be updated by _postprocess_iap_sent_sms
|
|
return False
|
|
|
|
return True
|
|
|
|
def _postprocess_iap_sent_sms(self, iap_results, failure_reason=None, delete_all=False):
|
|
"""Override to schedule retries for failed SMS."""
|
|
# Call parent method first
|
|
super(SmsSms, self)._postprocess_iap_sent_sms(iap_results, failure_reason=failure_reason, delete_all=delete_all)
|
|
|
|
# Process retries for failed SMS (only if retry is enabled)
|
|
if not self._is_retry_enabled():
|
|
return
|
|
|
|
for result in iap_results:
|
|
if result.get('state') != 'success':
|
|
sms_id = result.get('res_id')
|
|
if sms_id:
|
|
sms = self.browse(sms_id)
|
|
if sms.exists() and sms.state == 'error':
|
|
error_code = self.IAP_TO_SMS_STATE.get(result.get('state'), 'sms_server')
|
|
sms._schedule_retry(error_code=error_code)
|
|
|
|
@api.model
|
|
def _process_retry_queue(self):
|
|
"""Process SMS records that are ready for retry.
|
|
Called by cron job to find and retry SMS messages."""
|
|
company_ids = self.env['res.company'].search([('sms_retry_enabled', '=', True)])
|
|
if not company_ids:
|
|
_logger.debug("SMS retry is not enabled for any company")
|
|
return
|
|
|
|
now = fields.Datetime.now()
|
|
domain = [
|
|
('state', '=', 'error'),
|
|
('next_retry_time', '<=', now),
|
|
('next_retry_time', '!=', False),
|
|
]
|
|
|
|
sms_to_retry = self.search(domain, limit=1000)
|
|
|
|
if sms_to_retry:
|
|
_logger.info("Processing %s SMS records for retry", len(sms_to_retry))
|
|
for sms in sms_to_retry:
|
|
try:
|
|
# Check if retry is still enabled and valid before processing
|
|
# This will also check retry_count < max_retry_count
|
|
if sms._is_retry_enabled() and sms._should_retry():
|
|
sms._retry_sms()
|
|
else:
|
|
# Clear next_retry_time if retry is no longer valid
|
|
sms.write({'next_retry_time': False})
|
|
except Exception as e:
|
|
_logger.exception("Error retrying SMS %s: %s", sms.id, e)
|
|
|
|
# Note: Commit is handled automatically by the cron job framework
|
|
# Do not commit here as it breaks test savepoints
|