import requests import logging from django.core.management.base import BaseCommand from django.utils import timezone from django.db.models import Sum, Count, Avg, Q from persiantools.jdatetime import JalaliDateTime from admin_panel.models import Customer, Factor, Action, Article, Sarresid # تنظیم لاگر برای ثبت خطاهای احتمالی سیستم بدون نمایش به کاربر logger = logging.getLogger(__name__) class Command(BaseCommand): help = 'ارسال گزارش جامع عملیات روزانه به پیام‌رسان بله' def handle(self, *args, **options): # ⚠️ اطلاعات اتصال به API بله BALE_BOT_TOKEN = '422476936:gDClZVRB8NxDKXoo9XE7EcZPLr0yqkOu-F0' CHAT_ID = '5063785185' # --- ۱. تنظیمات زمان و تاریخ کاملا دقیق (با در نظر گرفتن تایم‌زون ایران) --- now = timezone.localtime(timezone.now()) today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) today_end = now.replace(hour=23, minute=59, second=59, microsecond=999999) # تبدیل تاریخ میلادی به تاریخ دقیق شمسی برای هدر پیام jalali_date = JalaliDateTime(now).strftime('%Y/%m/%d') try: # --- ۲. بهینه‌سازی کوئری‌ها (تجمیع در سطح دیتابیس برای حداکثر سرعت) --- # آمار مشتریان new_customers_count = Customer.objects.filter(create_date__range=(today_start, today_end)).count() # آمار فاکتورها (استفاده از Aggregate یکپارچه به جای چندین کوئری مجزا) factor_stats = Factor.objects.filter(create_date__range=(today_start, today_end)).aggregate( total_sales=Sum('amount', filter=Q(is_active=True)), active_count=Count('id', filter=Q(is_active=True)), canceled_count=Count('id', filter=Q(is_active=False)), nasb_count=Count('id', filter=Q(is_active=True, FactorType__iexact='Nasb')) ) total_sales = factor_stats['total_sales'] or 0 factors_count = factor_stats['active_count'] or 0 canceled_factors_count = factor_stats['canceled_count'] or 0 nasb_count = factor_stats['nasb_count'] or 0 # آمار وصولی‌ها (فقط مقادیر واریزی خالص بدون اسناد ابطالی) vosol_stats = Article.objects.filter( createdDT__range=(today_start, today_end), credit__gt=0, nahvePardakht__isnull=False ).exclude(payStatus='cancell').aggregate( total_vosol=Sum('credit'), vosol_count=Count('id') ) total_vosol = vosol_stats['total_vosol'] or 0 vosol_count = vosol_stats['vosol_count'] or 0 # آمار چک‌های خطرناک (بحران نقدینگی: سررسید شده و پاس نشده) overdue_checks = Sarresid.objects.filter( type='check', payStatus='false', sarresidDT__lte=today_end ).aggregate( total_amount=Sum('price'), check_count=Count('id') ) overdue_checks_amount = overdue_checks['total_amount'] or 0 overdue_checks_count = overdue_checks['check_count'] or 0 # آمار عملیات و پرسنل (بر اساس actionDateTime که در سیستم شما معیار "امروز" است) completed_actions = Action.objects.filter( actionDateTime__range=(today_start, today_end), status='finish' ) bazdid_count = completed_actions.filter(actionType='bazdid').count() staff_performance = completed_actions.exclude(forUser__username='admin').values( 'forUser__first_name', 'forUser__last_name', 'forUser__username' ).annotate(task_count=Count('id')).order_by('-task_count') staff_text = "" for staff in staff_performance: first = staff.get('forUser__first_name') or '' last = staff.get('forUser__last_name') or '' name = f"{first} {last}".strip() if not name: name = staff.get('forUser__username') or "کاربر ناشناس" staff_text += f"\n🔹 {name}: {staff['task_count']} کار" if not staff_text: staff_text = "\n🔹 هیچ کار/بازدیدی امروز تکمیل نشده است." # آمار نظرسنجی‌ها nazar_stats = Action.objects.filter( nazarDT__range=(today_start, today_end), grade__gt=0 ).aggregate( avg_grade=Avg('grade'), total_count=Count('id') ) nazar_count = nazar_stats['total_count'] or 0 nazar_avg = round(nazar_stats['avg_grade'] or 0, 1) except Exception as db_error: # اگر خطایی در دیتابیس رخ داد، کل پروسه با لاگ متوقف می‌شود و سایت اصلی آسیب نمی‌بیند logger.error(f"Database Error in daily report: {db_error}") self.stdout.write(self.style.ERROR('خطا در خواندن اطلاعات از دیتابیس')) return # --- ۳. قالب‌بندی پیام خروجی (با تاریخ شمسی) --- report_text = f"""📊 **گزارش جامع عملیات روزانه** 📅 تاریخ: {jalali_date} 👥 **آمار مشتریان:** 🔸 تشکیل پرونده جدید: {new_customers_count} نفر 🛒 **آمار فروش:** 🔸 جمع فروش: {total_sales:,.0f} تومان ({factors_count} فاکتور) ⚠️ فاکتورهای ابطال شده: {canceled_factors_count} مورد 💵 **خزانه و وصولی امروز:** 🔸 دریافتی و وصولی: {vosol_count} مورد (مبلغ {total_vosol:,.0f} تومان) 🚨 کل چک‌های پاس‌نشده تا الان: {overdue_checks_count} فقره (مبلغ {overdue_checks_amount:,.0f} تومان) ⚙️ **خدمات و عملیات امروز:** 🔸 نصب دستگاه جدید: {nasb_count} مورد 🔸 بازدید و تعویض فیلتر: {bazdid_count} مورد 👨‍🔧 **عملکرد پرسنل (تسک‌های تکمیل‌شده):** {staff_text} 📞 **مرکز تماس و رضایت‌سنجی امروز:** 🔸 نظرسنجی‌های ثبت شده: {nazar_count} مورد 🔸 میانگین رضایت: {nazar_avg} از 20 ⚡ *سلطان تصفیه آب ایران، اگه میخواهی مو رو از ماست بکشی به پنل مراجعه کن 😘.*""" # --- ۴. ارسال ایمن به API بله (بدون تداخل با سیستم اصلی) --- url = f"https://tapi.bale.ai/bot{BALE_BOT_TOKEN}/sendMessage" payload = { "chat_id": CHAT_ID, "text": report_text } try: # timeout=7 شدیدا توصیه می‌شود تا اسکریپت هنگ نکند و منابع سرور هدر نرود response = requests.post(url, json=payload, timeout=7) if response.status_code == 200: self.stdout.write(self.style.SUCCESS('گزارش با موفقیت به پیام‌رسان بله ارسال شد.')) else: self.stdout.write(self.style.ERROR(f'خطای API بله: {response.text}')) logger.error(f"Bale API Error: {response.text}") except requests.exceptions.Timeout: self.stdout.write(self.style.ERROR('خطا: پایان زمان اتصال به سرور بله (Timeout). پیام ارسال نشد.')) logger.warning("Bale API Timeout. Report not sent.") except requests.exceptions.RequestException as e: self.stdout.write(self.style.ERROR(f'خطای شبکه در ارتباط با سرور بله: {str(e)}')) logger.error(f"Bale API Network Error: {str(e)}") except Exception as e: self.stdout.write(self.style.ERROR('خطای سیستمی ناشناخته رخ داد.')) logger.critical(f"Unknown error in daily report API call: {str(e)}")