167 lines
8.4 KiB
Python
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)}") |