feat: Система автоматического подтверждения выигрышей с поддержкой множественных счетов
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Основные изменения: ✨ Новые функции: - Система регистрации пользователей с множественными счетами - Автоматическое подтверждение выигрышей через inline-кнопки - Механизм переигровки для неподтвержденных выигрышей (24 часа) - Подтверждение на уровне счетов (каждый счет подтверждается отдельно) - Скрипт полной очистки базы данных 🔧 Технические улучшения: - Исправлена ошибка MissingGreenlet при lazy loading (добавлен joinedload/selectinload) - Добавлено поле claimed_at для отслеживания времени подтверждения - Пакетное добавление счетов с выбором розыгрыша - Проверка владения конкретным счетом при подтверждении 📚 Документация: - docs/AUTO_CONFIRM_SYSTEM.md - Полная документация системы подтверждения - docs/ACCOUNT_BASED_CONFIRMATION.md - Подтверждение на уровне счетов - docs/REGISTRATION_SYSTEM.md - Система регистрации - docs/ADMIN_COMMANDS.md - Команды администратора - docs/CLEAR_DATABASE.md - Очистка БД - docs/QUICK_GUIDE.md - Быстрое начало - docs/UPDATE_LOG.md - Журнал обновлений 🗄️ База данных: - Миграция 003: Таблицы accounts, winner_verifications - Миграция 004: Поле claimed_at в таблице winners - Скрипт scripts/clear_database.py для полной очистки 🎮 Новые команды: Админские: - /check_unclaimed <lottery_id> - Проверка неподтвержденных выигрышей - /redraw <lottery_id> - Повторный розыгрыш - /add_accounts - Пакетное добавление счетов - /list_accounts <telegram_id> - Список счетов пользователя Пользовательские: - /register - Регистрация с вводом данных - /my_account - Просмотр своих счетов - Callback confirm_win_{id} - Подтверждение выигрыша 🛠️ Makefile: - make clear-db - Очистка всех данных из БД (с подтверждением) 🔒 Безопасность: - Проверка владения счетом при подтверждении - Защита от подтверждения чужих счетов - Независимое подтверждение каждого выигрышного счета 📊 Логика работы: 1. Пользователь регистрируется и добавляет счета 2. Счета участвуют в розыгрыше 3. Победители получают уведомление с кнопкой подтверждения 4. Каждый счет подтверждается отдельно (24 часа на подтверждение) 5. Неподтвержденные выигрыши переигрываются через /redraw
This commit is contained in:
150
src/handlers/registration_handlers.py
Normal file
150
src/handlers/registration_handlers.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""Обработчики для регистрации пользователей"""
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.filters import Command, StateFilter
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
|
||||
from src.core.database import async_session_maker
|
||||
from src.core.registration_services import RegistrationService, AccountService
|
||||
from src.core.services import UserService
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
class RegistrationStates(StatesGroup):
|
||||
"""Состояния для процесса регистрации"""
|
||||
waiting_for_club_card = State()
|
||||
waiting_for_phone = State()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "start_registration")
|
||||
async def start_registration(callback: CallbackQuery, state: FSMContext):
|
||||
"""Начать процесс регистрации"""
|
||||
text = (
|
||||
"📝 Регистрация в системе\n\n"
|
||||
"Для участия в розыгрышах необходимо зарегистрироваться.\n\n"
|
||||
"Введите номер вашей клубной карты:"
|
||||
)
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="❌ Отмена", callback_data="back_to_main")]
|
||||
])
|
||||
)
|
||||
await state.set_state(RegistrationStates.waiting_for_club_card)
|
||||
|
||||
|
||||
@router.message(StateFilter(RegistrationStates.waiting_for_club_card))
|
||||
async def process_club_card(message: Message, state: FSMContext):
|
||||
"""Обработка номера клубной карты"""
|
||||
club_card_number = message.text.strip()
|
||||
|
||||
# Проверяем, не занята ли карта
|
||||
async with async_session_maker() as session:
|
||||
existing_user = await RegistrationService.get_user_by_club_card(session, club_card_number)
|
||||
|
||||
if existing_user:
|
||||
await message.answer(
|
||||
f"❌ Клубная карта {club_card_number} уже зарегистрирована.\n\n"
|
||||
"Если это ваша карта, обратитесь к администратору."
|
||||
)
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
await state.update_data(club_card_number=club_card_number)
|
||||
|
||||
await message.answer(
|
||||
"📱 Теперь введите ваш номер телефона\n"
|
||||
"(или отправьте '-' чтобы пропустить):"
|
||||
)
|
||||
await state.set_state(RegistrationStates.waiting_for_phone)
|
||||
|
||||
|
||||
@router.message(StateFilter(RegistrationStates.waiting_for_phone))
|
||||
async def process_phone(message: Message, state: FSMContext):
|
||||
"""Обработка номера телефона"""
|
||||
phone = None if message.text.strip() == "-" else message.text.strip()
|
||||
|
||||
data = await state.get_data()
|
||||
club_card_number = data['club_card_number']
|
||||
|
||||
try:
|
||||
async with async_session_maker() as session:
|
||||
user = await RegistrationService.register_user(
|
||||
session,
|
||||
telegram_id=message.from_user.id,
|
||||
club_card_number=club_card_number,
|
||||
phone=phone
|
||||
)
|
||||
|
||||
text = (
|
||||
"✅ Регистрация завершена!\n\n"
|
||||
f"🎫 Клубная карта: {user.club_card_number}\n"
|
||||
f"🔑 Ваш код верификации: **{user.verification_code}**\n\n"
|
||||
"⚠️ Сохраните этот код! Он понадобится для подтверждения выигрыша.\n\n"
|
||||
"Теперь вы можете участвовать в розыгрышах!"
|
||||
)
|
||||
|
||||
await message.answer(text, parse_mode="Markdown")
|
||||
await state.clear()
|
||||
|
||||
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(Command("my_code"))
|
||||
async def show_verification_code(message: Message):
|
||||
"""Показать код верификации пользователя"""
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
|
||||
if not user or not user.is_registered:
|
||||
await message.answer(
|
||||
"❌ Вы не зарегистрированы в системе.\n\n"
|
||||
"Для регистрации отправьте /start и выберите 'Регистрация'"
|
||||
)
|
||||
return
|
||||
|
||||
text = (
|
||||
"🔑 Ваш код верификации:\n\n"
|
||||
f"**{user.verification_code}**\n\n"
|
||||
"Этот код используется для подтверждения выигрыша.\n"
|
||||
"Сообщите его администратору при получении приза."
|
||||
)
|
||||
|
||||
await message.answer(text, parse_mode="Markdown")
|
||||
|
||||
|
||||
@router.message(Command("my_accounts"))
|
||||
async def show_user_accounts(message: Message):
|
||||
"""Показать счета пользователя"""
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
|
||||
if not user or not user.is_registered:
|
||||
await message.answer("❌ Вы не зарегистрированы в системе")
|
||||
return
|
||||
|
||||
accounts = await AccountService.get_user_accounts(session, user.id)
|
||||
|
||||
if not accounts:
|
||||
await message.answer(
|
||||
"У вас пока нет привязанных счетов.\n\n"
|
||||
"Счета добавляются администратором."
|
||||
)
|
||||
return
|
||||
|
||||
text = f"💳 Ваши счета (Клубная карта: {user.club_card_number}):\n\n"
|
||||
|
||||
for i, account in enumerate(accounts, 1):
|
||||
status = "✅" if account.is_active else "❌"
|
||||
text += f"{i}. {status} {account.account_number}\n"
|
||||
|
||||
await message.answer(text)
|
||||
Reference in New Issue
Block a user