import logging from datetime import datetime, timedelta from aiogram import Dispatcher, F, types from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.database.crud.referral import get_referral_statistics from app.database.crud.subscription import get_subscriptions_statistics from app.database.crud.transaction import get_revenue_by_period, get_transactions_statistics from app.database.models import User from app.keyboards.admin import get_admin_statistics_keyboard from app.services.user_service import UserService 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 = """ 📊 Статистика системы Выберите раздел для просмотра статистики: """ 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""" 👥 Статистика пользователей Общие показатели: - Всего зарегистрировано: {stats['total_users']} - Активных: {stats['active_users']} ({active_rate}) - Заблокированных: {stats['blocked_users']} Новые регистрации: - Сегодня: {stats['new_today']} - За неделю: {stats['new_week']} - За месяц: {stats['new_month']} Активность: - Коэффициент активности: {active_rate} - Рост за месяц: +{stats['new_month']} ({format_percentage(stats['new_month'] / total_users * 100 if total_users > 0 else 0)}) Обновлено: {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""" 📱 Статистика подписок Общие показатели: - Всего подписок: {stats['total_subscriptions']} - Активных: {stats['active_subscriptions']} - Платных: {stats['paid_subscriptions']} - Триальных: {stats['trial_subscriptions']} Конверсия: - Из триала в платную: {conversion_rate} - Активных платных: {stats['paid_subscriptions']} Продажи: - Сегодня: {stats['purchased_today']} - За неделю: {stats['purchased_week']} - За месяц: {stats['purchased_month']} Обновлено: {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""" 💰 Статистика доходов За текущий месяц: - Доходы: {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'])} Сегодня: - Транзакций: {month_stats['today']['transactions_count']} - Доходы: {settings.format_price(month_stats['today']['income_kopeks'])} За все время: - Общий доход: {settings.format_price(all_time_stats['totals']['income_kopeks'])} - Общая прибыль: {settings.format_price(all_time_stats['totals']['profit_kopeks'])} Способы оплаты: """ 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Обновлено: {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""" 🤝 Реферальная статистика Общие показатели: - Пользователей с рефералами: {stats['users_with_referrals']} - Активных рефереров: {stats['active_referrers']} - Выплачено всего: {settings.format_price(stats['total_paid_kopeks'])} За период: - Сегодня: {settings.format_price(stats['today_earnings_kopeks'])} - За неделю: {settings.format_price(stats['week_earnings_kopeks'])} - За месяц: {settings.format_price(stats['month_earnings_kopeks'])} Средние показатели: - На одного рефререра: {settings.format_price(int(avg_per_referrer))} Топ рефереры: """ 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Обновлено: {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""" 📊 Общая сводка системы Пользователи: - Всего: {user_stats['total_users']} - Активных: {user_stats['active_users']} - Новых за месяц: {user_stats['new_month']} Подписки: - Активных: {sub_stats['active_subscriptions']} - Платных: {sub_stats['paid_subscriptions']} - Конверсия: {format_percentage(conversion_rate)} Финансы (месяц): - Доходы: {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())} Рост: - Пользователи: +{user_stats['new_month']} за месяц - Продажи: +{sub_stats['purchased_month']} за месяц Обновлено: {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""" 📈 Доходы за период: {period} Сводка: - Общий доход: {settings.format_price(total_revenue)} - Дней с данными: {len(revenue_data)} - Средний доход в день: {settings.format_price(int(avg_daily))} По дням: """ 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}')