""" Расширенная админ-панель для управления розыгрышами """ import logging from aiogram import Router, F from aiogram.types import ( CallbackQuery, Message, InlineKeyboardButton, InlineKeyboardMarkup ) from aiogram.exceptions import TelegramBadRequest 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 ..core.database import async_session_maker from ..core.services import UserService, LotteryService, ParticipationService from ..core.config import ADMIN_IDS from ..core.models import User, Lottery, Participation, Account logger = logging.getLogger(__name__) # Состояния для админки 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 ..core.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 ..core.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 "" # Безопасная обработка победителя - может быть без user_id if winner.user: username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name else: # Победитель по номеру счета без связанного пользователя username = f"Счет: {winner.account_number}" 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 if 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" else: # Если пользователя нет, показываем номер счета text += f"{i}. Счет: {participation.account_number or 'Не указан'}\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_remove_participant") async def remove_participant_start(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, limit=20) if not lotteries: await callback.message.edit_text( "❌ Нет розыгрышей в системе", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ]) ) return buttons = [] for lottery in lotteries: buttons.append([ InlineKeyboardButton( text=f"{'✅' if lottery.is_active else '🔴'} {lottery.title}", callback_data=f"admin_remove_part_from_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")]) await callback.message.edit_text( "➖ Удалить участника из розыгрыша\n\nВыберите розыгрыш:", reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons) ) @admin_router.callback_query(F.data.startswith("admin_remove_part_from_")) async def remove_participant_select_lottery(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 participant_count = await ParticipationService.get_participants_count(session, lottery_id) await state.update_data(remove_participant_lottery_id=lottery_id) await state.set_state(AdminStates.remove_participant_user) await callback.message.edit_text( f"➖ Удалить участника из розыгрыша\n\n" f"🎯 Розыгрыш: {lottery.title}\n" f"👥 Участников: {participant_count}\n\n" f"Отправьте Telegram ID пользователя для удаления:", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_remove_participant")] ]) ) @admin_router.message(StateFilter(AdminStates.remove_participant_user)) async def process_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.get("remove_participant_lottery_id") try: telegram_id = int(message.text.strip()) except ValueError: await message.answer( "❌ Неверный формат. Отправьте числовой Telegram ID.", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_remove_participant")] ]) ) return async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, telegram_id) if not user: await message.answer( f"❌ Пользователь с ID {telegram_id} не найден в системе", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_participants")] ]) ) await state.clear() return lottery = await LotteryService.get_lottery(session, lottery_id) if not lottery: await message.answer("❌ Розыгрыш не найден") await state.clear() return removed = await ParticipationService.remove_participant(session, lottery_id, user.id) await state.clear() username = f"@{user.username}" if user.username else "Нет username" if removed: 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 ..core.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\n" text += "• 98-76-54-32-10-98-76, 11-22-33-44-55-66-77\n" text += "• 12345678901234 (будет отформатирован)\n\n" text += "Формат: XX-XX-XX-XX-XX-XX-XX\n" text += "Всего 7 пар цифр разделенных дефисами" 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_utils для корректной обработки формата "КАРТА СЧЕТ" from ..utils.account_utils import parse_accounts_from_message account_inputs = parse_accounts_from_message(message.text) 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\n" text += "• 98-76-54-32-10-98-76, 11-22-33-44-55-66-77\n" text += "• 12345678901234 (будет отформатирован)\n\n" text += "Формат: 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_utils для корректной обработки формата "КАРТА СЧЕТ" from ..utils.account_utils import parse_accounts_from_message account_inputs = parse_accounts_from_message(message.text) 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 ..core.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_")) async def redirect_to_edit_lottery(callback: CallbackQuery, state: FSMContext): """Редирект на редактирование розыгрыша из детального просмотра""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return # Извлекаем lottery_id из callback_data (формат admin_edit_123) parts = callback.data.split("_") if len(parts) == 3: # admin_edit_123 lottery_id = int(parts[2]) # Напрямую вызываем обработчик вместо подмены callback_data await state.update_data(edit_lottery_id=lottery_id) await choose_edit_field(callback, state) else: # Если формат другой, то это уже правильный callback lottery_id = int(callback.data.split("_")[-1]) await state.update_data(edit_lottery_id=lottery_id) await choose_edit_field(callback, state) @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_set_winner_")) async def handle_set_winner_from_lottery(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]) # Напрямую вызываем обработчик вместо подмены callback_data await state.update_data(winner_lottery_id=lottery_id) await choose_winner_place(callback, state) @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 ..core.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_list_winners") async def list_all_winners(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 result = await session.execute( select(Winner) .options(selectinload(Winner.user), selectinload(Winner.lottery)) .order_by(Winner.created_at.desc()) .limit(50) ) winners = result.scalars().all() if not winners: await callback.message.edit_text( "📋 Список победителей пуст\n\nПока не было проведено ни одного розыгрыша.", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_winners")] ]) ) return text = "👑 Список победителей\n\n" # Группируем победителей по розыгрышам lotteries_dict = {} for winner in winners: lottery_id = winner.lottery_id if lottery_id not in lotteries_dict: lotteries_dict[lottery_id] = { 'lottery': winner.lottery, 'winners': [] } lotteries_dict[lottery_id]['winners'].append(winner) # Выводим информацию for lottery_id, data in list(lotteries_dict.items())[:10]: lottery = data['lottery'] winners_list = data['winners'] text += f"🎯 {lottery.title}\n" text += f"📅 {lottery.created_at.strftime('%d.%m.%Y')}\n" for winner in sorted(winners_list, key=lambda w: w.place): username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name manual_mark = "🔧" if winner.is_manual else "🎲" text += f" {manual_mark} {winner.place} место: {username} - {winner.prize}\n" text += "\n" if len(lotteries_dict) > 10: text += f"\n... и ещё {len(lotteries_dict) - 10} розыгрышей" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔄 Обновить", callback_data="admin_list_winners")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_winners")] ]) ) @admin_router.callback_query(F.data == "admin_edit_winner") async def edit_winner_start(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 result = await session.execute( select(Lottery) .join(Winner) .distinct() .order_by(Lottery.created_at.desc()) .limit(20) ) lotteries_with_winners = result.scalars().all() if not lotteries_with_winners: await callback.message.edit_text( "❌ Нет розыгрышей с победителями для редактирования", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_winners")] ]) ) return text = "📝 Редактировать победителя\n\nВыберите розыгрыш:\n\n" buttons = [] for lottery in lotteries_with_winners: buttons.append([ InlineKeyboardButton( text=f"🎯 {lottery.title}", callback_data=f"admin_edit_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_edit_winner_lottery_")) async def edit_winner_select_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) winners = await LotteryService.get_winners(session, lottery_id) if not winners: await callback.answer("❌ Нет победителей для редактирования", show_alert=True) return text = f"📝 Редактировать победителя\n\n🎯 {lottery.title}\n\nВыберите место:\n\n" buttons = [] for winner in winners: username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name buttons.append([ InlineKeyboardButton( text=f"🏆 {winner.place} место: {username} - {winner.prize}", callback_data=f"admin_edit_winner_id_{winner.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_edit_winner")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_edit_winner_id_")) async def edit_winner_details(callback: CallbackQuery): """Показать детали победителя (пока просто информационное сообщение)""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return winner_id = int(callback.data.split("_")[-1]) async with async_session_maker() as session: from sqlalchemy import select result = await session.execute( select(Winner) .options(selectinload(Winner.user), selectinload(Winner.lottery)) .where(Winner.id == winner_id) ) winner = result.scalar_one_or_none() if not winner: await callback.answer("❌ Победитель не найден", show_alert=True) return username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name manual_mark = "🔧 Установлен вручную" if winner.is_manual else "🎲 Выбран случайно" text = f"📝 Информация о победителе\n\n" text += f"🎯 Розыгрыш: {winner.lottery.title}\n" text += f"🏆 Место: {winner.place}\n" text += f"💰 Приз: {winner.prize}\n" text += f"👤 Пользователь: {username}\n" text += f"🆔 ID: {winner.user.telegram_id}\n" text += f"📊 Тип: {manual_mark}\n" text += f"📅 Дата: {winner.created_at.strftime('%d.%m.%Y %H:%M')}\n\n" text += "ℹ️ Редактирование победителей доступно через удаление и повторное добавление." await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_edit_winner_lottery_{winner.lottery_id}")] ]) ) @admin_router.callback_query(F.data == "admin_remove_winner") async def remove_winner_start(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 result = await session.execute( select(Lottery) .join(Winner) .distinct() .order_by(Lottery.created_at.desc()) .limit(20) ) lotteries_with_winners = result.scalars().all() if not lotteries_with_winners: await callback.message.edit_text( "❌ Нет розыгрышей с победителями для удаления", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_winners")] ]) ) return text = "❌ Удалить победителя\n\nВыберите розыгрыш:\n\n" buttons = [] for lottery in lotteries_with_winners: buttons.append([ InlineKeyboardButton( text=f"🎯 {lottery.title}", callback_data=f"admin_remove_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_remove_winner_lottery_")) async def remove_winner_select_place(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) winners = await LotteryService.get_winners(session, lottery_id) if not winners: await callback.answer("❌ Нет победителей для удаления", show_alert=True) return text = f"❌ Удалить победителя\n\n🎯 {lottery.title}\n\nВыберите победителя для удаления:\n\n" buttons = [] for winner in winners: username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name buttons.append([ InlineKeyboardButton( text=f"🏆 {winner.place} место: {username} - {winner.prize}", callback_data=f"admin_confirm_remove_winner_{winner.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_remove_winner")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_confirm_remove_winner_")) async def confirm_remove_winner(callback: CallbackQuery): """Подтверждение удаления победителя""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return winner_id = int(callback.data.split("_")[-1]) async with async_session_maker() as session: from sqlalchemy import select result = await session.execute( select(Winner) .options(selectinload(Winner.user), selectinload(Winner.lottery)) .where(Winner.id == winner_id) ) winner = result.scalar_one_or_none() if not winner: await callback.answer("❌ Победитель не найден", show_alert=True) return username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name text = f"⚠️ Подтверждение удаления\n\n" text += f"Вы действительно хотите удалить победителя?\n\n" text += f"🎯 Розыгрыш: {winner.lottery.title}\n" text += f"🏆 Место: {winner.place}\n" text += f"👤 Пользователь: {username}\n" text += f"💰 Приз: {winner.prize}\n\n" text += "⚠️ Это действие необратимо!" buttons = [ [ InlineKeyboardButton(text="✅ Да, удалить", callback_data=f"admin_do_remove_winner_{winner_id}"), InlineKeyboardButton(text="❌ Отмена", callback_data=f"admin_remove_winner_lottery_{winner.lottery_id}") ] ] await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_do_remove_winner_")) async def do_remove_winner(callback: CallbackQuery): """Выполнение удаления победителя""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return winner_id = int(callback.data.split("_")[-1]) async with async_session_maker() as session: from sqlalchemy import select, delete # Получаем информацию о победителе перед удалением result = await session.execute( select(Winner) .options(selectinload(Winner.user), selectinload(Winner.lottery)) .where(Winner.id == winner_id) ) winner = result.scalar_one_or_none() if not winner: await callback.answer("❌ Победитель не найден", show_alert=True) return lottery_id = winner.lottery_id username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name # Удаляем победителя await session.execute(delete(Winner).where(Winner.id == winner_id)) await session.commit() await callback.message.edit_text( f"✅ Победитель удалён!\n\n" f"👤 {username}\n" f"🏆 Место: {winner.place}\n" f"💰 Приз: {winner.prize}", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="👑 К управлению победителями", callback_data="admin_winners")] ]) ) # ====================== # ПРОВЕДЕНИЕ РОЗЫГРЫША # ====================== @admin_router.callback_query(F.data == "admin_conduct_draw") async def choose_lottery_for_draw(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, limit=100) if not lotteries: await callback.answer("Нет активных розыгрышей", show_alert=True) return text = "🎲 Выберите розыгрыш для проведения:\n\n" buttons = [] for lottery in lotteries: async with async_session_maker() as session: participants_count = await ParticipationService.get_participants_count(session, lottery.id) text += f"🎯 {lottery.title}\n" text += f" 👥 Участников: {participants_count}\n" if lottery.is_completed: text += f" ✅ Завершён\n" text += "\n" buttons.append([ InlineKeyboardButton( text=f"🎲 {lottery.title[:30]}...", callback_data=f"admin_conduct_{lottery.id}" ) ]) buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_draws")]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data.startswith("admin_conduct_")) async def conduct_lottery_draw_confirm(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 if lottery.is_completed: await callback.answer("Розыгрыш уже завершён", show_alert=True) return participants_count = await ParticipationService.get_participants_count(session, lottery_id) if participants_count == 0: await callback.answer("Нет участников для розыгрыша", show_alert=True) return # Подсчёт призов prizes_count = len(lottery.prizes) if lottery.prizes else 0 # Формируем сообщение с подтверждением text = f"⚠️ Подтверждение проведения розыгрыша\n\n" text += f"🎲 Розыгрыш: {lottery.title}\n" text += f"👥 Участников: {participants_count}\n" text += f"🏆 Призов: {prizes_count}\n\n" if lottery.prizes: text += "Призы:\n" for i, prize in enumerate(lottery.prizes, 1): text += f"{i}. {prize}\n" text += "\n" text += "❗️ Внимание: После проведения розыгрыша результаты нельзя будет изменить!\n\n" text += "Продолжить?" buttons = [ [InlineKeyboardButton(text="✅ Да, провести розыгрыш", callback_data=f"admin_conduct_confirmed_{lottery_id}")], [InlineKeyboardButton(text="❌ Отмена", callback_data=f"admin_lottery_{lottery_id}")] ] try: await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) except TelegramBadRequest as e: if "message is not modified" in str(e): await callback.answer("Сообщение уже актуально", show_alert=False) else: raise @admin_router.callback_query(F.data.startswith("admin_conduct_confirmed_")) async def conduct_lottery_draw(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 if lottery.is_completed: await callback.answer("Розыгрыш уже завершён", show_alert=True) return participants_count = await ParticipationService.get_participants_count(session, lottery_id) if participants_count == 0: await callback.answer("Нет участников для розыгрыша", show_alert=True) return # Показываем индикатор загрузки await callback.answer("⏳ Проводится розыгрыш...", show_alert=True) # Проводим розыгрыш через сервис winners_dict = await LotteryService.conduct_draw(session, lottery_id) if winners_dict: # Отправляем уведомления победителям from ..utils.notifications import notify_winners_async try: await notify_winners_async(callback.bot, session, lottery_id) logger.info(f"Уведомления отправлены для розыгрыша {lottery_id}") except Exception as e: logger.error(f"Ошибка при отправке уведомлений: {e}") # Получаем победителей из базы winners = await LotteryService.get_winners(session, lottery_id) text = f"🎉 Розыгрыш '{lottery.title}' завершён!\n\n" text += "🏆 Победители:\n" for winner in winners: if winner.account_number: text += f"{winner.place} место: {winner.account_number}\n" elif winner.user: username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name text += f"{winner.place} место: {username}\n" else: text += f"{winner.place} место: ID {winner.user_id}\n" text += "\n✅ Уведомления отправлены победителям" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔙 К розыгрышам", callback_data="admin_draws")] ]) ) else: await callback.answer("Ошибка при проведении розыгрыша", show_alert=True) # ====================== # СТАТИСТИКА # ====================== @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 ..core.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_export_data") async def export_data(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 # Собираем статистику users_count = await session.scalar(select(func.count(User.id))) lotteries_count = await session.scalar(select(func.count(Lottery.id))) participations_count = await session.scalar(select(func.count(Participation.id))) winners_count = await session.scalar(select(func.count(Winner.id))) import json from datetime import datetime # Формируем данные для экспорта export_info = { "export_date": datetime.now().isoformat(), "statistics": { "users": users_count, "lotteries": lotteries_count, "participations": participations_count, "winners": winners_count } } # Сохраняем в файл filename = f"export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" with open(filename, 'w', encoding='utf-8') as f: json.dump(export_info, f, ensure_ascii=False, indent=2) text = "💾 Экспорт данных\n\n" text += f"📊 Статистика:\n" text += f"👥 Пользователей: {users_count}\n" text += f"🎯 Розыгрышей: {lotteries_count}\n" text += f"🎫 Участий: {participations_count}\n" text += f"🏆 Победителей: {winners_count}\n\n" text += f"✅ Данные экспортированы в файл:\n{filename}" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔄 Экспортировать снова", callback_data="admin_export_data")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_settings")] ]) ) @admin_router.callback_query(F.data == "admin_cleanup") async def cleanup_old_data(callback: CallbackQuery): """Очистка старых данных""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return text = "🧹 Очистка старых данных\n\n" text += "Выберите тип данных для очистки:\n\n" text += "⚠️ Внимание! Это действие необратимо!" buttons = [ [InlineKeyboardButton(text="🗑️ Завершённые розыгрыши (>30 дней)", callback_data="admin_cleanup_old_lotteries")], [InlineKeyboardButton(text="👻 Неактивные пользователи (>90 дней)", callback_data="admin_cleanup_inactive_users")], [InlineKeyboardButton(text="📋 Старые участия (>60 дней)", callback_data="admin_cleanup_old_participations")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_settings")] ] await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @admin_router.callback_query(F.data == "admin_cleanup_old_lotteries") async def cleanup_old_lotteries(callback: CallbackQuery): """Очистка старых завершённых розыгрышей""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return from datetime import timedelta cutoff_date = datetime.now() - timedelta(days=30) async with async_session_maker() as session: from sqlalchemy import select, delete # Находим старые завершённые розыгрыши result = await session.execute( select(Lottery) .where( Lottery.is_completed == True, Lottery.created_at < cutoff_date ) ) old_lotteries = result.scalars().all() count = len(old_lotteries) if count == 0: await callback.answer("✅ Нет старых розыгрышей для удаления", show_alert=True) return # Удаляем старые розыгрыши for lottery in old_lotteries: await session.delete(lottery) await session.commit() text = f"✅ Очистка завершена!\n\n" text += f"🗑️ Удалено розыгрышей: {count}\n" text += f"📅 Старше: {cutoff_date.strftime('%d.%m.%Y')}" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🧹 К очистке", callback_data="admin_cleanup")], [InlineKeyboardButton(text="⚙️ К настройкам", callback_data="admin_settings")] ]) ) @admin_router.callback_query(F.data == "admin_cleanup_inactive_users") async def cleanup_inactive_users(callback: CallbackQuery): """Очистка неактивных пользователей""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return from datetime import timedelta # Удаляем только незарегистрированных пользователей, которые не были активны более 30 дней cutoff_date = datetime.now() - timedelta(days=30) async with async_session_maker() as session: from sqlalchemy import select, delete, and_ # Находим неактивных незарегистрированных пользователей без участий и аккаунтов result = await session.execute( select(User) .where( and_( User.is_registered == False, User.created_at < cutoff_date ) ) ) inactive_users = result.scalars().all() # Проверяем, что у них нет связанных данных deleted_count = 0 for user in inactive_users: # Проверяем участия participations = await session.execute( select(Participation).where(Participation.user_id == user.id) ) if participations.scalars().first(): continue # Проверяем счета accounts = await session.execute( select(Account).where(Account.user_id == user.id) ) if accounts.scalars().first(): continue # Безопасно удаляем await session.delete(user) deleted_count += 1 await session.commit() await callback.message.edit_text( f"✅ Очистка завершена\n\n" f"Удалено неактивных пользователей: {deleted_count}\n" f"Критерий: незарегистрированные, неактивные более 30 дней, без данных", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🧹 К очистке", callback_data="admin_cleanup")], [InlineKeyboardButton(text="⚙️ К настройкам", callback_data="admin_settings")] ]) ) @admin_router.callback_query(F.data == "admin_cleanup_old_participations") async def cleanup_old_participations(callback: CallbackQuery): """Очистка старых участий""" if not is_admin(callback.from_user.id): await callback.answer("❌ Недостаточно прав", show_alert=True) return from datetime import timedelta cutoff_date = datetime.now() - timedelta(days=60) async with async_session_maker() as session: from sqlalchemy import select, delete # Находим старые участия в завершённых розыгрышах result = await session.execute( select(Participation) .join(Lottery) .where( Lottery.is_completed == True, Participation.created_at < cutoff_date ) ) old_participations = result.scalars().all() count = len(old_participations) if count == 0: await callback.answer("✅ Нет старых участий для удаления", show_alert=True) return # Удаляем старые участия for participation in old_participations: await session.delete(participation) await session.commit() text = f"✅ Очистка завершена!\n\n" text += f"🗑️ Удалено участий: {count}\n" text += f"📅 Старше: {cutoff_date.strftime('%d.%m.%Y')}" await callback.message.edit_text( text, reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🧹 К очистке", callback_data="admin_cleanup")], [InlineKeyboardButton(text="⚙️ К настройкам", callback_data="admin_settings")] ]) ) @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 ..core.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']