feat: Система автоматического подтверждения выигрышей с поддержкой множественных счетов
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:
2025-11-16 14:01:30 +09:00
parent 31c4c5382a
commit 505d26f0e9
21 changed files with 4217 additions and 68 deletions

261
docs/REGISTRATION_SYSTEM.md Normal file
View File

@@ -0,0 +1,261 @@
# Система регистрации и верификации выигрышей
## Архитектура
### Модели данных
#### 1. User (Расширенная)
- `club_card_number` - номер клубной карты (уникальный идентификатор клиента)
- `phone` - телефон для связи
- `is_registered` - прошел ли полную регистрацию
- `verification_code` - секретный код для подтверждения выигрыша (генерируется автоматически)
#### 2. Account (Новая)
- `account_number` - номер счета в формате XX-XX-XX-XX-XX-XX-XX
- `owner_id` - владелец счета (связь с User через club_card_number)
- `is_active` - активен ли счет
#### 3. WinnerVerification (Новая)
- `winner_id` - связь с Winner
- `verification_token` - токен для подтверждения выигрыша
- `is_verified` - подтвержден ли выигрыш
- `expires_at` - срок действия токена (24 часа)
## Процессы
### 1. Регистрация пользователя
**Инициатор:** Обычный пользователь через бота
**Шаги:**
1. Пользователь отправляет `/start`
2. Бот проверяет `is_registered`
3. Если `False` - запрашивает:
- Номер клубной карты
- Телефон (опционально)
4. Генерируется `verification_code` (8-символьный уникальный код)
5. Код показывается пользователю: "Ваш код верификации: **AB12CD34**. Сохраните его!"
6. `is_registered = True`
### 2. Создание счета администратором
**Инициатор:** Администратор
**Формат команды:**
```
/add_account 2223 11-22-33-44-55-66-77
```
**Процесс:**
1. Админ отправляет команду с номером КК и счетом
2. Бот находит пользователя по `club_card_number = 2223`
3. Создается запись в таблице `accounts`:
```python
Account(
account_number="11-22-33-44-55-66-77",
owner_id=user.id,
is_active=True
)
```
4. Пользователю отправляется уведомление:
```
К вашему профилю добавлен счет:
11-22-33-44-55-66-77
```
### 3. Проведение розыгрыша с уведомлением победителей
**Процесс:**
1. Администратор проводит розыгрыш
2. Выбирается случайный счет (например, `11-22-33-44-55-66-77`)
3. Система ищет владельца счета:
```python
account = Account.query.filter_by(account_number="11-22-33-44-55-66-77").first()
user = account.owner if account else None
```
4. Создается запись `Winner` и `WinnerVerification`:
```python
winner = Winner(
lottery_id=lottery.id,
user_id=user.id if user else None,
account_number="11-22-33-44-55-66-77",
place=1,
prize="iPhone 15"
)
verification = WinnerVerification(
winner_id=winner.id,
verification_token=generate_token(),
expires_at=now + 24hours
)
```
5. Если `user` найден - отправляется личное сообщение:
```
🎉 ПОЗДРАВЛЯЕМ!
Вы выиграли 1 место в розыгрыше "Новогодний розыгрыш"!
🏆 Приз: iPhone 15
📋 Ваш выигрышный счет: 11-22-33-44-55-66-77
✅ Для подтверждения выигрыша:
1. Свяжитесь с администратором @admin_username
2. Сообщите ваш код верификации: AB12CD34
3. Администратор подтвердит ваш выигрыш в системе
⏰ Срок подтверждения: 24 часа
```
### 4. Верификация выигрыша администратором
**Сценарий:**
1. Победитель связывается с админом в личных сообщениях
2. Сообщает код верификации: `AB12CD34`
3. Админ проверяет код в боте:
```
/verify_winner AB12CD34 1
```
где `1` - ID розыгрыша
4. Система:
- Находит пользователя по `verification_code = "AB12CD34"`
- Проверяет, что у него есть выигрыш в розыгрыше #1
- Помечает `winner.is_claimed = True`
- Обновляет `verification.is_verified = True`
5. Победителю отправляется:
```
✅ Ваш выигрыш подтвержден!
Администратор свяжется с вами для получения приза.
```
## Дополнительные механизмы безопасности
### 1. Двухфакторная верификация (опционально)
Можно добавить отправку одноразового кода на телефон победителя.
### 2. Ограничение по времени
Токены верификации действуют 24 часа. После истечения срока требуется повторная генерация.
### 3. Логирование
Все действия с выигрышами логируются:
- Создание выигрыша
- Отправка уведомления
- Подтверждение администратором
- Получение приза
## API для админа
### Команды бота
#### `/add_account <club_card> <account_number>`
Добавить счет к профилю клиента
```
/add_account 2223 11-22-33-44-55-66-77
```
#### `/remove_account <account_number>`
Деактивировать счет
```
/remove_account 11-22-33-44-55-66-77
```
#### `/verify_winner <verification_code> <lottery_id>`
Подтвердить выигрыш победителя
```
/verify_winner AB12CD34 1
```
#### `/winner_status <lottery_id>`
Показать статус всех победителей розыгрыша
```
/winner_status 1
Результаты:
1 место - @username (КК: 2223) ✅ Подтвержден
2 место - Счет: 33-44-55-66-77-88-99 ⏳ Ожидает подтверждения
3 место - @user2 (КК: 4445) ❌ Не подтвержден (истек срок)
```
## Аналитика и отчеты
### Запросы для аналитики
```sql
-- Победители по клубным картам
SELECT
u.club_card_number,
u.first_name,
COUNT(w.id) as total_wins,
SUM(CASE WHEN w.is_claimed THEN 1 ELSE 0 END) as claimed_wins
FROM users u
JOIN winners w ON w.user_id = u.id
GROUP BY u.id;
-- Счета с наибольшим количеством участий
SELECT
a.account_number,
u.club_card_number,
COUNT(p.id) as participation_count
FROM accounts a
JOIN users u ON u.id = a.owner_id
JOIN participations p ON p.account_id = a.id
GROUP BY a.id, u.id
ORDER BY participation_count DESC;
-- Невостребованные выигрыши
SELECT
l.title,
w.place,
w.prize,
w.account_number,
u.club_card_number
FROM winners w
JOIN lotteries l ON l.id = w.lottery_id
LEFT JOIN users u ON u.id = w.user_id
WHERE w.is_claimed = false
AND w.created_at < NOW() - INTERVAL '24 hours';
```
## Миграция существующих данных
Если у вас уже есть данные в старой схеме:
```python
# Миграция старых account_number из users в таблицу accounts
async def migrate_accounts():
users = await session.execute(select(User).where(User.account_number != None))
for user in users.scalars():
account = Account(
account_number=user.account_number,
owner_id=user.id,
is_active=True
)
session.add(account)
user.account_number = None # Очищаем старое поле
await session.commit()
```
## Тестирование
### Сценарий 1: Полный цикл выигрыша
1. Регистрация пользователя (КК: 2223)
2. Админ добавляет счет: `/add_account 2223 11-22-33-44-55-66-77`
3. Счет участвует в розыгрыше
4. Счет выигрывает 1 место
5. Пользователю приходит уведомление с кодом
6. Пользователь связывается с админом, сообщает код
7. Админ подтверждает: `/verify_winner AB12CD34 1`
8. Выигрыш получен ✅
### Сценарий 2: Выигрыш незарегистрированного счета
1. Админ добавляет счет без владельца
2. Счет участвует и выигрывает
3. Бот показывает в публичном объявлении: "Счет 11-22-33-44-55-66-77 выиграл!"
4. Владелец счета регистрируется в боте
5. Админ привязывает счет к владельцу
6. Дальше стандартная процедура верификации