Files
new_lottery_bot/src/handlers/account_services.py
Andrew K. Choi 29a6ac2bd2 fix: обработка формата 'КАРТА СЧЕТ' в AccountParticipationService
Проблема:
- Когда админ отправляет счет в чат и нажимает 'Добавить в розыгрыш'
- Используется другой обработчик (account_handlers.py)
- Он вызывает add_account_to_lottery() которая НЕ разбирала формат

Решение:
- Добавлен split() по пробелу в add_account_to_lottery()
- Добавлено получение Account и user через AccountService
- Теперь сохраняются user_id и account_id
- Улучшены сообщения с указанием номера карты

Теперь ОБА пути работают:
1. Массовое добавление через админ-панель
2. Добавление из детектированных счетов в чате
2025-11-17 09:09:24 +09:00

341 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Сервис для работы с участием счетов в розыгрышах (без привязки к пользователям)
"""
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, delete, func
from ..core.models import Lottery, Participation, Winner
from ..utils.account_utils import validate_account_number, format_account_number, parse_accounts_from_message, search_accounts_by_pattern
from typing import List, Optional, Dict, Any
class AccountParticipationService:
"""Сервис для работы с участием счетов в розыгрышах"""
@staticmethod
async def add_account_to_lottery(
session: AsyncSession,
lottery_id: int,
account_number: str
) -> Dict[str, Any]:
"""
Добавить счет в розыгрыш.
Поддерживает форматы: "КАРТА СЧЕТ" или просто "СЧЕТ"
Returns:
Dict с ключами: success, message, account_number
"""
# Разделяем по пробелу если есть номер карты
parts = account_number.split()
if len(parts) == 2:
card_number = parts[0]
account_to_format = parts[1]
elif len(parts) == 1:
card_number = None
account_to_format = parts[0]
else:
return {
"success": False,
"message": f"Неверный формат: {account_number}",
"account_number": account_number
}
# Валидируем и форматируем только часть счета
formatted_account = format_account_number(account_to_format)
if not formatted_account:
card_info = f" (карта: {card_number})" if card_number else ""
return {
"success": False,
"message": f"Неверный формат счета: {account_to_format}{card_info}",
"account_number": account_number
}
# Проверяем существование розыгрыша
lottery = await session.get(Lottery, lottery_id)
if not lottery:
return {
"success": False,
"message": f"Розыгрыш #{lottery_id} не найден",
"account_number": formatted_account
}
# Проверяем, не участвует ли уже этот счет
existing = await session.execute(
select(Participation).where(
Participation.lottery_id == lottery_id,
Participation.account_number == formatted_account
)
)
if existing.scalar_one_or_none():
card_info = f" (карта: {card_number})" if card_number else ""
return {
"success": False,
"message": f"Счет {formatted_account}{card_info} уже участвует в розыгрыше",
"account_number": formatted_account
}
# Получаем запись Account и владельца
from ..core.registration_services import AccountService
from ..core.models import Account
user = await AccountService.get_account_owner(session, formatted_account)
account_record = await session.execute(
select(Account).where(Account.account_number == formatted_account)
)
account_record = account_record.scalar_one_or_none()
# Добавляем участие с полными данными
participation = Participation(
lottery_id=lottery_id,
account_number=formatted_account,
user_id=user.id if user else None,
account_id=account_record.id if account_record else None
)
session.add(participation)
await session.commit()
card_info = f" (карта: {card_number})" if card_number else ""
return {
"success": True,
"message": f"Счет {formatted_account}{card_info} добавлен в розыгрыш",
"account_number": formatted_account
}
@staticmethod
async def add_accounts_bulk(
session: AsyncSession,
lottery_id: int,
account_numbers: List[str]
) -> Dict[str, Any]:
"""
Массовое добавление счетов в розыгрыш
"""
results = {
"added": 0,
"skipped": 0,
"errors": [],
"details": [],
"added_accounts": [],
"skipped_accounts": []
}
for account in account_numbers:
result = await AccountParticipationService.add_account_to_lottery(
session, lottery_id, account
)
if result["success"]:
results["added"] += 1
results["added_accounts"].append(result["account_number"])
results["details"].append(f"{result['account_number']}")
else:
results["skipped"] += 1
results["skipped_accounts"].append(account)
results["errors"].append(result["message"])
results["details"].append(f"{result['message']}")
return results
@staticmethod
async def remove_account_from_lottery(
session: AsyncSession,
lottery_id: int,
account_number: str
) -> Dict[str, Any]:
"""Удалить счет из розыгрыша"""
formatted_account = format_account_number(account_number)
if not formatted_account:
return {
"success": False,
"message": f"Неверный формат счета: {account_number}"
}
participation = await session.execute(
select(Participation).where(
Participation.lottery_id == lottery_id,
Participation.account_number == formatted_account
)
)
participation = participation.scalar_one_or_none()
if not participation:
return {
"success": False,
"message": f"Счет {formatted_account} не участвует в розыгрыше"
}
await session.delete(participation)
await session.commit()
return {
"success": True,
"message": f"Счет {formatted_account} удален из розыгрыша"
}
@staticmethod
async def get_lottery_accounts(
session: AsyncSession,
lottery_id: int,
limit: Optional[int] = None,
offset: int = 0
) -> List[str]:
"""Получить все счета, участвующие в розыгрыше"""
query = select(Participation.account_number).where(
Participation.lottery_id == lottery_id,
Participation.account_number.isnot(None)
).order_by(Participation.created_at.desc())
if limit:
query = query.offset(offset).limit(limit)
result = await session.execute(query)
return [account for account in result.scalars().all() if account]
@staticmethod
async def get_accounts_count(session: AsyncSession, lottery_id: int) -> int:
"""Получить количество счетов в розыгрыше"""
result = await session.scalar(
select(func.count(Participation.id)).where(
Participation.lottery_id == lottery_id,
Participation.account_number.isnot(None)
)
)
return result or 0
@staticmethod
async def search_accounts_in_lottery(
session: AsyncSession,
lottery_id: int,
pattern: str,
limit: int = 20
) -> List[str]:
"""
Поиск счетов в розыгрыше по частичному совпадению
Args:
lottery_id: ID розыгрыша
pattern: Паттерн поиска (например "11-22" или "33")
limit: Максимальное количество результатов
"""
# Получаем все счета розыгрыша
all_accounts = await AccountParticipationService.get_lottery_accounts(
session, lottery_id
)
# Ищем совпадения
return search_accounts_by_pattern(pattern, all_accounts)[:limit]
@staticmethod
async def set_account_as_winner(
session: AsyncSession,
lottery_id: int,
account_number: str,
place: int = 1,
prize: str = ""
):
"""
Устанавливает счет как победителя в розыгрыше.
Поддерживает формат: "КАРТА СЧЕТ" или просто "СЧЕТ"
"""
# Разделяем номер карты и счета, если они указаны вместе
card_number = None
parts = account_number.split()
if len(parts) == 2:
# Формат: "КАРТА СЧЕТ"
card_number = parts[0]
account_to_format = parts[1]
elif len(parts) == 1:
# Формат: только "СЧЕТ"
account_to_format = parts[0]
else:
return {
"success": False,
"message": f"❌ Неверный формат: {account_number}"
}
# Форматируем номер счета
formatted_account = format_account_number(account_to_format)
if not formatted_account:
return {
"success": False,
"message": f"❌ Неверный формат счета: {account_number}"
}
# Проверяем, что счет участвует в розыгрыше
participation = await session.execute(
select(Participation).where(
Participation.lottery_id == lottery_id,
Participation.account_number == formatted_account
)
)
participation = participation.scalar_one_or_none()
if not participation:
card_info = f" (карта: {card_number})" if card_number else ""
return {
"success": False,
"message": f"❌ Счет {formatted_account}{card_info} не участвует в розыгрыше"
}
# Проверяем, не занято ли уже это место
existing_winner = await session.execute(
select(Winner).where(
Winner.lottery_id == lottery_id,
Winner.place == place
)
)
existing_winner = existing_winner.scalar_one_or_none()
if existing_winner:
# Обновляем существующего победителя
existing_winner.account_number = formatted_account
existing_winner.user_id = None
existing_winner.is_manual = True
if prize:
existing_winner.prize = prize
else:
# Создаем нового победителя
winner = Winner(
lottery_id=lottery_id,
account_number=formatted_account,
user_id=None,
place=place,
prize=prize,
is_manual=True
)
session.add(winner)
await session.commit()
card_info = f" (карта: {card_number})" if card_number else ""
return {
"success": True,
"message": f"✅ Счет {formatted_account}{card_info} установлен победителем на место {place}",
"account_number": formatted_account,
"place": place
}
@staticmethod
async def get_lottery_winners_accounts(
session: AsyncSession,
lottery_id: int
) -> List[Dict[str, Any]]:
"""Получить всех победителей розыгрыша (счета)"""
result = await session.execute(
select(Winner).where(
Winner.lottery_id == lottery_id,
Winner.account_number.isnot(None)
).order_by(Winner.place)
)
winners = result.scalars().all()
return [
{
"place": w.place,
"account_number": w.account_number,
"prize": w.prize,
"is_manual": w.is_manual
}
for w in winners
]