426 lines
18 KiB
Python
426 lines
18 KiB
Python
"""Команды для повторного розыгрыша неподтвержденных выигрышей"""
|
||
from aiogram import Router, F
|
||
from aiogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup
|
||
from aiogram.filters import Command
|
||
from sqlalchemy import select, and_
|
||
from datetime import datetime, timezone, timedelta
|
||
import random
|
||
|
||
from src.filters.case_insensitive import CaseInsensitiveCommand
|
||
from src.core.database import async_session_maker
|
||
from src.core.registration_services import AccountService, WinnerNotificationService
|
||
from src.core.services import LotteryService
|
||
from src.core.models import User, Winner
|
||
from src.core.config import ADMIN_IDS
|
||
from src.core.permissions import admin_only
|
||
|
||
|
||
router = Router()
|
||
|
||
|
||
@router.message(CaseInsensitiveCommand("check_unclaimed"))
|
||
@admin_only
|
||
async def check_unclaimed_winners(message: Message):
|
||
"""
|
||
Проверить неподтвержденные выигрыши (более 24 часов) (регистронезависимо)
|
||
Формат: /check_unclaimed <lottery_id>
|
||
"""
|
||
|
||
parts = message.text.split()
|
||
if len(parts) != 2:
|
||
await message.answer(
|
||
"❌ Неверный формат команды\n\n"
|
||
"Используйте: /check_unclaimed <lottery_id>"
|
||
)
|
||
return
|
||
|
||
try:
|
||
lottery_id = int(parts[1])
|
||
except ValueError:
|
||
await message.answer("❌ lottery_id должен быть числом")
|
||
return
|
||
|
||
try:
|
||
async with async_session_maker() as session:
|
||
from sqlalchemy.orm import selectinload
|
||
from src.core.models import Lottery
|
||
|
||
# Загружаем розыгрыш с участниками
|
||
lottery_result = await session.execute(
|
||
select(Lottery)
|
||
.options(selectinload(Lottery.participations))
|
||
.where(Lottery.id == lottery_id)
|
||
)
|
||
lottery = lottery_result.scalar_one_or_none()
|
||
|
||
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
|
||
|
||
# Находим неподтвержденные выигрыши старше 24 часов
|
||
now = datetime.now(timezone.utc)
|
||
unclaimed = []
|
||
|
||
for winner in winners:
|
||
if not winner.is_claimed and winner.is_notified:
|
||
# Проверяем, прошло ли 24 часа
|
||
time_passed = now - winner.created_at
|
||
if time_passed.total_seconds() > 24 * 3600: # 24 часа
|
||
unclaimed.append({
|
||
'winner': winner,
|
||
'hours_passed': int(time_passed.total_seconds() / 3600)
|
||
})
|
||
|
||
if not unclaimed:
|
||
await message.answer(
|
||
f"✅ Все победители розыгрыша '{lottery.title}' подтвердили выигрыш\n"
|
||
f"или срок подтверждения еще не истек."
|
||
)
|
||
return
|
||
|
||
text = f"⚠️ **Неподтвержденные выигрыши в розыгрыше '{lottery.title}':**\n\n"
|
||
|
||
for item in unclaimed:
|
||
winner = item['winner']
|
||
hours = item['hours_passed']
|
||
|
||
text += f"🏆 {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"
|
||
|
||
text += f" ⏰ Прошло: {hours} часов\n\n"
|
||
|
||
text += f"\n📊 Всего неподтвержденных: {len(unclaimed)}\n\n"
|
||
text += f"Используйте /redraw {lottery_id} для повторного розыгрыша"
|
||
|
||
await message.answer(text, parse_mode="Markdown")
|
||
|
||
except Exception as e:
|
||
await message.answer(f"❌ Ошибка: {str(e)}")
|
||
|
||
|
||
@router.message(CaseInsensitiveCommand("redraw"))
|
||
@admin_only
|
||
async def redraw_lottery(message: Message):
|
||
"""
|
||
Переиграть розыгрыш для неподтвержденных выигрышей (регистронезависимо)
|
||
Формат: /redraw <lottery_id>
|
||
"""
|
||
|
||
parts = message.text.split()
|
||
if len(parts) != 2:
|
||
await message.answer(
|
||
"❌ Неверный формат команды\n\n"
|
||
"Используйте: /redraw <lottery_id>"
|
||
)
|
||
return
|
||
|
||
try:
|
||
lottery_id = int(parts[1])
|
||
except ValueError:
|
||
await message.answer("❌ lottery_id должен быть числом")
|
||
return
|
||
|
||
try:
|
||
async with async_session_maker() as session:
|
||
from sqlalchemy.orm import selectinload
|
||
from src.core.models import Lottery
|
||
|
||
# Загружаем розыгрыш с участниками
|
||
lottery_result = await session.execute(
|
||
select(Lottery)
|
||
.options(selectinload(Lottery.participations))
|
||
.where(Lottery.id == lottery_id)
|
||
)
|
||
lottery = lottery_result.scalar_one_or_none()
|
||
|
||
if not lottery:
|
||
await message.answer(f"❌ Розыгрыш #{lottery_id} не найден")
|
||
return
|
||
|
||
winners = await LotteryService.get_winners(session, lottery_id)
|
||
|
||
# Находим неподтвержденные выигрыши старше 24 часов
|
||
now = datetime.now(timezone.utc)
|
||
unclaimed_winners = []
|
||
|
||
for winner in winners:
|
||
if not winner.is_claimed and winner.is_notified:
|
||
time_passed = now - winner.created_at
|
||
if time_passed.total_seconds() > 24 * 3600:
|
||
unclaimed_winners.append(winner)
|
||
|
||
if not unclaimed_winners:
|
||
await message.answer(
|
||
"✅ Нет неподтвержденных выигрышей старше 24 часов.\n"
|
||
"Повторный розыгрыш не требуется."
|
||
)
|
||
return
|
||
|
||
# Получаем всех участников, исключая текущих победителей
|
||
all_participants = []
|
||
current_winner_accounts = set()
|
||
|
||
for winner in winners:
|
||
if winner.account_number:
|
||
current_winner_accounts.add(winner.account_number)
|
||
|
||
for p in lottery.participations:
|
||
if p.account_number and p.account_number not in current_winner_accounts:
|
||
all_participants.append(p)
|
||
|
||
if not all_participants:
|
||
await message.answer(
|
||
"❌ Нет доступных участников для повторного розыгрыша.\n"
|
||
"Все участники уже являются победителями."
|
||
)
|
||
return
|
||
|
||
# Переигрываем каждое неподтвержденное место
|
||
redraw_results = []
|
||
|
||
for old_winner in unclaimed_winners:
|
||
if not all_participants:
|
||
break
|
||
|
||
# Выбираем нового победителя
|
||
new_participant = random.choice(all_participants)
|
||
all_participants.remove(new_participant)
|
||
|
||
# Удаляем старого победителя
|
||
await session.delete(old_winner)
|
||
|
||
# Создаем нового победителя
|
||
new_winner = Winner(
|
||
lottery_id=lottery_id,
|
||
user_id=None,
|
||
account_number=new_participant.account_number,
|
||
account_id=new_participant.account_id,
|
||
place=old_winner.place,
|
||
prize=old_winner.prize,
|
||
is_manual=False,
|
||
is_notified=False,
|
||
is_claimed=False
|
||
)
|
||
session.add(new_winner)
|
||
|
||
redraw_results.append({
|
||
'place': old_winner.place,
|
||
'prize': old_winner.prize,
|
||
'old_account': old_winner.account_number,
|
||
'new_account': new_participant.account_number
|
||
})
|
||
|
||
await session.commit()
|
||
|
||
# Отправляем уведомления новым победителям
|
||
for result in redraw_results:
|
||
# Находим нового победителя
|
||
new_winner_result = await session.execute(
|
||
select(Winner).where(
|
||
and_(
|
||
Winner.lottery_id == lottery_id,
|
||
Winner.place == result['place'],
|
||
Winner.account_number == result['new_account']
|
||
)
|
||
)
|
||
)
|
||
new_winner = new_winner_result.scalar_one_or_none()
|
||
|
||
if new_winner:
|
||
# Отправляем уведомление новому победителю
|
||
owner = await AccountService.get_account_owner(session, new_winner.account_number)
|
||
|
||
if owner and owner.telegram_id:
|
||
# Создаем токен верификации
|
||
await WinnerNotificationService.create_verification_token(
|
||
session,
|
||
new_winner.id
|
||
)
|
||
|
||
# Формируем сообщение
|
||
notification_message = (
|
||
f"🎉 Поздравляем! Ваш счет выиграл!\n\n"
|
||
f"🎯 Розыгрыш: {lottery.title}\n"
|
||
f"🏆 Место: {new_winner.place}\n"
|
||
f"🎁 Приз: {new_winner.prize}\n"
|
||
f"💳 Счет: {new_winner.account_number}\n\n"
|
||
f"⏰ **У вас есть 24 часа для подтверждения!**\n\n"
|
||
f"Нажмите кнопку ниже, чтобы подтвердить получение приза."
|
||
)
|
||
|
||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||
[InlineKeyboardButton(
|
||
text="✅ Подтвердить получение приза",
|
||
callback_data=f"confirm_win_{new_winner.id}"
|
||
)]
|
||
])
|
||
|
||
try:
|
||
await message.bot.send_message(
|
||
owner.telegram_id,
|
||
notification_message,
|
||
reply_markup=keyboard,
|
||
parse_mode="Markdown"
|
||
)
|
||
|
||
new_winner.is_notified = True
|
||
await session.commit()
|
||
except:
|
||
pass
|
||
|
||
# Формируем отчет для админа
|
||
text = f"🔄 **Повторный розыгрыш завершен!**\n\n"
|
||
text += f"🎯 Розыгрыш: {lottery.title}\n"
|
||
text += f"📊 Переиграно мест: {len(redraw_results)}\n\n"
|
||
|
||
for result in redraw_results:
|
||
text += f"🏆 {result['place']} место - {result['prize']}\n"
|
||
text += f" ❌ Было: {result['old_account']}\n"
|
||
text += f" ✅ Стало: {result['new_account']}\n\n"
|
||
|
||
text += "📨 Новым победителям отправлены уведомления"
|
||
|
||
await message.answer(text, parse_mode="Markdown")
|
||
|
||
except Exception as e:
|
||
await message.answer(f"❌ Ошибка: {str(e)}")
|
||
|
||
|
||
@router.callback_query(F.data.startswith("confirm_win_"))
|
||
async def confirm_winner_callback(callback_query):
|
||
"""Обработка подтверждения выигрыша победителем"""
|
||
from aiogram.types import CallbackQuery
|
||
|
||
winner_id = int(callback_query.data.split("_")[-1])
|
||
|
||
async with async_session_maker() as session:
|
||
# Получаем информацию о победителе
|
||
winner_result = await session.execute(
|
||
select(Winner).where(Winner.id == winner_id)
|
||
)
|
||
winner = winner_result.scalar_one_or_none()
|
||
|
||
if not winner:
|
||
await callback_query.answer("❌ Победитель не найден", show_alert=True)
|
||
return
|
||
|
||
if winner.is_claimed:
|
||
await callback_query.answer(
|
||
"✅ Этот выигрыш уже подтвержден!",
|
||
show_alert=True
|
||
)
|
||
return
|
||
|
||
# Проверяем, что пользователь является владельцем счёта
|
||
if winner.account_number:
|
||
owner = await AccountService.get_account_owner(session, winner.account_number)
|
||
if not owner or owner.telegram_id != callback_query.from_user.id:
|
||
await callback_query.answer(
|
||
"❌ Вы не являетесь владельцем этого счёта",
|
||
show_alert=True
|
||
)
|
||
return
|
||
|
||
# Проверяем срок действия (24 часа с момента создания winner)
|
||
if winner.created_at:
|
||
time_since_creation = datetime.now(timezone.utc) - winner.created_at
|
||
if time_since_creation > timedelta(hours=24):
|
||
await callback_query.answer(
|
||
"❌ Срок подтверждения истёк (24 часа). Приз будет разыгран заново.",
|
||
show_alert=True
|
||
)
|
||
return
|
||
|
||
# Подтверждаем выигрыш
|
||
winner.is_claimed = True
|
||
winner.claimed_at = datetime.now(timezone.utc)
|
||
await session.commit()
|
||
|
||
# Получаем данные о розыгрыше и пользователе
|
||
lottery = await LotteryService.get_lottery(session, winner.lottery_id)
|
||
|
||
# Получаем информацию о пользователе
|
||
owner = None
|
||
if winner.account_number:
|
||
owner = await AccountService.get_account_owner(session, winner.account_number)
|
||
elif winner.user_id:
|
||
user_result = await session.execute(
|
||
select(User).where(User.id == winner.user_id)
|
||
)
|
||
owner = user_result.scalar_one_or_none()
|
||
|
||
# Формируем отображаемое имя
|
||
display_name = "Пользователь"
|
||
if owner:
|
||
if owner.nickname:
|
||
display_name = owner.nickname
|
||
elif owner.username:
|
||
display_name = f"@{owner.username}"
|
||
elif owner.first_name:
|
||
display_name = owner.first_name
|
||
|
||
# Отправляем подтверждение пользователю
|
||
confirmation_text = (
|
||
f"✅ **Выигрыш подтвержден!**\n\n"
|
||
f"🎯 Розыгрыш: {lottery.title}\n"
|
||
f"🏆 Место: {winner.place}\n"
|
||
f"🎁 Приз: {winner.prize}\n"
|
||
f"💳 Счет: {winner.account_number}\n\n"
|
||
f"📞 С вами свяжется администратор для вручения приза.\n"
|
||
f"Спасибо за участие!"
|
||
)
|
||
|
||
await callback_query.message.edit_text(
|
||
confirmation_text,
|
||
parse_mode="Markdown"
|
||
)
|
||
|
||
# Уведомляем админов с nickname и клубной картой
|
||
for admin_id in ADMIN_IDS:
|
||
try:
|
||
# Формируем информацию для админа
|
||
user_info = display_name
|
||
if owner and owner.club_card_number:
|
||
user_info = f"{display_name} (карта: {owner.club_card_number})"
|
||
|
||
admin_text = (
|
||
f"✅ **Подтверждение выигрыша**\n\n"
|
||
f"👤 Пользователь: {user_info}\n"
|
||
f"🎯 Розыгрыш: {lottery.title}\n"
|
||
f"🏆 Место: {winner.place}\n"
|
||
f"🎁 Приз: {winner.prize}\n"
|
||
f"💳 Счет: {winner.account_number}"
|
||
)
|
||
|
||
from aiogram import Bot
|
||
from src.core.config import BOT_TOKEN
|
||
bot = Bot(token=BOT_TOKEN)
|
||
await bot.send_message(admin_id, admin_text, parse_mode="Markdown")
|
||
except Exception as e:
|
||
import logging
|
||
logging.getLogger(__name__).error(f"Ошибка отправки админу {admin_id}: {e}")
|
||
|
||
await callback_query.answer("✅ Выигрыш подтвержден!", show_alert=True)
|
||
|