423 lines
15 KiB
Python
423 lines
15 KiB
Python
"""
|
||
Дополнительные утилиты для админ-панели
|
||
"""
|
||
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 |