Фикс автопродления и Фиксы конкурсов

This commit is contained in:
gy9vin
2025-12-20 14:57:38 +03:00
parent c8c79ea807
commit bdc1df737b
4 changed files with 84 additions and 14 deletions
+2
View File
@@ -182,6 +182,8 @@ class Settings(BaseSettings):
AUTOPAY_WARNING_DAYS: str = "3,1"
ENABLE_AUTOPAY: bool = False
DEFAULT_AUTOPAY_ENABLED: bool = False
DEFAULT_AUTOPAY_DAYS_BEFORE: int = 3
MIN_BALANCE_FOR_AUTOPAY_KOPEKS: int = 10000
+10
View File
@@ -248,6 +248,16 @@ async def get_contest_events_count(
return int(result.scalar_one())
async def get_contest_events(
db: AsyncSession,
contest_id: int,
) -> List[ReferralContestEvent]:
result = await db.execute(
select(ReferralContestEvent).where(ReferralContestEvent.contest_id == contest_id)
)
return list(result.scalars().all())
async def mark_daily_summary_sent(
db: AsyncSession,
contest: ReferralContest,
+61 -8
View File
@@ -683,7 +683,8 @@ async def show_detailed_stats(
from app.services.referral_contest_service import referral_contest_service
stats = await referral_contest_service.get_detailed_contest_stats(db, contest_id)
lines = [
# Общее сообщение с основной статистикой
general_lines = [
"📈 <b>Детальная статистика конкурса</b>",
f"🏆 {contest.title}",
"",
@@ -692,23 +693,74 @@ async def show_detailed_stats(
f"💰 Оплатили подписок: <b>{stats['total_paid_amount'] // 100} руб.</b>",
f"❌ Не оплатили: <b>{stats['total_unpaid']}</b>",
"",
"📊 По участникам:",
"📊 Детали по участникам ниже:",
]
for p in stats['participants']:
lines.append(
f"{p['full_name']}: приглашено {p['total_referrals']}, оплатили {p['paid_referrals']}, не оплатили {p['unpaid_referrals']}, сумма {p['total_paid_amount'] // 100} руб."
)
await callback.message.edit_text(
"\n".join(lines),
"\n".join(general_lines),
reply_markup=get_referral_contest_manage_keyboard(
contest_id, is_active=contest.is_active, language=db_user.language
),
)
# Пагинация участников
if stats['participants']:
await show_detailed_stats_page(callback, db_user, db, contest_id=contest_id, page=1, stats=stats)
else:
await callback.message.reply_text("📊 Нет участников с рефералами.")
await callback.answer()
@admin_required
@error_handler
async def show_detailed_stats_page(
callback: types.CallbackQuery,
db_user,
db: AsyncSession,
contest_id: int = None,
page: int = 1,
stats: dict = None,
):
if contest_id is None or stats is None:
# Парсим из callback.data
parts = callback.data.split("_")
contest_id = int(parts[-2])
page = int(parts[-1])
# Получаем stats если не переданы
from app.services.referral_contest_service import referral_contest_service
stats = await referral_contest_service.get_detailed_contest_stats(db, contest_id)
participants = stats['participants']
total_participants = len(participants)
PAGE_SIZE = 10
total_pages = math.ceil(total_participants / PAGE_SIZE)
page = max(1, min(page, total_pages))
offset = (page - 1) * PAGE_SIZE
page_participants = participants[offset:offset + PAGE_SIZE]
lines = [f"📊 По участникам (страница {page}/{total_pages}):"]
for p in page_participants:
lines.append(
f"{p['full_name']}: приглашено {p['total_referrals']}, оплатили {p['paid_referrals']}, не оплатили {p['unpaid_referrals']}, сумма {p['total_paid_amount'] // 100} руб."
)
pagination = get_admin_pagination_keyboard(
page,
total_pages,
f"admin_contest_detailed_stats_page_{contest_id}",
back_callback=f"admin_contest_view_{contest_id}",
language=db_user.language,
)
await callback.message.reply_text(
"\n".join(lines),
reply_markup=pagination,
)
def register_handlers(dp: Dispatcher):
dp.callback_query.register(show_contests_menu, F.data == "admin_contests")
dp.callback_query.register(show_referral_contests_menu, F.data == "admin_contests_referral")
@@ -720,6 +772,7 @@ def register_handlers(dp: Dispatcher):
dp.callback_query.register(delete_contest, F.data.startswith("admin_contest_delete_"))
dp.callback_query.register(show_leaderboard, F.data.startswith("admin_contest_leaderboard_"))
dp.callback_query.register(show_detailed_stats, F.data.startswith("admin_contest_detailed_stats_"))
dp.callback_query.register(show_detailed_stats_page, F.data.startswith("admin_contest_detailed_stats_page_"))
dp.callback_query.register(start_contest_creation, F.data == "admin_contests_create")
dp.callback_query.register(select_contest_mode, F.data.in_(["admin_contest_mode_paid", "admin_contest_mode_registered"]))
+11 -6
View File
@@ -214,7 +214,8 @@ class MonitoringService:
await self._check_trial_inactivity_notifications(db)
await self._check_trial_channel_subscriptions(db)
await self._check_expired_subscription_followups(db)
await self._process_autopayments(db)
if settings.ENABLE_AUTOPAY:
await self._process_autopayments(db)
await self._cleanup_inactive_users(db)
await self._sync_with_remnawave(db)
@@ -931,7 +932,7 @@ class MonitoringService:
autopay_subscriptions = []
for sub in all_autopay_subscriptions:
days_before_expiry = (sub.end_date - current_time).days
if days_before_expiry <= sub.autopay_days_before:
if days_before_expiry <= min(sub.autopay_days_before, 3):
autopay_subscriptions.append(sub)
processed_count = 0
@@ -1059,12 +1060,16 @@ class MonitoringService:
texts = get_texts(user.language)
days_text = format_days_declension(days, user.language)
if subscription.autopay_enabled:
autopay_status = "✅ Включен - подписка продлится автоматически"
action_text = f"💰 Убедитесь, что на балансе достаточно средств: {texts.format_price(user.balance_kopeks)}"
if settings.ENABLE_AUTOPAY:
if subscription.autopay_enabled:
autopay_status = "✅ Включен - подписка продлится автоматически"
action_text = f"💰 Убедитесь, что на балансе достаточно средств: {texts.format_price(user.balance_kopeks)}"
else:
autopay_status = "❌ Отключен - не забудьте продлить вручную!"
action_text = "💡 Включите автоплатеж или продлите подписку вручную"
else:
autopay_status = "❌ Отключен - не забудьте продлить вручную!"
action_text = "💡 Включите автоплатеж или продлите подписку вручную"
action_text = "💡 Продлите подписку вручную"
message = f"""
⚠️ <b>Подписка истекает через {days_text}!</b>