Merge pull request #711 from Fr1ngg/revert-710-bedolaga/reorganize-admin-panel-menu

Revert "Reorganize admin panel pricing and server controls"
This commit is contained in:
Egor
2025-10-04 05:29:48 +03:00
committed by GitHub
8 changed files with 43 additions and 664 deletions

View File

@@ -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)

View File

@@ -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", "💰 <b>Управление ценами</b>"),
"",
texts.t(
"ADMIN_PRICING_DESCRIPTION",
"Настройте стоимость подписок, пакетов трафика и дополнительных опций.",
),
"",
texts.t("ADMIN_PRICING_SECTION_PERIODS", "<b>📅 Периоды подписки</b>"),
"\n".join(period_lines),
"",
texts.t("ADMIN_PRICING_SECTION_TRAFFIC", "<b>📦 Пакеты трафика</b>"),
"\n".join(traffic_lines),
"",
texts.t("ADMIN_PRICING_SECTION_EXTRAS", "<b>🔧 Дополнительные опции</b>"),
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", "💰 <b>Управление ценами</b>"),
"",
texts.t("ADMIN_PRICING_SECTION_PERIODS", "<b>📅 Периоды подписки</b>"),
"\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", "💰 <b>Управление ценами</b>"),
"",
texts.t("ADMIN_PRICING_SECTION_TRAFFIC", "<b>📦 Пакеты трафика</b>"),
"\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", "💰 <b>Управление ценами</b>"),
"",
texts.t("ADMIN_PRICING_SECTION_EXTRAS", "<b>🔧 Дополнительные опции</b>"),
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,
)

View File

@@ -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"""
📱 <b>Управление подписками</b>
@@ -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"""
⚙️ <b>Настройки цен</b>
<b>Периоды подписки:</b>
- 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)}
<b>Трафик-пакеты:</b>
- 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)}
<b>Дополнительно:</b>
- За устройство: {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()

View File

@@ -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")]

View File

@@ -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🛟 <b>Поддержка</b>\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🛟 <b>RemnaWave Support</b>\n\n"

View File

@@ -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()

View File

@@ -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": "💰 <b>Pricing management</b>",
"ADMIN_PRICING_DESCRIPTION": "Adjust subscription plans, traffic bundles and extra options.",
"ADMIN_PRICING_SECTION_PERIODS": "<b>📅 Subscription periods</b>",
"ADMIN_PRICING_SECTION_TRAFFIC": "<b>📦 Traffic bundles</b>",
"ADMIN_PRICING_SECTION_EXTRAS": "<b>🔧 Extra options</b>",
"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",

View File

@@ -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": "💰 <b>Управление ценами</b>",
"ADMIN_PRICING_DESCRIPTION": "Настройте стоимость подписок, пакетов трафика и дополнительных опций.",
"ADMIN_PRICING_SECTION_PERIODS": "<b>📅 Периоды подписки</b>",
"ADMIN_PRICING_SECTION_TRAFFIC": "<b>📦 Пакеты трафика</b>",
"ADMIN_PRICING_SECTION_EXTRAS": "<b>🔧 Дополнительные опции</b>",
"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",