"""قرار دادن پیامک در صف ارسال (insert-only، بی‌خطر برای مسیر request).""" import logging from uuid import uuid4 from django.db import IntegrityError from ..models import SMSDedup, SMSQueue, SMSTemplate, SMSType from .recipients import resolve_recipients from .render import render_template from .rules import is_sms_enabled logger = logging.getLogger(__name__) def enqueue_sms(*, customer=None, sms_type, context=None, dedup_key=None, message=None, recipients=None, created_by=None, approved=False): """پیامک را در صف قرار می‌دهد و تعداد رکوردهای ساخته‌شده را برمی‌گرداند. به‌صورت پیش‌فرض پیامک در وضعیت «در انتظار تأیید» ثبت می‌شود و تا وقتی مدیر آن را از پنل تأیید نکند، worker آن را ارسال نمی‌کند. دو راه برای پرش از این بازبینی هست: ``approved=True`` برای ارسال‌های دستیِ صریح مدیر (مثل پیامک سفارشی یا آزمایشی)، و ``SMSTemplate.auto_send`` که مدیر در صفحهٔ قالب‌ها برای هر نوع پیامک روشن می‌کند تا پیامک‌های خودکارِ اسکنر بدون صف تأیید ارسال شوند. هیچ استثنایی به فراخوان پرتاب نمی‌شود؛ خطاها فقط لاگ می‌شوند تا مسیر اصلی (مثلاً ایجاد مشتری یا اسکنر) هرگز به‌خاطر پیامک مختل نشود. """ try: if message is None: template = SMSTemplate.objects.filter(sms_type=sms_type, is_enabled=True).first() if template is None: return 0 message = render_template(template.text, context) # «ارسال خودکار» هر نوع پیامک در صفحهٔ قالب‌ها تعیین می‌شود؛ خاموش = بازبینی مدیر. approved = approved or template.auto_send if not is_sms_enabled(customer, sms_type): return 0 numbers = recipients if recipients is not None else resolve_recipients(customer) if not numbers: return 0 if dedup_key is None: dedup_key = f"{sms_type}:{uuid4().hex}" created = 0 for number in numbers: key = dedup_key if len(numbers) == 1 else f"{dedup_key}:{number}" try: # دفتر کلیدها مستقل از صف است: کلیدِ یک‌بار خورده هرگز دوباره # ایجاد نمی‌شود، حتی اگر رکورد صف را مدیر حذف کرده باشد. if not SMSDedup.objects.get_or_create(key=key)[1]: continue _, made = SMSQueue.objects.get_or_create( dedup_key=key, defaults={ "customer": customer, "sms_type": sms_type, "phone_number": number, "message": message, "created_by": created_by, "status": (SMSQueue.Status.APPROVED if approved else SMSQueue.Status.PENDING), }, ) created += 1 if made else 0 except IntegrityError: # رقابت هم‌زمان روی dedup_key؛ نادیده گرفته می‌شود. continue return created except Exception: logger.exception("enqueue_sms برای نوع=%s شکست خورد", sms_type) return 0