"""Админские обработчики для управления счетами и верификации""" from aiogram import Router, F, Bot from aiogram.types import Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup from aiogram.filters import Command from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from sqlalchemy import select, and_ from src.core.database import async_session_maker from src.core.registration_services import AccountService, WinnerNotificationService, RegistrationService from src.core.services import UserService, LotteryService, ParticipationService from src.core.models import User, Winner, Account, Participation from src.core.config import ADMIN_IDS router = Router() class AddAccountStates(StatesGroup): waiting_for_data = State() choosing_lottery = State() def is_admin(user_id: int) -> bool: """Проверка прав администратора""" return user_id in ADMIN_IDS @router.message(Command("add_account")) async def add_account_command(message: Message, state: FSMContext): """ Добавить счет пользователю по клубной карте Формат: /add_account Или: /add_account (затем вводить данные построчно) """ if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return parts = message.text.split(maxsplit=2) # Если данные указаны в команде if len(parts) == 3: club_card = parts[1] account_number = parts[2] await process_single_account(message, club_card, account_number, state) else: # Запрашиваем данные await state.set_state(AddAccountStates.waiting_for_data) await message.answer( "💳 **Добавление счетов**\n\n" "Отправьте данные в формате:\n" "`клубная_карта номер_счета`\n\n" "**Для одного счета:**\n" "`2223 11-22-33-44-55-66-77`\n\n" "**Для нескольких счетов (каждый с новой строки):**\n" "`2223 11-22-33-44-55-66-77`\n" "`2223 88-99-00-11-22-33-44`\n" "`3334 12-34-56-78-90-12-34`\n\n" "❌ Отправьте /cancel для отмены", parse_mode="Markdown" ) async def process_single_account(message: Message, club_card: str, account_number: str, state: FSMContext): """Обработка одного счета""" try: async with async_session_maker() as session: # Создаем счет account = await AccountService.create_account( session, club_card_number=club_card, account_number=account_number ) # Получаем владельца owner = await AccountService.get_account_owner(session, account_number) # Сохраняем данные счета в state для добавления в розыгрыш await state.update_data( accounts=[{ 'club_card': club_card, 'account_number': account_number, 'account_id': account.id }] ) text = f"✅ Счет успешно добавлен!\n\n" text += f"🎫 Клубная карта: {club_card}\n" text += f"💳 Счет: {account_number}\n" if owner: text += f"👤 Владелец: {owner.first_name}\n\n" # Отправляем уведомление владельцу try: await message.bot.send_message( owner.telegram_id, f"✅ К вашему профилю добавлен счет:\n\n" f"💳 {account_number}\n\n" f"Теперь вы можете участвовать в розыгрышах с этим счетом!" ) text += "📨 Владельцу отправлено уведомление\n\n" except Exception as e: text += f"⚠️ Не удалось отправить уведомление: {str(e)}\n\n" # Предлагаем добавить в розыгрыш await show_lottery_selection(message, text, state) except ValueError as e: await message.answer(f"❌ Ошибка: {str(e)}") await state.clear() except Exception as e: await message.answer(f"❌ Произошла ошибка: {str(e)}") await state.clear() @router.message(AddAccountStates.waiting_for_data) async def process_accounts_data(message: Message, state: FSMContext): """Обработка данных счетов (один или несколько)""" if message.text.strip().lower() == '/cancel': await state.clear() await message.answer("❌ Операция отменена") return lines = message.text.strip().split('\n') accounts_data = [] errors = [] for i, line in enumerate(lines, 1): parts = line.strip().split() if len(parts) != 2: errors.append(f"Строка {i}: неверный формат (ожидается: клубная_карта номер_счета)") continue club_card, account_number = parts try: async with async_session_maker() as session: account = await AccountService.create_account( session, club_card_number=club_card, account_number=account_number ) owner = await AccountService.get_account_owner(session, account_number) accounts_data.append({ 'club_card': club_card, 'account_number': account_number, 'account_id': account.id, 'owner': owner }) # Отправляем уведомление владельцу if owner: try: await message.bot.send_message( owner.telegram_id, f"✅ К вашему профилю добавлен счет:\n\n" f"💳 {account_number}\n\n" f"Теперь вы можете участвовать в розыгрышах!" ) except: pass except ValueError as e: errors.append(f"Строка {i} ({club_card} {account_number}): {str(e)}") except Exception as e: errors.append(f"Строка {i}: {str(e)}") # Формируем отчет text = f"📊 **Результаты добавления счетов**\n\n" if accounts_data: text += f"✅ **Успешно добавлено: {len(accounts_data)}**\n\n" for acc in accounts_data: text += f"• {acc['club_card']} → {acc['account_number']}\n" if acc['owner']: text += f" 👤 {acc['owner'].first_name}\n" text += "\n" if errors: text += f"❌ **Ошибки: {len(errors)}**\n\n" for error in errors[:5]: # Показываем максимум 5 ошибок text += f"• {error}\n" if len(errors) > 5: text += f"\n... и еще {len(errors) - 5} ошибок\n" if not accounts_data: await message.answer(text) await state.clear() return # Сохраняем данные и предлагаем добавить в розыгрыш await state.update_data(accounts=accounts_data) await show_lottery_selection(message, text, state) async def show_lottery_selection(message: Message, prev_text: str, state: FSMContext): """Показать выбор розыгрыша для добавления счетов""" async with async_session_maker() as session: lotteries = await LotteryService.get_active_lotteries(session) if not lotteries: await message.answer( prev_text + "ℹ️ Нет активных розыгрышей для добавления счетов" ) await state.clear() return await state.set_state(AddAccountStates.choosing_lottery) buttons = [] for lottery in lotteries[:10]: # Максимум 10 розыгрышей buttons.append([ InlineKeyboardButton( text=f"🎯 {lottery.title}", callback_data=f"add_to_lottery_{lottery.id}" ) ]) buttons.append([ InlineKeyboardButton(text="❌ Пропустить", callback_data="skip_lottery_add") ]) await message.answer( prev_text + "➕ **Добавить счета в розыгрыш?**\n\n" "Выберите розыгрыш из списка:", reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons) ) @router.callback_query(F.data.startswith("add_to_lottery_")) async def add_accounts_to_lottery(callback: CallbackQuery, state: FSMContext): """Добавить счета в выбранный розыгрыш""" lottery_id = int(callback.data.split("_")[-1]) data = await state.get_data() accounts = data.get('accounts', []) if not accounts: await callback.answer("❌ Нет данных о счетах", show_alert=True) await state.clear() return success_count = 0 errors = [] async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) if not lottery: await callback.answer("❌ Розыгрыш не найден", show_alert=True) await state.clear() return for acc in accounts: try: # Добавляем участие через account_id # Проверяем, не участвует ли уже existing = await session.execute( select(Participation).where( and_( Participation.lottery_id == lottery_id, Participation.account_id == acc['account_id'] ) ) ) if existing.scalar_one_or_none(): errors.append(f"{acc['account_number']}: уже участвует") continue # Создаем участие participation = Participation( lottery_id=lottery_id, account_id=acc['account_id'], account_number=acc['account_number'] ) session.add(participation) success_count += 1 except Exception as e: errors.append(f"{acc['account_number']}: {str(e)}") await session.commit() text = f"📊 **Добавление в розыгрыш '{lottery.title}'**\n\n" if success_count: text += f"✅ Добавлено счетов: {success_count}\n\n" if errors: text += f"⚠️ Ошибки: {len(errors)}\n" for error in errors[:3]: text += f"• {error}\n" await callback.message.edit_text(text) await state.clear() @router.callback_query(F.data == "skip_lottery_add") async def skip_lottery_add(callback: CallbackQuery, state: FSMContext): """Пропустить добавление в розыгрыш""" await callback.message.edit_text("✅ Счета добавлены без участия в розыгрышах") await state.clear() @router.message(Command("remove_account")) async def remove_account_command(message: Message): """ Деактивировать счет Формат: /remove_account """ if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return parts = message.text.split() if len(parts) != 2: await message.answer( "❌ Неверный формат команды\n\n" "Используйте: /remove_account " ) return account_number = parts[1] try: async with async_session_maker() as session: success = await AccountService.deactivate_account(session, account_number) if success: await message.answer(f"✅ Счет {account_number} деактивирован") else: await message.answer(f"❌ Счет {account_number} не найден") except Exception as e: await message.answer(f"❌ Ошибка: {str(e)}") @router.message(Command("verify_winner")) async def verify_winner_command(message: Message): """ Подтвердить выигрыш по коду верификации Формат: /verify_winner Пример: /verify_winner AB12CD34 1 """ if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return parts = message.text.split() if len(parts) != 3: await message.answer( "❌ Неверный формат команды\n\n" "Используйте:\n" "/verify_winner \n\n" "Пример:\n" "/verify_winner AB12CD34 1" ) return verification_code = parts[1].upper() try: lottery_id = int(parts[2]) except ValueError: await message.answer("❌ lottery_id должен быть числом") return try: async with async_session_maker() as session: # Проверяем существование розыгрыша lottery = await LotteryService.get_lottery(session, lottery_id) if not lottery: await message.answer(f"❌ Розыгрыш #{lottery_id} не найден") return # Подтверждаем выигрыш winner = await WinnerNotificationService.verify_winner( session, verification_code=verification_code, lottery_id=lottery_id ) if not winner: await message.answer( f"❌ Выигрыш не найден\n\n" f"Возможные причины:\n" f"• Неверный код верификации\n" f"• Пользователь не является победителем в розыгрыше #{lottery_id}\n" f"• Выигрыш уже был подтвержден" ) return # Получаем пользователя user = await RegistrationService.get_user_by_verification_code(session, verification_code) text = "✅ Выигрыш подтвержден!\n\n" text += f"🎯 Розыгрыш: {lottery.title}\n" text += f"🏆 Место: {winner.place}\n" text += f"🎁 Приз: {winner.prize}\n\n" if user: text += f"👤 Победитель: {user.first_name}\n" text += f"🎫 Клубная карта: {user.club_card_number}\n" if user.phone: text += f"📱 Телефон: {user.phone}\n" # Отправляем уведомление победителю try: bot = message.bot await bot.send_message( user.telegram_id, f"✅ Ваш выигрыш подтвержден!\n\n" f"🎯 Розыгрыш: {lottery.title}\n" f"🏆 Место: {winner.place}\n" f"🎁 Приз: {winner.prize}\n\n" f"Администратор свяжется с вами для получения приза." ) text += "\n📨 Победителю отправлено уведомление" except Exception as e: text += f"\n⚠️ Не удалось отправить уведомление: {str(e)}" if winner.account_number: text += f"💳 Счет: {winner.account_number}\n" await message.answer(text) except Exception as e: await message.answer(f"❌ Ошибка: {str(e)}") @router.message(Command("winner_status")) async def winner_status_command(message: Message): """ Показать статус всех победителей розыгрыша Формат: /winner_status """ if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return parts = message.text.split() if len(parts) != 2: await message.answer( "❌ Неверный формат команды\n\n" "Используйте: /winner_status " ) return try: lottery_id = int(parts[1]) except ValueError: await message.answer("❌ lottery_id должен быть числом") return try: async with async_session_maker() as session: lottery = await LotteryService.get_lottery(session, lottery_id) if not lottery: await message.answer(f"❌ Розыгрыш #{lottery_id} не найден") return winners = await LotteryService.get_winners(session, lottery_id) if not winners: await message.answer(f"В розыгрыше '{lottery.title}' пока нет победителей") return text = f"🏆 Победители розыгрыша '{lottery.title}':\n\n" for winner in winners: status_icon = "✅" if winner.is_claimed else "⏳" notified_icon = "📨" if winner.is_notified else "📭" text += f"{status_icon} {winner.place} место - {winner.prize}\n" # Получаем информацию о победителе async with async_session_maker() as session: if winner.user_id: user_result = await session.execute( select(User).where(User.id == winner.user_id) ) user = user_result.scalar_one_or_none() if user: text += f" 👤 {user.first_name}" if user.club_card_number: text += f" (КК: {user.club_card_number})" text += "\n" if winner.account_number: text += f" 💳 {winner.account_number}\n" # Статус подтверждения if winner.is_claimed: text += f" ✅ Подтвержден\n" else: text += f" ⏳ Ожидает подтверждения\n" text += "\n" await message.answer(text) except Exception as e: await message.answer(f"❌ Ошибка: {str(e)}") @router.message(Command("user_info")) async def user_info_command(message: Message): """ Показать информацию о пользователе Формат: /user_info """ if not is_admin(message.from_user.id): await message.answer("❌ Недостаточно прав") return parts = message.text.split() if len(parts) != 2: await message.answer( "❌ Неверный формат команды\n\n" "Используйте: /user_info " ) return club_card = parts[1] try: async with async_session_maker() as session: user = await RegistrationService.get_user_by_club_card(session, club_card) if not user: await message.answer(f"❌ Пользователь с клубной картой {club_card} не найден") return # Получаем счета accounts = await AccountService.get_user_accounts(session, user.id) # Получаем выигрыши winners_result = await session.execute( select(Winner).where(Winner.user_id == user.id) ) winners = winners_result.scalars().all() text = f"👤 Информация о пользователе\n\n" text += f"🎫 Клубная карта: {user.club_card_number}\n" text += f"👤 Имя: {user.first_name}" if user.last_name: text += f" {user.last_name}" text += "\n" if user.username: text += f"📱 Telegram: @{user.username}\n" if user.phone: text += f"📞 Телефон: {user.phone}\n" text += f"🔑 Код верификации: {user.verification_code}\n" text += f"📅 Зарегистрирован: {user.created_at.strftime('%d.%m.%Y')}\n\n" # Счета text += f"💳 Счета ({len(accounts)}):\n" if accounts: for acc in accounts: status = "✅" if acc.is_active else "❌" text += f" {status} {acc.account_number}\n" else: text += " Нет счетов\n" # Выигрыши text += f"\n🏆 Выигрыши ({len(winners)}):\n" if winners: for w in winners: status = "✅" if w.is_claimed else "⏳" text += f" {status} {w.place} место - {w.prize}\n" else: text += " Нет выигрышей\n" await message.answer(text) except Exception as e: await message.answer(f"❌ Ошибка: {str(e)}")