diff --git a/app/bot.py b/app/bot.py index bdb21e50..36532569 100644 --- a/app/bot.py +++ b/app/bot.py @@ -40,7 +40,6 @@ from app.handlers.admin import ( welcome_text as admin_welcome_text, tickets as admin_tickets, reports as admin_reports, - pricing as admin_pricing, bot_configuration as admin_bot_configuration, ) from app.handlers.stars_payments import register_stars_handlers @@ -145,7 +144,6 @@ async def setup_bot() -> tuple[Bot, Dispatcher]: admin_welcome_text.register_welcome_text_handlers(dp) admin_tickets.register_handlers(dp) admin_reports.register_handlers(dp) - admin_pricing.register_handlers(dp) admin_bot_configuration.register_handlers(dp) common.register_handlers(dp) register_stars_handlers(dp) diff --git a/app/handlers/admin/pricing.py b/app/handlers/admin/pricing.py deleted file mode 100644 index 26d3d2a9..00000000 --- a/app/handlers/admin/pricing.py +++ /dev/null @@ -1,566 +0,0 @@ -import logging -from decimal import Decimal, InvalidOperation, ROUND_HALF_UP -from typing import Optional, Tuple - -from aiogram import Dispatcher, F, types -from aiogram.fsm.context import FSMContext -from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup -from sqlalchemy.ext.asyncio import AsyncSession - -from app.config import settings -from app.database.models import User -from app.localization.texts import get_texts -from app.services.system_settings_service import bot_configuration_service -from app.states import AdminPricingStates -from app.utils.decorators import admin_required, error_handler - -logger = logging.getLogger(__name__) - -PERIOD_SETTINGS: Tuple[Tuple[int, str], ...] = ( - (14, "PRICE_14_DAYS"), - (30, "PRICE_30_DAYS"), - (60, "PRICE_60_DAYS"), - (90, "PRICE_90_DAYS"), - (180, "PRICE_180_DAYS"), - (360, "PRICE_360_DAYS"), -) - -TRAFFIC_SETTINGS: Tuple[Tuple[int, str], ...] = ( - (5, "PRICE_TRAFFIC_5GB"), - (10, "PRICE_TRAFFIC_10GB"), - (25, "PRICE_TRAFFIC_25GB"), - (50, "PRICE_TRAFFIC_50GB"), - (100, "PRICE_TRAFFIC_100GB"), - (250, "PRICE_TRAFFIC_250GB"), - (500, "PRICE_TRAFFIC_500GB"), - (1000, "PRICE_TRAFFIC_1000GB"), - (0, "PRICE_TRAFFIC_UNLIMITED"), -) - -EXTRA_SETTINGS: Tuple[Tuple[str, str], ...] = ( - ("device", "PRICE_PER_DEVICE"), -) - -MAX_PRICE_RUBLES = Decimal("100000") - - -def _format_price(value: Optional[int]) -> str: - return settings.format_price(int(value or 0)) - - -def _get_current_value(key: str) -> int: - current = bot_configuration_service.get_current_value(key) - if current is None: - original = bot_configuration_service.get_original_value(key) - return int(original or 0) - return int(current) - - -def _get_period_label(texts, days: int) -> str: - return texts.t("ADMIN_PRICING_DAYS_LABEL", "{days} дн.").format(days=days) - - -def _get_traffic_label(texts, amount: int) -> str: - if amount == 0: - return texts.t("ADMIN_PRICING_UNLIMITED_LABEL", "Безлимит") - return texts.t("ADMIN_PRICING_TRAFFIC_GB", "{gb} ГБ").format(gb=amount) - - -def _build_main_summary(texts) -> str: - period_lines = [ - texts.t( - f"PERIOD_{days}_DAYS", - f"📅 {days} дней - {_format_price(_get_current_value(key))}", - ) - for days, key in PERIOD_SETTINGS - ] - - traffic_lines = [] - for amount, key in TRAFFIC_SETTINGS: - traffic_key = "UNLIMITED" if amount == 0 else f"{amount}GB" - default_label = ( - f"📊 {_get_traffic_label(texts, amount)} - {_format_price(_get_current_value(key))}" - ) - traffic_lines.append( - texts.t(f"TRAFFIC_{traffic_key}", default_label) - ) - - device_line = texts.t( - "ADMIN_PRICING_DEVICE_LABEL", - "📱 Дополнительное устройство — {price}", - ).format(price=_format_price(_get_current_value("PRICE_PER_DEVICE"))) - - parts = [ - texts.t("ADMIN_PRICING_TITLE", "💰 Управление ценами"), - "", - texts.t( - "ADMIN_PRICING_DESCRIPTION", - "Настройте стоимость подписок, пакетов трафика и дополнительных опций.", - ), - "", - texts.t("ADMIN_PRICING_SECTION_PERIODS", "📅 Периоды подписки"), - "\n".join(period_lines), - "", - texts.t("ADMIN_PRICING_SECTION_TRAFFIC", "📦 Пакеты трафика"), - "\n".join(traffic_lines), - "", - texts.t("ADMIN_PRICING_SECTION_EXTRAS", "🔧 Дополнительные опции"), - device_line, - ] - - return "\n".join(part for part in parts if part) - - -def _period_keyboard(texts) -> InlineKeyboardMarkup: - rows = [ - [ - InlineKeyboardButton( - text=texts.t("ADMIN_PRICING_EDIT_PERIOD", "✏️ {label}").format( - label=_get_period_label(texts, days) - ), - callback_data=f"admin_pricing_edit_period_{days}", - ) - ] - for days, _ in PERIOD_SETTINGS - ] - rows.append([InlineKeyboardButton(text=texts.BACK, callback_data="admin_pricing")]) - return InlineKeyboardMarkup(inline_keyboard=rows) - - -def _traffic_keyboard(texts) -> InlineKeyboardMarkup: - rows = [ - [ - InlineKeyboardButton( - text=texts.t("ADMIN_PRICING_EDIT_TRAFFIC", "✏️ {label}").format( - label=_get_traffic_label(texts, amount) - ), - callback_data=( - f"admin_pricing_edit_traffic_{'unlimited' if amount == 0 else amount}" - ), - ) - ] - for amount, _ in TRAFFIC_SETTINGS - ] - rows.append([InlineKeyboardButton(text=texts.BACK, callback_data="admin_pricing")]) - return InlineKeyboardMarkup(inline_keyboard=rows) - - -def _extras_keyboard(texts) -> InlineKeyboardMarkup: - return InlineKeyboardMarkup(inline_keyboard=[ - [ - InlineKeyboardButton( - text=texts.t("ADMIN_PRICING_EDIT_DEVICE", "✏️ Цена за устройство"), - callback_data="admin_pricing_edit_extra_device", - ) - ], - [InlineKeyboardButton(text=texts.BACK, callback_data="admin_pricing")], - ]) - - -def _pricing_main_keyboard(texts) -> InlineKeyboardMarkup: - return InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text=texts.t("ADMIN_PRICING_PERIODS", "📅 Периоды подписки"), callback_data="admin_pricing_periods")], - [InlineKeyboardButton(text=texts.t("ADMIN_PRICING_TRAFFIC", "📦 Трафик-пакеты"), callback_data="admin_pricing_traffic")], - [InlineKeyboardButton(text=texts.t("ADMIN_PRICING_EXTRAS", "🔧 Доп. опции"), callback_data="admin_pricing_extras")], - [InlineKeyboardButton(text=texts.BACK, callback_data="admin_panel")], - ]) - - -def _find_period_setting(days: int) -> Optional[str]: - for item_days, key in PERIOD_SETTINGS: - if item_days == days: - return key - return None - - -def _find_traffic_setting(identifier: str) -> Optional[Tuple[int, str]]: - if identifier == "unlimited": - identifier_value = 0 - else: - try: - identifier_value = int(identifier) - except ValueError: - return None - for amount, key in TRAFFIC_SETTINGS: - if amount == identifier_value: - return amount, key - return None - - -def _find_extra_setting(slug: str) -> Optional[str]: - for extra_slug, key in EXTRA_SETTINGS: - if extra_slug == slug: - return key - return None - - -async def _prompt_price_input( - callback: types.CallbackQuery, - state: FSMContext, - texts, - label: str, - current_price: str, - setting_key: str, - return_callback: str, -) -> None: - await state.set_state(AdminPricingStates.waiting_for_price) - await state.update_data( - pricing_key=setting_key, - pricing_label=label, - return_callback=return_callback, - ) - - await callback.message.edit_text( - texts.t( - "ADMIN_PRICING_PROMPT", - "Введите новую цену для {label}.\nТекущая цена: {current}.\n\nПришлите значение в рублях (пример: 990 или 349.99).", - ).format(label=label, current=current_price), - reply_markup=InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text=texts.BACK, callback_data=return_callback)] - ]), - ) - await callback.answer() - - -@admin_required -@error_handler -async def show_pricing_dashboard( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, -): - texts = get_texts(db_user.language) - - await callback.message.edit_text( - _build_main_summary(texts), - reply_markup=_pricing_main_keyboard(texts), - ) - await callback.answer() - - -@admin_required -@error_handler -async def show_pricing_periods( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, -): - texts = get_texts(db_user.language) - - period_lines = [ - texts.t( - f"PERIOD_{days}_DAYS", - f"📅 {days} дней - {_format_price(_get_current_value(key))}", - ) - for days, key in PERIOD_SETTINGS - ] - - text = "\n".join( - part - for part in ( - texts.t("ADMIN_PRICING_TITLE", "💰 Управление ценами"), - "", - texts.t("ADMIN_PRICING_SECTION_PERIODS", "📅 Периоды подписки"), - "\n".join(period_lines), - "", - texts.t( - "ADMIN_PRICING_HINT_PERIODS", - "Выберите период, чтобы изменить его стоимость.", - ), - ) - if part - ) - - await callback.message.edit_text( - text, - reply_markup=_period_keyboard(texts), - ) - await callback.answer() - - -@admin_required -@error_handler -async def show_pricing_traffic( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, -): - texts = get_texts(db_user.language) - - traffic_lines = [] - for amount, key in TRAFFIC_SETTINGS: - traffic_key = "UNLIMITED" if amount == 0 else f"{amount}GB" - default_label = ( - f"📊 {_get_traffic_label(texts, amount)} - {_format_price(_get_current_value(key))}" - ) - traffic_lines.append(texts.t(f"TRAFFIC_{traffic_key}", default_label)) - - text = "\n".join( - part - for part in ( - texts.t("ADMIN_PRICING_TITLE", "💰 Управление ценами"), - "", - texts.t("ADMIN_PRICING_SECTION_TRAFFIC", "📦 Пакеты трафика"), - "\n".join(traffic_lines), - "", - texts.t( - "ADMIN_PRICING_HINT_TRAFFIC", - "Выберите пакет, чтобы обновить его цену.", - ), - ) - if part - ) - - await callback.message.edit_text( - text, - reply_markup=_traffic_keyboard(texts), - ) - await callback.answer() - - -@admin_required -@error_handler -async def show_pricing_extras( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, -): - texts = get_texts(db_user.language) - device_price = _format_price(_get_current_value("PRICE_PER_DEVICE")) - - text = "\n".join( - part - for part in ( - texts.t("ADMIN_PRICING_TITLE", "💰 Управление ценами"), - "", - texts.t("ADMIN_PRICING_SECTION_EXTRAS", "🔧 Дополнительные опции"), - texts.t("ADMIN_PRICING_DEVICE_LABEL", "📱 Дополнительное устройство — {price}").format( - price=device_price - ), - "", - texts.t( - "ADMIN_PRICING_HINT_EXTRAS", - "Обновите стоимость дополнительных опций.", - ), - ) - if part - ) - - await callback.message.edit_text( - text, - reply_markup=_extras_keyboard(texts), - ) - await callback.answer() - - -@admin_required -@error_handler -async def start_edit_period_price( - callback: types.CallbackQuery, - state: FSMContext, - db_user: User, - db: AsyncSession, -): - texts = get_texts(db_user.language) - try: - days = int(callback.data.split("_")[-1]) - except ValueError: - await callback.answer("❌", show_alert=True) - return - - setting_key = _find_period_setting(days) - if not setting_key: - await callback.answer("❌", show_alert=True) - return - - label = texts.t("ADMIN_PRICING_PERIOD_LABEL", "Тариф на {label}").format( - label=_get_period_label(texts, days) - ) - current_price = _format_price(_get_current_value(setting_key)) - - await _prompt_price_input( - callback, - state, - texts, - label, - current_price, - setting_key, - "admin_pricing_periods", - ) - - -@admin_required -@error_handler -async def start_edit_traffic_price( - callback: types.CallbackQuery, - state: FSMContext, - db_user: User, - db: AsyncSession, -): - texts = get_texts(db_user.language) - identifier = callback.data.split("_")[-1] - setting = _find_traffic_setting(identifier) - - if not setting: - await callback.answer("❌", show_alert=True) - return - - amount, setting_key = setting - label = texts.t("ADMIN_PRICING_TRAFFIC_LABEL", "Пакет {label}").format( - label=_get_traffic_label(texts, amount) - ) - current_price = _format_price(_get_current_value(setting_key)) - - await _prompt_price_input( - callback, - state, - texts, - label, - current_price, - setting_key, - "admin_pricing_traffic", - ) - - -@admin_required -@error_handler -async def start_edit_extra_price( - callback: types.CallbackQuery, - state: FSMContext, - db_user: User, - db: AsyncSession, -): - texts = get_texts(db_user.language) - slug = callback.data.split("_")[-1] - setting_key = _find_extra_setting(slug) - - if not setting_key: - await callback.answer("❌", show_alert=True) - return - - if slug == "device": - label = texts.t("ADMIN_PRICING_DEVICE_EDIT_LABEL", "Цена за дополнительное устройство") - else: - label = slug - - current_price = _format_price(_get_current_value(setting_key)) - - await _prompt_price_input( - callback, - state, - texts, - label, - current_price, - setting_key, - "admin_pricing_extras", - ) - - -def _parse_price(text: str) -> Decimal: - cleaned = (text or "").strip().replace(" ", "") - if not cleaned: - raise InvalidOperation - normalized = cleaned.replace(",", ".") - value = Decimal(normalized) - return value.quantize(Decimal("0.01")) - - -@admin_required -@error_handler -async def process_pricing_value( - message: types.Message, - state: FSMContext, - db_user: User, - db: AsyncSession, -): - data = await state.get_data() - setting_key = data.get("pricing_key") - label = data.get("pricing_label") - return_callback = data.get("return_callback", "admin_pricing") - texts = get_texts(db_user.language) - - if not setting_key or not label: - await message.answer(texts.t("ADMIN_PRICING_STATE_EXPIRED", "⚠️ Истекло состояние ввода. Попробуйте снова.")) - await state.clear() - return - - try: - price_rubles = _parse_price(message.text) - except (InvalidOperation, ValueError): - await message.answer( - texts.t( - "ADMIN_PRICING_INVALID_FORMAT", - "❌ Неверный формат. Используйте числа, например 990 или 349.99.", - ) - ) - return - - if price_rubles < 0: - await message.answer( - texts.t( - "ADMIN_PRICING_NEGATIVE_VALUE", - "❌ Цена не может быть отрицательной.", - ) - ) - return - - if price_rubles > MAX_PRICE_RUBLES: - await message.answer( - texts.t( - "ADMIN_PRICING_TOO_HIGH", - "❌ Слишком высокая цена. Укажите значение до {limit}.", - ).format(limit=_format_price(int(MAX_PRICE_RUBLES * 100))), - ) - return - - price_kopeks = int((price_rubles * 100).quantize(Decimal("1"), rounding=ROUND_HALF_UP)) - - await bot_configuration_service.set_value(db, setting_key, price_kopeks) - await state.clear() - - logger.info( - "Админ %s обновил %s: %s коп.", - db_user.telegram_id, - setting_key, - price_kopeks, - ) - - await message.answer( - texts.t( - "ADMIN_PRICING_SUCCESS", - "✅ Цена для {label} обновлена: {value}", - ).format(label=label, value=_format_price(price_kopeks)), - reply_markup=InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text=texts.BACK, callback_data=return_callback)], - [ - InlineKeyboardButton( - text=texts.t("ADMIN_PRICING_RETURN", "🏠 К управлению ценами"), - callback_data="admin_pricing", - ) - ], - ]), - ) - - -def register_handlers(dp: Dispatcher) -> None: - dp.callback_query.register(show_pricing_dashboard, F.data == "admin_pricing") - dp.callback_query.register(show_pricing_periods, F.data == "admin_pricing_periods") - dp.callback_query.register(show_pricing_traffic, F.data == "admin_pricing_traffic") - dp.callback_query.register(show_pricing_extras, F.data == "admin_pricing_extras") - dp.callback_query.register( - start_edit_period_price, - F.data.startswith("admin_pricing_edit_period_"), - ) - dp.callback_query.register( - start_edit_traffic_price, - F.data.startswith("admin_pricing_edit_traffic_"), - ) - dp.callback_query.register( - start_edit_extra_price, - F.data.startswith("admin_pricing_edit_extra_"), - ) - dp.message.register( - process_pricing_value, - AdminPricingStates.waiting_for_price, - ) - diff --git a/app/handlers/admin/subscriptions.py b/app/handlers/admin/subscriptions.py index 11771bdd..0c341a04 100644 --- a/app/handlers/admin/subscriptions.py +++ b/app/handlers/admin/subscriptions.py @@ -4,6 +4,7 @@ from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func +from app.config import settings from app.states import AdminStates from app.database.models import User from app.keyboards.admin import get_admin_subscriptions_keyboard @@ -61,7 +62,6 @@ async def show_subscriptions_menu( db: AsyncSession ): stats = await get_subscriptions_statistics(db) - texts = get_texts(db_user.language) text = f""" 📱 Управление подписками @@ -77,20 +77,20 @@ async def show_subscriptions_menu( - За неделю: {stats['purchased_week']} - За месяц: {stats['purchased_month']} -ℹ️ {texts.t("ADMIN_SUBSCRIPTIONS_MAIN_HINT", "Управление серверами и ценами теперь доступно на главной странице админ-панели.")} - Выберите действие: """ - + keyboard = [ [ types.InlineKeyboardButton(text="📋 Список подписок", callback_data="admin_subs_list"), types.InlineKeyboardButton(text="⏰ Истекающие", callback_data="admin_subs_expiring") ], [ - types.InlineKeyboardButton(text="📊 Статистика", callback_data="admin_subs_stats") + types.InlineKeyboardButton(text="📊 Статистика", callback_data="admin_subs_stats"), + types.InlineKeyboardButton(text="💰 Настройки цен", callback_data="admin_subs_pricing") ], [ + types.InlineKeyboardButton(text="🌐 Управление серверами", callback_data="admin_servers"), types.InlineKeyboardButton(text="🌍 География", callback_data="admin_subs_countries") ], [ @@ -282,22 +282,45 @@ async def show_pricing_settings( db_user: User, db: AsyncSession ): - texts = get_texts(db_user.language) + text = f""" +⚙️ Настройки цен +Периоды подписки: +- 14 дней: {settings.format_price(settings.PRICE_14_DAYS)} +- 30 дней: {settings.format_price(settings.PRICE_30_DAYS)} +- 60 дней: {settings.format_price(settings.PRICE_60_DAYS)} +- 90 дней: {settings.format_price(settings.PRICE_90_DAYS)} +- 180 дней: {settings.format_price(settings.PRICE_180_DAYS)} +- 360 дней: {settings.format_price(settings.PRICE_360_DAYS)} + +Трафик-пакеты: +- 5 ГБ: {settings.format_price(settings.PRICE_TRAFFIC_5GB)} +- 10 ГБ: {settings.format_price(settings.PRICE_TRAFFIC_10GB)} +- 25 ГБ: {settings.format_price(settings.PRICE_TRAFFIC_25GB)} +- 50 ГБ: {settings.format_price(settings.PRICE_TRAFFIC_50GB)} +- 100 ГБ: {settings.format_price(settings.PRICE_TRAFFIC_100GB)} +- 250 ГБ: {settings.format_price(settings.PRICE_TRAFFIC_250GB)} + +Дополнительно: +- За устройство: {settings.format_price(settings.PRICE_PER_DEVICE)} +""" + + keyboard = [ + # [ + # types.InlineKeyboardButton(text="📅 Периоды", callback_data="admin_edit_period_prices"), + # types.InlineKeyboardButton(text="📈 Трафик", callback_data="admin_edit_traffic_prices") + # ], + # [ + # types.InlineKeyboardButton(text="📱 Устройства", callback_data="admin_edit_device_price") + # ], + [ + types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_subscriptions") + ] + ] + await callback.message.edit_text( - texts.t( - "ADMIN_PRICING_RELOCATED", - "ℹ️ Раздел управления ценами переехал. Откройте главный экран админ-панели и выберите пункт «Цены и тарифы».", - ), - reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ - [ - types.InlineKeyboardButton( - text=texts.t("ADMIN_PRICING_OPEN", "💰 Перейти к управлению ценами"), - callback_data="admin_pricing" - ) - ], - [types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_subscriptions")] - ]) + text, + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard) ) await callback.answer() diff --git a/app/keyboards/admin.py b/app/keyboards/admin.py index 54ed11b8..ee7729af 100644 --- a/app/keyboards/admin.py +++ b/app/keyboards/admin.py @@ -17,8 +17,6 @@ def get_admin_main_keyboard(language: str = "ru") -> InlineKeyboardMarkup: [InlineKeyboardButton(text=_t(texts, "ADMIN_MAIN_PROMO_STATS", "💰 Промокоды/Статистика"), callback_data="admin_submenu_promo")], [InlineKeyboardButton(text=_t(texts, "ADMIN_MAIN_SUPPORT", "🛟 Поддержка"), callback_data="admin_submenu_support")], [InlineKeyboardButton(text=_t(texts, "ADMIN_MAIN_MESSAGES", "📨 Сообщения"), callback_data="admin_submenu_communications")], - [InlineKeyboardButton(text=_t(texts, "ADMIN_MAIN_SERVERS", "🌐 Серверы"), callback_data="admin_servers")], - [InlineKeyboardButton(text=_t(texts, "ADMIN_MAIN_PRICING", "💰 Цены и тарифы"), callback_data="admin_pricing")], [InlineKeyboardButton(text=_t(texts, "ADMIN_MAIN_SETTINGS", "⚙️ Настройки"), callback_data="admin_submenu_settings")], [InlineKeyboardButton(text=_t(texts, "ADMIN_MAIN_SYSTEM", "🛠️ Система"), callback_data="admin_submenu_system")], [InlineKeyboardButton(text=texts.BACK, callback_data="back_to_menu")] diff --git a/app/localization/texts.py b/app/localization/texts.py index a6157de8..ccd8eff1 100644 --- a/app/localization/texts.py +++ b/app/localization/texts.py @@ -42,8 +42,6 @@ def _build_dynamic_values(language: str) -> Dict[str, Any]: "TRAFFIC_50GB": f"📊 50 ГБ - {settings.format_price(settings.PRICE_TRAFFIC_50GB)}", "TRAFFIC_100GB": f"📊 100 ГБ - {settings.format_price(settings.PRICE_TRAFFIC_100GB)}", "TRAFFIC_250GB": f"📊 250 ГБ - {settings.format_price(settings.PRICE_TRAFFIC_250GB)}", - "TRAFFIC_500GB": f"📊 500 ГБ - {settings.format_price(settings.PRICE_TRAFFIC_500GB)}", - "TRAFFIC_1000GB": f"📊 1000 ГБ - {settings.format_price(settings.PRICE_TRAFFIC_1000GB)}", "TRAFFIC_UNLIMITED": f"📊 Безлимит - {settings.format_price(settings.PRICE_TRAFFIC_UNLIMITED)}", "SUPPORT_INFO": ( "\n🛟 Поддержка\n\n" @@ -69,8 +67,6 @@ def _build_dynamic_values(language: str) -> Dict[str, Any]: "TRAFFIC_50GB": f"📊 50 GB - {settings.format_price(settings.PRICE_TRAFFIC_50GB)}", "TRAFFIC_100GB": f"📊 100 GB - {settings.format_price(settings.PRICE_TRAFFIC_100GB)}", "TRAFFIC_250GB": f"📊 250 GB - {settings.format_price(settings.PRICE_TRAFFIC_250GB)}", - "TRAFFIC_500GB": f"📊 500 GB - {settings.format_price(settings.PRICE_TRAFFIC_500GB)}", - "TRAFFIC_1000GB": f"📊 1000 GB - {settings.format_price(settings.PRICE_TRAFFIC_1000GB)}", "TRAFFIC_UNLIMITED": f"📊 Unlimited - {settings.format_price(settings.PRICE_TRAFFIC_UNLIMITED)}", "SUPPORT_INFO": ( "\n🛟 RemnaWave Support\n\n" diff --git a/app/states.py b/app/states.py index 20936d4a..61015d48 100644 --- a/app/states.py +++ b/app/states.py @@ -30,7 +30,7 @@ class PromoCodeStates(StatesGroup): waiting_for_referral_code = State() class AdminStates(StatesGroup): - + waiting_for_user_search = State() editing_user_balance = State() extending_subscription = State() @@ -114,10 +114,6 @@ class AdminStates(StatesGroup): viewing_user_from_purchases_list = State() viewing_user_from_campaign_list = State() - -class AdminPricingStates(StatesGroup): - waiting_for_price = State() - class SupportStates(StatesGroup): waiting_for_message = State() diff --git a/locales/en.json b/locales/en.json index 494f34a5..d970b566 100644 --- a/locales/en.json +++ b/locales/en.json @@ -172,37 +172,6 @@ "ADMIN_PROMO_GROUPS_DEFAULT_LABEL": " (default)", "ADMIN_PROMO_GROUPS_MEMBERS_COUNT": "Members: {count}", "ADMIN_PROMO_GROUPS_EMPTY": "No promo groups found.", - "ADMIN_SUBSCRIPTIONS_MAIN_HINT": "Server and pricing management is now available from the admin home screen.", - "ADMIN_PRICING_TITLE": "💰 Pricing management", - "ADMIN_PRICING_DESCRIPTION": "Adjust subscription plans, traffic bundles and extra options.", - "ADMIN_PRICING_SECTION_PERIODS": "📅 Subscription periods", - "ADMIN_PRICING_SECTION_TRAFFIC": "📦 Traffic bundles", - "ADMIN_PRICING_SECTION_EXTRAS": "🔧 Extra options", - "ADMIN_PRICING_PERIODS": "📅 Subscription periods", - "ADMIN_PRICING_TRAFFIC": "📦 Traffic bundles", - "ADMIN_PRICING_EXTRAS": "🔧 Extra options", - "ADMIN_PRICING_EDIT_PERIOD": "✏️ {label}", - "ADMIN_PRICING_EDIT_TRAFFIC": "✏️ {label}", - "ADMIN_PRICING_EDIT_DEVICE": "✏️ Device price", - "ADMIN_PRICING_DAYS_LABEL": "{days} d.", - "ADMIN_PRICING_UNLIMITED_LABEL": "Unlimited", - "ADMIN_PRICING_TRAFFIC_GB": "{gb} GB", - "ADMIN_PRICING_DEVICE_LABEL": "📱 Extra device — {price}", - "ADMIN_PRICING_HINT_PERIODS": "Choose a period to change its price.", - "ADMIN_PRICING_HINT_TRAFFIC": "Choose a bundle to update its price.", - "ADMIN_PRICING_HINT_EXTRAS": "Update the cost of extra options.", - "ADMIN_PRICING_PERIOD_LABEL": "{label} plan", - "ADMIN_PRICING_TRAFFIC_LABEL": "{label} bundle", - "ADMIN_PRICING_DEVICE_EDIT_LABEL": "Extra device price", - "ADMIN_PRICING_PROMPT": "Enter a new price for {label}.\nCurrent price: {current}.\n\nSend the value in RUB (example: 990 or 349.99).", - "ADMIN_PRICING_STATE_EXPIRED": "⚠️ Input session expired. Please try again.", - "ADMIN_PRICING_INVALID_FORMAT": "❌ Invalid format. Use numbers such as 990 or 349.99.", - "ADMIN_PRICING_NEGATIVE_VALUE": "❌ Price cannot be negative.", - "ADMIN_PRICING_TOO_HIGH": "❌ Price is too high. Use a value up to {limit}.", - "ADMIN_PRICING_SUCCESS": "✅ Price for {label} updated: {value}", - "ADMIN_PRICING_RETURN": "🏠 Back to pricing", - "ADMIN_PRICING_RELOCATED": "ℹ️ Pricing management moved. Open the admin home screen and choose “Pricing”.", - "ADMIN_PRICING_OPEN": "💰 Go to pricing management", "CREATE_TICKET_BUTTON": "🎫 Create ticket", "MY_TICKETS_BUTTON": "📋 My tickets", "CONTACT_SUPPORT_BUTTON": "💬 Contact support", @@ -577,8 +546,6 @@ "ADMIN_MAIN_PROMO_STATS": "💰 Promo codes / Stats", "ADMIN_MAIN_SUPPORT": "🛟 Support", "ADMIN_MAIN_MESSAGES": "📨 Messages", - "ADMIN_MAIN_SERVERS": "🌐 Servers", - "ADMIN_MAIN_PRICING": "💰 Pricing", "ADMIN_MAIN_SETTINGS": "⚙️ Settings", "ADMIN_MAIN_SYSTEM": "🛠️ System", "ADMIN_USERS_SUBMENU_TITLE": "👥 **User and subscription management**\n\n", diff --git a/locales/ru.json b/locales/ru.json index 7b5aa4f5..13c5c434 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -30,37 +30,6 @@ "ADMIN_PROMO_GROUPS_DEFAULT_LABEL": " (базовая)", "ADMIN_PROMO_GROUPS_MEMBERS_COUNT": "Участников: {count}", "ADMIN_PROMO_GROUPS_EMPTY": "Промогруппы не найдены.", - "ADMIN_SUBSCRIPTIONS_MAIN_HINT": "Управление серверами и ценами теперь доступно на главной странице админ-панели.", - "ADMIN_PRICING_TITLE": "💰 Управление ценами", - "ADMIN_PRICING_DESCRIPTION": "Настройте стоимость подписок, пакетов трафика и дополнительных опций.", - "ADMIN_PRICING_SECTION_PERIODS": "📅 Периоды подписки", - "ADMIN_PRICING_SECTION_TRAFFIC": "📦 Пакеты трафика", - "ADMIN_PRICING_SECTION_EXTRAS": "🔧 Дополнительные опции", - "ADMIN_PRICING_PERIODS": "📅 Периоды подписки", - "ADMIN_PRICING_TRAFFIC": "📦 Трафик-пакеты", - "ADMIN_PRICING_EXTRAS": "🔧 Доп. опции", - "ADMIN_PRICING_EDIT_PERIOD": "✏️ {label}", - "ADMIN_PRICING_EDIT_TRAFFIC": "✏️ {label}", - "ADMIN_PRICING_EDIT_DEVICE": "✏️ Цена за устройство", - "ADMIN_PRICING_DAYS_LABEL": "{days} дн.", - "ADMIN_PRICING_UNLIMITED_LABEL": "Безлимит", - "ADMIN_PRICING_TRAFFIC_GB": "{gb} ГБ", - "ADMIN_PRICING_DEVICE_LABEL": "📱 Дополнительное устройство — {price}", - "ADMIN_PRICING_HINT_PERIODS": "Выберите период, чтобы изменить его стоимость.", - "ADMIN_PRICING_HINT_TRAFFIC": "Выберите пакет, чтобы обновить его цену.", - "ADMIN_PRICING_HINT_EXTRAS": "Обновите стоимость дополнительных опций.", - "ADMIN_PRICING_PERIOD_LABEL": "Тариф на {label}", - "ADMIN_PRICING_TRAFFIC_LABEL": "Пакет {label}", - "ADMIN_PRICING_DEVICE_EDIT_LABEL": "Цена за дополнительное устройство", - "ADMIN_PRICING_PROMPT": "Введите новую цену для {label}.\nТекущая цена: {current}.\n\nПришлите значение в рублях (пример: 990 или 349.99).", - "ADMIN_PRICING_STATE_EXPIRED": "⚠️ Истекло состояние ввода. Попробуйте снова.", - "ADMIN_PRICING_INVALID_FORMAT": "❌ Неверный формат. Используйте числа, например 990 или 349.99.", - "ADMIN_PRICING_NEGATIVE_VALUE": "❌ Цена не может быть отрицательной.", - "ADMIN_PRICING_TOO_HIGH": "❌ Слишком высокая цена. Укажите значение до {limit}.", - "ADMIN_PRICING_SUCCESS": "✅ Цена для {label} обновлена: {value}", - "ADMIN_PRICING_RETURN": "🏠 К управлению ценами", - "ADMIN_PRICING_RELOCATED": "ℹ️ Раздел управления ценами переехал. Откройте главный экран админ-панели и выберите пункт «Цены и тарифы».", - "ADMIN_PRICING_OPEN": "💰 Перейти к управлению ценами", "CREATE_TICKET_BUTTON": "🎫 Создать тикет", "MY_TICKETS_BUTTON": "📋 Мои тикеты", "CONTACT_SUPPORT_BUTTON": "💬 Связаться с поддержкой", @@ -577,8 +546,6 @@ "ADMIN_MAIN_PROMO_STATS": "💰 Промокоды/Статистика", "ADMIN_MAIN_SUPPORT": "🛟 Поддержка", "ADMIN_MAIN_MESSAGES": "📨 Сообщения", - "ADMIN_MAIN_SERVERS": "🌐 Серверы", - "ADMIN_MAIN_PRICING": "💰 Цены и тарифы", "ADMIN_MAIN_SETTINGS": "⚙️ Настройки", "ADMIN_MAIN_SYSTEM": "🛠️ Система", "ADMIN_USERS_SUBMENU_TITLE": "👥 **Управление пользователями и подписками**\n\n",