Проблема: - Когда админ отправляет счет в чат и нажимает 'Добавить в розыгрыш' - Используется другой обработчик (account_handlers.py) - Он вызывает add_account_to_lottery() которая НЕ разбирала формат Решение: - Добавлен split() по пробелу в add_account_to_lottery() - Добавлено получение Account и user через AccountService - Теперь сохраняются user_id и account_id - Улучшены сообщения с указанием номера карты Теперь ОБА пути работают: 1. Массовое добавление через админ-панель 2. Добавление из детектированных счетов в чате
341 lines
13 KiB
Python
341 lines
13 KiB
Python
"""
|
||
Сервис для работы с участием счетов в розыгрышах (без привязки к пользователям)
|
||
"""
|
||
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
|
||
]
|