""" Расширенная админ-панель для управления розыгрышами """ from aiogram import Router, F from aiogram.types import ( CallbackQuery, Message, InlineKeyboardButton, InlineKeyboardMarkup ) from aiogram.filters import StateFilter from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from sqlalchemy.ext.asyncio import AsyncSession from datetime import datetime, timedelta import json from database import async_session_maker from services import UserService, LotteryService, ParticipationService from config import ADMIN_IDS from models import User # Состояния для админки class AdminStates(StatesGroup): # Создание розыгрыша lottery_title = State() lottery_description = State() lottery_prizes = State() lottery_confirm = State() # Управление участниками add_participant_lottery = State() add_participant_user = State() add_participant_bulk = State() add_participant_bulk_accounts = State() remove_participant_lottery = State() remove_participant_user = State() remove_participant_bulk = State() remove_participant_bulk_accounts = State() participant_search = State() # Установка победителей set_winner_lottery = State() set_winner_place = State() set_winner_user = State() # Редактирование розыгрыша edit_lottery_select = State() edit_lottery_field = State() edit_lottery_value = State() # Настройки отображения победителей lottery_display_type_select = State() lottery_display_type_set = State() admin_router = Router() def is_admin(user_id: int) -> bool: """Проверка прав администратора""" return user_id in ADMIN_IDS def get_admin_main_keyboard() -> InlineKeyboardMarkup: """Главная админ-панель""" buttons = [ [InlineKeyboardButton(text="🎲 Управление розыгрышами", callback_data="admin_lotteries")], [InlineKeyboardButton(text="👥 Управление участниками", callback_data="admin_participants")], [InlineKeyboardButton(text="👑 Управление победителями", callback_data="admin_winners")], [InlineKeyboardButton(text="📊 Статистика", callback_data="admin_stats")], [InlineKeyboardButton(text="⚙️ Настройки", callback_data="admin_settings")], [InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_main")] ] return InlineKeyboardMarkup(inline_keyboard=buttons) def get_lottery_management_keyboard() -> InlineKeyboardMarkup: """Клавиатура управления розыгрышами""" buttons = [ [InlineKeyboardButton(text="➕ Создать розыгрыш", callback_data="admin_create_lottery")], [InlineKeyboardButton(text="📝 Редактировать розыгрыш", callback_data="admin_edit_lottery")], [InlineKeyboardButton(text="🎭 Настройка отображения победителей", callback_data="admin_winner_display_settings")], [InlineKeyboardButton(text="📋 Список всех розыгрышей", callback_data="admin_list_all_lotteries")], [InlineKeyboardButton(text="🏁 Завершить розыгрыш", callback_data="admin_finish_lottery")], [InlineKeyboardButton(text="🗑️ Удалить розыгрыш", callback_data="admin_delete_lottery")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")] ] return InlineKeyboardMarkup(inline_keyboard=buttons) def get_participant_management_keyboard() -> InlineKeyboardMarkup: """Клавиатура управления участниками""" buttons = [ [InlineKeyboardButton(text="➕ Добавить участника", callback_data="admin_add_participant")], [ InlineKeyboardButton(text="📥 Массовое добавление (ID)", callback_data="admin_bulk_add_participant"), InlineKeyboardButton(text="🏦 Массовое добавление (счета)", callback_data="admin_bulk_add_accounts") ], [InlineKeyboardButton(text="➖ Удалить участника", callback_data="admin_remove_participant")], [ InlineKeyboardButton(text="📤 Массовое удаление (ID)", callback_data="admin_bulk_remove_participant"), InlineKeyboardButton(text="🏦 Массовое удаление (счета)", callback_data="admin_bulk_remove_accounts") ], [InlineKeyboardButton(text="👥 Все участники", callback_data="admin_list_all_participants")], [InlineKeyboardButton(text="🔍 Поиск участников", callback_data="admin_search_participants")], [InlineKeyboardButton(text="📊 Участники по розыгрышам", callback_data="admin_participants_by_lottery")], [InlineKeyboardButton(text="📈 Отчет по участникам", callback_data="admin_participants_report")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")] ] return InlineKeyboardMarkup(inline_keyboard=buttons) def get_winner_management_keyboard() -> InlineKeyboardMarkup: """Клавиатура управления победителями""" buttons = [ [InlineKeyboardButton(text="👑 Установить победителя", callback_data="admin_set_manual_winner")], [InlineKeyboardButton(text="📝 Изменить победителя", callback_data="admin_edit_winner")], [InlineKeyboardButton(text="❌ Удалить победителя", callback_data="admin_remove_winner")], [InlineKeyboardButton(text="📋 Список победителей", callback_data="admin_list_winners")], [InlineKeyboardButton(text="🎲 Провести розыгрыш", callback_data="admin_conduct_draw")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")] ] return InlineKeyboardMarkup(inline_keyboard=buttons) @admin_router.callback_query(F.data == "admin_panel") async def show_admin_panel(callback: CallbackQuery): """Показать админ-панель""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: # Быстрая статистика from sqlalchemy import select, func from models import User, Lottery, Participation users_count = await session.scalar(select(func.count(User.id))) lotteries_count = 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) ) total_participations = await session.scalar(select(func.count(Participation.id))) text = f"🔧 Админ-панель\n\n" text += f"📊 Быстрая статистика:\n" text += f"👥 Пользователей: {users_count}\n" text += f"🎲 Всего розыгрышей: {lotteries_count}\n" text += f"🟢 Активных: {active_lotteries}\n" text += f"🎫 Участий: {total_participations}\n\n" text += "Выберите раздел для управления:" await callback.message.edit_text(text, reply_markup=get_admin_main_keyboard()) # ====================== # УПРАВЛЕНИЕ РОЗЫГРЫШАМИ # ====================== @admin_router.callback_query(F.data == "admin_lotteries") async def show_lottery_management(callback: CallbackQuery): """Управление розыгрышами""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return text = "🎲 Управление розыгрышами\n\n" text += "Здесь вы можете создавать, редактировать и управлять розыгрышами.\n\n" text += "Выберите действие:" await callback.message.edit_text(text, reply_markup=get_lottery_management_keyboard()) @admin_router.callback_query(F.data == "admin_create_lottery") async def start_create_lottery(callback: CallbackQuery, state: FSMContext): """Начать создание розыгрыша""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return text = "📝 Создание нового розыгрыша\n\n" text += "Шаг 1 из 4\n\n" text += "Введите название розыгрыша:" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_lotteries")] ]) ) await state.set_state(AdminStates.lottery_title) @admin_router.message(StateFilter(AdminStates.lottery_title)) async def process_lottery_title(message: Message, state: FSMContext): """Обработка названия розыгрыша""" if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return await state.update_data(title=message.text) text = f"📝 Создание нового розыгрыша\n\n" text += f"Шаг 2 из 4\n\n" text += f"✅ Название: {message.text}\n\n" text += f"Введите описание розыгрыша (или '-' для пропуска):" await message.answer(text) await state.set_state(AdminStates.lottery_description) @admin_router.message(StateFilter(AdminStates.lottery_description)) async def process_lottery_description(message: Message, state: FSMContext): """Обработка описания розыгрыша""" if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return description = None if message.text == "-" else message.text await state.update_data(description=description) data = await state.get_data() text = f"📝 Создание нового розыгрыша\n\n" text += f"Шаг 3 из 4\n\n" text += f"✅ Название: {data['title']}\n" text += f"✅ Описание: {description or 'Не указано'}\n\n" text += f"Введите призы (каждый с новой строки):\n\n" text += f"Пример:\n" text += f"🥇 iPhone 15 Pro\n" text += f"🥈 MacBook Air\n" text += f"🥉 AirPods Pro\n" text += f"🏆 10,000 рублей" await message.answer(text) await state.set_state(AdminStates.lottery_prizes) @admin_router.message(StateFilter(AdminStates.lottery_prizes)) async def process_lottery_prizes(message: Message, state: FSMContext): """Обработка призов розыгрыша""" if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return prizes = [prize.strip() for prize in message.text.split('\n') if prize.strip()] await state.update_data(prizes=prizes) data = await state.get_data() text = f"📝 Создание нового розыгрыша\n\n" text += f"Шаг 4 из 4 - Подтверждение\n\n" text += f"🎯 Название: {data['title']}\n" text += f"📋 Описание: {data['description'] or 'Не указано'}\n\n" text += f"🏆 Призы:\n" for i, prize in enumerate(prizes, 1): text += f"{i}. {prize}\n" text += f"\n✅ Подтвердите создание розыгрыша:" await message.answer( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="✅ Создать", callback_data="confirm_create_lottery")], [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_lotteries")] ]) ) await state.set_state(AdminStates.lottery_confirm) @admin_router.callback_query(F.data == "confirm_create_lottery", StateFilter(AdminStates.lottery_confirm)) async def confirm_create_lottery(callback: CallbackQuery, state: FSMContext): """Подтверждение создания розыгрыша""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return data = await state.get_data() async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, callback.from_user.id) lottery = await LotteryService.create_lottery( session, title=data['title'], description=data['description'], prizes=data['prizes'], creator_id=user.id ) await state.clear() text = f"✅ Розыгрыш успешно создан!\n\n" text += f"🆔 ID: {lottery.id}\n" text += f"🎯 Название: {lottery.title}\n" text += f"📅 Создан: {lottery.created_at.strftime('%d.%m.%Y %H:%M')}\n\n" text += f"Розыгрыш доступен для участников." await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🎲 К управлению розыгрышами", callback_data="admin_lotteries")], [InlineKeyboardButton(text="🏠 В главное меню", callback_data="back_to_main")] ]) ) @admin_router.callback_query(F.data == "admin_list_all_lotteries") async def list_all_lotteries(callback: CallbackQuery): """Список всех розыгрышей""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: from sqlalchemy import select from models import Lottery result = await session.execute( select(Lottery).order_by(Lottery.created_at.desc()) ) lotteries = result.scalars().all() if not lotteries: text = "📋 Розыгрышей пока нет" buttons = [[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")]] else: text = f"📋 Все розыгрыши ({len(lotteries)}):\n\n" buttons = [] for lottery in lotteries[:10]: # Показываем первые 10 status = "🟢" if lottery.is_active and not lottery.is_completed else "✅" if lottery.is_completed else "🔴" async with async_session_maker() as session: participants_count = await ParticipationService.get_participants_count( session, lottery.id ) text += f"{status} {lottery.title}\n" text += f" ID: {lottery.id} | Участников: {participants_count}\n" text += f" Создан: {lottery.created_at.strftime('%d.%m %H:%M')}\n\n" buttons.append([ InlineKeyboardButton( text=f"📝 {lottery.title[:25]}..." if len(lottery.title) > 25 else lottery.title, callback_data=f"admin_lottery_detail_{lottery.id}" ) ]) if len(lotteries) > 10: text += f"... и еще {len(lotteries) - 10} розыгрышей" buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_lottery_detail_")) async def show_lottery_detail(callback: CallbackQuery): """Детальная информация о розыгрыше""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) if not lottery: await callback.answer("Розыгрыш не найден", show_alert=True) return participants_count = await ParticipationService.get_participants_count(session, lottery_id) winners = await LotteryService.get_winners(session, lottery_id) if lottery.is_completed else [] status_emoji = "🟢" if lottery.is_active and not lottery.is_completed else "✅" if lottery.is_completed else "🔴" status_text = "Активен" if lottery.is_active and not lottery.is_completed else "Завершен" if lottery.is_completed else "Неактивен" text = f"🎲 Детали розыгрыша\n\n" text += f"🆔 ID: {lottery.id}\n" text += f"🎯 Название: {lottery.title}\n" text += f"📋 Описание: {lottery.description or 'Не указано'}\n" text += f"{status_emoji} Статус: {status_text}\n" text += f"👥 Участников: {participants_count}\n" text += f"📅 Создан: {lottery.created_at.strftime('%d.%m.%Y %H:%M')}\n\n" if lottery.prizes: text += f"🏆 Призы:\n" for i, prize in enumerate(lottery.prizes, 1): text += f"{i}. {prize}\n" text += "\n" # Ручные победители if lottery.manual_winners: text += f"👑 Предустановленные победители:\n" for place, telegram_id in lottery.manual_winners.items(): async with async_session_maker() as session: winner_user = await UserService.get_user_by_telegram_id(session, telegram_id) name = winner_user.username if winner_user and winner_user.username else str(telegram_id) text += f"{place} место: @{name}\n" text += "\n" # Результаты розыгрыша if lottery.is_completed and winners: text += f"🏆 Результаты:\n" for winner in winners: manual_mark = " 👑" if winner.is_manual else "" username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name text += f"{winner.place}. {username}{manual_mark}\n" buttons = [] if not lottery.is_completed: buttons.extend([ [InlineKeyboardButton(text="👑 Установить победителя", callback_data=f"admin_set_winner_{lottery_id}")], [InlineKeyboardButton(text="🎲 Провести розыгрыш", callback_data=f"admin_conduct_{lottery_id}")], ]) buttons.extend([ [InlineKeyboardButton(text="📝 Редактировать", callback_data=f"admin_edit_{lottery_id}")], [InlineKeyboardButton(text="👥 Участники", callback_data=f"admin_participants_{lottery_id}")], [InlineKeyboardButton(text="🔙 К списку", callback_data="admin_list_all_lotteries")] ]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) # ====================== # УПРАВЛЕНИЕ УЧАСТНИКАМИ # ====================== @admin_router.callback_query(F.data == "admin_participants") async def show_participant_management(callback: CallbackQuery): """Управление участниками""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return text = "👥 Управление участниками\n\n" text += "Здесь вы можете добавлять и удалять участников розыгрышей.\n\n" text += "Выберите действие:" await callback.message.edit_text(text, reply_markup=get_participant_management_keyboard()) @admin_router.callback_query(F.data.startswith("admin_participants_")) async def show_lottery_participants(callback: CallbackQuery): """Показать участников конкретного розыгрыша""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) if not lottery: await callback.answer("Розыгрыш не найден", show_alert=True) return text = f"👥 Участники розыгрыша\n" text += f"🎯 {lottery.title}\n\n" if not lottery.participations: text += "Участников пока нет" buttons = [[InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_lottery_detail_{lottery_id}")]] else: text += f"Всего участников: {len(lottery.participations)}\n\n" for i, participation in enumerate(lottery.participations[:20], 1): # Показываем первых 20 user = participation.user username = f"@{user.username}" if user.username else "Нет username" text += f"{i}. {user.first_name} {user.last_name or ''}\n" text += f" {username} | ID: {user.telegram_id}\n" text += f" Участвует с: {participation.created_at.strftime('%d.%m %H:%M')}\n\n" if len(lottery.participations) > 20: text += f"... и еще {len(lottery.participations) - 20} участников" buttons = [ [InlineKeyboardButton(text="➕ Добавить участника", callback_data=f"admin_add_to_{lottery_id}")], [InlineKeyboardButton(text="➖ Удалить участника", callback_data=f"admin_remove_from_{lottery_id}")], [InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_lottery_detail_{lottery_id}")] ] await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) # ====================== # НОВЫЕ ХЭНДЛЕРЫ ДЛЯ УПРАВЛЕНИЯ УЧАСТНИКАМИ # ====================== @admin_router.callback_query(F.data == "admin_add_participant") async def start_add_participant(callback: CallbackQuery, state: FSMContext): """Начать добавление участника""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: lotteries = await LotteryService.get_active_lotteries(session) if not lotteries: await callback.message.edit_text( "❌ Нет активных розыгрышей", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ]) ) return text = "➕ Добавление участника\n\n" text += "Выберите розыгрыш:\n\n" buttons = [] for lottery in lotteries: async with async_session_maker() as session: count = await ParticipationService.get_participants_count(session, lottery.id) text += f"🎯 {lottery.title} (участников: {count})\n" buttons.append([ InlineKeyboardButton( text=f"🎯 {lottery.title[:35]}...", callback_data=f"admin_add_part_to_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_add_part_to_")) async def choose_user_to_add(callback: CallbackQuery, state: FSMContext): """Выбор пользователя для добавления""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) await state.update_data(add_participant_lottery_id=lottery_id) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) text = f"➕ Добавление в: {lottery.title}\n\n" text += "Введите Telegram ID или username пользователя:\n\n" text += "Примеры:\n" text += "• @username\n" text += "• 123456789" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_add_participant")] ]) ) await state.set_state(AdminStates.add_participant_user) @admin_router.message(StateFilter(AdminStates.add_participant_user)) async def process_add_participant(message: Message, state: FSMContext): """Обработка добавления участника""" if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return data = await state.get_data() lottery_id = data['add_participant_lottery_id'] user_input = message.text.strip() async with async_session_maker() as session: # Ищем пользователя user = None if user_input.startswith('@'): username = user_input[1:] user = await UserService.get_user_by_username(session, username) elif user_input.isdigit(): telegram_id = int(user_input) user = await UserService.get_user_by_telegram_id(session, telegram_id) if not user: await message.answer( "❌ Пользователь не найден в системе.\n" "Пользователь должен сначала запустить бота командой /start" ) return # Добавляем участника success = await ParticipationService.add_participant(session, lottery_id, user.id) lottery = await LotteryService.get_lottery(session, lottery_id) await state.clear() if success: username = f"@{user.username}" if user.username else "Нет username" await message.answer( f"✅ Участник добавлен!\n\n" f"👤 Пользователь: {user.first_name} {user.last_name or ''}\n" f"📱 Username: {username}\n" f"🆔 ID: {user.telegram_id}\n" f"🎯 Розыгрыш: {lottery.title}", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")] ]) ) else: await message.answer( f"⚠️ Пользователь {user.first_name} уже участвует в этом розыгрыше", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")] ]) ) @admin_router.callback_query(F.data == "admin_list_all_participants") async def list_all_participants(callback: CallbackQuery): """Список всех участников""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: users = await UserService.get_all_users(session, limit=50) # Получаем статистику для каждого пользователя user_stats = [] for user in users: stats = await ParticipationService.get_participant_stats(session, user.id) user_stats.append((user, stats)) if not user_stats: await callback.message.edit_text( "❌ В системе нет пользователей", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ]) ) return text = "👥 Все участники системы\n\n" text += f"Всего пользователей: {len(users)}\n\n" for i, (user, stats) in enumerate(user_stats[:20], 1): username = f"@{user.username}" if user.username else "Нет username" text += f"{i}. {user.first_name} {user.last_name or ''}\n" text += f" {username} | ID: {user.telegram_id}\n" text += f" 🎫 Участий: {stats['participations_count']} | 🏆 Побед: {stats['wins_count']}\n" if stats['last_participation']: text += f" 📅 Последнее участие: {stats['last_participation'].strftime('%d.%m.%Y')}\n" text += "\n" if len(users) > 20: text += f"... и еще {len(users) - 20} пользователей" buttons = [ [InlineKeyboardButton(text="📊 Подробный отчет", callback_data="admin_participants_report")], [InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_list_all_participants")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ] await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data == "admin_participants_report") async def generate_participants_report(callback: CallbackQuery): """Генерация отчета по участникам""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: from sqlalchemy import func, select from models import User, Participation, Winner # Общие статистики total_users = await session.scalar(select(func.count(User.id))) total_participations = await session.scalar(select(func.count(Participation.id))) total_winners = await session.scalar(select(func.count(Winner.id))) # Топ участников по количеству участий top_participants = await session.execute( select(User.first_name, User.username, func.count(Participation.id).label('count')) .join(Participation) .group_by(User.id) .order_by(func.count(Participation.id).desc()) .limit(10) ) top_participants = top_participants.fetchall() # Топ победителей top_winners = await session.execute( select(User.first_name, User.username, func.count(Winner.id).label('wins')) .join(Winner) .group_by(User.id) .order_by(func.count(Winner.id).desc()) .limit(5) ) top_winners = top_winners.fetchall() # Недавняя активность recent_users = await session.execute( select(User.first_name, User.username, User.created_at) .order_by(User.created_at.desc()) .limit(5) ) recent_users = recent_users.fetchall() text = "📈 Подробный отчет по участникам\n\n" text += "📊 ОБЩАЯ СТАТИСТИКА\n" text += f"👥 Всего пользователей: {total_users}\n" text += f"🎫 Всего участий: {total_participations}\n" text += f"🏆 Всего побед: {total_winners}\n" if total_users > 0: avg_participations = total_participations / total_users text += f"📈 Среднее участий на пользователя: {avg_participations:.1f}\n" text += "\n" if top_participants: text += "🔥 ТОП УЧАСТНИКИ (по количеству участий)\n" for i, (first_name, username, count) in enumerate(top_participants, 1): name = f"@{username}" if username else first_name text += f"{i}. {name} - {count} участий\n" text += "\n" if top_winners: text += "👑 ТОП ПОБЕДИТЕЛИ\n" for i, (first_name, username, wins) in enumerate(top_winners, 1): name = f"@{username}" if username else first_name text += f"{i}. {name} - {wins} побед\n" text += "\n" if recent_users: text += "🆕 НЕДАВНИЕ РЕГИСТРАЦИИ\n" for first_name, username, created_at in recent_users: name = f"@{username}" if username else first_name text += f"• {name} - {created_at.strftime('%d.%m.%Y %H:%M')}\n" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="💾 Экспорт данных", callback_data="admin_export_participants")], [InlineKeyboardButton(text="🔄 Обновить отчет", callback_data="admin_participants_report")], [InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")] ]) ) @admin_router.callback_query(F.data == "admin_export_participants") async def export_participants_data(callback: CallbackQuery): """Экспорт данных участников""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return await callback.answer("📊 Генерируем отчет...", show_alert=False) async with async_session_maker() as session: users = await UserService.get_all_users(session) export_data = { "timestamp": datetime.now().isoformat(), "total_users": len(users), "users": [] } for user in users: stats = await ParticipationService.get_participant_stats(session, user.id) user_data = { "id": user.id, "telegram_id": user.telegram_id, "first_name": user.first_name, "last_name": user.last_name, "username": user.username, "created_at": user.created_at.isoformat() if user.created_at else None, "participations_count": stats["participations_count"], "wins_count": stats["wins_count"], "last_participation": stats["last_participation"].isoformat() if stats["last_participation"] else None } export_data["users"].append(user_data) # Формируем JSON для вывода import json json_data = json.dumps(export_data, ensure_ascii=False, indent=2) # Отправляем JSON как текст (в реальном боте можно отправить как файл) text = f"📊 Экспорт данных участников\n\n" text += f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n" text += f"Всего пользователей: {len(users)}\n\n" text += "Данные готовы к экспорту (JSON формат)\n" text += f"Размер: {len(json_data)} символов" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="📈 К отчету", callback_data="admin_participants_report")], [InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")] ]) ) @admin_router.callback_query(F.data == "admin_search_participants") async def start_search_participants(callback: CallbackQuery, state: FSMContext): """Начать поиск участников""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return text = "🔍 Поиск участников\n\n" text += "Введите имя, фамилию или username для поиска:\n\n" text += "Примеры:\n" text += "• Иван\n" text += "• username\n" text += "• Петров" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_participants")] ]) ) await state.set_state(AdminStates.participant_search) @admin_router.message(StateFilter(AdminStates.participant_search)) async def process_search_participants(message: Message, state: FSMContext): """Обработка поиска участников""" if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return search_term = message.text.strip() async with async_session_maker() as session: users = await UserService.search_users(session, search_term) # Получаем статистику для найденных пользователей user_stats = [] for user in users: stats = await ParticipationService.get_participant_stats(session, user.id) user_stats.append((user, stats)) await state.clear() if not user_stats: await message.answer( f"❌ Пользователи с поисковым запросом '{search_term}' не найдены", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")] ]) ) return text = f"🔍 Результаты поиска: '{search_term}'\n\n" text += f"Найдено: {len(users)} пользователей\n\n" for i, (user, stats) in enumerate(user_stats[:15], 1): username = f"@{user.username}" if user.username else "Нет username" text += f"{i}. {user.first_name} {user.last_name or ''}\n" text += f" {username} | ID: {user.telegram_id}\n" text += f" 🎫 Участий: {stats['participations_count']} | 🏆 Побед: {stats['wins_count']}\n" text += "\n" if len(users) > 15: text += f"... и еще {len(users) - 15} найденных пользователей" await message.answer( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔍 Новый поиск", callback_data="admin_search_participants")], [InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")] ]) ) @admin_router.callback_query(F.data == "admin_bulk_add_participant") async def start_bulk_add_participant(callback: CallbackQuery, state: FSMContext): """Начать массовое добавление участников""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: lotteries = await LotteryService.get_active_lotteries(session) if not lotteries: await callback.message.edit_text( "❌ Нет активных розыгрышей", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ]) ) return text = "📥 Массовое добавление участников\n\n" text += "Выберите розыгрыш:\n\n" buttons = [] for lottery in lotteries: async with async_session_maker() as session: count = await ParticipationService.get_participants_count(session, lottery.id) text += f"🎯 {lottery.title} (участников: {count})\n" buttons.append([ InlineKeyboardButton( text=f"🎯 {lottery.title[:35]}...", callback_data=f"admin_bulk_add_to_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_bulk_add_to_")) async def choose_users_bulk_add(callback: CallbackQuery, state: FSMContext): """Выбор пользователей для массового добавления""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) await state.update_data(bulk_add_lottery_id=lottery_id) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) text = f"📥 Массовое добавление в: {lottery.title}\n\n" text += "Введите список Telegram ID или username через запятую:\n\n" text += "Примеры:\n" text += "• @user1, @user2, @user3\n" text += "• 123456789, 987654321, 555444333\n" text += "• @user1, 123456789, @user3" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_bulk_add_participant")] ]) ) await state.set_state(AdminStates.add_participant_bulk) @admin_router.message(StateFilter(AdminStates.add_participant_bulk)) async def process_bulk_add_participant(message: Message, state: FSMContext): """Обработка массового добавления участников""" if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return data = await state.get_data() lottery_id = data['bulk_add_lottery_id'] # Парсим входные данные user_inputs = [x.strip() for x in message.text.split(',') if x.strip()] telegram_ids = [] async with async_session_maker() as session: for user_input in user_inputs: try: if user_input.startswith('@'): username = user_input[1:] user = await UserService.get_user_by_username(session, username) if user: telegram_ids.append(user.telegram_id) elif user_input.isdigit(): telegram_ids.append(int(user_input)) except: continue # Массовое добавление results = await ParticipationService.add_participants_bulk(session, lottery_id, telegram_ids) lottery = await LotteryService.get_lottery(session, lottery_id) await state.clear() text = f"📥 Результат массового добавления\n\n" text += f"🎯 Розыгрыш: {lottery.title}\n\n" text += f"✅ Добавлено: {results['added']}\n" text += f"⚠️ Уже участвуют: {results['skipped']}\n" text += f"❌ Ошибок: {len(results['errors'])}\n\n" if results['details']: text += "Детали:\n" for detail in results['details'][:10]: # Первые 10 text += f"• {detail}\n" if len(results['details']) > 10: text += f"... и еще {len(results['details']) - 10} записей" await message.answer( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")] ]) ) @admin_router.callback_query(F.data == "admin_bulk_remove_participant") async def start_bulk_remove_participant(callback: CallbackQuery, state: FSMContext): """Начать массовое удаление участников""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: lotteries = await LotteryService.get_all_lotteries(session) if not lotteries: await callback.message.edit_text( "❌ Нет розыгрышей", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ]) ) return text = "📤 Массовое удаление участников\n\n" text += "Выберите розыгрыш:\n\n" buttons = [] for lottery in lotteries: async with async_session_maker() as session: count = await ParticipationService.get_participants_count(session, lottery.id) if count > 0: text += f"🎯 {lottery.title} (участников: {count})\n" buttons.append([ InlineKeyboardButton( text=f"🎯 {lottery.title[:35]}...", callback_data=f"admin_bulk_remove_from_{lottery.id}" ) ]) if not buttons: await callback.message.edit_text( "❌ Нет розыгрышей с участниками", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ]) ) return buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_bulk_remove_from_")) async def choose_users_bulk_remove(callback: CallbackQuery, state: FSMContext): """Выбор пользователей для массового удаления""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) await state.update_data(bulk_remove_lottery_id=lottery_id) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) text = f"📤 Массовое удаление из: {lottery.title}\n\n" text += "Введите список Telegram ID или username через запятую:\n\n" text += "Примеры:\n" text += "• @user1, @user2, @user3\n" text += "• 123456789, 987654321, 555444333\n" text += "• @user1, 123456789, @user3" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_bulk_remove_participant")] ]) ) await state.set_state(AdminStates.remove_participant_bulk) @admin_router.message(StateFilter(AdminStates.remove_participant_bulk)) async def process_bulk_remove_participant(message: Message, state: FSMContext): """Обработка массового удаления участников""" if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return data = await state.get_data() lottery_id = data['bulk_remove_lottery_id'] # Парсим входные данные user_inputs = [x.strip() for x in message.text.split(',') if x.strip()] telegram_ids = [] async with async_session_maker() as session: for user_input in user_inputs: try: if user_input.startswith('@'): username = user_input[1:] user = await UserService.get_user_by_username(session, username) if user: telegram_ids.append(user.telegram_id) elif user_input.isdigit(): telegram_ids.append(int(user_input)) except: continue # Массовое удаление results = await ParticipationService.remove_participants_bulk(session, lottery_id, telegram_ids) lottery = await LotteryService.get_lottery(session, lottery_id) await state.clear() text = f"📤 Результат массового удаления\n\n" text += f"🎯 Розыгрыш: {lottery.title}\n\n" text += f"✅ Удалено: {results['removed']}\n" text += f"⚠️ Не найдено: {results['not_found']}\n" text += f"❌ Ошибок: {len(results['errors'])}\n\n" if results['details']: text += "Детали:\n" for detail in results['details'][:10]: # Первые 10 text += f"• {detail}\n" if len(results['details']) > 10: text += f"... и еще {len(results['details']) - 10} записей" await message.answer( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")] ]) ) # ====================== # МАССОВОЕ УПРАВЛЕНИЕ УЧАСТНИКАМИ ПО СЧЕТАМ # ====================== @admin_router.callback_query(F.data == "admin_bulk_add_accounts") async def start_bulk_add_accounts(callback: CallbackQuery, state: FSMContext): """Начать массовое добавление участников по номерам счетов""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: lotteries = await LotteryService.get_active_lotteries(session) if not lotteries: await callback.message.edit_text( "❌ Нет активных розыгрышей", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ]) ) return text = "🏦 Массовое добавление по номерам счетов\n\n" text += "Выберите розыгрыш:\n\n" buttons = [] for lottery in lotteries: async with async_session_maker() as session: count = await ParticipationService.get_participants_count(session, lottery.id) text += f"🎯 {lottery.title} (участников: {count})\n" buttons.append([ InlineKeyboardButton( text=f"🎯 {lottery.title[:35]}...", callback_data=f"admin_bulk_add_accounts_to_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_bulk_add_accounts_to_")) async def choose_accounts_bulk_add(callback: CallbackQuery, state: FSMContext): """Выбор номеров счетов для массового добавления""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) await state.update_data(bulk_add_accounts_lottery_id=lottery_id) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) text = f"🏦 Массовое добавление в: {lottery.title}\n\n" text += "Введите список номеров счетов через запятую или новую строку:\n\n" text += "Примеры:\n" text += "• 12-34-56-78-90-12-34-56\n" text += "• 98-76-54-32-10-98-76-54, 11-22-33-44-55-66-77-88\n" text += "• 12345678901234567890 (будет отформатирован)\n\n" text += "Формат: XX-XX-XX-XX-XX-XX-XX-XX\n" text += "Всего 8 пар цифр разделенных дефисами" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_bulk_add_accounts")] ]) ) await state.set_state(AdminStates.add_participant_bulk_accounts) @admin_router.message(StateFilter(AdminStates.add_participant_bulk_accounts)) async def process_bulk_add_accounts(message: Message, state: FSMContext): """Обработка массового добавления участников по номерам счетов""" if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return data = await state.get_data() lottery_id = data['bulk_add_accounts_lottery_id'] # Парсим входные данные - поддерживаем и запятые, и переносы строк account_inputs = [] for line in message.text.split('\n'): for account in line.split(','): account = account.strip() if account: account_inputs.append(account) async with async_session_maker() as session: # Массовое добавление по номерам счетов results = await ParticipationService.add_participants_by_accounts_bulk(session, lottery_id, account_inputs) lottery = await LotteryService.get_lottery(session, lottery_id) await state.clear() text = f"🏦 Результат массового добавления по счетам\n\n" text += f"🎯 Розыгрыш: {lottery.title}\n\n" text += f"✅ Добавлено: {results['added']}\n" text += f"⚠️ Уже участвуют: {results['skipped']}\n" text += f"🚫 Неверных форматов: {len(results['invalid_accounts'])}\n" text += f"❌ Ошибок: {len(results['errors'])}\n\n" if results['details']: text += "✅ Успешно добавлены:\n" for detail in results['details'][:7]: # Первые 7 text += f"• {detail}\n" if len(results['details']) > 7: text += f"... и еще {len(results['details']) - 7} записей\n\n" if results['invalid_accounts']: text += "\n🚫 Неверные форматы:\n" for invalid in results['invalid_accounts'][:5]: text += f"• {invalid}\n" if len(results['invalid_accounts']) > 5: text += f"... и еще {len(results['invalid_accounts']) - 5} номеров\n" if results['errors']: text += "\n❌ Ошибки:\n" for error in results['errors'][:3]: text += f"• {error}\n" if len(results['errors']) > 3: text += f"... и еще {len(results['errors']) - 3} ошибок\n" await message.answer( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")] ]) ) @admin_router.callback_query(F.data == "admin_bulk_remove_accounts") async def start_bulk_remove_accounts(callback: CallbackQuery, state: FSMContext): """Начать массовое удаление участников по номерам счетов""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: lotteries = await LotteryService.get_all_lotteries(session) if not lotteries: await callback.message.edit_text( "❌ Нет розыгрышей", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ]) ) return text = "🏦 Массовое удаление по номерам счетов\n\n" text += "Выберите розыгрыш:\n\n" buttons = [] for lottery in lotteries: async with async_session_maker() as session: count = await ParticipationService.get_participants_count(session, lottery.id) text += f"🎯 {lottery.title} (участников: {count})\n" buttons.append([ InlineKeyboardButton( text=f"🎯 {lottery.title[:35]}...", callback_data=f"admin_bulk_remove_accounts_from_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_bulk_remove_accounts_from_")) async def choose_accounts_bulk_remove(callback: CallbackQuery, state: FSMContext): """Выбор номеров счетов для массового удаления""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) await state.update_data(bulk_remove_accounts_lottery_id=lottery_id) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) text = f"🏦 Массовое удаление из: {lottery.title}\n\n" text += "Введите список номеров счетов через запятую или новую строку:\n\n" text += "Примеры:\n" text += "• 12-34-56-78-90-12-34-56\n" text += "• 98-76-54-32-10-98-76-54, 11-22-33-44-55-66-77-88\n" text += "• 12345678901234567890 (будет отформатирован)\n\n" text += "Формат: XX-XX-XX-XX-XX-XX-XX-XX" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_bulk_remove_accounts")] ]) ) await state.set_state(AdminStates.remove_participant_bulk_accounts) @admin_router.message(StateFilter(AdminStates.remove_participant_bulk_accounts)) async def process_bulk_remove_accounts(message: Message, state: FSMContext): """Обработка массового удаления участников по номерам счетов""" if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return data = await state.get_data() lottery_id = data['bulk_remove_accounts_lottery_id'] # Парсим входные данные - поддерживаем и запятые, и переносы строк account_inputs = [] for line in message.text.split('\n'): for account in line.split(','): account = account.strip() if account: account_inputs.append(account) async with async_session_maker() as session: # Массовое удаление по номерам счетов results = await ParticipationService.remove_participants_by_accounts_bulk(session, lottery_id, account_inputs) lottery = await LotteryService.get_lottery(session, lottery_id) await state.clear() text = f"🏦 Результат массового удаления по счетам\n\n" text += f"🎯 Розыгрыш: {lottery.title}\n\n" text += f"✅ Удалено: {results['removed']}\n" text += f"⚠️ Не найдено: {results['not_found']}\n" text += f"🚫 Неверных форматов: {len(results['invalid_accounts'])}\n" text += f"❌ Ошибок: {len(results['errors'])}\n\n" if results['details']: text += "✅ Успешно удалены:\n" for detail in results['details'][:7]: # Первые 7 text += f"• {detail}\n" if len(results['details']) > 7: text += f"... и еще {len(results['details']) - 7} записей\n\n" if results['invalid_accounts']: text += "\n🚫 Неверные форматы:\n" for invalid in results['invalid_accounts'][:5]: text += f"• {invalid}\n" if len(results['invalid_accounts']) > 5: text += f"... и еще {len(results['invalid_accounts']) - 5} номеров\n" if results['errors']: text += "\n❌ Ошибки:\n" for error in results['errors'][:3]: text += f"• {error}\n" if len(results['errors']) > 3: text += f"... и еще {len(results['errors']) - 3} ошибок\n" await message.answer( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="👥 К управлению участниками", callback_data="admin_participants")] ]) ) # ====================== # ДОПОЛНИТЕЛЬНЫЕ ХЭНДЛЕРЫ УЧАСТНИКОВ # ====================== @admin_router.callback_query(F.data == "admin_participants_by_lottery") async def show_participants_by_lottery(callback: CallbackQuery): """Показать участников по розыгрышам""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: lotteries = await LotteryService.get_all_lotteries(session) if not lotteries: await callback.message.edit_text( "❌ Нет розыгрышей", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ]) ) return text = "📊 Участники по розыгрышам\n\n" for lottery in lotteries[:15]: # Показываем первые 15 async with async_session_maker() as session: count = await ParticipationService.get_participants_count(session, lottery.id) status = "🟢" if getattr(lottery, 'is_active', True) else "🔴" text += f"{status} {lottery.title}: {count} участников\n" if len(lotteries) > 15: text += f"\n... и еще {len(lotteries) - 15} розыгрышей" buttons = [] for lottery in lotteries[:10]: # Кнопки для первых 10 buttons.append([ InlineKeyboardButton( text=f"👥 {lottery.title[:30]}...", callback_data=f"admin_participants_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data == "admin_participants_report") async def show_participants_report(callback: CallbackQuery): """Отчет по участникам""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: from sqlalchemy import func, select from models import User, Participation, Lottery # Общая статистика по участникам total_participants = await session.scalar( select(func.count(func.distinct(User.id))) .select_from(User) .join(Participation) ) total_participations = await session.scalar(select(func.count(Participation.id))) # Топ активных участников top_participants = await session.execute( select( User.first_name, User.username, User.account_number, func.count(Participation.id).label('participations') ) .join(Participation) .group_by(User.id) .order_by(func.count(Participation.id).desc()) .limit(10) ) top_participants = top_participants.fetchall() # Участники с аккаунтами vs без users_with_accounts = await session.scalar( select(func.count(User.id)).where(User.account_number.isnot(None)) ) users_without_accounts = await session.scalar( select(func.count(User.id)).where(User.account_number.is_(None)) ) text = "📈 Отчет по участникам\n\n" text += f"👥 Всего уникальных участников: {total_participants}\n" text += f"📊 Всего участий: {total_participations}\n" text += f"🏦 С номерами счетов: {users_with_accounts}\n" text += f"🆔 Только Telegram ID: {users_without_accounts}\n\n" if top_participants: text += "🏆 Топ-10 активных участников:\n" for i, (name, username, account, count) in enumerate(top_participants, 1): display_name = f"@{username}" if username else name if account: display_name += f" ({account[-7:]})" # Последние 7 символов счёта text += f"{i}. {display_name} - {count} участий\n" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_participants_report")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ]) ) @admin_router.callback_query(F.data == "admin_edit_lottery") async def start_edit_lottery(callback: CallbackQuery, state: FSMContext): """Начать редактирование розыгрыша""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: lotteries = await LotteryService.get_all_lotteries(session) if not lotteries: await callback.message.edit_text( "❌ Нет розыгрышей для редактирования", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")] ]) ) return text = "📝 Редактирование розыгрыша\n\n" text += "Выберите розыгрыш для редактирования:\n\n" buttons = [] for lottery in lotteries[:10]: # Первые 10 розыгрышей status = "🟢" if getattr(lottery, 'is_active', True) else "🔴" text += f"{status} {lottery.title}\n" buttons.append([ InlineKeyboardButton( text=f"📝 {lottery.title[:30]}...", callback_data=f"admin_edit_lottery_select_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_edit_lottery_select_")) async def choose_edit_field(callback: CallbackQuery, state: FSMContext): """Выбор поля для редактирования""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) await state.update_data(edit_lottery_id=lottery_id) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) text = f"📝 Редактирование: {lottery.title}\n\n" text += "Выберите, что хотите изменить:\n\n" text += f"📝 Название: {lottery.title}\n" text += f"📄 Описание: {lottery.description[:50]}{'...' if len(lottery.description) > 50 else ''}\n" text += f"🎁 Призы: {len(getattr(lottery, 'prizes', []))} шт.\n" text += f"🎭 Отображение: {getattr(lottery, 'winner_display_type', 'username')}\n" text += f"🟢 Активен: {'Да' if getattr(lottery, 'is_active', True) else 'Нет'}" buttons = [ [InlineKeyboardButton(text="📝 Изменить название", callback_data=f"admin_edit_field_{lottery_id}_title")], [InlineKeyboardButton(text="📄 Изменить описание", callback_data=f"admin_edit_field_{lottery_id}_description")], [InlineKeyboardButton(text="🎁 Изменить призы", callback_data=f"admin_edit_field_{lottery_id}_prizes")], [ InlineKeyboardButton(text="⏸️ Деактивировать" if getattr(lottery, 'is_active', True) else "▶️ Активировать", callback_data=f"admin_toggle_active_{lottery_id}"), InlineKeyboardButton(text="🎭 Тип отображения", callback_data=f"admin_set_display_{lottery_id}") ], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_edit_lottery")] ] await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_toggle_active_")) async def toggle_lottery_active(callback: CallbackQuery): """Переключить активность розыгрыша""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) current_active = getattr(lottery, 'is_active', True) # Переключаем статус success = await LotteryService.set_lottery_active(session, lottery_id, not current_active) if success: new_status = "активирован" if not current_active else "деактивирован" await callback.answer(f"✅ Розыгрыш {new_status}!", show_alert=True) else: await callback.answer("❌ Ошибка изменения статуса", show_alert=True) # Обновляем отображение await choose_edit_field(callback, None) @admin_router.callback_query(F.data == "admin_finish_lottery") async def start_finish_lottery(callback: CallbackQuery): """Завершить розыгрыш""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: lotteries = await LotteryService.get_active_lotteries(session) if not lotteries: await callback.message.edit_text( "❌ Нет активных розыгрышей для завершения", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")] ]) ) return text = "🏁 Завершение розыгрыша\n\n" text += "Выберите розыгрыш для завершения:\n\n" buttons = [] for lottery in lotteries: async with async_session_maker() as session: count = await ParticipationService.get_participants_count(session, lottery.id) text += f"🎯 {lottery.title} ({count} участников)\n" buttons.append([ InlineKeyboardButton( text=f"🏁 {lottery.title[:30]}...", callback_data=f"admin_confirm_finish_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_confirm_finish_")) async def confirm_finish_lottery(callback: CallbackQuery): """Подтвердить завершение розыгрыша""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) count = await ParticipationService.get_participants_count(session, lottery_id) text = f"🏁 Завершение розыгрыша\n\n" text += f"🎯 {lottery.title}\n" text += f"👥 Участников: {count}\n\n" text += "⚠️ После завершения розыгрыш станет неактивным и новые участники не смогут присоединиться.\n\n" text += "Вы уверены?" buttons = [ [ InlineKeyboardButton(text="✅ Да, завершить", callback_data=f"admin_do_finish_{lottery_id}"), InlineKeyboardButton(text="❌ Отмена", callback_data="admin_finish_lottery") ] ] await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_do_finish_")) async def do_finish_lottery(callback: CallbackQuery): """Выполнить завершение розыгрыша""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) async with async_session_maker() as session: success = await LotteryService.complete_lottery(session, lottery_id) lottery = await LotteryService.get_lottery(session, lottery_id) if success: text = f"✅ Розыгрыш завершён!\n\n" text += f"🎯 {lottery.title}\n" text += f"📅 Завершён: {datetime.now().strftime('%d.%m.%Y %H:%M')}" await callback.answer("✅ Розыгрыш завершён!", show_alert=True) else: text = "❌ Ошибка завершения розыгрыша" await callback.answer("❌ Ошибка завершения", show_alert=True) await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🎲 К управлению розыгрышами", callback_data="admin_lotteries")] ]) ) @admin_router.callback_query(F.data == "admin_delete_lottery") async def start_delete_lottery(callback: CallbackQuery): """Удаление розыгрыша""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: lotteries = await LotteryService.get_all_lotteries(session) if not lotteries: await callback.message.edit_text( "❌ Нет розыгрышей для удаления", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")] ]) ) return text = "🗑️ Удаление розыгрыша\n\n" text += "⚠️ ВНИМАНИЕ! Это действие нельзя отменить!\n\n" text += "Выберите розыгрыш для удаления:\n\n" buttons = [] for lottery in lotteries[:10]: status = "🟢" if getattr(lottery, 'is_active', True) else "🔴" async with async_session_maker() as session: count = await ParticipationService.get_participants_count(session, lottery.id) text += f"{status} {lottery.title} ({count} участников)\n" buttons.append([ InlineKeyboardButton( text=f"🗑️ {lottery.title[:25]}...", callback_data=f"admin_confirm_delete_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_confirm_delete_")) async def confirm_delete_lottery(callback: CallbackQuery): """Подтвердить удаление розыгрыша""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) count = await ParticipationService.get_participants_count(session, lottery_id) text = f"🗑️ Удаление розыгрыша\n\n" text += f"🎯 {lottery.title}\n" text += f"👥 Участников: {count}\n\n" text += "⚠️ ВНИМАНИЕ!\n" text += "• Все данные о розыгрыше будут удалены навсегда\n" text += "• Все участия в розыгрыше будут удалены\n" text += "• Это действие НЕЛЬЗЯ отменить!\n\n" text += "Вы ТОЧНО уверены?" buttons = [ [ InlineKeyboardButton(text="🗑️ ДА, УДАЛИТЬ", callback_data=f"admin_do_delete_{lottery_id}"), InlineKeyboardButton(text="❌ ОТМЕНА", callback_data="admin_delete_lottery") ] ] await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_do_delete_")) async def do_delete_lottery(callback: CallbackQuery): """Выполнить удаление розыгрыша""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) lottery_title = lottery.title success = await LotteryService.delete_lottery(session, lottery_id) if success: text = f"✅ Розыгрыш удалён!\n\n" text += f"🎯 {lottery_title}\n" text += f"📅 Удалён: {datetime.now().strftime('%d.%m.%Y %H:%M')}" await callback.answer("✅ Розыгрыш удалён!", show_alert=True) else: text = "❌ Ошибка удаления розыгрыша" await callback.answer("❌ Ошибка удаления", show_alert=True) await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🎲 К управлению розыгрышами", callback_data="admin_lotteries")] ]) ) # ====================== # УПРАВЛЕНИЕ ПОБЕДИТЕЛЯМИ # ====================== @admin_router.callback_query(F.data == "admin_winners") async def show_winner_management(callback: CallbackQuery): """Управление победителями""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return text = "👑 Управление победителями\n\n" text += "Здесь вы можете устанавливать предопределенных победителей и проводить розыгрыши.\n\n" text += "Выберите действие:" await callback.message.edit_text(text, reply_markup=get_winner_management_keyboard()) @admin_router.callback_query(F.data == "admin_set_manual_winner") async def start_set_manual_winner(callback: CallbackQuery, state: FSMContext): """Начать установку ручного победителя""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: lotteries = await LotteryService.get_active_lotteries(session) if not lotteries: await callback.message.edit_text( "❌ Нет активных розыгрышей для установки победителей", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_winners")] ]) ) return text = "👑 Установка предопределенного победителя\n\n" text += "Выберите розыгрыш:\n\n" buttons = [] for lottery in lotteries: text += f"🎯 {lottery.title} (ID: {lottery.id})\n" # Показываем уже установленных ручных победителей if lottery.manual_winners: text += f" 👑 Установлены места: {', '.join(lottery.manual_winners.keys())}\n" text += "\n" buttons.append([ InlineKeyboardButton( text=f"🎯 {lottery.title[:30]}...", callback_data=f"admin_choose_winner_lottery_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_winners")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_choose_winner_lottery_")) async def choose_winner_place(callback: CallbackQuery, state: FSMContext): """Выбор места для победителя""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) if not lottery: await callback.answer("Розыгрыш не найден", show_alert=True) return await state.update_data(lottery_id=lottery_id) num_prizes = len(lottery.prizes) if lottery.prizes else 5 text = f"👑 Установка победителя\n" text += f"🎯 Розыгрыш: {lottery.title}\n\n" if lottery.manual_winners: text += f"Уже установлены места: {', '.join(lottery.manual_winners.keys())}\n\n" text += f"Введите номер места (1-{num_prizes}):" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_set_manual_winner")] ]) ) await state.set_state(AdminStates.set_winner_place) @admin_router.message(StateFilter(AdminStates.set_winner_place)) async def process_winner_place(message: Message, state: FSMContext): """Обработка места победителя""" if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return try: place = int(message.text) if place < 1: raise ValueError except ValueError: await message.answer("❌ Введите корректный номер места (положительное число)") return data = await state.get_data() lottery_id = data['lottery_id'] # Проверяем, не занято ли место async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) if lottery.manual_winners and str(place) in lottery.manual_winners: existing_id = lottery.manual_winners[str(place)] existing_user = await UserService.get_user_by_telegram_id(session, existing_id) name = existing_user.username if existing_user and existing_user.username else str(existing_id) await message.answer( f"⚠️ Место {place} уже занято пользователем @{name}\n" f"Введите другой номер места:" ) return await state.update_data(place=place) text = f"👑 Установка победителя на {place} место\n" text += f"🎯 Розыгрыш: {lottery.title}\n\n" text += f"Введите Telegram ID или username пользователя:" await message.answer(text) await state.set_state(AdminStates.set_winner_user) @admin_router.message(StateFilter(AdminStates.set_winner_user)) async def process_winner_user(message: Message, state: FSMContext): """Обработка пользователя-победителя""" if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return user_input = message.text.strip() # Пробуем определить, это ID или username if user_input.startswith('@'): user_input = user_input[1:] # Убираем @ is_username = True elif user_input.isdigit(): is_username = False telegram_id = int(user_input) else: is_username = True async with async_session_maker() as session: if is_username: # Поиск по username from sqlalchemy import select from models import User result = await session.execute( select(User).where(User.username == user_input) ) user = result.scalar_one_or_none() if not user: await message.answer("❌ Пользователь с таким username не найден") return telegram_id = user.telegram_id else: user = await UserService.get_user_by_telegram_id(session, telegram_id) if not user: await message.answer("❌ Пользователь с таким ID не найден") return data = await state.get_data() async with async_session_maker() as session: success = await LotteryService.set_manual_winner( session, data['lottery_id'], data['place'], telegram_id ) await state.clear() if success: username = f"@{user.username}" if user.username else user.first_name await message.answer( f"✅ Предопределенный победитель установлен!\n\n" f"🏆 Место: {data['place']}\n" f"👤 Пользователь: {username}\n" f"🆔 ID: {telegram_id}\n\n" f"При проведении розыгрыша этот пользователь автоматически займет {data['place']} место.", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="👑 К управлению победителями", callback_data="admin_winners")] ]) ) else: await message.answer( "❌ Не удалось установить победителя. Проверьте данные.", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="👑 К управлению победителями", callback_data="admin_winners")] ]) ) # ====================== # СТАТИСТИКА # ====================== @admin_router.callback_query(F.data == "admin_stats") async def show_detailed_stats(callback: CallbackQuery): """Подробная статистика""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: from sqlalchemy import select, func from models import User, Lottery, Participation, Winner # Общая статистика 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))) manual_winners = await session.scalar( select(func.count(Winner.id)).where(Winner.is_manual == True) ) # Топ активных пользователей top_users = await session.execute( select(User.first_name, User.username, func.count(Participation.id).label('count')) .join(Participation) .group_by(User.id) .order_by(func.count(Participation.id).desc()) .limit(5) ) top_users = top_users.fetchall() text = "📊 Детальная статистика\n\n" text += "👥 ПОЛЬЗОВАТЕЛИ\n" text += f"Всего зарегистрировано: {total_users}\n\n" text += "🎲 РОЗЫГРЫШИ\n" text += f"Всего создано: {total_lotteries}\n" text += f"🟢 Активных: {active_lotteries}\n" text += f"✅ Завершенных: {completed_lotteries}\n\n" text += "🎫 УЧАСТИЕ\n" text += f"Всего участий: {total_participations}\n" if total_lotteries > 0: avg_participation = total_participations / total_lotteries text += f"Среднее участие на розыгрыш: {avg_participation:.1f}\n\n" text += "🏆 ПОБЕДИТЕЛИ\n" text += f"Всего победителей: {total_winners}\n" text += f"👑 Предустановленных: {manual_winners}\n" text += f"🎲 Случайных: {total_winners - manual_winners}\n\n" if top_users: text += "🔥 САМЫЕ АКТИВНЫЕ УЧАСТНИКИ\n" for i, (first_name, username, count) in enumerate(top_users, 1): name = f"@{username}" if username else first_name text += f"{i}. {name} - {count} участий\n" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_stats")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")] ]) ) # ====================== # НАСТРОЙКИ # ====================== @admin_router.callback_query(F.data == "admin_settings") async def show_admin_settings(callback: CallbackQuery): """Настройки админ-панели""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return text = "⚙️ Настройки системы\n\n" text += f"👑 Администраторы: {len(ADMIN_IDS)}\n" text += f"🗄️ База данных: SQLAlchemy ORM\n" text += f"📅 Сегодня: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n\n" text += "Доступные действия:" buttons = [ [InlineKeyboardButton(text="💾 Экспорт данных", callback_data="admin_export_data")], [InlineKeyboardButton(text="🧹 Очистка старых данных", callback_data="admin_cleanup")], [InlineKeyboardButton(text="📋 Системная информация", callback_data="admin_system_info")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")] ] await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data == "admin_system_info") async def show_system_info(callback: CallbackQuery): """Системная информация""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return import sys import platform from config import DATABASE_URL text = "💻 Системная информация\n\n" text += f"🐍 Python: {sys.version.split()[0]}\n" text += f"💾 Платформа: {platform.system()} {platform.release()}\n" text += f"🗄️ База данных: {DATABASE_URL.split('://')[0]}\n" text += f"👑 Админов: {len(ADMIN_IDS)}\n" text += f"🕐 Время работы: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_settings")] ]) ) # ====================== # НАСТРОЙКА ОТОБРАЖЕНИЯ ПОБЕДИТЕЛЕЙ # ====================== @admin_router.callback_query(F.data == "admin_winner_display_settings") async def show_winner_display_settings(callback: CallbackQuery, state: FSMContext): """Настройка отображения победителей для розыгрышей""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return async with async_session_maker() as session: lotteries = await LotteryService.get_all_lotteries(session) if not lotteries: await callback.message.edit_text( "❌ Нет розыгрышей", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")] ]) ) return text = "🎭 Настройка отображения победителей\n\n" text += "Выберите розыгрыш для настройки:\n\n" buttons = [] for lottery in lotteries[:10]: # Первые 10 розыгрышей display_type_emoji = { 'username': '👤', 'chat_id': '🆔', 'account_number': '🏦' }.get(getattr(lottery, 'winner_display_type', 'username'), '👤') text += f"{display_type_emoji} {lottery.title} - {getattr(lottery, 'winner_display_type', 'username')}\n" buttons.append([ InlineKeyboardButton( text=f"{display_type_emoji} {lottery.title[:30]}...", callback_data=f"admin_set_display_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_lotteries")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_set_display_")) async def choose_display_type(callback: CallbackQuery, state: FSMContext): """Выбор типа отображения для конкретного розыгрыша""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return lottery_id = int(callback.data.split("_")[-1]) await state.update_data(display_lottery_id=lottery_id) async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) current_type = getattr(lottery, 'winner_display_type', 'username') text = f"🎭 Настройка отображения для:\n{lottery.title}\n\n" text += f"Текущий тип: {current_type}\n\n" text += "Выберите новый тип отображения:\n\n" text += "👤 Username - показывает @username или имя\n" text += "🆔 Chat ID - показывает Telegram ID пользователя\n" text += "🏦 Account Number - показывает номер клиентского счета" buttons = [ [ InlineKeyboardButton(text="👤 Username", callback_data=f"admin_apply_display_{lottery_id}_username"), InlineKeyboardButton(text="🆔 Chat ID", callback_data=f"admin_apply_display_{lottery_id}_chat_id") ], [InlineKeyboardButton(text="🏦 Account Number", callback_data=f"admin_apply_display_{lottery_id}_account_number")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_winner_display_settings")] ] await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_apply_display_")) async def apply_display_type(callback: CallbackQuery, state: FSMContext): """Применить выбранный тип отображения""" import logging logger = logging.getLogger(__name__) logger.info(f"🎭 Попытка смены типа отображения. Callback data: {callback.data}") if not is_admin(callback.from_user.id): logger.warning(f"🚫 Отказ в доступе пользователю {callback.from_user.id}") await callback.answer("❌ Недостаточно прав", show_alert=True) return try: parts = callback.data.split("_") logger.info(f"🔍 Разбор callback data: {parts}") # Format: admin_apply_display_{lottery_id}_{display_type} # Для account_number нужно склеить последние части lottery_id = int(parts[3]) display_type = "_".join(parts[4:]) # Склеиваем все остальные части logger.info(f"🎯 Розыгрыш ID: {lottery_id}, Новый тип: {display_type}") async with async_session_maker() as session: logger.info(f"📝 Вызов set_winner_display_type({lottery_id}, {display_type})") success = await LotteryService.set_winner_display_type(session, lottery_id, display_type) logger.info(f"💾 Результат сохранения: {success}") lottery = await LotteryService.get_lottery(session, lottery_id) logger.info(f"📋 Получен розыгрыш: {lottery.title if lottery else 'None'}") if success: display_type_name = { 'username': 'Username (@username или имя)', 'chat_id': 'Chat ID (Telegram ID)', 'account_number': 'Account Number (номер счета)' }.get(display_type, display_type) text = f"✅ Тип отображения изменен!\n\n" text += f"🎯 Розыгрыш: {lottery.title}\n" text += f"🎭 Новый тип: {display_type_name}\n\n" text += "Теперь победители этого розыгрыша будут отображаться в выбранном формате." logger.info(f"✅ Успех! Тип изменен на {display_type}") await callback.answer("✅ Настройка сохранена!", show_alert=True) else: text = "❌ Ошибка при сохранении настройки" logger.error(f"❌ Ошибка сохранения для розыгрыша {lottery_id}, тип {display_type}") except Exception as e: logger.error(f"💥 Исключение при смене типа отображения: {e}") text = f"❌ Ошибка: {str(e)}" await callback.answer("❌ Ошибка при сохранении!", show_alert=True) return await callback.answer("❌ Ошибка сохранения", show_alert=True) await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🎭 К настройке отображения", callback_data="admin_winner_display_settings")], [InlineKeyboardButton(text="🎲 К управлению розыгрышами", callback_data="admin_lotteries")] ]) ) await state.clear() # Экспорт роутера __all__ = ['admin_router']