some fix
This commit is contained in:
1 parent
6b841dc5fa
commit
0ed677e5fc
10 files changed
+282
-16
No files matched your search
@@ -1,7 +1,7 @@
|
||||
from django.contrib import admin
|
||||
from .models import NotificationJob,NotificationPolicy,SMSTemplate
|
||||
@admin.register(NotificationJob)
|
||||
class NotificationJobAdmin(admin.ModelAdmin): list_display=("channel","event_type","status","attempts","provider_status_code","delivery_status","provider_message_id","created_at"); list_filter=("channel","status","delivery_status"); readonly_fields=("provider_message_id","provider_status_code","delivery_status_code","delivery_description","delivery_checked_at","dedupe_key","created_at","sent_at")
|
||||
class NotificationJobAdmin(admin.ModelAdmin): list_display=("channel","event_type","status","attempts","provider_status_code","delivery_status","delivery_retry_source","provider_message_id","created_at"); list_filter=("channel","status","delivery_status","delivery_is_final"); readonly_fields=("provider_message_id","provider_status_code","delivery_status_code","delivery_description","delivery_checked_at","delivery_is_final","delivery_retry_source","dedupe_key","created_at","sent_at")
|
||||
@admin.register(SMSTemplate)
|
||||
class SMSTemplateAdmin(admin.ModelAdmin):
|
||||
list_display=("event_type","unit","title","is_active","updated_at")
|
||||
|
||||
@@ -19,6 +19,12 @@ 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,
|
||||
}
|
||||
|
||||
def _magic_link(job, reason):
|
||||
token,link = TicketMagicLink.issue_with_record(job.ticket, reason=reason, created_by=job.staff_user or job.ticket.owner or job.ticket.created_by)
|
||||
return link, f"{settings.PUBLIC_BASE_URL}/t/{token}/"
|
||||
@@ -158,11 +164,50 @@ def _record_success(job, result):
|
||||
job.last_error = ""
|
||||
job.provider_message_id = getattr(result, "provider_id", "") if result is not None else ""
|
||||
job.provider_status_code = getattr(result, "provider_code", "") if result is not None else ""
|
||||
job.delivery_is_final = False
|
||||
if job.channel == NotificationJob.Channel.SMS and job.provider_message_id:
|
||||
job.delivery_status = NotificationJob.DeliveryStatus.PENDING
|
||||
job.delivery_next_check_at = timezone.now() + timedelta(seconds=settings.SMS_DELIVERY_CHECK_DELAY_SECONDS)
|
||||
|
||||
|
||||
def _delivery_retry_dedupe_key(job):
|
||||
suffix = f":definitive-delivery-retry:{job.pk}"
|
||||
return f"{job.dedupe_key[:200-len(suffix)]}{suffix}"
|
||||
|
||||
|
||||
def _schedule_definitive_delivery_retry(job):
|
||||
"""Schedule exactly one delayed resend for a definitive individual-ticket failure."""
|
||||
if (
|
||||
job.channel != NotificationJob.Channel.SMS
|
||||
or job.event_type != DEFINITIVE_DELIVERY_RETRY_EVENT
|
||||
or job.delivery_retry_source_id
|
||||
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
|
||||
):
|
||||
return None
|
||||
sent_at = job.sent_at or job.created_at
|
||||
due = max(
|
||||
timezone.now(),
|
||||
sent_at + timedelta(seconds=settings.SMS_DEFINITIVE_DELIVERY_RETRY_AFTER_SECONDS),
|
||||
)
|
||||
retry, _ = NotificationJob.objects.get_or_create(
|
||||
delivery_retry_source=job,
|
||||
defaults={
|
||||
"channel": job.channel,
|
||||
"event_type": job.event_type,
|
||||
"customer_id": job.customer_id,
|
||||
"staff_user_id": job.staff_user_id,
|
||||
"ticket_id": job.ticket_id,
|
||||
"payload": job.payload,
|
||||
"next_attempt_at": due,
|
||||
"dedupe_key": _delivery_retry_dedupe_key(job),
|
||||
},
|
||||
)
|
||||
return retry
|
||||
|
||||
|
||||
def _process_delivery_checks(options):
|
||||
if options.get("channel") not in (None, NotificationJob.Channel.SMS):
|
||||
return
|
||||
@@ -207,11 +252,13 @@ def _process_delivery_checks(options):
|
||||
job.delivery_status = ""
|
||||
job.delivery_next_check_at = None
|
||||
job.delivery_description = "این ارائهدهنده از استعلام دلیوری پشتیبانی نمیکند."
|
||||
job.delivery_is_final = False
|
||||
else:
|
||||
job.delivery_check_attempts += 1
|
||||
job.delivery_checked_at = timezone.now()
|
||||
job.delivery_status_code = result.provider_code[:50]
|
||||
job.delivery_description = result.description[:500]
|
||||
job.delivery_is_final = result.is_final
|
||||
if result.is_final:
|
||||
job.delivery_status = result.status
|
||||
job.delivery_next_check_at = None
|
||||
@@ -225,7 +272,9 @@ def _process_delivery_checks(options):
|
||||
job.save(update_fields=[
|
||||
"delivery_status", "delivery_status_code", "delivery_description",
|
||||
"delivery_checked_at", "delivery_check_attempts", "delivery_next_check_at",
|
||||
"delivery_is_final",
|
||||
])
|
||||
_schedule_definitive_delivery_retry(job)
|
||||
if job.ticket_id and job.ticket.group_dispatch_id:
|
||||
from apps.tickets.services import refresh_group_dispatch_status
|
||||
refresh_group_dispatch_status(job.ticket.group_dispatch_id)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def _retry_dedupe_key(job):
|
||||
suffix = f":definitive-delivery-retry:{job.pk}"
|
||||
return f"{job.dedupe_key[:200-len(suffix)]}{suffix}"
|
||||
|
||||
|
||||
def queue_historical_definitive_delivery_retries(apps, schema_editor):
|
||||
"""Queue each old, definitively expired individual staff-created SMS once."""
|
||||
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)
|
||||
now = timezone.now()
|
||||
sources = NotificationJob.objects.filter(
|
||||
channel="SMS",
|
||||
event_type="STAFF_TICKET_CREATED",
|
||||
status="SENT",
|
||||
ticket__group_dispatch__isnull=True,
|
||||
delivery_status="EXPIRED",
|
||||
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,
|
||||
)
|
||||
queued += 1
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("notifications", "0012_supervisor_sms_template")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="notificationjob",
|
||||
name="delivery_is_final",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="notificationjob",
|
||||
name="delivery_retry_source",
|
||||
field=models.OneToOneField(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="delivery_retry_job",
|
||||
to="notifications.notificationjob",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(queue_historical_definitive_delivery_retries, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -63,5 +63,10 @@ class NotificationJob(models.Model):
|
||||
delivery_checked_at=models.DateTimeField(null=True,blank=True)
|
||||
delivery_check_attempts=models.PositiveSmallIntegerField(default=0)
|
||||
delivery_next_check_at=models.DateTimeField(null=True,blank=True,db_index=True)
|
||||
delivery_is_final=models.BooleanField(default=False)
|
||||
delivery_retry_source=models.OneToOneField(
|
||||
"self",on_delete=models.CASCADE,null=True,blank=True,
|
||||
related_name="delivery_retry_job",
|
||||
)
|
||||
class Meta:
|
||||
indexes=[models.Index(fields=["status","next_attempt_at"],name="notif_due_idx"),models.Index(fields=["ticket","channel","status"],name="notif_ticket_chan_idx")]
|
||||
Reference in new issue
Block a user