Fix text dialog routing and club card registration recovery
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-09-13 21:39:40 +09:00
parent 3c1a6a02a5
commit f3f8d0f737
28 changed files with 788 additions and 334 deletions

View File

@@ -1,8 +1,12 @@
"""Админские обработчики для управления счетами и верификации"""
from src.utils.account_input import parse_account_records
from src.utils.text_output import answer_plain_pages
from src.utils.input_validation import numeric_id
from src.filters.dialog_input import NotCommand
from src.utils.errors import public_error
from aiogram import Router, F, Bot
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.filters import Command
from src.filters.case_insensitive import CaseInsensitiveCommand as Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from sqlalchemy import select, and_
@@ -121,7 +125,7 @@ async def process_single_account(message: Message, club_card: str, account_numbe
await state.clear()
@router.message(AddAccountStates.waiting_for_data, F.text)
@router.message(AddAccountStates.waiting_for_data, F.text, NotCommand())
async def process_accounts_data(message: Message, state: FSMContext):
"""Обработка данных счетов (один или несколько)"""
if message.text.strip().lower() == '/cancel':
@@ -140,7 +144,6 @@ async def process_accounts_data(message: Message, state: FSMContext):
f"Вы отправили: {len(lines)} строк\n\n"
f"Разделите данные на несколько частей."
)
await state.clear()
return
# Отправляем начальное уведомление
@@ -150,46 +153,17 @@ async def process_accounts_data(message: Message, state: FSMContext):
)
accounts_data = []
errors = []
entries, errors = parse_account_records(message.text)
BATCH_SIZE = 100 # Обрабатываем по 100 счетов за раз
# Универсальный парсер: поддержка однострочного и многострочного формата
i = 0
while i < len(lines):
line = lines[i].strip()
# Пропускаем пустые строки и строки с названиями/датами
if not line or any(x in line.lower() for x in ['viposnova', '0.00', ':']):
i += 1
for entry in entries:
parts = entry.split()
if len(parts) != 2:
errors.append("Для счёта не указана клубная карта: " + entry)
continue
# Проверяем, есть ли в строке пробел (однострочный формат: "карта счет")
if ' ' in line:
# Однострочный формат: разделяем по первому пробелу
parts = line.split(maxsplit=1)
if len(parts) == 2:
club_card, account_number = parts
else:
errors.append(f"Строка {i+1}: неверный формат")
i += 1
continue
else:
# Многострочный формат: текущая строка - счет, следующая - карта
account_number = line
i += 1
if i >= len(lines):
errors.append(f"Строка {i}: отсутствует номер карты после счета {account_number}")
break
club_card = lines[i].strip()
# Пропускаем, если следующая строка содержит мусор
if not club_card or any(x in club_card.lower() for x in ['viposnova', '0.00', ':']):
errors.append(f"Строка {i}: некорректный номер карты после счета {account_number}")
i += 1
continue
# Создаем счет
club_card, account_number = parts
try:
async with async_session_maker() as session:
account = await AccountService.create_account(
@@ -223,7 +197,6 @@ async def process_accounts_data(message: Message, state: FSMContext):
except Exception as e:
errors.append(f"Счет {account_number}: {public_error(e)}")
i += 1
# Удаляем progress сообщение
try:
@@ -317,12 +290,11 @@ async def process_accounts_data(message: Message, state: FSMContext):
text += f"\n... и еще {len(errors) - 5} ошибок\n"
if not accounts_data:
await message.answer(text)
await state.clear()
await answer_plain_pages(message, text)
return
# Сохраняем данные и предлагаем добавить в розыгрыш
await state.update_data(accounts=accounts_data)
await state.update_data(accounts=[{key: value for key, value in account.items() if key != "owner"} for account in accounts_data])
await show_lottery_selection(message, text, state)
@@ -332,7 +304,7 @@ async def show_lottery_selection(message: Message, prev_text: str, state: FSMCon
lotteries = await LotteryService.get_active_lotteries(session)
if not lotteries:
await message.answer(
await answer_plain_pages(message,
prev_text + " Нет активных розыгрышей для добавления счетов"
)
await state.clear()
@@ -353,7 +325,7 @@ async def show_lottery_selection(message: Message, prev_text: str, state: FSMCon
InlineKeyboardButton(text="❌ Пропустить", callback_data="skip_lottery_add")
])
await message.answer(
await answer_plain_pages(message,
prev_text + " **Добавить счета в розыгрыш?**\n\n"
"Выберите розыгрыш из списка:",
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)
@@ -503,7 +475,7 @@ async def verify_winner_command(message: Message):
verification_code = parts[1].upper()
try:
lottery_id = int(parts[2])
lottery_id = numeric_id(parts[2], maximum=2**31 - 1)
except ValueError:
await message.answer("❌ lottery_id должен быть числом")
return
@@ -588,7 +560,7 @@ async def winner_status_command(message: Message):
return
try:
lottery_id = int(parts[1])
lottery_id = numeric_id(parts[1], maximum=2**31 - 1)
except ValueError:
await message.answer("❌ lottery_id должен быть числом")
return