diff --git a/bot/handlers/admin/user_management.py b/bot/handlers/admin/user_management.py index 2cbcadb..4089055 100644 --- a/bot/handlers/admin/user_management.py +++ b/bot/handlers/admin/user_management.py @@ -15,6 +15,7 @@ from bot.states.admin_states import AdminStates from bot.keyboards.inline.admin_keyboards import get_back_to_admin_panel_keyboard from bot.services.subscription_service import SubscriptionService from bot.services.panel_api_service import PanelApiService +from bot.services.referral_service import ReferralService from bot.middlewares.i18n import JsonI18n from bot.utils import get_message_content, send_direct_message from aiogram.utils.keyboard import InlineKeyboardBuilder, InlineKeyboardButton @@ -106,7 +107,8 @@ def get_user_card_keyboard(user_id: int, i18n_instance, lang: str) -> InlineKeyb async def format_user_card(user: User, session: AsyncSession, subscription_service: SubscriptionService, - i18n_instance, lang: str) -> str: + i18n_instance, lang: str, + referral_service: Optional[ReferralService] = None) -> str: """Format user information as a detailed card""" _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) @@ -176,6 +178,17 @@ async def format_user_card(user: User, session: AsyncSession, had_subscriptions = await subscription_service.has_had_any_subscription(session, user.user_id) trial_status = _("admin_user_trial_used", default="Использовал") if had_subscriptions else _("admin_user_trial_not_used", default="Не использовал") card_parts.append(f"{_('admin_user_trial_label', default='🏡 Триал:')} {hcode(trial_status)}") + + # Referral stats + if referral_service is not None: + try: + stats = await referral_service.get_referral_stats(session, user.user_id) + invited_count = stats.get('invited_count', 0) + purchased_count = stats.get('purchased_count', 0) + card_parts.append(f"{_('admin_user_invited_friends_label', default='👥 Приглашено друзей:')} {hcode(str(invited_count))}") + card_parts.append(f"{_('admin_user_ref_purchased_label', default='💳 Купили подписку:')} {hcode(str(purchased_count))}") + except Exception as e_rs: + logging.error(f"Failed to build referral stats for admin card {user.user_id}: {e_rs}") except Exception as e: logging.error(f"Error getting user statistics for {user.user_id}: {e}") @@ -224,7 +237,8 @@ async def process_user_search_handler(message: types.Message, state: FSMContext, # Format and send user card try: - user_card_text = await format_user_card(user_model, session, subscription_service, i18n, current_lang) + referral_service = ReferralService(settings, subscription_service, message.bot, i18n) + user_card_text = await format_user_card(user_model, session, subscription_service, i18n, current_lang, referral_service) keyboard = get_user_card_keyboard(user_model.user_id, i18n, current_lang) await message.answer( @@ -482,7 +496,10 @@ async def handle_refresh_user_card(callback: types.CallbackQuery, user: User, await callback.answer("User not found", show_alert=True) return - user_card_text = await format_user_card(fresh_user, session, subscription_service, i18n_instance, lang) + from config.settings import Settings as _Settings + _settings = _Settings() + referral_service = ReferralService(_settings, subscription_service, callback.message.bot, i18n_instance) + user_card_text = await format_user_card(fresh_user, session, subscription_service, i18n_instance, lang, referral_service) keyboard = get_user_card_keyboard(fresh_user.user_id, i18n_instance, lang) try: @@ -556,7 +573,8 @@ async def process_subscription_days_handler(message: types.Message, state: FSMCo # Show updated user card user = await user_dal.get_user_by_id(session, target_user_id) if user: - user_card_text = await format_user_card(user, session, subscription_service, i18n, current_lang) + referral_service = ReferralService(settings, subscription_service, message.bot, i18n) + user_card_text = await format_user_card(user, session, subscription_service, i18n, current_lang, referral_service) keyboard = get_user_card_keyboard(user.user_id, i18n, current_lang) await message.answer( @@ -664,7 +682,8 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte from bot.services.panel_api_service import PanelApiService async with PanelApiService(settings) as panel_service: subscription_service = SubscriptionService(settings, panel_service) - user_card_text = await format_user_card(target_user, session, subscription_service, i18n, current_lang) + referral_service = ReferralService(settings, subscription_service, bot, i18n) + user_card_text = await format_user_card(target_user, session, subscription_service, i18n, current_lang, referral_service) keyboard = get_user_card_keyboard(target_user.user_id, i18n, current_lang) await message.answer( diff --git a/bot/services/tribute_service.py b/bot/services/tribute_service.py index 06d1d25..3057d3c 100644 --- a/bot/services/tribute_service.py +++ b/bot/services/tribute_service.py @@ -250,8 +250,39 @@ class TributeService: from bot.keyboards.inline.user_keyboards import get_subscribe_only_markup try: - # Set all user's subscriptions to expire in 1 day (grace period) - await subscription_dal.set_user_subscriptions_cancelled_with_grace(session, user_id, grace_days=1) + grace_days = 1 + grace_end = datetime.now(timezone.utc) + timedelta(days=grace_days) + + active_subscriptions = await subscription_dal.get_active_subscriptions_for_user(session, user_id) + + panel_users_updated: set[str] = set() + for sub in active_subscriptions: + updated_sub = await subscription_dal.update_subscription( + session, + sub.subscription_id, + { + "end_date": grace_end, + "status_from_panel": "CANCELLED", + "skip_notifications": True, + }, + ) + + panel_uuid = updated_sub.panel_user_uuid if updated_sub else None + if panel_uuid and panel_uuid not in panel_users_updated: + panel_users_updated.add(panel_uuid) + panel_payload = { + "expireAt": grace_end.isoformat(timespec="milliseconds").replace("+00:00", "Z"), + } + try: + await self.panel_service.update_user_details_on_panel( + panel_uuid, + panel_payload, + log_response=False, + ) + except Exception as panel_err: + logging.error( + f"Failed to update panel expiry for user {user_id} (panel_uuid {panel_uuid}) during Tribute cancellation: {panel_err}") + await session.commit() # Send notification about cancellation if enabled diff --git a/locales/en.json b/locales/en.json index f772b3b..6d6b0a3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -228,6 +228,7 @@ "admin_user_search_new_button": "🔍 Find Another", "admin_user_view_all_logs_button": "📋 All Actions", "admin_user_back_to_card_button": "🔙 Back to Card", + "admin_user_recent_actions_title": "📜 Recent actions of user {user_id}:", "admin_user_not_found": "❌ User not found: {input}", "admin_user_not_found_action": "User not found", "admin_user_card_error": "❌ Error displaying user card", @@ -316,6 +317,8 @@ "admin_user_na_value": "N/A", "admin_user_subscription_none": "No active subscription", "admin_user_actions_count_label": "📜 Total actions:", + "admin_user_invited_friends_label": "👥 Friends invited:", + "admin_user_ref_purchased_label": "💳 Purchased subscription:", "admin_user_subscription_active_until": "⏰ Active until:", "admin_user_subscription_error": "Loading error", "admin_promo_management_button": "🎟 Promo Management", diff --git a/locales/ru.json b/locales/ru.json index 7227c27..686bc23 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -227,6 +227,7 @@ "admin_user_search_new_button": "🔍 Новый поиск", "admin_user_view_all_logs_button": "📋 Все логи", "admin_user_back_to_card_button": "🔙 К карточке", + "admin_user_recent_actions_title": "📜 Последние действия пользователя {user_id}:", "admin_user_not_found": "❌ Пользователь не найден: {input}", "admin_user_not_found_action": "Пользователь не найден", "admin_user_card_error": "❌ Ошибка отображения карточки пользователя", @@ -315,6 +316,8 @@ "admin_user_na_value": "N/A", "admin_user_subscription_none": "Нет активной подписки", "admin_user_actions_count_label": "📜 Всего действий:", + "admin_user_invited_friends_label": "👥 Приглашено друзей:", + "admin_user_ref_purchased_label": "💳 Купили подписку:", "admin_user_subscription_active_until": "⏰ Действует до:", "admin_user_subscription_error": "Ошибка загрузки", "admin_promo_management_button": "🎟 Управление промокодами",