Files
new_lottery_bot/admin_utils.py
2025-11-12 20:57:36 +09:00

423 lines
15 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, update, func
from models import User, Lottery, Participation, Winner
from typing import List, Dict, Optional
import csv
import json
from datetime import datetime
class AdminUtils:
"""Утилиты для админ-панели"""
@staticmethod
async def get_lottery_statistics(session: AsyncSession, lottery_id: int) -> Dict:
"""Получить детальную статистику по розыгрышу"""
lottery = await session.get(Lottery, lottery_id)
if not lottery:
return {}
# Количество участников
participants_count = await session.scalar(
select(func.count(Participation.id))
.where(Participation.lottery_id == lottery_id)
)
# Победители
winners_count = await session.scalar(
select(func.count(Winner.id))
.where(Winner.lottery_id == lottery_id)
)
# Ручные победители
manual_winners_count = await session.scalar(
select(func.count(Winner.id))
.where(Winner.lottery_id == lottery_id, Winner.is_manual == True)
)
# Участники по дням
participants_by_date = await session.execute(
select(
func.date(Participation.created_at).label('date'),
func.count(Participation.id).label('count')
)
.where(Participation.lottery_id == lottery_id)
.group_by(func.date(Participation.created_at))
.order_by(func.date(Participation.created_at))
)
return {
'lottery': lottery,
'participants_count': participants_count,
'winners_count': winners_count,
'manual_winners_count': manual_winners_count,
'random_winners_count': winners_count - manual_winners_count,
'participants_by_date': participants_by_date.fetchall()
}
@staticmethod
async def export_lottery_data(session: AsyncSession, lottery_id: int) -> Dict:
"""Экспорт данных розыгрыша"""
lottery = await session.get(Lottery, lottery_id)
if not lottery:
return {}
# Участники
participants = await session.execute(
select(User, Participation)
.join(Participation)
.where(Participation.lottery_id == lottery_id)
.order_by(Participation.created_at)
)
participants_data = []
for user, participation in participants:
participants_data.append({
'telegram_id': user.telegram_id,
'username': user.username,
'first_name': user.first_name,
'last_name': user.last_name,
'joined_at': participation.created_at.isoformat()
})
# Победители
winners = await session.execute(
select(Winner, User)
.join(User)
.where(Winner.lottery_id == lottery_id)
.order_by(Winner.place)
)
winners_data = []
for winner, user in winners:
winners_data.append({
'place': winner.place,
'telegram_id': user.telegram_id,
'username': user.username,
'first_name': user.first_name,
'prize': winner.prize,
'is_manual': winner.is_manual,
'won_at': winner.created_at.isoformat()
})
return {
'lottery': {
'id': lottery.id,
'title': lottery.title,
'description': lottery.description,
'created_at': lottery.created_at.isoformat(),
'is_completed': lottery.is_completed,
'prizes': lottery.prizes,
'manual_winners': lottery.manual_winners
},
'participants': participants_data,
'winners': winners_data,
'export_date': datetime.now().isoformat()
}
@staticmethod
async def bulk_add_participants(
session: AsyncSession,
lottery_id: int,
telegram_ids: List[int]
) -> Dict[str, int]:
"""Массовое добавление участников"""
added = 0
skipped = 0
errors = []
for telegram_id in telegram_ids:
try:
# Проверяем, есть ли пользователь
user = await session.execute(
select(User).where(User.telegram_id == telegram_id)
)
user = user.scalar_one_or_none()
if not user:
errors.append(f"Пользователь {telegram_id} не найден")
continue
# Проверяем, не участвует ли уже
existing = await session.execute(
select(Participation).where(
Participation.lottery_id == lottery_id,
Participation.user_id == user.id
)
)
if existing.scalar_one_or_none():
skipped += 1
continue
# Добавляем участника
participation = Participation(
lottery_id=lottery_id,
user_id=user.id
)
session.add(participation)
added += 1
except Exception as e:
errors.append(f"Ошибка с {telegram_id}: {str(e)}")
if added > 0:
await session.commit()
return {
'added': added,
'skipped': skipped,
'errors': errors
}
@staticmethod
async def remove_participant(
session: AsyncSession,
lottery_id: int,
telegram_id: int
) -> bool:
"""Удалить участника из розыгрыша"""
user = await session.execute(
select(User).where(User.telegram_id == telegram_id)
)
user = user.scalar_one_or_none()
if not user:
return False
result = await session.execute(
delete(Participation).where(
Participation.lottery_id == lottery_id,
Participation.user_id == user.id
)
)
await session.commit()
return result.rowcount > 0
@staticmethod
async def update_lottery(
session: AsyncSession,
lottery_id: int,
**updates
) -> bool:
"""Обновить данные розыгрыша"""
try:
await session.execute(
update(Lottery)
.where(Lottery.id == lottery_id)
.values(**updates)
)
await session.commit()
return True
except Exception:
return False
@staticmethod
async def delete_lottery(session: AsyncSession, lottery_id: int) -> bool:
"""Удалить розыгрыш (со всеми связанными данными)"""
try:
# Удаляем победителей
await session.execute(
delete(Winner).where(Winner.lottery_id == lottery_id)
)
# Удаляем участников
await session.execute(
delete(Participation).where(Participation.lottery_id == lottery_id)
)
# Удаляем сам розыгрыш
await session.execute(
delete(Lottery).where(Lottery.id == lottery_id)
)
await session.commit()
return True
except Exception:
await session.rollback()
return False
@staticmethod
async def get_user_activity(
session: AsyncSession,
telegram_id: int
) -> Dict:
"""Получить активность пользователя"""
user = await session.execute(
select(User).where(User.telegram_id == telegram_id)
)
user = user.scalar_one_or_none()
if not user:
return {}
# Участия
participations = await session.execute(
select(Participation, Lottery)
.join(Lottery)
.where(Participation.user_id == user.id)
.order_by(Participation.created_at.desc())
)
# Выигрыши
wins = await session.execute(
select(Winner, Lottery)
.join(Lottery)
.where(Winner.user_id == user.id)
.order_by(Winner.created_at.desc())
)
participations_data = []
for participation, lottery in participations:
participations_data.append({
'lottery_title': lottery.title,
'lottery_id': lottery.id,
'joined_at': participation.created_at,
'lottery_completed': lottery.is_completed
})
wins_data = []
for win, lottery in wins:
wins_data.append({
'lottery_title': lottery.title,
'lottery_id': lottery.id,
'place': win.place,
'prize': win.prize,
'is_manual': win.is_manual,
'won_at': win.created_at
})
return {
'user': user,
'total_participations': len(participations_data),
'total_wins': len(wins_data),
'participations': participations_data,
'wins': wins_data
}
@staticmethod
async def cleanup_old_data(session: AsyncSession, days: int = 30) -> Dict[str, int]:
"""Очистка старых данных"""
from datetime import datetime, timedelta
cutoff_date = datetime.now() - timedelta(days=days)
# Удаляем старые завершенные розыгрыши
old_lotteries = await session.execute(
select(Lottery.id)
.where(
Lottery.is_completed == True,
Lottery.created_at < cutoff_date
)
)
lottery_ids = [row[0] for row in old_lotteries.fetchall()]
deleted_winners = 0
deleted_participations = 0
deleted_lotteries = 0
for lottery_id in lottery_ids:
# Удаляем победителей
result = await session.execute(
delete(Winner).where(Winner.lottery_id == lottery_id)
)
deleted_winners += result.rowcount
# Удаляем участников
result = await session.execute(
delete(Participation).where(Participation.lottery_id == lottery_id)
)
deleted_participations += result.rowcount
# Удаляем розыгрыш
result = await session.execute(
delete(Lottery).where(Lottery.id == lottery_id)
)
deleted_lotteries += result.rowcount
await session.commit()
return {
'deleted_lotteries': deleted_lotteries,
'deleted_participations': deleted_participations,
'deleted_winners': deleted_winners,
'cutoff_date': cutoff_date.isoformat()
}
class ReportGenerator:
"""Генератор отчетов"""
@staticmethod
async def generate_summary_report(session: AsyncSession) -> str:
"""Генерация сводного отчета"""
# Общая статистика
total_users = await session.scalar(select(func.count(User.id)))
total_lotteries = await session.scalar(select(func.count(Lottery.id)))
active_lotteries = await session.scalar(
select(func.count(Lottery.id))
.where(Lottery.is_active == True, Lottery.is_completed == False)
)
completed_lotteries = await session.scalar(
select(func.count(Lottery.id)).where(Lottery.is_completed == True)
)
total_participations = await session.scalar(select(func.count(Participation.id)))
total_winners = await session.scalar(select(func.count(Winner.id)))
# Топ розыгрыши по участникам
top_lotteries = await session.execute(
select(
Lottery.title,
Lottery.created_at,
func.count(Participation.id).label('participants')
)
.join(Participation, isouter=True)
.group_by(Lottery.id)
.order_by(func.count(Participation.id).desc())
.limit(5)
)
# Топ активные пользователи
top_users = await session.execute(
select(
User.first_name,
User.username,
func.count(Participation.id).label('participations'),
func.count(Winner.id).label('wins')
)
.join(Participation, isouter=True)
.join(Winner, isouter=True)
.group_by(User.id)
.order_by(func.count(Participation.id).desc())
.limit(5)
)
report = f"📊 СВОДНЫЙ ОТЧЕТ\n"
report += f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n\n"
report += f"📈 ОБЩАЯ СТАТИСТИКА\n"
report += f"👥 Пользователей: {total_users}\n"
report += f"🎲 Всего розыгрышей: {total_lotteries}\n"
report += f"🟢 Активных: {active_lotteries}\n"
report += f"✅ Завершенных: {completed_lotteries}\n"
report += f"🎫 Всего участий: {total_participations}\n"
report += f"🏆 Всего победителей: {total_winners}\n\n"
if total_lotteries > 0:
avg_participation = total_participations / total_lotteries
report += f"📊 Среднее участие на розыгрыш: {avg_participation:.1f}\n\n"
report += f"🏆 ТОП РОЗЫГРЫШИ ПО УЧАСТНИКАМ\n"
for i, (title, created_at, participants) in enumerate(top_lotteries.fetchall(), 1):
report += f"{i}. {title}\n"
report += f" Участников: {participants} | {created_at.strftime('%d.%m.%Y')}\n\n"
report += f"🔥 ТОП АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ\n"
for i, (first_name, username, participations, wins) in enumerate(top_users.fetchall(), 1):
name = f"@{username}" if username else first_name
report += f"{i}. {name}\n"
report += f" Участий: {participations} | Побед: {wins}\n\n"
return report