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, ), ]