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

439
docs/AUTO_CONFIRM_SYSTEM.md Normal file
View File

@@ -0,0 +1,439 @@
# Система автоматического подтверждения и повторного розыгрыша
## 🎯 Обзор системы
Реализована полная система автоматического подтверждения выигрышей с возможностью повторного розыгрыша для неподтвержденных призов.
---
## 🔄 Как это работает
### 1. Победитель получает уведомление
После проведения розыгрыша победителю автоматически отправляется сообщение с **интерактивной кнопкой**:
```
🎉 Поздравляем! Ваш счет выиграл!
🎯 Розыгрыш: Новогодний розыгрыш
🏆 Место: 1
🎁 Приз: Главный приз
💳 Счет: 11-22-33-44-55-66-77
У вас есть 24 часа для подтверждения!
Нажмите кнопку ниже, чтобы подтвердить получение приза.
Если вы не подтвердите в течение 24 часов, приз будет разыгран заново.
[✅ Подтвердить получение приза]
[📞 Связаться с администратором]
```
### 2. Победитель подтверждает выигрыш
Пользователь нажимает кнопку "✅ Подтвердить получение приза":
**Что происходит:**
- В БД устанавливается `is_claimed = True`
- Сохраняется время подтверждения `claimed_at`
- Победитель видит подтверждение:
```
✅ Выигрыш успешно подтвержден!
🎯 Розыгрыш: Новогодний розыгрыш
🏆 Место: 1
🎁 Приз: Главный приз
🎊 Поздравляем! Администратор свяжется с вами
для передачи приза в ближайшее время.
Спасибо за участие!
```
**Администраторы получают уведомление:**
```
✅ Победитель подтвердил получение приза!
🎯 Розыгрыш: Новогодний розыгрыш
🏆 Место: 1
🎁 Приз: Главный приз
👤 Победитель: Иван (@ivan)
🎫 Клубная карта: 2223
📱 Телефон: +7 900 123-45-67
💳 Счет: 11-22-33-44-55-66-77
```
### 3. Если победитель не подтверждает (24 часа)
Администратор проверяет неподтвержденные выигрыши:
```
/check_unclaimed 1
```
**Ответ бота:**
```
⚠️ Неподтвержденные выигрыши в розыгрыше 'Новогодний розыгрыш':
🏆 1 место - Главный приз
👤 Иван (КК: 2223)
💳 11-22-33-44-55-66-77
⏰ Прошло: 26 часов
🏆 3 место - Третий приз
👤 Петр (КК: 3334)
💳 22-33-44-55-66-77-88
⏰ Прошло: 30 часов
📊 Всего неподтвержденных: 2
Используйте /redraw 1 для повторного розыгрыша
```
### 4. Повторный розыгрыш
Администратор запускает переигровку:
```
/redraw 1
```
**Что происходит:**
1. **Система находит неподтвержденные выигрыши** (старше 24 часов)
2. **Получает пул участников** (исключая текущих победителей)
3. **Случайно выбирает новых победителей** для каждого неподтвержденного места
4. **Удаляет старых победителей** из БД
5. **Создает новых победителей**
6. **Отправляет уведомления** новым победителям (с кнопкой подтверждения)
**Результат для администратора:**
```
🔄 Повторный розыгрыш завершен!
🎯 Розыгрыш: Новогодний розыгрыш
📊 Переиграно мест: 2
🏆 1 место - Главный приз
❌ Было: 11-22-33-44-55-66-77
✅ Стало: 99-88-77-66-55-44-33
🏆 3 место - Третий приз
❌ Было: 22-33-44-55-66-77-88
✅ Стало: 12-34-56-78-90-12-34
📨 Новым победителям отправлены уведомления
```
**Новые победители получают** то же уведомление с кнопкой подтверждения и 24-часовым лимитом.
---
## 📋 Админские команды
### `/check_unclaimed <lottery_id>`
Проверить неподтвержденные выигрыши старше 24 часов.
**Пример:**
```
/check_unclaimed 1
```
**Показывает:**
- Список всех неподтвержденных выигрышей
- Информацию о победителях
- Сколько времени прошло с момента уведомления
### `/redraw <lottery_id>`
Переиграть розыгрыш для неподтвержденных выигрышей.
**Пример:**
```
/redraw 1
```
**Требования:**
- Должны быть неподтвержденные выигрыши старше 24 часов
- Должны быть доступные участники (не победители)
---
## 🗄️ Изменения в базе данных
### Таблица `winners`
Добавлено новое поле:
- `claimed_at` (TIMESTAMP) - время подтверждения выигрыша победителем
### Миграция
Файл: `migrations/versions/004_add_claimed_at.py`
Применить:
```bash
alembic upgrade head
```
---
## 🔧 Технические детали
### Обработчик подтверждения
**Файл:** `main.py`
**Callback:** `confirm_win_{winner_id}`
**Функция:** `confirm_winner_response()`
**Логика:**
1. Проверяет существование выигрыша
2. Проверяет, что подтверждает владелец
3. Устанавливает `is_claimed = True` и `claimed_at = now()`
4. Обновляет сообщение победителя
5. Уведомляет всех администраторов
### Система уведомлений
**Функция:** `notify_winners_async()`
**Изменения:**
- Добавлена кнопка "✅ Подтвердить получение приза"
- Добавлено предупреждение о 24-часовом лимите
- Добавлена кнопка "📞 Связаться с администратором"
### Повторный розыгрыш
**Файл:** `src/handlers/redraw_handlers.py`
**Команды:**
- `check_unclaimed_winners()` - проверка
- `redraw_lottery()` - переигровка
**Алгоритм:**
1. Получает всех победителей розыгрыша
2. Фильтрует неподтвержденных (is_claimed=False, is_notified=True, >24ч)
3. Получает пул участников (исключая победителей)
4. Для каждого неподтвержденного:
- Выбирает случайного участника
- Удаляет старого победителя
- Создает нового победителя
- Отправляет уведомление
---
## 📊 Состояния выигрыша
### Таймлайн жизни выигрыша:
```
1. Розыгрыш проведен
├─ is_notified: False
├─ is_claimed: False
└─ claimed_at: NULL
2. Уведомление отправлено
├─ is_notified: True
├─ is_claimed: False
└─ claimed_at: NULL
3А. Победитель подтвердил (успех)
├─ is_notified: True
├─ is_claimed: True
└─ claimed_at: 2025-11-16 13:00:00
3Б. Прошло 24 часа (неудача)
├─ is_notified: True
├─ is_claimed: False
├─ claimed_at: NULL
└─ ⏰ time_passed > 24h → переигровка
4. После переигровки (новый победитель)
├─ Старый победитель удален
├─ Создан новый победитель
├─ is_notified: True (после отправки)
├─ is_claimed: False
└─ claimed_at: NULL → снова 24 часа
```
---
## ⚠️ Важные моменты
### Безопасность
1. **Проверка владельца**: Только владелец счета может подтвердить выигрыш
2. **Повторное подтверждение**: Если выигрыш уже подтвержден, показывается соответствующее сообщение
3. **Права администратора**: Только админы могут запускать `/redraw`
### Ограничения
1. **24-часовой лимит**: Жестко закодирован, но легко изменить в коде
2. **Пул участников**: Если все участники уже победители, переигровка невозможна
3. **Уникальность**: Один счет не может выиграть дважды в одном розыгрыше
### Отказоустойчивость
1. **Уведомления**: Если не удается отправить - продолжает работу
2. **Транзакции**: Все изменения в БД атомарны
3. **Логирование**: Все действия записываются в лог
---
## 🎯 Типичные сценарии
### Сценарий 1: Все подтвердили
```
1. Розыгрыш → 3 победителя
2. Все 3 нажали кнопку подтверждения
3. Админ: /check_unclaimed 1
→ "✅ Все победители подтвердили выигрыш"
4. Приз передается всем победителям
```
### Сценарий 2: Один не подтвердил
```
1. Розыгрыш → 3 победителя
2. 2 подтвердили, 1 игнорирует
3. Через 25 часов админ: /check_unclaimed 1
→ "⚠️ 1 неподтвержденный выигрыш"
4. Админ: /redraw 1
5. Система переигрывает 1 место
6. Новый победитель получает уведомление
7. У нового победителя снова 24 часа
```
### Сценарий 3: Множественная переигровка
```
1. Розыгрыш → 5 победителей
2. 3 подтвердили, 2 игнорируют
3. Через 25 часов: /redraw 1
4. 2 новых победителя выбраны
5. Один из новых тоже игнорирует
6. Через 25 часов: /redraw 1 снова
7. Выбран еще один новый победитель
```
---
## 📈 Статистика и мониторинг
### Проверка статуса
```
/winner_status 1
```
Покажет:
- ✅ Подтвержденные выигрыши (с is_claimed=True)
- ⏳ Ожидающие подтверждения (с is_claimed=False)
- 📨 Статус уведомления (is_notified)
### SQL запросы для админа
**Найти все неподтвержденные старше 24 часов:**
```sql
SELECT w.*, l.title
FROM winners w
JOIN lotteries l ON w.lottery_id = l.id
WHERE w.is_notified = TRUE
AND w.is_claimed = FALSE
AND w.created_at < NOW() - INTERVAL '24 hours';
```
**Статистика по подтверждениям:**
```sql
SELECT
lottery_id,
COUNT(*) as total_winners,
SUM(CASE WHEN is_claimed THEN 1 ELSE 0 END) as confirmed,
SUM(CASE WHEN NOT is_claimed THEN 1 ELSE 0 END) as unconfirmed
FROM winners
WHERE is_notified = TRUE
GROUP BY lottery_id;
```
---
## 🚀 Использование
### Для администратора
1. **После розыгрыша**: Ничего не делать - система отправит уведомления
2. **Через 24-30 часов**: Проверить `/check_unclaimed <id>`
3. **Если есть неподтвержденные**: Запустить `/redraw <id>`
4. **Повторить** при необходимости
### Для победителя
1. **Получить уведомление** с кнопкой
2. **Нажать "✅ Подтвердить"**
3. **Дождаться связи с админом**
4. **Получить приз**
---
## 💡 Рекомендации
### Оптимальные настройки
- **Лимит подтверждения**: 24 часа (можно увеличить до 48ч)
- **Частота проверки**: 1-2 раза в день
- **Уведомления**: Включить push-уведомления в боте
### Коммуникация с пользователями
После розыгрыша отправьте общее сообщение:
```
🎉 Розыгрыш завершен!
Если вы выиграли, вам придет сообщение с кнопкой подтверждения.
У вас будет 24 часа чтобы подтвердить выигрыш!
Если не подтвердите - приз будет переигран.
```
---
## 🔮 Возможные улучшения
1. **Напоминания**: Отправка напоминания за 2 часа до истечения срока
2. **Гибкий лимит**: Разные лимиты для разных типов призов
3. **История**: Логирование всех переигровок
4. **Статистика**: Процент подтверждений, среднее время подтверждения
5. **Автоматизация**: Cron-задача для автоматической переигровки
---
## ✅ Преимущества системы
- 🤖 **Полная автоматизация**: Минимум ручной работы для админа
- ⏱️ **Справедливость**: Четкий дедлайн для всех
- 🔄 **Эффективность**: Призы не "зависают" у неактивных победителей
- 📊 **Прозрачность**: Полная история всех действий
- 🛡️ **Безопасность**: Только владелец может подтвердить
- 💬 **UX**: Простая кнопка вместо сложной верификации
---
## 📞 Поддержка
Если что-то пошло не так:
1. Проверьте логи бота
2. Проверьте состояние БД (claimed_at, is_notified, is_claimed)
3. Используйте `/winner_status <id>` для диагностики
4. При критических ошибках - используйте `/verify_winner` для ручного подтверждения