feat: доработки функционала бота

1. Подтверждение запуска розыгрыша:
   - Показ диалога с информацией об участниках и призах
   - Кнопки 'Да, провести' и 'Отмена'
   - Индикатор загрузки при проведении

2. Удаление сообщений администратором:
   - Команда /delete для удаления сообщений бота (ответ на сообщение)
   - Callback кнопка delete_message
   - Новый роутер message_admin_router

3. Определение владельцев счетов:
   - Извлечение номера клубной карты (последние 4 цифры)
   - Поиск владельца по club_card_number
   - Отображение владельца в списке обнаруженных счетов
   - Метод UserService.get_user_by_club_card()

4. Тестирование производительности:
   - Скрипт generate_test_accounts.py
   - Генерация файлов с 100, 500, 1000, 2000, 5000 счетов
   - Готовые тестовые файлы для проверки

5. Улучшения парсинга:
   - Обработка текста из кабинета с мусорными данными
   - Построчный парсинг с разбором по пробелам
   - Поддержка формата 'Viposnova  16-11-2025 22:19:36  17-24-66-42-38-31-53  0.00    2918'

6. Исправления багов:
   - AttributeError при отображении победителей без user_id
   - Безопасная обработка winner.user == None
This commit is contained in:
2025-11-17 10:42:41 +09:00
parent 65b550f8c8
commit 79eb66cf51
13 changed files with 8951 additions and 34 deletions

View File

@@ -409,7 +409,14 @@ async def show_lottery_detail(callback: CallbackQuery):
text += f"🏆 Результаты:\n"
for winner in winners:
manual_mark = " 👑" if winner.is_manual else ""
username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name
# Безопасная обработка победителя - может быть без user_id
if winner.user:
username = f"@{winner.user.username}" if winner.user.username else winner.user.first_name
else:
# Победитель по номеру счета без связанного пользователя
username = f"Счет: {winner.account_number}"
text += f"{winner.place}. {username}{manual_mark}\n"
buttons = []
@@ -2586,8 +2593,8 @@ async def choose_lottery_for_draw(callback: CallbackQuery):
@admin_router.callback_query(F.data.startswith("admin_conduct_"))
async def conduct_lottery_draw(callback: CallbackQuery):
"""Проведение розыгрыша"""
async def conduct_lottery_draw_confirm(callback: CallbackQuery):
"""Запрос подтверждения проведения розыгрыша"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
@@ -2611,6 +2618,61 @@ async def conduct_lottery_draw(callback: CallbackQuery):
await callback.answer("Нет участников для розыгрыша", show_alert=True)
return
# Подсчёт призов
prizes_count = len(lottery.prizes) if lottery.prizes else 0
# Формируем сообщение с подтверждением
text = f"⚠️ <b>Подтверждение проведения розыгрыша</b>\n\n"
text += f"🎲 <b>Розыгрыш:</b> {lottery.title}\n"
text += f"👥 <b>Участников:</b> {participants_count}\n"
text += f"🏆 <b>Призов:</b> {prizes_count}\n\n"
if lottery.prizes:
text += "<b>Призы:</b>\n"
for i, prize in enumerate(lottery.prizes, 1):
text += f"{i}. {prize}\n"
text += "\n"
text += "❗️ <b>Внимание:</b> После проведения розыгрыша результаты нельзя будет изменить!\n\n"
text += "Продолжить?"
buttons = [
[InlineKeyboardButton(text="✅ Да, провести розыгрыш", callback_data=f"admin_conduct_confirmed_{lottery_id}")],
[InlineKeyboardButton(text="❌ Отмена", callback_data=f"admin_lottery_{lottery_id}")]
]
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
@admin_router.callback_query(F.data.startswith("admin_conduct_confirmed_"))
async def conduct_lottery_draw(callback: CallbackQuery):
"""Проведение розыгрыша после подтверждения"""
if not is_admin(callback.from_user.id):
await callback.answer("❌ Недостаточно прав", show_alert=True)
return
lottery_id = int(callback.data.split("_")[-1])
async with async_session_maker() as session:
lottery = await LotteryService.get_lottery(session, lottery_id)
if not lottery:
await callback.answer("Розыгрыш не найден", show_alert=True)
return
if lottery.is_completed:
await callback.answer("Розыгрыш уже завершён", show_alert=True)
return
participants_count = await ParticipationService.get_participants_count(session, lottery_id)
if participants_count == 0:
await callback.answer("Нет участников для розыгрыша", show_alert=True)
return
# Показываем индикатор загрузки
await callback.answer("⏳ Проводится розыгрыш...", show_alert=True)
# Проводим розыгрыш через сервис
winners_dict = await LotteryService.conduct_draw(session, lottery_id)