diff --git a/database/statistics.py b/database/statistics.py index d1db5776..4d1b2b14 100644 --- a/database/statistics.py +++ b/database/statistics.py @@ -33,10 +33,28 @@ async def count_active_keys(session: AsyncSession) -> int: return await session.scalar(select(func.count()).select_from(Key).where(Key.expiry_time > current_time_ms)) -async def count_trial_keys(session: AsyncSession) -> int: +async def count_active_paid_keys(session: AsyncSession) -> int: + current_time_ms = int(datetime.utcnow().timestamp() * 1000) trial_tariffs_subquery = select(Tariff.id).where(Tariff.group_code == "trial") + + return await session.scalar( + select(func.count()) + .select_from(Key) + .where(Key.expiry_time > current_time_ms) + .where(~Key.tariff_id.in_(trial_tariffs_subquery)) + ) - return await session.scalar(select(func.count()).select_from(Key).where(Key.tariff_id.in_(trial_tariffs_subquery))) + +async def count_active_trial_keys(session: AsyncSession) -> int: + current_time_ms = int(datetime.utcnow().timestamp() * 1000) + trial_tariffs_subquery = select(Tariff.id).where(Tariff.group_code == "trial") + + return await session.scalar( + select(func.count()) + .select_from(Key) + .where(Key.expiry_time > current_time_ms) + .where(Key.tariff_id.in_(trial_tariffs_subquery)) + ) async def get_tariff_distribution( @@ -80,6 +98,14 @@ async def get_tariff_durations(session: AsyncSession, tariff_ids: list[int]) -> return dict(result.all()) +async def get_tariff_subgroups(session: AsyncSession, tariff_ids: list[int]) -> dict[int, str | None]: + if not tariff_ids: + return {} + + result = await session.execute(select(Tariff.id, Tariff.subgroup_title).where(Tariff.id.in_(tariff_ids))) + return dict(result.all()) + + async def count_total_referrals(session: AsyncSession) -> int: return await session.scalar(select(func.count()).select_from(Referral)) diff --git a/handlers/admin/stats/stats_handler.py b/handlers/admin/stats/stats_handler.py index a956817d..8d038d6e 100644 --- a/handlers/admin/stats/stats_handler.py +++ b/handlers/admin/stats/stats_handler.py @@ -12,11 +12,12 @@ from bot import bot from config import ADMIN_ID from database import ( count_active_keys, + count_active_paid_keys, + count_active_trial_keys, count_hot_leads, count_total_keys, count_total_referrals, count_total_users, - count_trial_keys, count_users_registered_between, count_users_registered_since, count_users_updated_today, @@ -24,6 +25,7 @@ from database import ( get_tariff_durations, get_tariff_groups, get_tariff_names, + get_tariff_subgroups, sum_payments_between, sum_payments_since, sum_total_payments, @@ -92,20 +94,25 @@ async def handle_stats(callback_query: CallbackQuery, session: AsyncSession): total_keys = await count_total_keys(session) active_keys = await count_active_keys(session) + active_paid_keys = await count_active_paid_keys(session) + active_trial_keys = await count_active_trial_keys(session) expired_keys = total_keys - active_keys - trial_keys_count = await count_trial_keys(session) tariff_counts, no_tariff_keys = await get_tariff_distribution(session, include_unbound=True) tariff_names = await get_tariff_names(session, [tid for tid, _ in tariff_counts]) tariff_groups = await get_tariff_groups(session, [tid for tid, _ in tariff_counts]) + tariff_subgroups = await get_tariff_subgroups(session, [tid for tid, _ in tariff_counts]) tariff_durations = await get_tariff_durations(session, [tid for tid, _ in tariff_counts]) grouped_tariffs = {} for tid, count in tariff_counts: group = tariff_groups.get(tid, "unknown") + subgroup = tariff_subgroups.get(tid) if group not in grouped_tariffs: - grouped_tariffs[group] = [] - grouped_tariffs[group].append((tid, count)) + grouped_tariffs[group] = {} + if subgroup not in grouped_tariffs[group]: + grouped_tariffs[group][subgroup] = [] + grouped_tariffs[group][subgroup].append((tid, count)) tariff_stats_text = "" duration_buckets = Counter() @@ -137,12 +144,40 @@ async def handle_stats(callback_query: CallbackQuery, session: AsyncSession): for name, count in sorted_buckets: tariff_stats_text += f"├ {name}: {count}\n" - for group, tariffs in grouped_tariffs.items(): - tariff_stats_text += f"Тариф {group}\n" - sorted_tariffs = sorted(tariffs, key=lambda x: tariff_durations.get(x[0], 0)) - for tid, count in sorted_tariffs: - name = tariff_names.get(tid, f"ID {tid}") - tariff_stats_text += f" ├ {name}: {count}\n" + for group_idx, (group, subgroups_dict) in enumerate(grouped_tariffs.items()): + group_total = 0 + for tariffs_list in subgroups_dict.values(): + group_total += sum(count for _, count in tariffs_list) + + tariff_stats_text += f"Тариф {group} ({group_total})\n" + sorted_subgroups = sorted( + subgroups_dict.items(), + key=lambda x: (x[0] is None, x[0] or "") + ) + for subgroup_idx, (subgroup, tariffs) in enumerate(sorted_subgroups): + sorted_tariffs = sorted(tariffs, key=lambda x: tariff_durations.get(x[0], 0)) + subgroup_total = sum(count for _, count in sorted_tariffs) + is_last_subgroup = subgroup_idx == len(sorted_subgroups) - 1 + + if subgroup: + prefix = "└─" if is_last_subgroup else "├─" + tariff_stats_text += f" {prefix} Подгруппа: {subgroup} ({subgroup_total})\n" + + for tariff_idx, (tid, count) in enumerate(sorted_tariffs): + name = tariff_names.get(tid, f"ID {tid}") + is_last_tariff = tariff_idx == len(sorted_tariffs) - 1 + + if subgroup: + if is_last_tariff and is_last_subgroup: + prefix = " └─" + else: + prefix = " ├─" + else: + if is_last_tariff and is_last_subgroup: + prefix = " └─" + else: + prefix = " ├─" + tariff_stats_text += f"{prefix} {name}: {count}\n" tariff_stats_text = ( "└ По тарифам и срокам:\n" + tariff_stats_text if tariff_stats_text else "└ Нет данных по тарифам\n" @@ -183,8 +218,9 @@ async def handle_stats(callback_query: CallbackQuery, session: AsyncSession): f"
" f"├ 📦 Всего сгенерировано: {total_keys}\n" f"├ ✅ Активных: {active_keys}\n" + f"│ ├ 💰 Платных: {active_paid_keys}\n" + f"│ └ 🧪 Триальных: {active_trial_keys}\n" f"├ ❌ Просроченных: {expired_keys}\n" - f"├ 🧪 Триальных: {trial_keys_count}\n" f"{tariff_stats_text}" f"\n" f"💰 Финансы:\n" diff --git a/handlers/keys/key_renew.py b/handlers/keys/key_renew.py index a1fcb892..459d2e70 100644 --- a/handlers/keys/key_renew.py +++ b/handlers/keys/key_renew.py @@ -277,6 +277,26 @@ async def show_tariffs_in_renew_subgroup(callback: CallbackQuery, state: FSMCont group_code = row[0] + tariff_id = record.get("tariff_id") + if tariff_id: + if await check_tariff_exists(session, tariff_id): + current_tariff = await get_tariff_by_id(session, tariff_id) + + forbidden_groups = ["discounts", "discounts_max", "gifts", "trial"] + + try: + hook_results = await run_hooks( + "renewal_forbidden_groups", chat_id=callback.from_user.id, admin=False, session=session + ) + for hook_result in hook_results: + additional_groups = hook_result.get("additional_groups", []) + forbidden_groups.extend(additional_groups) + except Exception as e: + logger.warning(f"[RENEW_SUBGROUP] Ошибка при получении дополнительных групп: {e}") + + if current_tariff and current_tariff["group_code"] not in forbidden_groups: + group_code = current_tariff["group_code"] + tg_id = callback.from_user.id language_code = callback.from_user.language_code discount_info = await check_hot_lead_discount(session, tg_id)