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

View File

@@ -0,0 +1,279 @@
# Подтверждение выигрышей по счетам
## 🎯 Концепция
Система подтверждения выигрышей работает **на уровне счетов**, а не пользователей.
### Основные принципы:
1. **Один пользователь = Много счетов**
- У клиента может быть несколько счетов (11-22-33..., 44-55-66..., и т.д.)
- Каждый счет участвует в розыгрыше независимо
2. **Один счет = Один выигрыш = Одно подтверждение**
- Если счет `11-22-33-44-55-66-77` выиграл 1 место - требуется подтверждение
- Если счет `44-55-66-77-88-99-00` выиграл 3 место - требуется отдельное подтверждение
- Даже если оба счета принадлежат одному клиенту
3. **Независимое подтверждение**
- Каждый выигрышный счет подтверждается отдельной кнопкой
- Подтверждение одного счета не влияет на другие
- У каждого счета свой 24-часовой лимит
## 📱 Как это работает для пользователя
### Сценарий: У клиента 3 счета, выиграли 2
**Счета клиента:**
- `11-22-33-44-55-66-77` ✅ Выиграл 1 место (iPhone 15)
- `22-33-44-55-66-77-88`Не выиграл
- `33-44-55-66-77-88-99` ✅ Выиграл 3 место (AirPods Pro)
**Клиент получает 2 сообщения:**
### Сообщение 1:
```
🎉 Поздравляем! Ваш счет выиграл!
🎯 Розыгрыш: Новогодний розыгрыш
🏆 Место: 1
🎁 Приз: iPhone 15
💳 Выигрышный счет: 11-22-33-44-55-66-77
У вас есть 24 часа для подтверждения!
Нажмите кнопку ниже, чтобы подтвердить получение приза по этому счету.
Если вы не подтвердите в течение 24 часов, приз будет разыгран заново.
Если у вас несколько выигрышных счетов, подтвердите каждый из них отдельно.
[✅ Подтвердить счет 11-22-33-44-55-66-77]
[📞 Связаться с администратором]
```
### Сообщение 2:
```
🎉 Поздравляем! Ваш счет выиграл!
🎯 Розыгрыш: Новогодний розыгрыш
🏆 Место: 3
🎁 Приз: AirPods Pro
💳 Выигрышный счет: 33-44-55-66-77-88-99
У вас есть 24 часа для подтверждения!
[✅ Подтвердить счет 33-44-55-66-77-88-99]
[📞 Связаться с администратором]
```
### Действия клиента:
1. **Нажимает первую кнопку** → Счет `11-22-33-44-55-66-77` подтвержден ✅
2. **Нажимает вторую кнопку** → Счет `33-44-55-66-77-88-99` подтвержден ✅
## 🔒 Безопасность
### Проверка владения счетом
При нажатии кнопки подтверждения система проверяет:
```python
# Получаем владельца КОНКРЕТНОГО счета
owner = await AccountService.get_account_owner(session, winner.account_number)
# Проверяем что текущий пользователь - владелец ЭТОГО счета
if not owner or owner.telegram_id != callback.from_user.id:
await callback.answer(
f"❌ Счет {winner.account_number} вам не принадлежит",
show_alert=True
)
return
```
### Что НЕ может сделать пользователь:
❌ Подтвердить чужой счет
❌ Подтвердить счет, который ему не принадлежит
❌ Подтвердить один счет дважды
### Что может сделать пользователь:
✅ Подтвердить только свои счета
✅ Подтвердить каждый свой выигрышный счет отдельно
✅ Видеть номер счета на каждой кнопке
## 🎊 После подтверждения
### Сообщение пользователю:
```
✅ Выигрыш успешно подтвержден!
🎯 Розыгрыш: Новогодний розыгрыш
🏆 Место: 1
🎁 Приз: iPhone 15
💳 Счет: 11-22-33-44-55-66-77
🎊 Поздравляем! Администратор свяжется с вами
для передачи приза в ближайшее время.
Спасибо за участие!
```
### Уведомление администратору:
```
✅ Победитель подтвердил получение приза!
🎯 Розыгрыш: Новогодний розыгрыш
🏆 Место: 1
🎁 Приз: iPhone 15
💳 Подтвержденный счет: 11-22-33-44-55-66-77
👤 Владелец: Иван Петров (@ivan)
🎫 Клубная карта: 2223
📱 Телефон: +7 900 123-45-67
```
## 📊 База данных
### Таблица `winners`
Каждая запись = один выигрышный счет:
```sql
id | lottery_id | account_number | place | prize | is_claimed | claimed_at
---|------------|-----------------------|-------|------------|------------|------------------
1 | 5 | 11-22-33-44-55-66-77 | 1 | iPhone 15 | TRUE | 2025-11-16 14:00
2 | 5 | 33-44-55-66-77-88-99 | 3 | AirPods | TRUE | 2025-11-16 14:05
3 | 5 | 55-66-77-88-99-00-11 | 2 | MacBook | FALSE | NULL
```
### Ключевые поля:
- `account_number` - конкретный выигрышный счет
- `is_claimed` - подтвержден ли ЭТОТ счет
- `claimed_at` - когда ЭТОТ счет был подтвержден
## 🔄 Повторный розыгрыш
Если счет не подтвержден в течение 24 часов:
1. **Админ проверяет**: `/check_unclaimed 5`
```
⚠️ Неподтвержденные выигрыши:
🏆 2 место - MacBook
💳 55-66-77-88-99-00-11
⏰ Прошло: 26 часов
```
2. **Админ переигрывает**: `/redraw 5`
- Удаляется Winner с `account_number = 55-66-77-88-99-00-11`
- Выбирается новый случайный счет
- Новому владельцу счета отправляется уведомление
- Новый владелец получает свои 24 часа
## 💡 Преимущества подхода
### ✅ Для пользователей:
1. **Понятность** - видят конкретный номер счета на кнопке
2. **Контроль** - могут подтверждать счета независимо
3. **Гибкость** - один счет подтвердил, другой нет (если забыл)
### ✅ Для администраторов:
1. **Точность** - знают какой именно счет подтвержден
2. **Прозрачность** - видят все действия по каждому счету
3. **Справедливость** - переиграть можно конкретный неподтвержденный счет
### ✅ Для системы:
1. **Масштабируемость** - неограниченное количество счетов у одного пользователя
2. **Независимость** - каждый счет живет своей жизнью
3. **Целостность** - нет конфликтов между разными выигрышами
## 🔍 Примеры использования
### Пример 1: Один пользователь, два выигрыша
```
Клиент: Иван Петров (КК: 2223)
Счета:
- 11-22-33-44-55-66-77 → Выиграл 1 место ✅ Подтвержден
- 22-33-44-55-66-77-88 → Выиграл 3 место ✅ Подтвержден
Результат: Иван получит 2 приза
```
### Пример 2: Частичное подтверждение
```
Клиент: Мария Сидорова (КК: 3334)
Счета:
- 33-44-55-66-77-88-99 → Выиграл 2 место ✅ Подтвержден
- 44-55-66-77-88-99-00 → Выиграл 4 место ❌ Не подтвержден (> 24ч)
Результат:
- Мария получит приз за 2 место
- 4 место будет переиграно
```
### Пример 3: Полная неявка
```
Клиент: Петр Иванов (КК: 5556)
Счета:
- 55-66-77-88-99-00-11 → Выиграл 1 место ❌ Не подтвержден
- 66-77-88-99-00-11-22 → Выиграл 2 место ❌ Не подтвержден
Результат: Оба места будут переиграны отдельно
```
## 📝 Технические детали
### Callback данные
```python
# Формат: confirm_win_{winner_id}
callback_data = f"confirm_win_{winner.id}"
# winner.id - уникальный ID записи в таблице winners
# Каждый счет-выигрыш имеет свой winner_id
```
### Проверка при подтверждении
```python
# 1. Получаем winner по ID
winner = await session.get(Winner, winner_id)
# 2. Проверяем что счет принадлежит пользователю
owner = await AccountService.get_account_owner(session, winner.account_number)
if owner.telegram_id != current_user_id:
return "Не ваш счет"
# 3. Подтверждаем ЭТОТ счет
winner.is_claimed = True
winner.claimed_at = datetime.now(timezone.utc)
await session.commit()
```
## 🎓 Выводы
Подход "счет = выигрыш" обеспечивает:
- 🎯 **Точность** - подтверждается конкретный счет, а не абстрактный выигрыш
- 🔒 **Безопасность** - только владелец счета может подтвердить
- 📊 **Масштабируемость** - неограниченное количество счетов и выигрышей
- 👥 **Справедливость** - каждый счет обрабатывается независимо
- 💡 **Прозрачность** - всегда понятно какой именно счет подтверждается
---
## 📞 См. также
- `AUTO_CONFIRM_SYSTEM.md` - Полная документация системы подтверждения
- `REGISTRATION_SYSTEM.md` - Система регистрации счетов
- `ADMIN_GUIDE.md` - Руководство администратора