From ec3430db278e30dd5da988c6ce09476cf8040feb Mon Sep 17 00:00:00 2001 From: Egor Date: Fri, 19 Sep 2025 04:34:32 +0300 Subject: [PATCH 1/6] Update states.py --- app/states.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/states.py b/app/states.py index b0479c08..218e6aee 100644 --- a/app/states.py +++ b/app/states.py @@ -16,6 +16,7 @@ class SubscriptionStates(StatesGroup): adding_devices = State() extending_subscription = State() confirming_traffic_reset = State() + cart_saved_for_topup = State() class BalanceStates(StatesGroup): waiting_for_amount = State() From e43f6afd8a5d6a183f57cae612c5f383fa1e00c0 Mon Sep 17 00:00:00 2001 From: Egor Date: Fri, 19 Sep 2025 04:39:24 +0300 Subject: [PATCH 2/6] Update subscription.py --- app/handlers/subscription.py | 131 +++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 6 deletions(-) diff --git a/app/handlers/subscription.py b/app/handlers/subscription.py index af67d840..81eba8b1 100644 --- a/app/handlers/subscription.py +++ b/app/handlers/subscription.py @@ -517,7 +517,97 @@ async def start_subscription_purchase( await state.set_state(SubscriptionStates.selecting_period) await callback.answer() +async def save_cart_and_redirect_to_topup( + callback: types.CallbackQuery, + state: FSMContext, + db_user: User, + missing_amount: int +): + from app.handlers.balance import show_payment_methods + + texts = get_texts(db_user.language) + data = await state.get_data() + + await state.set_state(SubscriptionStates.cart_saved_for_topup) + await state.update_data({ + **data, + 'saved_cart': True, + 'missing_amount': missing_amount, + 'return_to_cart': True + }) + + await callback.message.edit_text( + f"💰 Недостаточно средств для оформления подписки\n\n" + f"Требуется: {texts.format_price(missing_amount)}\n" + f"У вас: {texts.format_price(db_user.balance_kopeks)}\n\n" + f"🛒 Ваша корзина сохранена!\n" + f"После пополнения баланса вы сможете вернуться к оформлению подписки.\n\n" + f"Выберите способ пополнения:", + reply_markup=get_payment_methods_keyboard_with_cart(db_user.language), + parse_mode="HTML" + ) +async def return_to_saved_cart( + callback: types.CallbackQuery, + state: FSMContext, + db_user: User, + db: AsyncSession +): + data = await state.get_data() + texts = get_texts(db_user.language) + + if not data.get('saved_cart'): + await callback.answer("❌ Сохраненная корзина не найдена", show_alert=True) + return + + total_price = data.get('total_price', 0) + + if db_user.balance_kopeks < total_price: + missing_amount = total_price - db_user.balance_kopeks + await callback.message.edit_text( + f"❌ Все еще недостаточно средств\n\n" + f"Требуется: {texts.format_price(total_price)}\n" + f"У вас: {texts.format_price(db_user.balance_kopeks)}\n" + f"Не хватает: {texts.format_price(missing_amount)}", + reply_markup=get_insufficient_balance_keyboard_with_cart(db_user.language) + ) + return + + from app.utils.pricing_utils import calculate_months_from_days, format_period_description + + countries = await _get_available_countries() + selected_countries_names = [] + + months_in_period = calculate_months_from_days(data['period_days']) + period_display = format_period_description(data['period_days'], db_user.language) + + for country in countries: + if country['uuid'] in data['countries']: + selected_countries_names.append(country['name']) + + if settings.is_traffic_fixed(): + traffic_display = "Безлимитный" if data['traffic_gb'] == 0 else f"{data['traffic_gb']} ГБ" + else: + traffic_display = "Безлимитный" if data['traffic_gb'] == 0 else f"{data['traffic_gb']} ГБ" + + summary_text = ( + "🛒 Восстановленная корзина\n\n" + f"📅 Период: {period_display}\n" + f"📊 Трафик: {traffic_display}\n" + f"🌍 Страны: {', '.join(selected_countries_names)}\n" + f"📱 Устройства: {data['devices']}\n\n" + f"💎 Общая стоимость: {texts.format_price(total_price)}\n\n" + "Подтверждаете покупку?" + ) + + await callback.message.edit_text( + summary_text, + reply_markup=get_subscription_confirm_keyboard_with_cart(db_user.language), + parse_mode="HTML" + ) + + await state.set_state(SubscriptionStates.confirming_purchase) + await callback.answer("✅ Корзина восстановлена!") async def handle_add_countries( callback: types.CallbackQuery, @@ -2217,12 +2307,18 @@ async def confirm_purchase( if db_user.balance_kopeks < final_price: missing_kopeks = final_price - db_user.balance_kopeks - await callback.message.edit_text( - texts.INSUFFICIENT_BALANCE.format(amount=texts.format_price(missing_kopeks)), - reply_markup=get_insufficient_balance_keyboard(db_user.language), - ) - await callback.answer() - return + + subscription = db_user.subscription + if not subscription or subscription.is_trial: + await save_cart_and_redirect_to_topup(callback, state, db_user, missing_kopeks) + return + else: + await callback.message.edit_text( + texts.INSUFFICIENT_BALANCE.format(amount=texts.format_price(missing_kopeks)), + reply_markup=get_insufficient_balance_keyboard(db_user.language), + ) + await callback.answer() + return try: success = await subtract_user_balance( @@ -3505,6 +3601,19 @@ async def confirm_switch_traffic( await callback.answer() +async def clear_saved_cart( + callback: types.CallbackQuery, + state: FSMContext, + db_user: User, + db: AsyncSession +): + await state.clear() + + from app.handlers.menu import show_main_menu + await show_main_menu(callback, db_user, db) + + await callback.answer("🗑️ Корзина очищена") + async def execute_switch_traffic( callback: types.CallbackQuery, @@ -3802,6 +3911,16 @@ def register_handlers(dp: Dispatcher): F.data == "subscription_confirm", SubscriptionStates.confirming_purchase ) + + dp.callback_query.register( + return_to_saved_cart, + F.data == "return_to_saved_cart" + ) + + dp.callback_query.register( + clear_saved_cart, + F.data == "clear_saved_cart" + ) dp.callback_query.register( handle_autopay_menu, From 7baebd85c7a8e2aaa7c1049d1ccf27f71c665f2d Mon Sep 17 00:00:00 2001 From: Egor Date: Fri, 19 Sep 2025 04:40:56 +0300 Subject: [PATCH 3/6] Update balance.py --- app/handlers/balance.py | 71 +++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/app/handlers/balance.py b/app/handlers/balance.py index f01f9588..9f2ea2be 100644 --- a/app/handlers/balance.py +++ b/app/handlers/balance.py @@ -26,30 +26,22 @@ TRANSACTIONS_PER_PAGE = 10 def get_quick_amount_buttons(language: str) -> list: - """ - Генерирует кнопки быстрого выбора суммы пополнения на основе - AVAILABLE_SUBSCRIPTION_PERIODS и PRICE_*_DAYS - """ if not settings.YOOKASSA_QUICK_AMOUNT_SELECTION_ENABLED: return [] buttons = [] periods = settings.get_available_subscription_periods() - # Ограничиваем до 6 кнопок (3 ряда по 2 кнопки) periods = periods[:6] for period in periods: - # Получаем цену из настроек price_attr = f"PRICE_{period}_DAYS" if hasattr(settings, price_attr): price_kopeks = getattr(settings, price_attr) price_rubles = price_kopeks // 100 - # Создаем callback_data для каждой кнопки callback_data = f"quick_amount_{price_kopeks}" - # Добавляем кнопку buttons.append( types.InlineKeyboardButton( text=f"{price_rubles} ₽ ({period} дней)", @@ -57,7 +49,6 @@ def get_quick_amount_buttons(language: str) -> list: ) ) - # Разбиваем кнопки на ряды (по 2 в ряд) keyboard_rows = [] for i in range(0, len(buttons), 2): keyboard_rows.append(buttons[i:i + 2]) @@ -386,7 +377,67 @@ async def start_tribute_payment( await callback.answer("❌ Ошибка создания платежа", show_alert=True) await callback.answer() - + +async def handle_successful_topup_with_cart( + user_id: int, + amount_kopeks: int, + bot, + db: AsyncSession +): + from app.database.crud.user import get_user_by_id + from aiogram.fsm.context import FSMContext + from aiogram.fsm.storage.base import StorageKey + from app.bot import dp + + user = await get_user_by_id(db, user_id) + if not user: + return + + storage = dp.storage + key = StorageKey(bot_id=bot.id, chat_id=user.telegram_id, user_id=user.telegram_id) + + try: + state_data = await storage.get_data(key) + current_state = await storage.get_state(key) + + if (current_state == "SubscriptionStates:cart_saved_for_topup" and + state_data.get('saved_cart')): + + texts = get_texts(user.language) + total_price = state_data.get('total_price', 0) + + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton( + text="🛒 Вернуться к оформлению подписки", + callback_data="return_to_saved_cart" + )], + [types.InlineKeyboardButton( + text="💰 Мой баланс", + callback_data="menu_balance" + )], + [types.InlineKeyboardButton( + text="🏠 Главное меню", + callback_data="back_to_menu" + )] + ]) + + success_text = ( + f"✅ Баланс пополнен на {texts.format_price(amount_kopeks)}!\n\n" + f"💰 Текущий баланс: {texts.format_price(user.balance_kopeks)}\n\n" + f"🛒 У вас есть сохраненная корзина подписки\n" + f"Стоимость: {texts.format_price(total_price)}\n\n" + f"Хотите продолжить оформление?" + ) + + await bot.send_message( + chat_id=user.telegram_id, + text=success_text, + reply_markup=keyboard, + parse_mode="HTML" + ) + + except Exception as e: + logger.error(f"Ошибка обработки успешного пополнения с корзиной: {e}") @error_handler async def request_support_topup( From 8273a31512ea761c78be0191c708f08ae926428c Mon Sep 17 00:00:00 2001 From: Egor Date: Fri, 19 Sep 2025 04:41:44 +0300 Subject: [PATCH 4/6] Update inline.py --- app/keyboards/inline.py | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/app/keyboards/inline.py b/app/keyboards/inline.py index a55bd6c6..48222cfa 100644 --- a/app/keyboards/inline.py +++ b/app/keyboards/inline.py @@ -246,6 +246,51 @@ def get_subscription_keyboard( return InlineKeyboardMarkup(inline_keyboard=keyboard) +def get_payment_methods_keyboard_with_cart(language: str = "ru") -> InlineKeyboardMarkup: + keyboard = get_payment_methods_keyboard(0, language) + + # Добавляем кнопку "Очистить корзину" + keyboard.inline_keyboard.append([ + InlineKeyboardButton( + text="🗑️ Очистить корзину и вернуться", + callback_data="clear_saved_cart" + ) + ]) + + return keyboard + +def get_subscription_confirm_keyboard_with_cart(language: str = "ru") -> InlineKeyboardMarkup: + return InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton( + text="✅ Подтвердить покупку", + callback_data="subscription_confirm" + )], + [InlineKeyboardButton( + text="🗑️ Очистить корзину", + callback_data="clear_saved_cart" + )], + [InlineKeyboardButton( + text="🔙 Назад", + callback_data="back_to_menu" + )] + ]) + +def get_insufficient_balance_keyboard_with_cart(language: str = "ru") -> InlineKeyboardMarkup: + return InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton( + text="💰 Пополнить баланс", + callback_data="balance_topup" + )], + [InlineKeyboardButton( + text="🗑️ Очистить корзину", + callback_data="clear_saved_cart" + )], + [InlineKeyboardButton( + text="🔙 Назад", + callback_data="back_to_menu" + )] + ]) + def get_trial_keyboard(language: str = "ru") -> InlineKeyboardMarkup: texts = get_texts(language) return InlineKeyboardMarkup(inline_keyboard=[ From efd8b191ff37916e27c3e55190839b911eb1cc42 Mon Sep 17 00:00:00 2001 From: Egor Date: Fri, 19 Sep 2025 04:46:31 +0300 Subject: [PATCH 5/6] Update subscription.py --- app/handlers/subscription.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/handlers/subscription.py b/app/handlers/subscription.py index 81eba8b1..b5805fdb 100644 --- a/app/handlers/subscription.py +++ b/app/handlers/subscription.py @@ -37,7 +37,10 @@ from app.keyboards.inline import ( get_updated_subscription_settings_keyboard, get_insufficient_balance_keyboard, get_extend_subscription_keyboard_with_prices, get_confirm_change_devices_keyboard, get_devices_management_keyboard, get_device_reset_confirm_keyboard, - get_device_management_help_keyboard + get_device_management_help_keyboard, + get_payment_methods_keyboard_with_cart, + get_subscription_confirm_keyboard_with_cart, + get_insufficient_balance_keyboard_with_cart ) from app.localization.texts import get_texts from app.services.remnawave_service import RemnaWaveService From 7f4899b885847d70c59a7b2444ea296e953f7872 Mon Sep 17 00:00:00 2001 From: gy9vin Date: Fri, 19 Sep 2025 08:40:29 +0300 Subject: [PATCH 6/6] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=BF=D0=BE=20=D0=B1=D0=B0?= =?UTF-8?q?=D0=BB=D0=B0=D0=BD=D1=81=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/database/crud/user.py | 11 ++- app/handlers/admin/users.py | 187 +++++++++++++++++++++++++++++++++-- app/keyboards/admin.py | 18 +++- app/services/user_service.py | 5 +- app/states.py | 3 + 5 files changed, 212 insertions(+), 12 deletions(-) diff --git a/app/database/crud/user.py b/app/database/crud/user.py index 004c5610..ff25714b 100644 --- a/app/database/crud/user.py +++ b/app/database/crud/user.py @@ -216,7 +216,8 @@ async def get_users_list( offset: int = 0, limit: int = 50, search: Optional[str] = None, - status: Optional[UserStatus] = None + status: Optional[UserStatus] = None, + order_by_balance: bool = False ) -> List[User]: query = select(User).options(selectinload(User.subscription)) @@ -237,7 +238,13 @@ async def get_users_list( query = query.where(or_(*conditions)) - query = query.order_by(User.created_at.desc()).offset(offset).limit(limit) + # Сортировка по балансу в порядке убывания, если order_by_balance=True + if order_by_balance: + query = query.order_by(User.balance_kopeks.desc()) + else: + query = query.order_by(User.created_at.desc()) + + query = query.offset(offset).limit(limit) result = await db.execute(query) return result.scalars().all() diff --git a/app/handlers/admin/users.py b/app/handlers/admin/users.py index b1e0a92a..02596078 100644 --- a/app/handlers/admin/users.py +++ b/app/handlers/admin/users.py @@ -11,7 +11,8 @@ from app.database.models import User, UserStatus, Subscription, SubscriptionStat from app.database.crud.user import get_user_by_id from app.keyboards.admin import ( get_admin_users_keyboard, get_user_management_keyboard, - get_admin_pagination_keyboard, get_confirmation_keyboard + get_admin_pagination_keyboard, get_confirmation_keyboard, + get_admin_users_filters_keyboard ) from app.localization.texts import get_texts from app.services.user_service import UserService @@ -58,15 +59,36 @@ async def show_users_menu( await callback.answer() +@admin_required +@error_handler +async def show_users_filters( + callback: types.CallbackQuery, + db_user: User, + state: FSMContext +): + + text = "⚙️ Фильтры пользователей\n\nВыберите фильтр для отображения пользователей:" + + await callback.message.edit_text( + text, + reply_markup=get_admin_users_filters_keyboard(db_user.language) + ) + await callback.answer() + + @admin_required @error_handler async def show_users_list( callback: types.CallbackQuery, db_user: User, db: AsyncSession, + state: FSMContext, page: int = 1 ): + # Сбрасываем состояние, так как мы в обычном списке + await state.set_state(None) + user_service = UserService() users_data = await user_service.get_users_page(db, page=page, limit=10) @@ -152,20 +174,139 @@ async def show_users_list( await callback.answer() +@admin_required +@error_handler +async def show_users_list_by_balance( + callback: types.CallbackQuery, + db_user: User, + db: AsyncSession, + state: FSMContext, + page: int = 1 +): + + # Устанавливаем состояние, чтобы отслеживать, откуда пришел пользователь + await state.set_state(AdminStates.viewing_user_from_balance_list) + + user_service = UserService() + users_data = await user_service.get_users_page(db, page=page, limit=10, order_by_balance=True) + + if not users_data["users"]: + await callback.message.edit_text( + "👥 Пользователи не найдены", + reply_markup=get_admin_users_keyboard(db_user.language) + ) + await callback.answer() + return + + text = f"👥 Список пользователей по балансу (стр. {page}/{users_data['total_pages']})\n\n" + text += "Нажмите на пользователя для управления:" + + keyboard = [] + + for user in users_data["users"]: + if user.status == UserStatus.ACTIVE.value: + status_emoji = "✅" + elif user.status == UserStatus.BLOCKED.value: + status_emoji = "🚫" + else: + status_emoji = "🗑️" + + subscription_emoji = "" + if user.subscription: + if user.subscription.is_trial: + subscription_emoji = "🎁" + elif user.subscription.is_active: + subscription_emoji = "💎" + else: + subscription_emoji = "⏰" + else: + subscription_emoji = "❌" + + button_text = f"{status_emoji} {subscription_emoji} {user.full_name}" + + if user.balance_kopeks > 0: + button_text += f" | 💰 {settings.format_price(user.balance_kopeks)}" + + # Добавляем дату окончания подписки, если есть подписка + if user.subscription and user.subscription.end_date: + days_left = (user.subscription.end_date - datetime.utcnow()).days + button_text += f" | 📅 {days_left}д" + + if len(button_text) > 60: + short_name = user.full_name + if len(short_name) > 20: + short_name = short_name[:17] + "..." + + button_text = f"{status_emoji} {subscription_emoji} {short_name}" + if user.balance_kopeks > 0: + button_text += f" | 💰 {settings.format_price(user.balance_kopeks)}" + + keyboard.append([ + types.InlineKeyboardButton( + text=button_text, + callback_data=f"admin_user_manage_{user.id}" + ) + ]) + + if users_data["total_pages"] > 1: + pagination_row = get_admin_pagination_keyboard( + users_data["current_page"], + users_data["total_pages"], + "admin_users_balance_list", + "admin_users", + db_user.language + ).inline_keyboard[0] + keyboard.append(pagination_row) + + keyboard.extend([ + [ + types.InlineKeyboardButton(text="🔍 Поиск", callback_data="admin_users_search"), + types.InlineKeyboardButton(text="📊 Статистика", callback_data="admin_users_stats") + ], + [ + types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_users") + ] + ]) + + await callback.message.edit_text( + text, + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard) + ) + await callback.answer() + + @admin_required @error_handler async def handle_users_list_pagination_fixed( callback: types.CallbackQuery, db_user: User, - db: AsyncSession + db: AsyncSession, + state: FSMContext ): try: callback_parts = callback.data.split('_') page = int(callback_parts[-1]) - await show_users_list(callback, db_user, db, page) + await show_users_list(callback, db_user, db, state, page) except (ValueError, IndexError) as e: logger.error(f"Ошибка парсинга номера страницы: {e}") - await show_users_list(callback, db_user, db, 1) + await show_users_list(callback, db_user, db, state, 1) + + +@admin_required +@error_handler +async def handle_users_balance_list_pagination( + callback: types.CallbackQuery, + db_user: User, + db: AsyncSession, + state: FSMContext +): + try: + callback_parts = callback.data.split('_') + page = int(callback_parts[-1]) + await show_users_list_by_balance(callback, db_user, db, state, page) + except (ValueError, IndexError) as e: + logger.error(f"Ошибка парсинга номера страницы: {e}") + await show_users_list_by_balance(callback, db_user, db, state, 1) @admin_required @@ -564,11 +705,18 @@ async def process_user_search( async def show_user_management( callback: types.CallbackQuery, db_user: User, - db: AsyncSession + db: AsyncSession, + state: FSMContext ): user_id = int(callback.data.split('_')[-1]) + # Проверяем, откуда пришел пользователь + back_callback = "admin_users_list" + + # Если callback_data содержит информацию о том, что мы пришли из списка по балансу + # В реальности это сложно определить, поэтому будем использовать состояние + user_service = UserService() profile = await user_service.get_user_profile(db, user_id) @@ -621,13 +769,19 @@ async def show_user_management( else: text += "\nПодписка: Отсутствует" + # Проверяем состояние, чтобы определить, откуда пришел пользователь + current_state = await state.get_state() + if current_state == AdminStates.viewing_user_from_balance_list: + back_callback = "admin_users_balance_filter" + await callback.message.edit_text( text, - reply_markup=get_user_management_keyboard(user.id, user.status, db_user.language) + reply_markup=get_user_management_keyboard(user.id, user.status, db_user.language, back_callback) ) await callback.answer() + @admin_required @error_handler async def start_balance_edit( @@ -2756,6 +2910,11 @@ def register_handlers(dp: Dispatcher): F.data.startswith("admin_users_list_page_") ) + dp.callback_query.register( + handle_users_balance_list_pagination, + F.data.startswith("admin_users_balance_list_page_") + ) + dp.callback_query.register( start_user_search, F.data == "admin_users_search" @@ -2938,6 +3097,22 @@ def register_handlers(dp: Dispatcher): admin_buy_subscription_execute, F.data.startswith("admin_buy_sub_execute_") ) + + # Регистрация обработчиков для фильтрации пользователей + dp.callback_query.register( + show_users_filters, + F.data == "admin_users_filters" + ) + + dp.callback_query.register( + show_users_list_by_balance, + F.data == "admin_users_balance_filter" + ) + + dp.callback_query.register( + show_users_list_by_balance, + F.data.startswith("admin_users_balance_list_page_") + ) diff --git a/app/keyboards/admin.py b/app/keyboards/admin.py index 067c1e83..d7e44296 100644 --- a/app/keyboards/admin.py +++ b/app/keyboards/admin.py @@ -107,12 +107,26 @@ def get_admin_users_keyboard(language: str = "ru") -> InlineKeyboardMarkup: InlineKeyboardButton(text="📊 Статистика", callback_data="admin_users_stats"), InlineKeyboardButton(text="🗑️ Неактивные", callback_data="admin_users_inactive") ], + [ + InlineKeyboardButton(text="⚙️ Фильтры", callback_data="admin_users_filters") + ], [ InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_submenu_users") ] ]) +def get_admin_users_filters_keyboard(language: str = "ru") -> InlineKeyboardMarkup: + return InlineKeyboardMarkup(inline_keyboard=[ + [ + InlineKeyboardButton(text="💰 По балансу", callback_data="admin_users_balance_filter") + ], + [ + InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_users") + ] + ]) + + def get_admin_subscriptions_keyboard(language: str = "ru") -> InlineKeyboardMarkup: return InlineKeyboardMarkup(inline_keyboard=[ [ @@ -236,7 +250,7 @@ def get_admin_statistics_keyboard(language: str = "ru") -> InlineKeyboardMarkup: ]) -def get_user_management_keyboard(user_id: int, user_status: str, language: str = "ru") -> InlineKeyboardMarkup: +def get_user_management_keyboard(user_id: int, user_status: str, language: str = "ru", back_callback: str = "admin_users_list") -> InlineKeyboardMarkup: keyboard = [ [ InlineKeyboardButton(text="💰 Баланс", callback_data=f"admin_user_balance_{user_id}"), @@ -267,7 +281,7 @@ def get_user_management_keyboard(user_id: int, user_status: str, language: str = ]) keyboard.append([ - InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_users_list") + InlineKeyboardButton(text="⬅️ Назад", callback_data=back_callback) ]) return InlineKeyboardMarkup(inline_keyboard=keyboard) diff --git a/app/services/user_service.py b/app/services/user_service.py index f26e14b2..c2dcd37f 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -145,13 +145,14 @@ class UserService: db: AsyncSession, page: int = 1, limit: int = 20, - status: Optional[UserStatus] = None + status: Optional[UserStatus] = None, + order_by_balance: bool = False ) -> Dict[str, Any]: try: offset = (page - 1) * limit users = await get_users_list( - db, offset=offset, limit=limit, status=status + db, offset=offset, limit=limit, status=status, order_by_balance=order_by_balance ) total_count = await get_users_count(db, status=status) diff --git a/app/states.py b/app/states.py index 218e6aee..9f826d63 100644 --- a/app/states.py +++ b/app/states.py @@ -70,6 +70,9 @@ class AdminStates(StatesGroup): editing_welcome_text = State() waiting_for_message_buttons = "waiting_for_message_buttons" + + # Состояния для отслеживания источника перехода + viewing_user_from_balance_list = State() class SupportStates(StatesGroup): waiting_for_message = State()