mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-20 03:40:26 +00:00
397 lines
15 KiB
Python
397 lines
15 KiB
Python
import logging
|
||
from datetime import datetime, timedelta
|
||
from aiogram import Dispatcher, types, F
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.config import settings
|
||
from app.database.models import User
|
||
from app.keyboards.admin import get_admin_statistics_keyboard, get_period_selection_keyboard
|
||
from app.localization.texts import get_texts
|
||
from app.services.user_service import UserService
|
||
from app.database.crud.subscription import get_subscriptions_statistics
|
||
from app.database.crud.transaction import get_transactions_statistics, get_revenue_by_period
|
||
from app.database.crud.referral import get_referral_statistics
|
||
from app.utils.decorators import admin_required, error_handler
|
||
from app.utils.formatters import format_datetime, format_percentage
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_statistics_menu(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession
|
||
):
|
||
text = """
|
||
📊 <b>Статистика системы</b>
|
||
|
||
Выберите раздел для просмотра статистики:
|
||
"""
|
||
|
||
await callback.message.edit_text(
|
||
text,
|
||
reply_markup=get_admin_statistics_keyboard(db_user.language)
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_users_statistics(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession
|
||
):
|
||
user_service = UserService()
|
||
stats = await user_service.get_user_statistics(db)
|
||
|
||
total_users = stats['total_users']
|
||
active_rate = format_percentage(stats['active_users'] / total_users * 100 if total_users > 0 else 0)
|
||
|
||
current_time = format_datetime(datetime.utcnow())
|
||
|
||
text = f"""
|
||
👥 <b>Статистика пользователей</b>
|
||
|
||
<b>Общие показатели:</b>
|
||
- Всего зарегистрировано: {stats['total_users']}
|
||
- Активных: {stats['active_users']} ({active_rate})
|
||
- Заблокированных: {stats['blocked_users']}
|
||
|
||
<b>Новые регистрации:</b>
|
||
- Сегодня: {stats['new_today']}
|
||
- За неделю: {stats['new_week']}
|
||
- За месяц: {stats['new_month']}
|
||
|
||
<b>Активность:</b>
|
||
- Коэффициент активности: {active_rate}
|
||
- Рост за месяц: +{stats['new_month']} ({format_percentage(stats['new_month'] / total_users * 100 if total_users > 0 else 0)})
|
||
|
||
<b>Обновлено:</b> {current_time}
|
||
"""
|
||
|
||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
||
[types.InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_stats_users")],
|
||
[types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_statistics")]
|
||
])
|
||
|
||
try:
|
||
await callback.message.edit_text(text, reply_markup=keyboard)
|
||
except Exception as e:
|
||
if "message is not modified" in str(e):
|
||
await callback.answer("📊 Данные актуальны", show_alert=False)
|
||
else:
|
||
logger.error(f"Ошибка обновления статистики пользователей: {e}")
|
||
await callback.answer("❌ Ошибка обновления данных", show_alert=True)
|
||
return
|
||
|
||
await callback.answer("✅ Статистика обновлена")
|
||
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_subscriptions_statistics(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession
|
||
):
|
||
stats = await get_subscriptions_statistics(db)
|
||
|
||
total_subs = stats['total_subscriptions']
|
||
conversion_rate = format_percentage(stats['paid_subscriptions'] / total_subs * 100 if total_subs > 0 else 0)
|
||
current_time = format_datetime(datetime.utcnow())
|
||
|
||
text = f"""
|
||
📱 <b>Статистика подписок</b>
|
||
|
||
<b>Общие показатели:</b>
|
||
- Всего подписок: {stats['total_subscriptions']}
|
||
- Активных: {stats['active_subscriptions']}
|
||
- Платных: {stats['paid_subscriptions']}
|
||
- Триальных: {stats['trial_subscriptions']}
|
||
|
||
<b>Конверсия:</b>
|
||
- Из триала в платную: {conversion_rate}
|
||
- Активных платных: {stats['paid_subscriptions']}
|
||
|
||
<b>Продажи:</b>
|
||
- Сегодня: {stats['purchased_today']}
|
||
- За неделю: {stats['purchased_week']}
|
||
- За месяц: {stats['purchased_month']}
|
||
|
||
<b>Обновлено:</b> {current_time}
|
||
"""
|
||
|
||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
||
[types.InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_stats_subs")],
|
||
[types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_statistics")]
|
||
])
|
||
|
||
try:
|
||
await callback.message.edit_text(text, reply_markup=keyboard)
|
||
await callback.answer("✅ Статистика обновлена")
|
||
except Exception as e:
|
||
if "message is not modified" in str(e):
|
||
await callback.answer("📊 Данные актуальны", show_alert=False)
|
||
else:
|
||
logger.error(f"Ошибка обновления статистики подписок: {e}")
|
||
await callback.answer("❌ Ошибка обновления данных", show_alert=True)
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_revenue_statistics(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession
|
||
):
|
||
now = datetime.utcnow()
|
||
month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||
|
||
month_stats = await get_transactions_statistics(db, month_start, now)
|
||
all_time_stats = await get_transactions_statistics(db)
|
||
current_time = format_datetime(datetime.utcnow())
|
||
|
||
text = f"""
|
||
💰 <b>Статистика доходов</b>
|
||
|
||
<b>За текущий месяц:</b>
|
||
- Доходы: {settings.format_price(month_stats['totals']['income_kopeks'])}
|
||
- Расходы: {settings.format_price(month_stats['totals']['expenses_kopeks'])}
|
||
- Прибыль: {settings.format_price(month_stats['totals']['profit_kopeks'])}
|
||
- От подписок: {settings.format_price(month_stats['totals']['subscription_income_kopeks'])}
|
||
|
||
<b>Сегодня:</b>
|
||
- Транзакций: {month_stats['today']['transactions_count']}
|
||
- Доходы: {settings.format_price(month_stats['today']['income_kopeks'])}
|
||
|
||
<b>За все время:</b>
|
||
- Общий доход: {settings.format_price(all_time_stats['totals']['income_kopeks'])}
|
||
- Общая прибыль: {settings.format_price(all_time_stats['totals']['profit_kopeks'])}
|
||
|
||
<b>Способы оплаты:</b>
|
||
"""
|
||
|
||
for method, data in month_stats['by_payment_method'].items():
|
||
if method and data['count'] > 0:
|
||
text += f"• {method}: {data['count']} ({settings.format_price(data['amount'])})\n"
|
||
|
||
text += f"\n<b>Обновлено:</b> {current_time}"
|
||
|
||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
||
# [types.InlineKeyboardButton(text="📈 Период", callback_data="admin_revenue_period")],
|
||
[types.InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_stats_revenue")],
|
||
[types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_statistics")]
|
||
])
|
||
|
||
try:
|
||
await callback.message.edit_text(text, reply_markup=keyboard)
|
||
await callback.answer("✅ Статистика обновлена")
|
||
except Exception as e:
|
||
if "message is not modified" in str(e):
|
||
await callback.answer("📊 Данные актуальны", show_alert=False)
|
||
else:
|
||
logger.error(f"Ошибка обновления статистики доходов: {e}")
|
||
await callback.answer("❌ Ошибка обновления данных", show_alert=True)
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_referral_statistics(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession
|
||
):
|
||
stats = await get_referral_statistics(db)
|
||
current_time = format_datetime(datetime.utcnow())
|
||
|
||
avg_per_referrer = 0
|
||
if stats['active_referrers'] > 0:
|
||
avg_per_referrer = stats['total_paid_kopeks'] / stats['active_referrers']
|
||
|
||
text = f"""
|
||
🤝 <b>Реферальная статистика</b>
|
||
|
||
<b>Общие показатели:</b>
|
||
- Пользователей с рефералами: {stats['users_with_referrals']}
|
||
- Активных рефереров: {stats['active_referrers']}
|
||
- Выплачено всего: {settings.format_price(stats['total_paid_kopeks'])}
|
||
|
||
<b>За период:</b>
|
||
- Сегодня: {settings.format_price(stats['today_earnings_kopeks'])}
|
||
- За неделю: {settings.format_price(stats['week_earnings_kopeks'])}
|
||
- За месяц: {settings.format_price(stats['month_earnings_kopeks'])}
|
||
|
||
<b>Средние показатели:</b>
|
||
- На одного рефререра: {settings.format_price(int(avg_per_referrer))}
|
||
|
||
<b>Топ рефереры:</b>
|
||
"""
|
||
|
||
if stats['top_referrers']:
|
||
for i, referrer in enumerate(stats['top_referrers'][:5], 1):
|
||
name = referrer['display_name']
|
||
earned = settings.format_price(referrer['total_earned_kopeks'])
|
||
count = referrer['referrals_count']
|
||
text += f"{i}. {name}: {earned} ({count} реф.)\n"
|
||
else:
|
||
text += "Пока нет активных рефереров"
|
||
|
||
text += f"\n<b>Обновлено:</b> {current_time}"
|
||
|
||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
||
[types.InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_stats_referrals")],
|
||
[types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_statistics")]
|
||
])
|
||
|
||
try:
|
||
await callback.message.edit_text(text, reply_markup=keyboard)
|
||
await callback.answer("✅ Статистика обновлена")
|
||
except Exception as e:
|
||
if "message is not modified" in str(e):
|
||
await callback.answer("📊 Данные актуальны", show_alert=False)
|
||
else:
|
||
logger.error(f"Ошибка обновления реферальной статистики: {e}")
|
||
await callback.answer("❌ Ошибка обновления данных", show_alert=True)
|
||
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_summary_statistics(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession
|
||
):
|
||
user_service = UserService()
|
||
user_stats = await user_service.get_user_statistics(db)
|
||
sub_stats = await get_subscriptions_statistics(db)
|
||
|
||
now = datetime.utcnow()
|
||
month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||
revenue_stats = await get_transactions_statistics(db, month_start, now)
|
||
current_time = format_datetime(datetime.utcnow())
|
||
|
||
conversion_rate = 0
|
||
if user_stats['total_users'] > 0:
|
||
conversion_rate = sub_stats['paid_subscriptions'] / user_stats['total_users'] * 100
|
||
|
||
arpu = 0
|
||
if user_stats['active_users'] > 0:
|
||
arpu = revenue_stats['totals']['income_kopeks'] / user_stats['active_users']
|
||
|
||
text = f"""
|
||
📊 <b>Общая сводка системы</b>
|
||
|
||
<b>Пользователи:</b>
|
||
- Всего: {user_stats['total_users']}
|
||
- Активных: {user_stats['active_users']}
|
||
- Новых за месяц: {user_stats['new_month']}
|
||
|
||
<b>Подписки:</b>
|
||
- Активных: {sub_stats['active_subscriptions']}
|
||
- Платных: {sub_stats['paid_subscriptions']}
|
||
- Конверсия: {format_percentage(conversion_rate)}
|
||
|
||
<b>Финансы (месяц):</b>
|
||
- Доходы: {settings.format_price(revenue_stats['totals']['income_kopeks'])}
|
||
- ARPU: {settings.format_price(int(arpu))}
|
||
- Транзакций: {sum(data['count'] for data in revenue_stats['by_type'].values())}
|
||
|
||
<b>Рост:</b>
|
||
- Пользователи: +{user_stats['new_month']} за месяц
|
||
- Продажи: +{sub_stats['purchased_month']} за месяц
|
||
|
||
<b>Обновлено:</b> {current_time}
|
||
"""
|
||
|
||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
||
[types.InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_stats_summary")],
|
||
[types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_statistics")]
|
||
])
|
||
|
||
try:
|
||
await callback.message.edit_text(text, reply_markup=keyboard)
|
||
await callback.answer("✅ Статистика обновлена")
|
||
except Exception as e:
|
||
if "message is not modified" in str(e):
|
||
await callback.answer("📊 Данные актуальны", show_alert=False)
|
||
else:
|
||
logger.error(f"Ошибка обновления общей статистики: {e}")
|
||
await callback.answer("❌ Ошибка обновления данных", show_alert=True)
|
||
|
||
@admin_required
|
||
@error_handler
|
||
async def show_revenue_by_period(
|
||
callback: types.CallbackQuery,
|
||
db_user: User,
|
||
db: AsyncSession
|
||
):
|
||
period = callback.data.split('_')[-1]
|
||
|
||
period_map = {
|
||
"today": 1,
|
||
"yesterday": 1,
|
||
"week": 7,
|
||
"month": 30,
|
||
"all": 365
|
||
}
|
||
|
||
days = period_map.get(period, 30)
|
||
revenue_data = await get_revenue_by_period(db, days)
|
||
|
||
if period == "yesterday":
|
||
yesterday = datetime.utcnow().date() - timedelta(days=1)
|
||
revenue_data = [r for r in revenue_data if r['date'] == yesterday]
|
||
elif period == "today":
|
||
today = datetime.utcnow().date()
|
||
revenue_data = [r for r in revenue_data if r['date'] == today]
|
||
|
||
total_revenue = sum(r['amount_kopeks'] for r in revenue_data)
|
||
avg_daily = total_revenue / len(revenue_data) if revenue_data else 0
|
||
|
||
text = f"""
|
||
📈 <b>Доходы за период: {period}</b>
|
||
|
||
<b>Сводка:</b>
|
||
- Общий доход: {settings.format_price(total_revenue)}
|
||
- Дней с данными: {len(revenue_data)}
|
||
- Средний доход в день: {settings.format_price(int(avg_daily))}
|
||
|
||
<b>По дням:</b>
|
||
"""
|
||
|
||
for revenue in revenue_data[-10:]:
|
||
text += f"• {revenue['date'].strftime('%d.%m')}: {settings.format_price(revenue['amount_kopeks'])}\n"
|
||
|
||
if len(revenue_data) > 10:
|
||
text += f"... и еще {len(revenue_data) - 10} дней"
|
||
|
||
await callback.message.edit_text(
|
||
text,
|
||
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[
|
||
[types.InlineKeyboardButton(text="📊 Другой период", callback_data="admin_revenue_period")],
|
||
[types.InlineKeyboardButton(text="⬅️ К доходам", callback_data="admin_stats_revenue")]
|
||
])
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
def register_handlers(dp: Dispatcher):
|
||
dp.callback_query.register(show_statistics_menu, F.data == "admin_statistics")
|
||
dp.callback_query.register(show_users_statistics, F.data == "admin_stats_users")
|
||
dp.callback_query.register(show_subscriptions_statistics, F.data == "admin_stats_subs")
|
||
dp.callback_query.register(show_revenue_statistics, F.data == "admin_stats_revenue")
|
||
dp.callback_query.register(show_referral_statistics, F.data == "admin_stats_referrals")
|
||
dp.callback_query.register(show_summary_statistics, F.data == "admin_stats_summary")
|
||
dp.callback_query.register(show_revenue_by_period, F.data.startswith("period_"))
|
||
|
||
periods = ["today", "yesterday", "week", "month", "all"]
|
||
for period in periods:
|
||
dp.callback_query.register(
|
||
show_revenue_by_period,
|
||
F.data == f"period_{period}"
|
||
)
|