Revert "Reorganize admin menu and add pricing management"

This commit is contained in:
Egor
2025-10-04 05:35:06 +03:00
committed by GitHub
parent 17d51ab286
commit c31da6c2ae
6 changed files with 21 additions and 732 deletions

View File

@@ -30,7 +30,6 @@ from app.handlers.admin import (
remnawave as admin_remnawave,
statistics as admin_statistics,
servers as admin_servers,
pricing as admin_pricing,
maintenance as admin_maintenance,
promo_groups as admin_promo_groups,
campaigns as admin_campaigns,
@@ -127,8 +126,7 @@ async def setup_bot() -> tuple[Bot, Dispatcher]:
admin_main.register_handlers(dp)
admin_users.register_handlers(dp)
admin_subscriptions.register_handlers(dp)
admin_servers.register_handlers(dp)
admin_pricing.register_handlers(dp)
admin_servers.register_handlers(dp)
admin_promocodes.register_handlers(dp)
admin_messages.register_handlers(dp)
admin_monitoring.register_handlers(dp)

View File

@@ -142,28 +142,6 @@ for _group_key, _title, _category_keys in CATEGORY_GROUP_DEFINITIONS:
CATEGORY_FALLBACK_KEY = "other"
CATEGORY_FALLBACK_TITLE = "📦 Прочие настройки"
PRICING_SETTING_KEYS: set[str] = {
"BASE_SUBSCRIPTION_PRICE",
"PRICE_14_DAYS",
"PRICE_30_DAYS",
"PRICE_60_DAYS",
"PRICE_90_DAYS",
"PRICE_180_DAYS",
"PRICE_360_DAYS",
"PRICE_PER_DEVICE",
"PRICE_TRAFFIC_5GB",
"PRICE_TRAFFIC_10GB",
"PRICE_TRAFFIC_25GB",
"PRICE_TRAFFIC_50GB",
"PRICE_TRAFFIC_100GB",
"PRICE_TRAFFIC_250GB",
"PRICE_TRAFFIC_500GB",
"PRICE_TRAFFIC_1000GB",
"PRICE_TRAFFIC_UNLIMITED",
}
PRICING_CATEGORY_KEYS: set[str] = {"SUBSCRIPTION_PRICES", "TRAFFIC_PACKAGES"}
PRESET_CONFIGS: Dict[str, Dict[str, object]] = {
"recommended": {
"ENABLE_NOTIFICATIONS": True,
@@ -224,49 +202,6 @@ def _get_group_description(group_key: str) -> str:
return str(meta.get("description", ""))
def _is_pricing_setting_key(key: str) -> bool:
return key in PRICING_SETTING_KEYS
def _filter_pricing_definitions(definitions):
return [definition for definition in definitions if not _is_pricing_setting_key(definition.key)]
def _get_visible_definitions(category_key: str):
definitions = bot_configuration_service.get_settings_for_category(category_key)
return _filter_pricing_definitions(definitions)
def _is_pricing_category_key(category_key: str) -> bool:
if category_key in PRICING_CATEGORY_KEYS:
return True
definitions = bot_configuration_service.get_settings_for_category(category_key)
if not definitions:
return False
return not _filter_pricing_definitions(definitions)
def _build_pricing_redirect_keyboard(texts) -> types.InlineKeyboardMarkup:
return types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_PRICING_GO_TO_SECTION", "Перейти к управлению ценами"),
callback_data="admin_pricing",
)
],
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_PRICING_BACK_TO_CONFIG", "⬅️ К разделам"),
callback_data="admin_bot_config",
)
],
]
)
def _get_group_icon(group_key: str) -> str:
meta = _get_group_meta(group_key)
return str(meta.get("icon", "⚙️"))
@@ -402,11 +337,9 @@ def _render_dashboard_overview() -> str:
for group_key, _title, items in grouped:
for category_key, _label, count in items:
total_settings += count
definitions = _get_visible_definitions(category_key)
definitions = bot_configuration_service.get_settings_for_category(category_key)
total_overrides += sum(
1
for definition in definitions
if bot_configuration_service.has_override(definition.key)
1 for definition in definitions if bot_configuration_service.has_override(definition.key)
)
lines: List[str] = [
@@ -458,13 +391,7 @@ def _perform_settings_search(query: str) -> List[Dict[str, object]]:
else:
category_page = 1
visible_definitions = [
definition
for definition in definitions
if not _is_pricing_setting_key(definition.key)
]
for definition_index, definition in enumerate(visible_definitions):
for definition_index, definition in enumerate(definitions):
fields = [definition.key.lower(), definition.display_name.lower()]
guidance = bot_configuration_service.get_setting_guidance(definition.key)
fields.extend(
@@ -1127,34 +1054,25 @@ def _parse_group_payload(payload: str) -> Tuple[str, int]:
def _get_grouped_categories() -> List[Tuple[str, str, List[Tuple[str, str, int]]]]:
categories = bot_configuration_service.get_categories()
categories_map: Dict[str, Tuple[str, int]] = {}
categories_map = {key: (label, count) for key, label, count in categories}
used: set[str] = set()
grouped: List[Tuple[str, str, List[Tuple[str, str, int]]]] = []
for key, label, _count in categories:
if _is_pricing_category_key(key):
continue
visible = _get_visible_definitions(key)
if not visible:
continue
categories_map[key] = (label, len(visible))
for group_key, title, category_keys in CATEGORY_GROUP_DEFINITIONS:
items: List[Tuple[str, str, int]] = []
for category_key in category_keys:
if category_key not in categories_map:
continue
label, count = categories_map[category_key]
items.append((category_key, label, count))
used.add(category_key)
if category_key in categories_map:
label, count = categories_map[category_key]
items.append((category_key, label, count))
used.add(category_key)
if items:
grouped.append((group_key, title, items))
remaining: List[Tuple[str, str, int]] = []
for key, (label, count) in categories_map.items():
if key in used:
continue
remaining.append((key, label, count))
remaining = [
(key, label, count)
for key, (label, count) in categories_map.items()
if key not in used
]
if remaining:
remaining.sort(key=lambda item: item[1])
@@ -1261,14 +1179,10 @@ def _build_categories_keyboard(
buttons: List[types.InlineKeyboardButton] = []
for category_key, label, count in sliced:
definitions = _get_visible_definitions(category_key)
if not definitions:
continue
overrides = sum(
1
for definition in definitions
if bot_configuration_service.has_override(definition.key)
)
overrides = 0
for definition in bot_configuration_service.get_settings_for_category(category_key):
if bot_configuration_service.has_override(definition.key):
overrides += 1
badge = "✳️" if overrides else ""
button_text = f"{badge} {label} ({count})"
buttons.append(
@@ -1324,26 +1238,7 @@ def _build_settings_keyboard(
language: str,
page: int = 1,
) -> types.InlineKeyboardMarkup:
definitions = _get_visible_definitions(category_key)
if not definitions:
texts = get_texts(language)
return types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_PRICING_GO_TO_SECTION", "Перейти к управлению ценами"),
callback_data="admin_pricing",
)
],
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_PRICING_BACK_TO_CONFIG", "⬅️ К разделам"),
callback_data="admin_bot_config",
)
],
]
)
definitions = bot_configuration_service.get_settings_for_category(category_key)
total_pages = max(1, math.ceil(len(definitions) / SETTINGS_PAGE_SIZE))
page = max(1, min(page, total_pages))
@@ -1633,17 +1528,10 @@ async def show_bot_config_category(
group_key, category_key, category_page, settings_page = _parse_category_payload(
callback.data
)
definitions = _get_visible_definitions(category_key)
definitions = bot_configuration_service.get_settings_for_category(category_key)
if not definitions:
texts = get_texts(db_user.language)
message = texts.t(
"ADMIN_PRICING_MOVED_MESSAGE",
"💡 Управление ценами переехало в раздел «Цены» на главной странице админ-панели.",
)
keyboard = _build_pricing_redirect_keyboard(texts)
await callback.message.edit_text(message, reply_markup=keyboard, parse_mode="HTML")
await callback.answer()
await callback.answer("В этой категории пока нет настроек", show_alert=True)
return
category_label = definitions[0].category_label
@@ -2141,19 +2029,6 @@ async def show_bot_config_setting(
except KeyError:
await callback.answer("Эта настройка больше недоступна", show_alert=True)
return
if _is_pricing_setting_key(key):
texts = get_texts(db_user.language)
message = texts.t(
"ADMIN_PRICING_MOVED_MESSAGE",
"💡 Управление ценами переехало в раздел «Цены» на главной странице админ-панели.",
)
await callback.message.edit_text(
message,
reply_markup=_build_pricing_redirect_keyboard(texts),
parse_mode="HTML",
)
await callback.answer()
return
text = _render_setting_text(key)
keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page)
await callback.message.edit_text(text, reply_markup=keyboard)
@@ -2191,19 +2066,6 @@ async def start_edit_setting(
except KeyError:
await callback.answer("Эта настройка больше недоступна", show_alert=True)
return
if _is_pricing_setting_key(key):
texts = get_texts(db_user.language)
message = texts.t(
"ADMIN_PRICING_MOVED_MESSAGE",
"💡 Управление ценами переехало в раздел «Цены» на главной странице админ-панели.",
)
await callback.message.edit_text(
message,
reply_markup=_build_pricing_redirect_keyboard(texts),
parse_mode="HTML",
)
await callback.answer()
return
definition = bot_configuration_service.get_definition(key)
summary = bot_configuration_service.get_setting_summary(key)
@@ -2358,19 +2220,6 @@ async def reset_setting(
except KeyError:
await callback.answer("Эта настройка больше недоступна", show_alert=True)
return
if _is_pricing_setting_key(key):
texts = get_texts(db_user.language)
message = texts.t(
"ADMIN_PRICING_MOVED_MESSAGE",
"💡 Управление ценами переехало в раздел «Цены» на главной странице админ-панели.",
)
await callback.message.edit_text(
message,
reply_markup=_build_pricing_redirect_keyboard(texts),
parse_mode="HTML",
)
await callback.answer()
return
await bot_configuration_service.reset_value(db, key)
await db.commit()
@@ -2411,19 +2260,6 @@ async def toggle_setting(
except KeyError:
await callback.answer("Эта настройка больше недоступна", show_alert=True)
return
if _is_pricing_setting_key(key):
texts = get_texts(db_user.language)
message = texts.t(
"ADMIN_PRICING_MOVED_MESSAGE",
"💡 Управление ценами переехало в раздел «Цены» на главной странице админ-панели.",
)
await callback.message.edit_text(
message,
reply_markup=_build_pricing_redirect_keyboard(texts),
parse_mode="HTML",
)
await callback.answer()
return
current = bot_configuration_service.get_current_value(key)
new_value = not bool(current)
await bot_configuration_service.set_value(db, key, new_value)
@@ -2468,19 +2304,6 @@ async def apply_setting_choice(
except KeyError:
await callback.answer("Эта настройка больше недоступна", show_alert=True)
return
if _is_pricing_setting_key(key):
texts = get_texts(db_user.language)
message = texts.t(
"ADMIN_PRICING_MOVED_MESSAGE",
"💡 Управление ценами переехало в раздел «Цены» на главной странице админ-панели.",
)
await callback.message.edit_text(
message,
reply_markup=_build_pricing_redirect_keyboard(texts),
parse_mode="HTML",
)
await callback.answer()
return
try:
value = bot_configuration_service.resolve_choice_token(key, choice_token)

View File

@@ -1,470 +0,0 @@
import logging
from typing import Iterable, List, Tuple
from aiogram import Dispatcher, F, types
from aiogram.fsm.context import FSMContext
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import User
from app.keyboards.admin import get_admin_pricing_keyboard
from app.localization.texts import get_texts
from app.services.system_settings_service import bot_configuration_service
from app.states import PricingStates
from app.utils.decorators import admin_required, error_handler
logger = logging.getLogger(__name__)
SUBSCRIPTION_PRICE_ENTRIES: List[Tuple[str, str, str]] = [
("base", "BASE_SUBSCRIPTION_PRICE", "Базовая цена"),
("14", "PRICE_14_DAYS", "14 дней"),
("30", "PRICE_30_DAYS", "30 дней"),
("60", "PRICE_60_DAYS", "60 дней"),
("90", "PRICE_90_DAYS", "90 дней"),
("180", "PRICE_180_DAYS", "180 дней"),
("360", "PRICE_360_DAYS", "360 дней"),
]
TRAFFIC_PRICE_ENTRIES: List[Tuple[str, str, str]] = [
("5", "PRICE_TRAFFIC_5GB", "5 ГБ"),
("10", "PRICE_TRAFFIC_10GB", "10 ГБ"),
("25", "PRICE_TRAFFIC_25GB", "25 ГБ"),
("50", "PRICE_TRAFFIC_50GB", "50 ГБ"),
("100", "PRICE_TRAFFIC_100GB", "100 ГБ"),
("250", "PRICE_TRAFFIC_250GB", "250 ГБ"),
("500", "PRICE_TRAFFIC_500GB", "500 ГБ"),
("1000", "PRICE_TRAFFIC_1000GB", "1000 ГБ"),
("unlimited", "PRICE_TRAFFIC_UNLIMITED", "Безлимит"),
]
DEVICE_PRICE_ENTRY: Tuple[str, str, str] = (
"devices",
"PRICE_PER_DEVICE",
"Дополнительное устройство",
)
MAX_PRICE_RUBLES = 1_000_000
def _format_price(value: int) -> str:
return settings.format_price(int(value))
def _build_price_buttons(
entries: Iterable[Tuple[str, str, str]],
prefix: str,
) -> List[List[types.InlineKeyboardButton]]:
buttons: List[List[types.InlineKeyboardButton]] = []
row: List[types.InlineKeyboardButton] = []
for token, key, label in entries:
current_value = bot_configuration_service.get_current_value(key)
button = types.InlineKeyboardButton(
text=f"{label}{_format_price(current_value)}",
callback_data=f"admin_pricing_edit_{prefix}_{token}",
)
row.append(button)
if len(row) == 2:
buttons.append(row)
row = []
if row:
buttons.append(row)
return buttons
async def _prompt_price_input(
callback: types.CallbackQuery,
state: FSMContext,
key: str,
label: str,
return_callback: str,
language: str,
) -> None:
texts = get_texts(language)
current_value = bot_configuration_service.get_current_value(key)
current_price = _format_price(current_value)
await state.set_state(PricingStates.waiting_for_value)
await state.update_data(
target_key=key,
target_label=label,
return_callback=return_callback,
)
prompt_lines = [
texts.t("ADMIN_PRICING_EDIT_TITLE", "💰 <b>Изменение цены</b>"),
"",
texts.t("ADMIN_PRICING_CURRENT_PRICE", "Текущая цена: {price}").format(
price=current_price
),
texts.t("ADMIN_PRICING_PROMPT", "Отправьте новую цену для {name}:").format(
name=label
),
texts.t(
"ADMIN_PRICING_ENTER_PRICE",
"Введите новую цену в рублях (можно использовать копейки через точку).",
),
texts.t("ADMIN_PRICING_CANCEL_HINT", "Для отмены воспользуйтесь кнопкой ниже."),
]
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_PRICING_CANCEL", "❌ Отмена"),
callback_data=return_callback,
)
]
]
)
await callback.message.edit_text(
"\n".join(prompt_lines),
reply_markup=keyboard,
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def show_pricing_menu(
callback: types.CallbackQuery,
state: FSMContext,
db_user: User,
db: AsyncSession,
):
await state.clear()
texts = get_texts(db_user.language)
summary_lines = [
texts.t("ADMIN_PRICING_TITLE", "💰 <b>Управление ценами</b>"),
"",
texts.t(
"ADMIN_PRICING_DESCRIPTION",
"Выберите раздел для редактирования стоимости подписок, трафика и устройств.",
),
"",
]
summary_lines.append(texts.t("ADMIN_PRICING_OVERVIEW", "Ключевые значения:"))
summary_lines.append(
f"• 30 дней: {_format_price(settings.PRICE_30_DAYS)}"
)
summary_lines.append(
f"• 90 дней: {_format_price(settings.PRICE_90_DAYS)}"
)
summary_lines.append(
f"{_format_price(settings.PRICE_PER_DEVICE)}{texts.t('ADMIN_PRICING_DEVICE_SHORT', 'дополнительное устройство')}"
)
await callback.message.edit_text(
"\n".join(summary_lines),
reply_markup=get_admin_pricing_keyboard(db_user.language),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def show_subscription_prices(
callback: types.CallbackQuery,
state: FSMContext,
db_user: User,
db: AsyncSession,
):
await state.clear()
texts = get_texts(db_user.language)
lines = [
texts.t("ADMIN_PRICING_SUBSCRIPTIONS_TITLE", "📅 <b>Стоимость подписок</b>"),
"",
texts.t(
"ADMIN_PRICING_SUBSCRIPTIONS_HELP",
"Нажмите на период, чтобы изменить цену. Значения указаны в месяцах.",
),
"",
]
for _token, key, label in SUBSCRIPTION_PRICE_ENTRIES:
lines.append(f"{label}: {_format_price(bot_configuration_service.get_current_value(key))}")
keyboard_rows = _build_price_buttons(SUBSCRIPTION_PRICE_ENTRIES, "subscription")
keyboard_rows.append(
[types.InlineKeyboardButton(text=texts.BACK, callback_data="admin_pricing")]
)
await callback.message.edit_text(
"\n".join(lines),
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard_rows),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def show_traffic_prices(
callback: types.CallbackQuery,
state: FSMContext,
db_user: User,
db: AsyncSession,
):
await state.clear()
texts = get_texts(db_user.language)
lines = [
texts.t("ADMIN_PRICING_TRAFFIC_TITLE", "📦 <b>Стоимость пакетов трафика</b>"),
"",
texts.t(
"ADMIN_PRICING_TRAFFIC_HELP",
"Нажмите на пакет, чтобы обновить цену. Цена применяется за выбранный объём трафика.",
),
"",
]
for _token, key, label in TRAFFIC_PRICE_ENTRIES:
lines.append(f"{label}: {_format_price(bot_configuration_service.get_current_value(key))}")
keyboard_rows = _build_price_buttons(TRAFFIC_PRICE_ENTRIES, "traffic")
keyboard_rows.append(
[types.InlineKeyboardButton(text=texts.BACK, callback_data="admin_pricing")]
)
await callback.message.edit_text(
"\n".join(lines),
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard_rows),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def show_device_price(
callback: types.CallbackQuery,
state: FSMContext,
db_user: User,
db: AsyncSession,
):
await state.clear()
texts = get_texts(db_user.language)
_token, key, label = DEVICE_PRICE_ENTRY
current_value = bot_configuration_service.get_current_value(key)
lines = [
texts.t("ADMIN_PRICING_DEVICES_TITLE", "📱 <b>Стоимость дополнительных устройств</b>"),
"",
texts.t(
"ADMIN_PRICING_DEVICES_HELP",
"Цена применяется за каждое устройство сверх базового лимита подписки.",
),
"",
f"{label}: {_format_price(current_value)}",
]
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_PRICING_EDIT_BUTTON", "✏️ Изменить цену"),
callback_data="admin_pricing_edit_devices",
)
],
[types.InlineKeyboardButton(text=texts.BACK, callback_data="admin_pricing")],
]
)
await callback.message.edit_text(
"\n".join(lines),
reply_markup=keyboard,
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def start_subscription_price_edit(
callback: types.CallbackQuery,
state: FSMContext,
db_user: User,
db: AsyncSession,
):
token = callback.data.split("_")[-1]
for entry_token, key, label in SUBSCRIPTION_PRICE_ENTRIES:
if entry_token == token:
await _prompt_price_input(
callback,
state,
key,
label,
"admin_pricing_subscriptions",
db_user.language,
)
return
await callback.answer("❌ Значение недоступно", show_alert=True)
@admin_required
@error_handler
async def start_traffic_price_edit(
callback: types.CallbackQuery,
state: FSMContext,
db_user: User,
db: AsyncSession,
):
token = callback.data.split("_")[-1]
for entry_token, key, label in TRAFFIC_PRICE_ENTRIES:
if entry_token == token:
await _prompt_price_input(
callback,
state,
key,
label,
"admin_pricing_traffic",
db_user.language,
)
return
await callback.answer("❌ Значение недоступно", show_alert=True)
@admin_required
@error_handler
async def start_device_price_edit(
callback: types.CallbackQuery,
state: FSMContext,
db_user: User,
db: AsyncSession,
):
_token, key, label = DEVICE_PRICE_ENTRY
await _prompt_price_input(
callback,
state,
key,
label,
"admin_pricing_devices",
db_user.language,
)
@admin_required
@error_handler
async def process_price_input(
message: types.Message,
state: FSMContext,
db_user: User,
db: AsyncSession,
):
data = await state.get_data()
key = data.get("target_key")
label = data.get("target_label", "")
return_callback = data.get("return_callback", "admin_pricing")
texts = get_texts(db_user.language)
if not key:
await message.answer("Не удалось определить настройку цены. Попробуйте снова из меню цен.")
await state.clear()
return
raw_text = (message.text or "").strip()
if raw_text.lower() in {"cancel", "отмена"}:
await state.clear()
await message.answer(
texts.t("ADMIN_PRICING_CANCELLED", "Отменено."),
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.BACK,
callback_data=return_callback,
)
]
]
),
)
return
try:
price_rubles = float(raw_text.replace(",", "."))
except ValueError:
await message.answer(
texts.t(
"ADMIN_PRICING_INVALID_PRICE",
"❌ Неверный формат цены. Используйте число, например 199.90",
)
)
return
if price_rubles < 0:
await message.answer(
texts.t("ADMIN_PRICING_INVALID_PRICE", "❌ Неверный формат цены. Используйте число, например 199.90"),
)
return
if price_rubles > MAX_PRICE_RUBLES:
await message.answer(
texts.t(
"ADMIN_PRICING_TOO_HIGH",
"❌ Слишком высокая цена. Укажите значение до 1 000 000 ₽.",
)
)
return
price_kopeks = int(round(price_rubles * 100))
await bot_configuration_service.set_value(db, key, price_kopeks)
await state.clear()
logger.info("✅ Обновлена цена %s: %s", key, price_rubles)
confirmation = texts.t(
"ADMIN_PRICING_PRICE_UPDATED",
"✅ Цена обновлена: {name}{price}",
).format(name=label or key, price=_format_price(price_kopeks))
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_PRICING_BACK_TO_SECTION", "⬅️ Назад к разделу"),
callback_data=return_callback,
)
]
]
)
await message.answer(confirmation, reply_markup=keyboard, parse_mode="HTML")
def register_handlers(dp: Dispatcher) -> None:
dp.callback_query.register(show_pricing_menu, F.data == "admin_pricing")
dp.callback_query.register(
show_subscription_prices, F.data == "admin_pricing_subscriptions"
)
dp.callback_query.register(
show_traffic_prices, F.data == "admin_pricing_traffic"
)
dp.callback_query.register(show_device_price, F.data == "admin_pricing_devices")
dp.callback_query.register(
start_subscription_price_edit,
F.data.startswith("admin_pricing_edit_subscription_"),
)
dp.callback_query.register(
start_traffic_price_edit, F.data.startswith("admin_pricing_edit_traffic_"),
)
dp.callback_query.register(
start_device_price_edit, F.data == "admin_pricing_edit_devices"
)
dp.message.register(
process_price_input,
PricingStates.waiting_for_value,
)

