mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-20 11:50:27 +00:00
356 lines
14 KiB
Python
356 lines
14 KiB
Python
import logging
|
|
import secrets
|
|
import string
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional, Dict, List
|
|
from sqlalchemy import select, func, and_, update
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from app.config import settings
|
|
from app.database.models import User, ReferralEarning, Transaction, TransactionType
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def format_referrer_info(user: User) -> str:
|
|
"""Return formatted referrer info for admin notifications."""
|
|
|
|
referred_by_id = getattr(user, "referred_by_id", None)
|
|
|
|
if not referred_by_id:
|
|
return "Нет"
|
|
|
|
try:
|
|
# Проверяем, является ли referrer обычным объектом или InstrumentedList
|
|
referrer = getattr(user, "referrer", None)
|
|
|
|
# Если referrer это InstrumentedList или None, то возвращаем информацию по ID
|
|
if referrer is None:
|
|
return f"ID {referred_by_id} (не найден)"
|
|
|
|
# Пытаемся получить атрибуты referrer, если они доступны
|
|
referrer_username = getattr(referrer, "username", None)
|
|
referrer_telegram_id = getattr(referrer, "telegram_id", None)
|
|
|
|
if referrer_username:
|
|
return f"@{referrer_username} (ID: {referred_by_id})"
|
|
|
|
return f"ID {referrer_telegram_id or referred_by_id}"
|
|
|
|
except (AttributeError, TypeError):
|
|
# Если возникла ошибка при обращении к атрибутам, просто возвращаем ID
|
|
return f"ID {referred_by_id} (ошибка загрузки)"
|
|
|
|
|
|
async def generate_unique_referral_code(db: AsyncSession, telegram_id: int) -> str:
|
|
max_attempts = 10
|
|
|
|
for _ in range(max_attempts):
|
|
code = f"ref{''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(8))}"
|
|
|
|
result = await db.execute(
|
|
select(User).where(User.referral_code == code)
|
|
)
|
|
if not result.scalar_one_or_none():
|
|
return code
|
|
|
|
timestamp = str(int(datetime.utcnow().timestamp()))[-6:]
|
|
return f"ref{timestamp}"
|
|
|
|
|
|
def get_effective_referral_commission_percent(user: User) -> int:
|
|
"""Возвращает индивидуальный процент комиссии пользователя или дефолтное значение."""
|
|
|
|
percent = getattr(user, "referral_commission_percent", None)
|
|
|
|
if percent is None:
|
|
percent = settings.REFERRAL_COMMISSION_PERCENT
|
|
|
|
if percent < 0 or percent > 100:
|
|
logger.error(
|
|
"❌ Некорректный процент комиссии для пользователя %s: %s",
|
|
getattr(user, "telegram_id", None),
|
|
percent,
|
|
)
|
|
return max(0, min(100, settings.REFERRAL_COMMISSION_PERCENT))
|
|
|
|
return percent
|
|
|
|
|
|
async def mark_user_as_had_paid_subscription(db: AsyncSession, user: User) -> bool:
|
|
try:
|
|
if user.has_had_paid_subscription:
|
|
logger.debug(f"Пользователь {user.id} уже отмечен как имевший платную подписку")
|
|
return True
|
|
|
|
await db.execute(
|
|
update(User)
|
|
.where(User.id == user.id)
|
|
.values(
|
|
has_had_paid_subscription=True,
|
|
updated_at=datetime.utcnow()
|
|
)
|
|
)
|
|
|
|
await db.commit()
|
|
logger.info(f"✅ Пользователь {user.id} отмечен как имевший платную подписку")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Ошибка отметки пользователя {user.id} как имевшего платную подписку: {e}")
|
|
try:
|
|
await db.rollback()
|
|
except Exception as rollback_error:
|
|
logger.error(f"Ошибка отката транзакции: {rollback_error}")
|
|
return False
|
|
|
|
|
|
async def get_user_referral_summary(db: AsyncSession, user_id: int) -> Dict:
|
|
try:
|
|
invited_count_result = await db.execute(
|
|
select(func.count(User.id)).where(User.referred_by_id == user_id)
|
|
)
|
|
invited_count = invited_count_result.scalar() or 0
|
|
|
|
referrals_result = await db.execute(
|
|
select(User).where(User.referred_by_id == user_id)
|
|
)
|
|
referrals = referrals_result.scalars().all()
|
|
|
|
paid_referrals_count = sum(1 for ref in referrals if ref.has_made_first_topup)
|
|
|
|
total_earnings_result = await db.execute(
|
|
select(func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0))
|
|
.where(ReferralEarning.user_id == user_id)
|
|
)
|
|
total_earned_kopeks = total_earnings_result.scalar() or 0
|
|
|
|
month_ago = datetime.utcnow() - timedelta(days=30)
|
|
month_earnings_result = await db.execute(
|
|
select(func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0))
|
|
.where(
|
|
and_(
|
|
ReferralEarning.user_id == user_id,
|
|
ReferralEarning.created_at >= month_ago
|
|
)
|
|
)
|
|
)
|
|
month_earned_kopeks = month_earnings_result.scalar() or 0
|
|
|
|
recent_earnings_result = await db.execute(
|
|
select(ReferralEarning)
|
|
.options(selectinload(ReferralEarning.referral))
|
|
.where(ReferralEarning.user_id == user_id)
|
|
.order_by(ReferralEarning.created_at.desc())
|
|
.limit(5)
|
|
)
|
|
recent_earnings_raw = recent_earnings_result.scalars().all()
|
|
|
|
recent_earnings = []
|
|
for earning in recent_earnings_raw:
|
|
if earning.referral:
|
|
recent_earnings.append({
|
|
'amount_kopeks': earning.amount_kopeks,
|
|
'reason': earning.reason,
|
|
'referral_name': earning.referral.full_name,
|
|
'created_at': earning.created_at
|
|
})
|
|
|
|
earnings_by_type = {}
|
|
earnings_by_type_result = await db.execute(
|
|
select(
|
|
ReferralEarning.reason,
|
|
func.count(ReferralEarning.id).label('count'),
|
|
func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0).label('total_amount')
|
|
)
|
|
.where(ReferralEarning.user_id == user_id)
|
|
.group_by(ReferralEarning.reason)
|
|
)
|
|
|
|
for row in earnings_by_type_result:
|
|
earnings_by_type[row.reason] = {
|
|
'count': row.count,
|
|
'total_amount_kopeks': row.total_amount
|
|
}
|
|
|
|
active_referrals_count = 0
|
|
for referral in referrals:
|
|
if referral.last_activity and referral.last_activity >= month_ago:
|
|
active_referrals_count += 1
|
|
|
|
return {
|
|
'invited_count': invited_count,
|
|
'paid_referrals_count': paid_referrals_count,
|
|
'active_referrals_count': active_referrals_count,
|
|
'total_earned_kopeks': total_earned_kopeks,
|
|
'month_earned_kopeks': month_earned_kopeks,
|
|
'recent_earnings': recent_earnings,
|
|
'earnings_by_type': earnings_by_type,
|
|
'conversion_rate': round((paid_referrals_count / invited_count * 100) if invited_count > 0 else 0, 1)
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Ошибка получения статистики рефералов для пользователя {user_id}: {e}")
|
|
return {
|
|
'invited_count': 0,
|
|
'paid_referrals_count': 0,
|
|
'active_referrals_count': 0,
|
|
'total_earned_kopeks': 0,
|
|
'month_earned_kopeks': 0,
|
|
'recent_earnings': [],
|
|
'earnings_by_type': {},
|
|
'conversion_rate': 0.0
|
|
}
|
|
|
|
|
|
async def get_detailed_referral_list(db: AsyncSession, user_id: int, limit: int = 20, offset: int = 0) -> Dict:
|
|
try:
|
|
referrals_result = await db.execute(
|
|
select(User)
|
|
.where(User.referred_by_id == user_id)
|
|
.order_by(User.created_at.desc())
|
|
.offset(offset)
|
|
.limit(limit)
|
|
)
|
|
referrals = referrals_result.scalars().all()
|
|
|
|
total_count_result = await db.execute(
|
|
select(func.count(User.id)).where(User.referred_by_id == user_id)
|
|
)
|
|
total_count = total_count_result.scalar() or 0
|
|
|
|
detailed_referrals = []
|
|
for referral in referrals:
|
|
earnings_result = await db.execute(
|
|
select(func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0))
|
|
.where(
|
|
and_(
|
|
ReferralEarning.user_id == user_id,
|
|
ReferralEarning.referral_id == referral.id
|
|
)
|
|
)
|
|
)
|
|
total_earned_from_referral = earnings_result.scalar() or 0
|
|
|
|
topups_result = await db.execute(
|
|
select(func.count(Transaction.id))
|
|
.where(
|
|
and_(
|
|
Transaction.user_id == referral.id,
|
|
Transaction.type == TransactionType.DEPOSIT.value,
|
|
Transaction.is_completed.is_(True)
|
|
)
|
|
)
|
|
)
|
|
topups_count = topups_result.scalar() or 0
|
|
|
|
days_since_registration = (datetime.utcnow() - referral.created_at).days
|
|
|
|
days_since_activity = None
|
|
if referral.last_activity:
|
|
days_since_activity = (datetime.utcnow() - referral.last_activity).days
|
|
|
|
detailed_referrals.append({
|
|
'id': referral.id,
|
|
'telegram_id': referral.telegram_id,
|
|
'full_name': referral.full_name,
|
|
'username': referral.username,
|
|
'created_at': referral.created_at,
|
|
'last_activity': referral.last_activity,
|
|
'has_made_first_topup': referral.has_made_first_topup,
|
|
'balance_kopeks': referral.balance_kopeks,
|
|
'total_earned_kopeks': total_earned_from_referral,
|
|
'topups_count': topups_count,
|
|
'days_since_registration': days_since_registration,
|
|
'days_since_activity': days_since_activity,
|
|
'status': 'active' if days_since_activity is not None and days_since_activity <= 30 else 'inactive'
|
|
})
|
|
|
|
return {
|
|
'referrals': detailed_referrals,
|
|
'total_count': total_count,
|
|
'has_next': offset + limit < total_count,
|
|
'has_prev': offset > 0,
|
|
'current_page': (offset // limit) + 1,
|
|
'total_pages': (total_count + limit - 1) // limit
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Ошибка получения списка рефералов для пользователя {user_id}: {e}")
|
|
return {
|
|
'referrals': [],
|
|
'total_count': 0,
|
|
'has_next': False,
|
|
'has_prev': False,
|
|
'current_page': 1,
|
|
'total_pages': 1
|
|
}
|
|
|
|
|
|
async def get_referral_analytics(db: AsyncSession, user_id: int) -> Dict:
|
|
try:
|
|
now = datetime.utcnow()
|
|
periods = {
|
|
'today': now.replace(hour=0, minute=0, second=0, microsecond=0),
|
|
'week': now - timedelta(days=7),
|
|
'month': now - timedelta(days=30),
|
|
'quarter': now - timedelta(days=90)
|
|
}
|
|
|
|
earnings_by_period = {}
|
|
for period_name, start_date in periods.items():
|
|
result = await db.execute(
|
|
select(func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0))
|
|
.where(
|
|
and_(
|
|
ReferralEarning.user_id == user_id,
|
|
ReferralEarning.created_at >= start_date
|
|
)
|
|
)
|
|
)
|
|
earnings_by_period[period_name] = result.scalar() or 0
|
|
|
|
top_referrals_result = await db.execute(
|
|
select(
|
|
ReferralEarning.referral_id,
|
|
func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0).label('total_earned'),
|
|
func.count(ReferralEarning.id).label('earnings_count')
|
|
)
|
|
.where(ReferralEarning.user_id == user_id)
|
|
.group_by(ReferralEarning.referral_id)
|
|
.order_by(func.sum(ReferralEarning.amount_kopeks).desc())
|
|
.limit(5)
|
|
)
|
|
|
|
top_referrals = []
|
|
for row in top_referrals_result:
|
|
referral_result = await db.execute(
|
|
select(User).where(User.id == row.referral_id)
|
|
)
|
|
referral = referral_result.scalar_one_or_none()
|
|
if referral:
|
|
top_referrals.append({
|
|
'referral_name': referral.full_name,
|
|
'total_earned_kopeks': row.total_earned,
|
|
'earnings_count': row.earnings_count
|
|
})
|
|
|
|
return {
|
|
'earnings_by_period': earnings_by_period,
|
|
'top_referrals': top_referrals
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Ошибка получения аналитики рефералов для пользователя {user_id}: {e}")
|
|
return {
|
|
'earnings_by_period': {
|
|
'today': 0,
|
|
'week': 0,
|
|
'month': 0,
|
|
'quarter': 0
|
|
},
|
|
'top_referrals': []
|
|
}
|