diff --git a/admin_panel/management/commands/send_daily_report.py b/admin_panel/management/commands/send_daily_report.py new file mode 100644 index 0000000..b399dd9 --- /dev/null +++ b/admin_panel/management/commands/send_daily_report.py @@ -0,0 +1,166 @@ +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)}") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index fcdb5fc..6e24fe1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ playwright==1.44.0 opencv-python-headless==4.9.0.80 numpy==1.26.4 ddddocr==1.4.11 -asgiref==3.4.1 \ No newline at end of file +asgiref==3.4.1 +requests==2.31.0 \ No newline at end of file