View File

@@ -14,8 +14,6 @@ def get_admin_main_keyboard(language: str = "ru") -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text=_t(texts, "ADMIN_MAIN_USERS_SUBSCRIPTIONS", "👥 Юзеры/Подписки"), callback_data="admin_submenu_users")],
[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_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")],
@@ -173,32 +171,6 @@ def get_admin_system_submenu_keyboard(language: str = "ru") -> InlineKeyboardMar
])
def get_admin_pricing_keyboard(language: str = "ru") -> InlineKeyboardMarkup:
texts = get_texts(language)
return InlineKeyboardMarkup(inline_keyboard=[
[
InlineKeyboardButton(
text=_t(texts, "ADMIN_PRICING_SUBSCRIPTIONS_BUTTON", "📅 Подписки"),
callback_data="admin_pricing_subscriptions",
)
],
[
InlineKeyboardButton(
text=_t(texts, "ADMIN_PRICING_TRAFFIC_BUTTON", "📦 Трафик"),
callback_data="admin_pricing_traffic",
)
],
[
InlineKeyboardButton(
text=_t(texts, "ADMIN_PRICING_DEVICES_BUTTON", "📱 Устройства"),
callback_data="admin_pricing_devices",
)
],
[InlineKeyboardButton(text=texts.BACK, callback_data="admin_panel")],
])
def get_admin_reports_keyboard(language: str = "ru") -> InlineKeyboardMarkup:
texts = get_texts(language)

