diff --git a/app/cabinet/routes/admin_tariffs.py b/app/cabinet/routes/admin_tariffs.py index a03e39ba..3375041d 100644 --- a/app/cabinet/routes/admin_tariffs.py +++ b/app/cabinet/routes/admin_tariffs.py @@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func -from app.database.models import User, Tariff, Subscription, ServerSquad, PromoGroup +from app.database.models import User, Tariff, Subscription, ServerSquad, PromoGroup, Transaction, TransactionType from app.database.crud.tariff import ( get_all_tariffs, get_tariff_by_id, @@ -444,7 +444,11 @@ async def toggle_trial_tariff( admin: User = Depends(get_current_admin_user), db: AsyncSession = Depends(get_cabinet_db), ): - """Toggle tariff trial availability.""" + """Toggle tariff trial availability. + + When enabling trial on a tariff, removes trial flag from all other tariffs + (only one tariff can be the trial tariff at a time). + """ tariff = await get_tariff_by_id(db, tariff_id) if not tariff: raise HTTPException( @@ -453,6 +457,17 @@ async def toggle_trial_tariff( ) new_status = not tariff.is_trial_available + + if new_status: + # При включении триала - снимаем флаг со ВСЕХ тарифов, затем ставим на текущий + # Это гарантирует, что триальным будет только один тариф + await db.execute( + Tariff.__table__.update().values(is_trial_available=False) + ) + await db.commit() + # Обновляем объект тарифа после массового обновления + await db.refresh(tariff) + await update_tariff(db, tariff, is_trial_available=new_status) status_text = "set as trial" if new_status else "removed from trial" @@ -506,8 +521,17 @@ async def get_tariff_stats( ) trial_count = trial_result.scalar() or 0 - # TODO: Calculate revenue from transactions - revenue_kopeks = 0 + # Calculate revenue from subscription payments for users on this tariff + revenue_result = await db.execute( + select(func.coalesce(func.sum(Transaction.amount_kopeks), 0)) + .join(Subscription, Transaction.user_id == Subscription.user_id) + .where( + Subscription.tariff_id == tariff_id, + Transaction.type == TransactionType.SUBSCRIPTION_PAYMENT.value, + Transaction.is_completed == True, + ) + ) + revenue_kopeks = revenue_result.scalar() or 0 return TariffStatsResponse( id=tariff_id, diff --git a/app/database/crud/tariff.py b/app/database/crud/tariff.py index b88ca9ca..5ca6401b 100644 --- a/app/database/crud/tariff.py +++ b/app/database/crud/tariff.py @@ -83,12 +83,17 @@ async def count_tariffs(db: AsyncSession, *, include_inactive: bool = False) -> async def get_trial_tariff(db: AsyncSession) -> Optional[Tariff]: - """Получает тариф, доступный для триала (is_trial_available=True).""" + """Получает тариф, доступный для триала (is_trial_available=True). + + Сортируется по updated_at DESC, чтобы вернуть последний установленный + триальный тариф (на случай если их несколько). + """ query = ( select(Tariff) .where(Tariff.is_trial_available.is_(True)) .where(Tariff.is_active.is_(True)) .options(selectinload(Tariff.allowed_promo_groups)) + .order_by(Tariff.updated_at.desc().nullslast(), Tariff.id.desc()) .limit(1) ) result = await db.execute(query)