some fix sms retry

This commit is contained in:
javad committed 2026-09-16 14:45:10 +03:30
1 parent 0ed677e5fc
commit 863ca89485
3 files changed
+206 -8

No files matched your search

@@ -20,9 +20,10 @@ from apps.tickets.models import Ticket
logger = logging.getLogger(__name__)
DEFINITIVE_DELIVERY_RETRY_EVENT = "STAFF_TICKET_CREATED"
DEFINITIVE_DELIVERY_RETRY_STATUSES = {
NotificationJob.DeliveryStatus.UNDELIVERED,
NotificationJob.DeliveryStatus.EXPIRED,
SAFE_0098_DELIVERY_RETRY_RESULTS = {
(NotificationJob.DeliveryStatus.UNDELIVERED, "6"),
(NotificationJob.DeliveryStatus.EXPIRED, "7"),
(NotificationJob.DeliveryStatus.ERROR, "-31"),
}
def _magic_link(job, reason):
@@ -175,6 +176,10 @@ def _delivery_retry_dedupe_key(job):
return f"{job.dedupe_key[:200-len(suffix)]}{suffix}"
def _is_safe_0098_delivery_retry_result(job):
return (job.delivery_status, (job.delivery_status_code or "").strip()) in SAFE_0098_DELIVERY_RETRY_RESULTS
def _schedule_definitive_delivery_retry(job):
"""Schedule exactly one delayed resend for a definitive individual-ticket failure."""
if (
@@ -184,7 +189,7 @@ def _schedule_definitive_delivery_retry(job):
or not job.ticket_id
or job.ticket.group_dispatch_id
or not job.delivery_is_final
or job.delivery_status not in DEFINITIVE_DELIVERY_RETRY_STATUSES
or not _is_safe_0098_delivery_retry_result(job)
):
return None
sent_at = job.sent_at or job.created_at
@@ -268,6 +273,13 @@ def _process_delivery_checks(options):
else:
job.delivery_status = result.status if result.status in NotificationJob.DeliveryStatus.values else NotificationJob.DeliveryStatus.ERROR
job.delivery_next_check_at = None
# 0098SMS code 6 remains provisional during polling. Once the
# complete monitoring window is exhausted it is safe to treat
# this exact result as terminal for the single delayed resend.
job.delivery_is_final = (
job.delivery_status == NotificationJob.DeliveryStatus.UNDELIVERED
and job.delivery_status_code == "6"
)
job.delivery_description = f"{result.description} نتیجه پس از {job.delivery_check_attempts} نوبت استعلام در بازه پایش نهایی شد."[:500]
job.save(update_fields=[
"delivery_status", "delivery_status_code", "delivery_description",
@@ -0,0 +1,78 @@
from datetime import timedelta
from django.conf import settings
from django.db import migrations
from django.db.models import Q
from django.utils import timezone
SAFE_0098_RESULTS = (
("UNDELIVERED", "6"),
("EXPIRED", "7"),
("ERROR", "-31"),
)
def _retry_dedupe_key(job):
suffix = f":definitive-delivery-retry:{job.pk}"
return f"{job.dedupe_key[:200-len(suffix)]}{suffix}"
def queue_historical_safe_0098_delivery_retries(apps, schema_editor):
"""Queue one delayed resend for old individual staff-ticket SMS jobs."""
NotificationJob = apps.get_model("notifications", "NotificationJob")
delay = getattr(settings, "SMS_DEFINITIVE_DELIVERY_RETRY_AFTER_SECONDS", 2700)
stagger = getattr(settings, "SMS_HISTORICAL_DELIVERY_RETRY_STAGGER_SECONDS", 30)
max_checks = getattr(settings, "SMS_DELIVERY_MAX_CHECKS", 15)
now = timezone.now()
monitor_cutoff = now - timedelta(seconds=delay)
safe_result = (
Q(delivery_status="EXPIRED", delivery_status_code="7")
| Q(delivery_status="ERROR", delivery_status_code="-31")
| Q(delivery_status="UNDELIVERED", delivery_status_code="6", sent_at__lte=monitor_cutoff)
| Q(delivery_status="UNDELIVERED", delivery_status_code="6", delivery_check_attempts__gte=max_checks)
)
sources = NotificationJob.objects.filter(
safe_result,
channel="SMS",
event_type="STAFF_TICKET_CREATED",
status="SENT",
ticket__group_dispatch__isnull=True,
delivery_next_check_at__isnull=True,
delivery_retry_source__isnull=True,
).order_by("id")
queued = 0
for source in sources.iterator():
if NotificationJob.objects.filter(delivery_retry_source_id=source.pk).exists():
continue
sent_at = source.sent_at or source.created_at
due = max(
sent_at + timedelta(seconds=delay),
now + timedelta(seconds=(queued + 1) * stagger),
)
NotificationJob.objects.create(
channel=source.channel,
event_type=source.event_type,
customer_id=source.customer_id,
staff_user_id=source.staff_user_id,
ticket_id=source.ticket_id,
payload=source.payload,
status="PENDING",
attempts=0,
next_attempt_at=due,
dedupe_key=_retry_dedupe_key(source),
delivery_retry_source_id=source.pk,
)
NotificationJob.objects.filter(pk=source.pk).update(delivery_is_final=True)
queued += 1
class Migration(migrations.Migration):
dependencies = [("notifications", "0013_notificationjob_definitive_delivery_retry")]
operations = [
migrations.RunPython(
queue_historical_safe_0098_delivery_retries,
migrations.RunPython.noop,
),
]