""" Дополнительные утилиты для админ-панели """ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, delete, update, func from models import User, Lottery, Participation, Winner from typing import List, Dict, Optional import csv import json from datetime import datetime class AdminUtils: """Утилиты для админ-панели""" @staticmethod async def get_lottery_statistics(session: AsyncSession, lottery_id: int) -> Dict: """Получить детальную статистику по розыгрышу""" lottery = await session.get(Lottery, lottery_id) if not lottery: return {} # Количество участников participants_count = await session.scalar( select(func.count(Participation.id)) .where(Participation.lottery_id == lottery_id) ) # Победители winners_count = await session.scalar( select(func.count(Winner.id)) .where(Winner.lottery_id == lottery_id) ) # Ручные победители manual_winners_count = await session.scalar( select(func.count(Winner.id)) .where(Winner.lottery_id == lottery_id, Winner.is_manual == True) ) # Участники по дням participants_by_date = await session.execute( select( func.date(Participation.created_at).label('date'), func.count(Participation.id).label('count') ) .where(Participation.lottery_id == lottery_id) .group_by(func.date(Participation.created_at)) .order_by(func.date(Participation.created_at)) ) return { 'lottery': lottery, 'participants_count': participants_count, 'winners_count': winners_count, 'manual_winners_count': manual_winners_count, 'random_winners_count': winners_count - manual_winners_count, 'participants_by_date': participants_by_date.fetchall() } @staticmethod async def export_lottery_data(session: AsyncSession, lottery_id: int) -> Dict: """Экспорт данных розыгрыша""" lottery = await session.get(Lottery, lottery_id) if not lottery: return {} # Участники participants = await session.execute( select(User, Participation) .join(Participation) .where(Participation.lottery_id == lottery_id) .order_by(Participation.created_at) ) participants_data = [] for user, participation in participants: participants_data.append({ 'telegram_id': user.telegram_id, 'username': user.username, 'first_name': user.first_name, 'last_name': user.last_name, 'joined_at': participation.created_at.isoformat() }) # Победители winners = await session.execute( select(Winner, User) .join(User) .where(Winner.lottery_id == lottery_id) .order_by(Winner.place) ) winners_data = [] for winner, user in winners: winners_data.append({ 'place': winner.place, 'telegram_id': user.telegram_id, 'username': user.username, 'first_name': user.first_name, 'prize': winner.prize, 'is_manual': winner.is_manual, 'won_at': winner.created_at.isoformat() }) return { 'lottery': { 'id': lottery.id, 'title': lottery.title, 'description': lottery.description, 'created_at': lottery.created_at.isoformat(), 'is_completed': lottery.is_completed, 'prizes': lottery.prizes, 'manual_winners': lottery.manual_winners }, 'participants': participants_data, 'winners': winners_data, 'export_date': datetime.now().isoformat() } @staticmethod async def bulk_add_participants( session: AsyncSession, lottery_id: int, telegram_ids: List[int] ) -> Dict[str, int]: """Массовое добавление участников""" added = 0 skipped = 0 errors = [] for telegram_id in telegram_ids: try: # Проверяем, есть ли пользователь user = await session.execute( select(User).where(User.telegram_id == telegram_id) ) user = user.scalar_one_or_none() if not user: errors.append(f"Пользователь {telegram_id} не найден") continue # Проверяем, не участвует ли уже existing = await session.execute( select(Participation).where( Participation.lottery_id == lottery_id, Participation.user_id == user.id ) ) if existing.scalar_one_or_none(): skipped += 1 continue # Добавляем участника participation = Participation( lottery_id=lottery_id, user_id=user.id ) session.add(participation) added += 1 except Exception as e: errors.append(f"Ошибка с {telegram_id}: {str(e)}") if added > 0: await session.commit() return { 'added': added, 'skipped': skipped, 'errors': errors } @staticmethod async def remove_participant( session: AsyncSession, lottery_id: int, telegram_id: int ) -> bool: """Удалить участника из розыгрыша""" user = await session.execute( select(User).where(User.telegram_id == telegram_id) ) user = user.scalar_one_or_none() if not user: return False result = await session.execute( delete(Participation).where( Participation.lottery_id == lottery_id, Participation.user_id == user.id ) ) await session.commit() return result.rowcount > 0 @staticmethod async def update_lottery( session: AsyncSession, lottery_id: int, **updates ) -> bool: """Обновить данные розыгрыша""" try: await session.execute( update(Lottery) .where(Lottery.id == lottery_id) .values(**updates) ) await session.commit() return True except Exception: return False @staticmethod async def delete_lottery(session: AsyncSession, lottery_id: int) -> bool: """Удалить розыгрыш (со всеми связанными данными)""" try: # Удаляем победителей await session.execute( delete(Winner).where(Winner.lottery_id == lottery_id) ) # Удаляем участников await session.execute( delete(Participation).where(Participation.lottery_id == lottery_id) ) # Удаляем сам розыгрыш await session.execute( delete(Lottery).where(Lottery.id == lottery_id) ) await session.commit() return True except Exception: await session.rollback() return False @staticmethod async def get_user_activity( session: AsyncSession, telegram_id: int ) -> Dict: """Получить активность пользователя""" user = await session.execute( select(User).where(User.telegram_id == telegram_id) ) user = user.scalar_one_or_none() if not user: return {} # Участия participations = await session.execute( select(Participation, Lottery) .join(Lottery) .where(Participation.user_id == user.id) .order_by(Participation.created_at.desc()) ) # Выигрыши wins = await session.execute( select(Winner, Lottery) .join(Lottery) .where(Winner.user_id == user.id) .order_by(Winner.created_at.desc()) ) participations_data = [] for participation, lottery in participations: participations_data.append({ 'lottery_title': lottery.title, 'lottery_id': lottery.id, 'joined_at': participation.created_at, 'lottery_completed': lottery.is_completed }) wins_data = [] for win, lottery in wins: wins_data.append({ 'lottery_title': lottery.title, 'lottery_id': lottery.id, 'place': win.place, 'prize': win.prize, 'is_manual': win.is_manual, 'won_at': win.created_at }) return { 'user': user, 'total_participations': len(participations_data), 'total_wins': len(wins_data), 'participations': participations_data, 'wins': wins_data } @staticmethod async def cleanup_old_data(session: AsyncSession, days: int = 30) -> Dict[str, int]: """Очистка старых данных""" from datetime import datetime, timedelta cutoff_date = datetime.now() - timedelta(days=days) # Удаляем старые завершенные розыгрыши old_lotteries = await session.execute( select(Lottery.id) .where( Lottery.is_completed == True, Lottery.created_at < cutoff_date ) ) lottery_ids = [row[0] for row in old_lotteries.fetchall()] deleted_winners = 0 deleted_participations = 0 deleted_lotteries = 0 for lottery_id in lottery_ids: # Удаляем победителей result = await session.execute( delete(Winner).where(Winner.lottery_id == lottery_id) ) deleted_winners += result.rowcount # Удаляем участников result = await session.execute( delete(Participation).where(Participation.lottery_id == lottery_id) ) deleted_participations += result.rowcount # Удаляем розыгрыш result = await session.execute( delete(Lottery).where(Lottery.id == lottery_id) ) deleted_lotteries += result.rowcount await session.commit() return { 'deleted_lotteries': deleted_lotteries, 'deleted_participations': deleted_participations, 'deleted_winners': deleted_winners, 'cutoff_date': cutoff_date.isoformat() } class ReportGenerator: """Генератор отчетов""" @staticmethod async def generate_summary_report(session: AsyncSession) -> str: """Генерация сводного отчета""" # Общая статистика total_users = await session.scalar(select(func.count(User.id))) total_lotteries = await session.scalar(select(func.count(Lottery.id))) active_lotteries = await session.scalar( select(func.count(Lottery.id)) .where(Lottery.is_active == True, Lottery.is_completed == False) ) completed_lotteries = await session.scalar( select(func.count(Lottery.id)).where(Lottery.is_completed == True) ) total_participations = await session.scalar(select(func.count(Participation.id))) total_winners = await session.scalar(select(func.count(Winner.id))) # Топ розыгрыши по участникам top_lotteries = await session.execute( select( Lottery.title, Lottery.created_at, func.count(Participation.id).label('participants') ) .join(Participation, isouter=True) .group_by(Lottery.id) .order_by(func.count(Participation.id).desc()) .limit(5) ) # Топ активные пользователи top_users = await session.execute( select( User.first_name, User.username, func.count(Participation.id).label('participations'), func.count(Winner.id).label('wins') ) .join(Participation, isouter=True) .join(Winner, isouter=True) .group_by(User.id) .order_by(func.count(Participation.id).desc()) .limit(5) ) report = f"📊 СВОДНЫЙ ОТЧЕТ\n" report += f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n\n" report += f"📈 ОБЩАЯ СТАТИСТИКА\n" report += f"👥 Пользователей: {total_users}\n" report += f"🎲 Всего розыгрышей: {total_lotteries}\n" report += f"🟢 Активных: {active_lotteries}\n" report += f"✅ Завершенных: {completed_lotteries}\n" report += f"🎫 Всего участий: {total_participations}\n" report += f"🏆 Всего победителей: {total_winners}\n\n" if total_lotteries > 0: avg_participation = total_participations / total_lotteries report += f"📊 Среднее участие на розыгрыш: {avg_participation:.1f}\n\n" report += f"🏆 ТОП РОЗЫГРЫШИ ПО УЧАСТНИКАМ\n" for i, (title, created_at, participants) in enumerate(top_lotteries.fetchall(), 1): report += f"{i}. {title}\n" report += f" Участников: {participants} | {created_at.strftime('%d.%m.%Y')}\n\n" report += f"🔥 ТОП АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ\n" for i, (first_name, username, participations, wins) in enumerate(top_users.fetchall(), 1): name = f"@{username}" if username else first_name report += f"{i}. {name}\n" report += f" Участий: {participations} | Побед: {wins}\n\n" return report