View File

@@ -20,8 +20,6 @@
"ADMIN_MESSAGES": "📨 Рассылки",
"ADMIN_MONITORING": "🔍 Мониторинг",
"ADMIN_PANEL": "\n⚙ <b>Административная панель</b>\n\nВыберите раздел для управления:\n",
"ADMIN_MAIN_SERVERS": "🌐 Сервера",
"ADMIN_MAIN_PRICING": "💰 Цены",
"ADMIN_PROMOCODES": "🎫 Промокоды",
"ADMIN_REFERRALS": "🤝 Партнерка",
"ADMIN_REMNAWAVE": "🖥️ Remnawave",
@@ -40,34 +38,6 @@
"ADMIN_PROMO_GROUP_TOGGLE_ADDON_DISCOUNT_DISABLE": "🧩 Отключить скидки на доп. услуги",
"ADMIN_PROMO_GROUP_ADDON_DISCOUNT_UPDATED_ENABLED": "🧩 Скидки на докупку доп. услуг <b>включены</b>.",
"ADMIN_PROMO_GROUP_ADDON_DISCOUNT_UPDATED_DISABLED": "🧩 Скидки на докупку доп. услуг <b>отключены</b>.",
"ADMIN_PRICING_SUBSCRIPTIONS_BUTTON": "📅 Подписки",
"ADMIN_PRICING_TRAFFIC_BUTTON": "📦 Трафик",
"ADMIN_PRICING_DEVICES_BUTTON": "📱 Устройства",
"ADMIN_PRICING_TITLE": "💰 <b>Управление ценами</b>",
"ADMIN_PRICING_DESCRIPTION": "Выберите раздел для редактирования стоимости подписок, трафика и устройств.",
"ADMIN_PRICING_OVERVIEW": "Ключевые значения:",
"ADMIN_PRICING_DEVICE_SHORT": "дополнительное устройство",
"ADMIN_PRICING_SUBSCRIPTIONS_TITLE": "📅 <b>Стоимость подписок</b>",
"ADMIN_PRICING_SUBSCRIPTIONS_HELP": "Нажмите на период, чтобы изменить цену. Значения указаны в месяцах.",
"ADMIN_PRICING_TRAFFIC_TITLE": "📦 <b>Стоимость пакетов трафика</b>",
"ADMIN_PRICING_TRAFFIC_HELP": "Нажмите на пакет, чтобы обновить цену. Цена применяется за выбранный объём трафика.",
"ADMIN_PRICING_DEVICES_TITLE": "📱 <b>Стоимость дополнительных устройств</b>",
"ADMIN_PRICING_DEVICES_HELP": "Цена применяется за каждое устройство сверх базового лимита подписки.",
"ADMIN_PRICING_EDIT_BUTTON": "✏️ Изменить цену",
"ADMIN_PRICING_EDIT_TITLE": "💰 <b>Изменение цены</b>",
"ADMIN_PRICING_CURRENT_PRICE": "Текущая цена: {price}",
"ADMIN_PRICING_PROMPT": "Отправьте новую цену для {name}:",
"ADMIN_PRICING_ENTER_PRICE": "Введите новую цену в рублях (можно использовать копейки через точку).",
"ADMIN_PRICING_CANCEL_HINT": "Для отмены воспользуйтесь кнопкой ниже.",
"ADMIN_PRICING_CANCEL": "❌ Отмена",
"ADMIN_PRICING_CANCELLED": "Отменено.",
"ADMIN_PRICING_INVALID_PRICE": "❌ Неверный формат цены. Используйте число, например 199.90",
"ADMIN_PRICING_TOO_HIGH": "❌ Слишком высокая цена. Укажите значение до 1 000 000 ₽.",
"ADMIN_PRICING_PRICE_UPDATED": "✅ Цена обновлена: {name} — {price}",
"ADMIN_PRICING_BACK_TO_SECTION": "⬅️ Назад к разделу",
"ADMIN_PRICING_GO_TO_SECTION": "Перейти к управлению ценами",
"ADMIN_PRICING_BACK_TO_CONFIG": "⬅️ К разделам",
"ADMIN_PRICING_MOVED_MESSAGE": "💡 Управление ценами переехало в раздел «Цены» на главной странице админ-панели.",
"ADMIN_PROMO_GROUPS_DEFAULT_LABEL": " (базовая)",
"ADMIN_PROMO_GROUPS_MEMBERS_COUNT": "Участников: {count}",
"ADMIN_PROMO_GROUPS_EMPTY": "Промогруппы не найдены.",

View File

@@ -135,10 +135,6 @@ class BotConfigStates(StatesGroup):
waiting_for_search_query = State()
waiting_for_import_file = State()
class PricingStates(StatesGroup):
waiting_for_value = State()
class AutoPayStates(StatesGroup):
setting_autopay_days = State()
confirming_autopay_toggle = State()