mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-03-05 13:23:48 +00:00
@@ -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()
|
||||
|
||||
@@ -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 = "⚙️ <b>Фильтры пользователей</b>\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"👥 <b>Список пользователей по балансу</b> (стр. {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<b>Подписка:</b> Отсутствует"
|
||||
|
||||
# Проверяем состояние, чтобы определить, откуда пришел пользователь
|
||||
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_")
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
@@ -626,7 +629,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,
|
||||
@@ -3602,6 +3695,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,
|
||||
@@ -3899,11 +4005,6 @@ def register_handlers(dp: Dispatcher):
|
||||
F.data == "subscription_confirm",
|
||||
SubscriptionStates.confirming_purchase
|
||||
)
|
||||
|
||||
dp.callback_query.register(
|
||||
resume_subscription_checkout,
|
||||
F.data == "subscription_resume_checkout",
|
||||
)
|
||||
|
||||
dp.callback_query.register(
|
||||
handle_autopay_menu,
|
||||
|
||||
@@ -110,12 +110,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=[
|
||||
[
|
||||
@@ -375,7 +389,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}"),
|
||||
@@ -406,7 +420,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)
|
||||
|
||||
@@ -269,6 +269,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=[
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -86,6 +87,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()
|
||||
|
||||
Reference in New Issue
Block a user