[FIX]
This commit is contained in:
parent
ed75c386b9
commit
de7278c74b
|
|
@ -1,8 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# SMS Retry Module
|
|
||||||
# Copyright (C) 2024 Expert Co. Ltd. (<http://exp-sa.com>).
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
from . import models
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# SMS Retry Module
|
|
||||||
# Copyright (C) 2024 Expert Co. Ltd. (<http://exp-sa.com>).
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
{
|
|
||||||
'name': 'SMS Retry',
|
|
||||||
'version': '14.0.1.0.0',
|
|
||||||
'category': 'Odex25-base/Communication',
|
|
||||||
'author': 'Expert Co. Ltd.',
|
|
||||||
'company': 'Expert Co. Ltd.',
|
|
||||||
'maintainer': 'Expert Co. Ltd.',
|
|
||||||
'website': 'http://www.exp-sa.com',
|
|
||||||
'summary': 'Retry failed SMS messages with configurable intervals',
|
|
||||||
'description': """
|
|
||||||
SMS Retry Module
|
|
||||||
================
|
|
||||||
This module allows automatic retry of failed SMS messages with configurable:
|
|
||||||
* Retry intervals (in minutes)
|
|
||||||
* Maximum retry count
|
|
||||||
* Next retry time tracking per SMS record
|
|
||||||
|
|
||||||
Features:
|
|
||||||
---------
|
|
||||||
* Automatically retries failed SMS messages
|
|
||||||
* Configurable retry interval
|
|
||||||
* Configurable maximum retry attempts
|
|
||||||
* Stores next retry time in each SMS record
|
|
||||||
* Cron job processes retries based on next_retry_time
|
|
||||||
""",
|
|
||||||
'depends': ['sms', 'base'],
|
|
||||||
'data': [
|
|
||||||
'security/ir.model.access.csv',
|
|
||||||
'data/ir_cron_data.xml',
|
|
||||||
'views/res_config_settings_views.xml',
|
|
||||||
'views/sms_sms_views.xml',
|
|
||||||
],
|
|
||||||
'installable': True,
|
|
||||||
'auto_install': False,
|
|
||||||
'application': False,
|
|
||||||
'license': 'LGPL-3',
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<odoo>
|
|
||||||
<data noupdate="1">
|
|
||||||
<record id="ir_cron_sms_retry_queue" model="ir.cron">
|
|
||||||
<field name="name">SMS: Process Retry Queue</field>
|
|
||||||
<field name="model_id" ref="sms.model_sms_sms"/>
|
|
||||||
<field name="state">code</field>
|
|
||||||
<field name="code">model._process_retry_queue()</field>
|
|
||||||
<field name="user_id" ref="base.user_root"/>
|
|
||||||
<field name="interval_number">15</field>
|
|
||||||
<field name="interval_type">minutes</field>
|
|
||||||
<field name="numbercall">-1</field>
|
|
||||||
<field name="doall" eval="False"/>
|
|
||||||
<field name="active" eval="True"/>
|
|
||||||
</record>
|
|
||||||
</data>
|
|
||||||
</odoo>
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# SMS Retry Module
|
|
||||||
# Copyright (C) 2024 Expert Co. Ltd. (<http://exp-sa.com>).
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
from . import sms_sms
|
|
||||||
from . import res_config_settings
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# SMS Retry Module
|
|
||||||
# Copyright (C) 2024 Expert Co. Ltd. (<http://exp-sa.com>).
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
from odoo import api, fields, models
|
|
||||||
|
|
||||||
|
|
||||||
class ResConfigSettings(models.TransientModel):
|
|
||||||
_inherit = 'res.config.settings'
|
|
||||||
|
|
||||||
sms_retry_enabled = fields.Boolean(
|
|
||||||
string='Enable SMS Retry',
|
|
||||||
related='company_id.sms_retry_enabled',
|
|
||||||
readonly=False,
|
|
||||||
help='Enable automatic retry of failed SMS messages'
|
|
||||||
)
|
|
||||||
sms_retry_interval_minutes = fields.Integer(
|
|
||||||
string='Retry Interval (minutes)',
|
|
||||||
related='company_id.sms_retry_interval_minutes',
|
|
||||||
readonly=False,
|
|
||||||
help='Time interval in minutes between retry attempts'
|
|
||||||
)
|
|
||||||
sms_max_retry_count = fields.Integer(
|
|
||||||
string='Max Retry Count',
|
|
||||||
related='company_id.sms_max_retry_count',
|
|
||||||
readonly=False,
|
|
||||||
help='Maximum number of retry attempts per SMS'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ResCompany(models.Model):
|
|
||||||
_inherit = 'res.company'
|
|
||||||
|
|
||||||
sms_retry_enabled = fields.Boolean(
|
|
||||||
string='Enable SMS Retry',
|
|
||||||
default=False,
|
|
||||||
help='Enable automatic retry of failed SMS messages for this company'
|
|
||||||
)
|
|
||||||
sms_retry_interval_minutes = fields.Integer(
|
|
||||||
string='Retry Interval (minutes)',
|
|
||||||
default=60,
|
|
||||||
help='Time interval in minutes between retry attempts. Default: 60 minutes'
|
|
||||||
)
|
|
||||||
sms_max_retry_count = fields.Integer(
|
|
||||||
string='Max Retry Count',
|
|
||||||
default=3,
|
|
||||||
help='Maximum number of retry attempts per SMS. Default: 3 attempts'
|
|
||||||
)
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
# -*- 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
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
|
||||||
access_sms_sms_user,sms.sms.user,model_sms_sms,base.group_user,1,1,1,1
|
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<odoo>
|
|
||||||
<data>
|
|
||||||
<record id="res_config_settings_view_form_inherit_sms_retry" model="ir.ui.view">
|
|
||||||
<field name="name">res.config.settings.view.form.inherit.sms.retry</field>
|
|
||||||
<field name="model">res.config.settings</field>
|
|
||||||
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//div[hasclass('settings')]" position="inside">
|
|
||||||
<div class="app_settings_block" data-string="SMS Retry" string="SMS Retry" data-key="sms_retry">
|
|
||||||
<h2>SMS Retry Configuration</h2>
|
|
||||||
<div class="row mt16 o_settings_container">
|
|
||||||
<div class="col-12 col-lg-6 o_setting_box">
|
|
||||||
<div class="o_setting_left_pane">
|
|
||||||
<field name="sms_retry_enabled"/>
|
|
||||||
</div>
|
|
||||||
<div class="o_setting_right_pane">
|
|
||||||
<label for="sms_retry_enabled"/>
|
|
||||||
<div class="text-muted">
|
|
||||||
Enable automatic retry of failed SMS messages
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt16 o_settings_container" attrs="{'invisible': [('sms_retry_enabled', '=', False)]}">
|
|
||||||
<div class="col-12 col-lg-6 o_setting_box">
|
|
||||||
<div class="o_setting_left_pane">
|
|
||||||
</div>
|
|
||||||
<div class="o_setting_right_pane">
|
|
||||||
<label for="sms_retry_interval_minutes"/>
|
|
||||||
<div class="text-muted">
|
|
||||||
Time interval in minutes between retry attempts. Default: 60 minutes
|
|
||||||
</div>
|
|
||||||
<field name="sms_retry_interval_minutes"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt16 o_settings_container" attrs="{'invisible': [('sms_retry_enabled', '=', False)]}">
|
|
||||||
<div class="col-12 col-lg-6 o_setting_box">
|
|
||||||
<div class="o_setting_left_pane">
|
|
||||||
</div>
|
|
||||||
<div class="o_setting_right_pane">
|
|
||||||
<label for="sms_max_retry_count"/>
|
|
||||||
<div class="text-muted">
|
|
||||||
Maximum number of retry attempts per SMS. Default: 3 attempts
|
|
||||||
</div>
|
|
||||||
<field name="sms_max_retry_count"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
</data>
|
|
||||||
</odoo>
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<odoo>
|
|
||||||
<data>
|
|
||||||
<record id="sms_sms_view_form_inherit_sms_retry" model="ir.ui.view">
|
|
||||||
<field name="name">sms.sms.view.form.inherit.sms.retry</field>
|
|
||||||
<field name="model">sms.sms</field>
|
|
||||||
<field name="inherit_id" ref="sms.sms_tsms_view_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//field[@name='error_code']" position="after">
|
|
||||||
<group string="Retry Information" attrs="{'invisible': [('state', '!=', 'error')]}">
|
|
||||||
<group>
|
|
||||||
<field name="retry_count" readonly="1"/>
|
|
||||||
<field name="max_retry_count" readonly="1"/>
|
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<field name="next_retry_time" readonly="1" attrs="{'invisible': [('next_retry_time', '=', False)]}"/>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="sms_sms_view_tree_inherit_sms_retry" model="ir.ui.view">
|
|
||||||
<field name="name">sms.sms.view.tree.inherit.sms.retry</field>
|
|
||||||
<field name="model">sms.sms</field>
|
|
||||||
<field name="inherit_id" ref="sms.sms_sms_view_tree"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<field name="state" position="after">
|
|
||||||
<field name="retry_count" optional="hide"/>
|
|
||||||
<field name="next_retry_time" optional="hide" attrs="{'invisible': [('next_retry_time', '=', False)]}"/>
|
|
||||||
</field>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
</data>
|
|
||||||
</odoo>
|
|
||||||
Loading…
Reference in New Issue