from django.db.models import Count from rest_framework import status from rest_framework.exceptions import NotFound, ValidationError from rest_framework.generics import RetrieveAPIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from apps.banker.models import ContactGroup from apps.billing.permissions import HasActiveFeature from apps.messaging.models import BulkMessage, MessageRecipient from apps.billing.forms import normalize_iran_phone from .serializers import ( BulkMessageSerializer, ContactMessageHistorySerializer, SendBulkMessageSerializer, ) from .services import BulkMessageService class SendBulkMessageView(APIView): """POST: می‌سازد و بلافاصله dispatch می‌کند (پیامک/کانال‌های Business-API-دار خودکار، بقیه needs_manual برای صف deep-link سمت اپ).""" permission_classes = [IsAuthenticated, HasActiveFeature] feature_key = "sms_bank" throttle_scope = "messaging_send" def post(self, request): serializer = SendBulkMessageSerializer(data=request.data) serializer.is_valid(raise_exception=True) data = serializer.validated_data if data["group_id"] and not ContactGroup.objects.filter(user=request.user, id=data["group_id"]).exists(): raise ValidationError({"group_id": "گروه یافت نشد."}) bulk = BulkMessageService.create_and_dispatch( user=request.user, channels=data["channels"], group_id=data["group_id"], contact_ids=data["contact_ids"], template_id=data["template_id"], body_override=data["body_override"], ) return Response(BulkMessageSerializer(bulk).data, status=status.HTTP_201_CREATED) class BulkMessageDetailView(RetrieveAPIView): serializer_class = BulkMessageSerializer permission_classes = [IsAuthenticated] def get_queryset(self): return BulkMessage.objects.filter(user=self.request.user) class MessagingStatsView(APIView): """آمار ارسال: چند پیام موفق به گیت‌وی/API فرستاده شده، چند تا ناموفق بوده، چند تا نیاز به تایید دستی داشته/دارد — بر اساس MessageRecipient (نه رسید تحویل واقعی گوشی، چون آن نیاز به webhook از سرویس‌دهنده‌ی پیامک دارد که در این نسخه وصل نشده؛ «ارسال‌شده» یعنی سرور با موفقیت آن را به API/گیت‌وی تحویل داده).""" permission_classes = [IsAuthenticated, HasActiveFeature] feature_key = "sms_bank" def get(self, request): rows = ( MessageRecipient.objects.filter(bulk_message__user=request.user) .values("status") .annotate(count=Count("id")) ) counts = {row["status"]: row["count"] for row in rows} total = sum(counts.values()) return Response( { "total": total, "sent": counts.get(MessageRecipient.Status.SENT, 0), "failed": counts.get(MessageRecipient.Status.FAILED, 0), "needs_manual": counts.get(MessageRecipient.Status.NEEDS_MANUAL, 0), "manual_confirmed": counts.get(MessageRecipient.Status.MANUAL_CONFIRMED, 0), } ) class ConfirmManualSendView(APIView): """اپ بعد از این‌که کاربر دستی در واتساپ/تلگرام/بله/ایتا زد «ارسال»، این را صدا می‌زند.""" permission_classes = [IsAuthenticated, HasActiveFeature] feature_key = "sms_bank" def post(self, request, bulk_id, recipient_id): try: recipient = BulkMessageService.confirm_manual(request.user, bulk_id, recipient_id) except MessageRecipient.DoesNotExist: raise NotFound("گیرنده یافت نشد.") return Response({"success": True, "status": recipient.status}) class ContactMessageHistoryView(APIView): """تاریخچه‌ی پیام‌های فرستاده‌شده به یک شماره. چرا بر اساس *شماره* و نه شناسه‌ی مخاطب: صفحه‌ی «مشتریان» و «مخاطبین» دو موجودیتِ جدا هستند و کاربر انتظار دارد روی پروفایلِ مشتری هم ببیند چه پیامی برایش رفته. شماره تنها چیزی است که این دو را به هم وصل می‌کند، پس با شماره‌ی نرمال‌شده جستجو می‌کنیم. """ permission_classes = [IsAuthenticated] def get(self, request): raw = request.query_params.get("phone", "") or "" phone = normalize_iran_phone(raw) if not phone: raise ValidationError({"phone": "شماره لازم است."}) # ارقامِ ذخیره‌شده ممکن است با ۰ یا +۹۸ باشند؛ روی چند شکل تطبیق # می‌دهیم تا تاریخچه به‌خاطر تفاوتِ قالبِ شماره گم نشود. variants = {phone, phone.lstrip("0"), "+98" + phone.lstrip("0"), "98" + phone.lstrip("0")} rows = ( MessageRecipient.objects.filter( bulk_message__user=request.user, contact__phone__in=variants, ) .select_related("bulk_message") .order_by("-bulk_message__created_at", "-id")[:100] ) return Response(ContactMessageHistorySerializer(rows, many=True).data)