From 388c4e8aadc5b23f353ea0c9d43304d820a5205e Mon Sep 17 00:00:00 2001 From: "Andrew K. Choi" Date: Fri, 13 Feb 2026 20:03:44 +0900 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=81=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=BA=D0=B0=20=D0=BA=D0=BB=D0=B0=D0=B2=D0=B8=D0=B0=D1=82?= =?UTF-8?q?=D1=83=D1=80,=20=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3=20=D1=87=D0=B0=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ACCOUNT_BASED_CONFIRMATION.md | 2 - .../20260213_1812_12_41aae82e631b_.py | 28 + src/handlers/admin_panel.py | 596 +++++++++++++++++- 3 files changed, 616 insertions(+), 10 deletions(-) create mode 100644 migrations/versions/20260213_1812_12_41aae82e631b_.py diff --git a/docs/ACCOUNT_BASED_CONFIRMATION.md b/docs/ACCOUNT_BASED_CONFIRMATION.md index 3a251ce..b0f2e1e 100644 --- a/docs/ACCOUNT_BASED_CONFIRMATION.md +++ b/docs/ACCOUNT_BASED_CONFIRMATION.md @@ -93,12 +93,10 @@ if not owner or owner.telegram_id != callback.from_user.id: ### Что НЕ может сделать пользователь: ❌ Подтвердить чужой счет -❌ Подтвердить счет, который ему не принадлежит ❌ Подтвердить один счет дважды ### Что может сделать пользователь: -✅ Подтвердить только свои счета ✅ Подтвердить каждый свой выигрышный счет отдельно ✅ Видеть номер счета на каждой кнопке diff --git a/migrations/versions/20260213_1812_12_41aae82e631b_.py b/migrations/versions/20260213_1812_12_41aae82e631b_.py new file mode 100644 index 0000000..2745117 --- /dev/null +++ b/migrations/versions/20260213_1812_12_41aae82e631b_.py @@ -0,0 +1,28 @@ +""" + +Revision ID: 41aae82e631b +Revises: 64c4f8a81afa +Create Date: 2026-02-13 18:12:12.031589 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '41aae82e631b' +down_revision = '64c4f8a81afa' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### \ No newline at end of file diff --git a/src/handlers/admin_panel.py b/src/handlers/admin_panel.py index 3a4c627..2325241 100644 --- a/src/handlers/admin_panel.py +++ b/src/handlers/admin_panel.py @@ -68,6 +68,10 @@ class AdminStates(StatesGroup): remove_participant_bulk_accounts = State() participant_search = State() + # Добавление/удаление участников в конкретном розыгрыше + add_to_lottery_user = State() + remove_from_lottery_user = State() + # Установка победителей set_winner_lottery = State() set_winner_place = State() @@ -115,11 +119,8 @@ def get_lottery_management_keyboard() -> InlineKeyboardMarkup: """Клавиатура управления розыгрышами""" buttons = [ [InlineKeyboardButton(text="➕ Создать розыгрыш", callback_data="admin_create_lottery")], - [InlineKeyboardButton(text="📝 Редактировать розыгрыш", callback_data="admin_edit_lottery")], + [InlineKeyboardButton(text="� Список всех розыгрышей", callback_data="admin_list_all_lotteries")], [InlineKeyboardButton(text="🎭 Настройка отображения победителей", callback_data="admin_winner_display_settings")], - [InlineKeyboardButton(text="📋 Список всех розыгрышей", callback_data="admin_list_all_lotteries")], - [InlineKeyboardButton(text="🏁 Завершить розыгрыш", callback_data="admin_finish_lottery")], - [InlineKeyboardButton(text="🗑️ Удалить розыгрыш", callback_data="admin_delete_lottery")], [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")] ] return InlineKeyboardMarkup(inline_keyboard=buttons) @@ -153,9 +154,7 @@ def get_winner_management_keyboard() -> InlineKeyboardMarkup: [InlineKeyboardButton(text="👑 Установить победителя", callback_data="admin_set_manual_winner")], [InlineKeyboardButton(text="📝 Изменить победителя", callback_data="admin_edit_winner")], [InlineKeyboardButton(text="❌ Удалить победителя", callback_data="admin_remove_winner")], - [InlineKeyboardButton(text="📋 Список победителей", callback_data="admin_list_winners")], - [InlineKeyboardButton(text="🎲 Провести розыгрыш", callback_data="admin_conduct_draw")], - [InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")] + [InlineKeyboardButton(text=" Назад", callback_data="admin_panel")] ] return InlineKeyboardMarkup(inline_keyboard=buttons) @@ -568,14 +567,22 @@ async def show_lottery_detail(callback: CallbackQuery): buttons = [] if not lottery.is_completed: + # Розыгрыш ещё не проведён buttons.extend([ - [InlineKeyboardButton(text="👑 Установить победителя", callback_data=f"admin_set_winner_{lottery_id}")], [InlineKeyboardButton(text="🎲 Провести розыгрыш", callback_data=f"admin_conduct_{lottery_id}")], + [InlineKeyboardButton(text="👑 Установить победителя", callback_data=f"admin_set_winner_{lottery_id}")], + ]) + else: + # Розыгрыш завершён - показываем управление победителями + buttons.extend([ + [InlineKeyboardButton(text="✅ Проверка победителей", callback_data=f"admin_check_winners_{lottery_id}")], + [InlineKeyboardButton(text="🔄 Провести повторно", callback_data=f"admin_redraw_{lottery_id}")], ]) buttons.extend([ [InlineKeyboardButton(text="📝 Редактировать", callback_data=f"admin_edit_{lottery_id}")], [InlineKeyboardButton(text="👥 Участники", callback_data=f"admin_participants_{lottery_id}")], + [InlineKeyboardButton(text="🗑️ Удалить", callback_data=f"admin_del_lottery_{lottery_id}")], [InlineKeyboardButton(text="🔙 К списку", callback_data="admin_list_all_lotteries")] ]) @@ -1417,6 +1424,579 @@ async def process_bulk_remove_participant(message: Message, state: FSMContext): ) +# ====================== +# ДОБАВЛЕНИЕ/УДАЛЕНИЕ УЧАСТНИКОВ В КОНКРЕТНОМ РОЗЫГРЫШЕ +# ====================== + +@admin_router.callback_query(F.data.startswith("admin_add_to_")) +async def add_participant_to_lottery(callback: CallbackQuery, state: FSMContext): + """Добавление участника в конкретный розыгрыш""" + if not is_admin(callback.from_user.id): + await callback.answer("❌ Недостаточно прав", show_alert=True) + return + + lottery_id = int(callback.data.split("_")[-1]) + await state.update_data(add_to_lottery_id=lottery_id) + + 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 + + text = f"➕ Добавление участника\n\n" + text += f"🎯 Розыгрыш: {lottery.title}\n\n" + text += "Введите данные участника:\n" + text += "• Telegram ID (число)\n" + text += "• @username\n" + text += "• Номер счета (XX-XX-XX-XX-XX-XX-XX)" + + await callback.message.edit_text( + text, + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="❌ Отмена", callback_data=f"admin_participants_{lottery_id}")] + ]) + ) + await state.set_state(AdminStates.add_to_lottery_user) + + +@admin_router.message(StateFilter(AdminStates.add_to_lottery_user)) +async def process_add_to_lottery(message: Message, state: FSMContext): + """Обработка добавления участника в конкретный розыгрыш""" + if not is_admin(message.from_user.id): + await message.answer("❌ Недостаточно прав") + return + + data = await state.get_data() + lottery_id = data.get('add_to_lottery_id') + user_input = message.text.strip() + + async with async_session_maker() as session: + lottery = await LotteryService.get_lottery(session, lottery_id) + if not lottery: + await message.answer("❌ Розыгрыш не найден") + await state.clear() + return + + # Определяем тип ввода + user = None + account_number = None + + if user_input.startswith('@'): + # Username + username = user_input[1:] + user = await UserService.get_user_by_username(session, username) + elif user_input.isdigit(): + # Telegram ID + telegram_id = int(user_input) + user = await UserService.get_user_by_telegram_id(session, telegram_id) + elif '-' in user_input: + # Номер счета + from src.utils.account_utils import parse_accounts_from_message + accounts = parse_accounts_from_message(user_input) + if accounts: + account_number = accounts[0] + + if not user and not account_number: + await message.answer( + "❌ Не удалось найти пользователя или распознать счет.\n" + "Пользователь должен запустить бота командой /start", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_participants_{lottery_id}")] + ]) + ) + await state.clear() + return + + # Добавляем участника + if user: + success = await ParticipationService.add_participant(session, lottery_id, user.id) + name = f"@{user.username}" if user.username else f"{user.first_name} (ID: {user.telegram_id})" + else: + # Добавление по номеру счета + from sqlalchemy import select + from ..core.models import Participation + + # Проверяем, не добавлен ли уже этот счет + existing = await session.execute( + select(Participation).where( + Participation.lottery_id == lottery_id, + Participation.account_number == account_number + ) + ) + if existing.scalar_one_or_none(): + await message.answer( + f"⚠️ Счет {account_number} уже участвует в этом розыгрыше", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_participants_{lottery_id}")] + ]) + ) + await state.clear() + return + + participation = Participation(lottery_id=lottery_id, account_number=account_number) + session.add(participation) + await session.commit() + success = True + name = f"Счет: {account_number}" + + await state.clear() + + if success: + await message.answer( + f"✅ Участник добавлен!\n\n" + f"👤 {name}\n" + f"🎯 Розыгрыш: {lottery.title}", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="➕ Добавить ещё", callback_data=f"admin_add_to_{lottery_id}")], + [InlineKeyboardButton(text="👥 К участникам", callback_data=f"admin_participants_{lottery_id}")] + ]) + ) + else: + await message.answer( + f"⚠️ Участник уже добавлен в этот розыгрыш", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_participants_{lottery_id}")] + ]) + ) + + +@admin_router.callback_query(F.data.startswith("admin_remove_from_")) +async def remove_participant_from_lottery(callback: CallbackQuery, state: FSMContext): + """Удаление участника из конкретного розыгрыша""" + if not is_admin(callback.from_user.id): + await callback.answer("❌ Недостаточно прав", show_alert=True) + return + + lottery_id = int(callback.data.split("_")[-1]) + await state.update_data(remove_from_lottery_id=lottery_id) + + async with async_session_maker() as session: + lottery = await LotteryService.get_lottery(session, lottery_id) + participants_count = await ParticipationService.get_participants_count(session, lottery_id) + + if not lottery: + await callback.answer("❌ Розыгрыш не найден", show_alert=True) + return + + text = f"➖ Удаление участника\n\n" + text += f"🎯 Розыгрыш: {lottery.title}\n" + text += f"👥 Участников: {participants_count}\n\n" + text += "Введите данные участника для удаления:\n" + text += "• Telegram ID (число)\n" + text += "• @username\n" + text += "• Номер счета (XX-XX-XX-XX-XX-XX-XX)" + + await callback.message.edit_text( + text, + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="❌ Отмена", callback_data=f"admin_participants_{lottery_id}")] + ]) + ) + await state.set_state(AdminStates.remove_from_lottery_user) + + +@admin_router.message(StateFilter(AdminStates.remove_from_lottery_user)) +async def process_remove_from_lottery(message: Message, state: FSMContext): + """Обработка удаления участника из конкретного розыгрыша""" + if not is_admin(message.from_user.id): + await message.answer("❌ Недостаточно прав") + return + + data = await state.get_data() + lottery_id = data.get('remove_from_lottery_id') + user_input = message.text.strip() + + async with async_session_maker() as session: + lottery = await LotteryService.get_lottery(session, lottery_id) + if not lottery: + await message.answer("❌ Розыгрыш не найден") + await state.clear() + return + + removed = False + name = user_input + + if user_input.startswith('@'): + # Username + username = user_input[1:] + user = await UserService.get_user_by_username(session, username) + if user: + removed = await ParticipationService.remove_participant(session, lottery_id, user.id) + name = f"@{user.username}" if user.username else f"{user.first_name}" + elif user_input.isdigit(): + # Telegram ID + telegram_id = int(user_input) + user = await UserService.get_user_by_telegram_id(session, telegram_id) + if user: + removed = await ParticipationService.remove_participant(session, lottery_id, user.id) + name = f"@{user.username}" if user and user.username else f"ID: {telegram_id}" + elif '-' in user_input: + # Номер счета + from sqlalchemy import select, delete + from ..core.models import Participation + + from src.utils.account_utils import parse_accounts_from_message + accounts = parse_accounts_from_message(user_input) + if accounts: + account_number = accounts[0] + result = await session.execute( + select(Participation).where( + Participation.lottery_id == lottery_id, + Participation.account_number == account_number + ) + ) + participation = result.scalar_one_or_none() + if participation: + await session.delete(participation) + await session.commit() + removed = True + name = f"Счет: {account_number}" + + await state.clear() + + if removed: + await message.answer( + f"✅ Участник удалён!\n\n" + f"👤 {name}\n" + f"🎯 Розыгрыш: {lottery.title}", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="➖ Удалить ещё", callback_data=f"admin_remove_from_{lottery_id}")], + [InlineKeyboardButton(text="👥 К участникам", callback_data=f"admin_participants_{lottery_id}")] + ]) + ) + else: + await message.answer( + f"⚠️ Участник не найден в этом розыгрыше", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_participants_{lottery_id}")] + ]) + ) + + +# ====================== +# ПРОВЕРКА ПОБЕДИТЕЛЕЙ И ПОВТОРНЫЙ РОЗЫГРЫШ +# ====================== + +@admin_router.callback_query(F.data.startswith("admin_check_winners_")) +async def check_winners(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 + + winners = await LotteryService.get_winners(session, lottery_id) + + if not winners: + await callback.message.edit_text( + f"🏆 Проверка победителей\n\n" + f"🎯 Розыгрыш: {lottery.title}\n\n" + f"❌ Победители не определены. Сначала проведите розыгрыш.", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_lottery_detail_{lottery_id}")] + ]) + ) + return + + text = f"🏆 Проверка победителей\n\n" + text += f"🎯 Розыгрыш: {lottery.title}\n\n" + + confirmed_count = 0 + unconfirmed_count = 0 + + for winner in winners: + status = "✅" if winner.is_claimed else "⏳" + + if winner.is_claimed: + confirmed_count += 1 + else: + unconfirmed_count += 1 + + # Определяем имя победителя + if winner.account_number: + name = f"Счет: {winner.account_number}" + elif winner.user: + name = f"@{winner.user.username}" if winner.user.username else winner.user.first_name + else: + name = f"ID: {winner.user_id}" + + # Приз + prize = lottery.prizes[winner.place - 1] if lottery.prizes and len(lottery.prizes) >= winner.place else "Не указан" + + text += f"{status} {winner.place} место: {name}\n" + text += f" 🎁 Приз: {prize}\n" + if winner.is_claimed and winner.claimed_at: + text += f" 📅 Подтверждено: {winner.claimed_at.strftime('%d.%m.%Y %H:%M')}\n" + text += "\n" + + text += f"📊 Итого: {confirmed_count} подтверждено, {unconfirmed_count} ожидает\n" + + buttons = [] + if unconfirmed_count > 0: + buttons.append([InlineKeyboardButton(text="🔄 Переиграть неподтверждённые", callback_data=f"admin_redraw_{lottery_id}")]) + buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_lottery_detail_{lottery_id}")]) + + await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) + + +@admin_router.callback_query(F.data.startswith("admin_redraw_")) +async def redraw_lottery(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 + + winners = await LotteryService.get_winners(session, lottery_id) + + # Находим неподтверждённых победителей + unconfirmed = [w for w in winners if not w.is_claimed] + + if not unconfirmed: + await callback.answer("✅ Все победители подтверждены!", show_alert=True) + return + + # Показываем подтверждение + text = f"⚠️ Повторный розыгрыш\n\n" + text += f"🎯 Розыгрыш: {lottery.title}\n\n" + text += f"Будут переиграны {len(unconfirmed)} неподтверждённых мест:\n\n" + + for winner in unconfirmed: + if winner.account_number: + name = f"Счет: {winner.account_number}" + elif winner.user: + name = f"@{winner.user.username}" if winner.user.username else winner.user.first_name + else: + name = f"ID: {winner.user_id}" + + prize = lottery.prizes[winner.place - 1] if lottery.prizes and len(lottery.prizes) >= winner.place else "Не указан" + text += f"• {winner.place} место: {name} → {prize}\n" + + text += "\n❗️ Эти счета будут исключены из повторного розыгрыша." + + await callback.message.edit_text( + text, + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="✅ Подтвердить переигровку", callback_data=f"admin_redraw_confirm_{lottery_id}")], + [InlineKeyboardButton(text="❌ Отмена", callback_data=f"admin_check_winners_{lottery_id}")] + ]) + ) + + +@admin_router.callback_query(F.data.startswith("admin_redraw_confirm_")) +async def confirm_redraw(callback: CallbackQuery): + """Подтверждение и выполнение повторного розыгрыша""" + if not is_admin(callback.from_user.id): + await callback.answer("❌ Недостаточно прав", show_alert=True) + return + + lottery_id = int(callback.data.split("_")[-1]) + + await callback.answer("⏳ Проводится повторный розыгрыш...", show_alert=True) + + async with async_session_maker() as session: + from sqlalchemy import select, delete + from ..core.models import Winner, Participation + import random + + lottery = await LotteryService.get_lottery(session, lottery_id) + if not lottery: + await callback.message.edit_text("❌ Розыгрыш не найден") + return + + winners = await LotteryService.get_winners(session, lottery_id) + + # Собираем подтверждённые и неподтверждённые + confirmed_winners = [w for w in winners if w.is_claimed] + unconfirmed_winners = [w for w in winners if not w.is_claimed] + + if not unconfirmed_winners: + await callback.message.edit_text( + "✅ Все победители уже подтверждены!", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_lottery_detail_{lottery_id}")] + ]) + ) + return + + # Собираем исключённые счета (подтверждённые победители + бывшие неподтверждённые) + excluded_accounts = set() + for w in winners: + if w.account_number: + excluded_accounts.add(w.account_number) + + # Получаем всех участников, исключая уже выигравших + result = await session.execute( + select(Participation) + .where(Participation.lottery_id == lottery_id) + ) + all_participations = result.scalars().all() + + # Фильтруем участников + available_participations = [ + p for p in all_participations + if p.account_number not in excluded_accounts + ] + + if not available_participations: + await callback.message.edit_text( + "❌ Нет доступных участников для переигровки.\n" + "Все участники уже выиграли или были исключены.", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="🔙 Назад", callback_data=f"admin_lottery_detail_{lottery_id}")] + ]) + ) + return + + # Удаляем неподтверждённых победителей + for winner in unconfirmed_winners: + await session.delete(winner) + + # Проводим розыгрыш для неподтверждённых мест + new_winners_text = "" + random.shuffle(available_participations) + + for i, old_winner in enumerate(unconfirmed_winners): + if i >= len(available_participations): + break + + new_participation = available_participations[i] + + # Создаём нового победителя + new_winner = Winner( + lottery_id=lottery_id, + user_id=new_participation.user_id, + account_number=new_participation.account_number, + place=old_winner.place, + is_manual=False, + is_claimed=False + ) + session.add(new_winner) + + # Исключаем из следующих итераций + if new_participation.account_number: + excluded_accounts.add(new_participation.account_number) + + prize = lottery.prizes[old_winner.place - 1] if lottery.prizes and len(lottery.prizes) >= old_winner.place else "Приз" + name = new_participation.account_number or f"ID: {new_participation.user_id}" + new_winners_text += f"🏆 {old_winner.place} место: {name} → {prize}\n" + + await session.commit() + + # Отправляем уведомления новым победителям + from ..utils.notifications import notify_winners_async + try: + await notify_winners_async(callback.bot, session, lottery_id) + except Exception as e: + logger.error(f"Ошибка при отправке уведомлений: {e}") + + text = f"🎉 Повторный розыгрыш завершён!\n\n" + text += f"🎯 Розыгрыш: {lottery.title}\n\n" + text += f"Новые победители:\n{new_winners_text}\n" + text += "✅ Уведомления отправлены новым победителям" + + await callback.message.edit_text( + text, + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="✅ Проверить победителей", callback_data=f"admin_check_winners_{lottery_id}")], + [InlineKeyboardButton(text="🔙 К розыгрышу", callback_data=f"admin_lottery_detail_{lottery_id}")] + ]) + ) + + +# ====================== +# УДАЛЕНИЕ РОЗЫГРЫША +# ====================== + +@admin_router.callback_query(F.data.startswith("admin_del_lottery_")) +async def delete_lottery_confirm(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 + + participants_count = await ParticipationService.get_participants_count(session, lottery_id) + winners = await LotteryService.get_winners(session, lottery_id) + + text = f"⚠️ Удаление розыгрыша\n\n" + text += f"🎯 Название: {lottery.title}\n" + text += f"👥 Участников: {participants_count}\n" + text += f"🏆 Победителей: {len(winners)}\n\n" + text += "❗️ Это действие необратимо!\n" + text += "Все данные об участниках и победителях будут удалены." + + await callback.message.edit_text( + text, + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="🗑️ Да, удалить", callback_data=f"admin_del_lottery_yes_{lottery_id}")], + [InlineKeyboardButton(text="❌ Отмена", callback_data=f"admin_lottery_detail_{lottery_id}")] + ]) + ) + + +@admin_router.callback_query(F.data.startswith("admin_del_lottery_yes_")) +async def delete_lottery_execute(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: + from sqlalchemy import delete as sql_delete + from ..core.models import Winner, Participation + + lottery = await LotteryService.get_lottery(session, lottery_id) + if not lottery: + await callback.answer("❌ Розыгрыш не найден", show_alert=True) + return + + lottery_title = lottery.title + + # Удаляем победителей + await session.execute(sql_delete(Winner).where(Winner.lottery_id == lottery_id)) + + # Удаляем участников + await session.execute(sql_delete(Participation).where(Participation.lottery_id == lottery_id)) + + # Удаляем розыгрыш + await session.delete(lottery) + await session.commit() + + await callback.message.edit_text( + f"✅ Розыгрыш удалён\n\n" + f"🗑️ {lottery_title}", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="📋 К списку розыгрышей", callback_data="admin_list_all_lotteries")] + ]) + ) + + # ====================== # МАССОВОЕ УПРАВЛЕНИЕ УЧАСТНИКАМИ ПО СЧЕТАМ # ======================