Files
khatibi/admin_panel/management/commands/send_daily_report.py
T
2026-10-21 16:34:13 +03:30

167 lines
8.4 KiB
Python

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,
deleted_at__isnull=True,
).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)}")