diff --git a/app/bot.py b/app/bot.py
index c598bdca..36532569 100644
--- a/app/bot.py
+++ b/app/bot.py
@@ -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)
diff --git a/app/handlers/admin/bot_configuration.py b/app/handlers/admin/bot_configuration.py
index 5fa0381f..ad0bea38 100644
--- a/app/handlers/admin/bot_configuration.py
+++ b/app/handlers/admin/bot_configuration.py
@@ -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)
diff --git a/app/handlers/admin/pricing.py b/app/handlers/admin/pricing.py
deleted file mode 100644
index b2d37278..00000000
--- a/app/handlers/admin/pricing.py
+++ /dev/null
@@ -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", "💰 Изменение цены"),
- "",
- 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", "💰 Управление ценами"),
- "",
- 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", "📅 Стоимость подписок"),
- "",
- 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", "📦 Стоимость пакетов трафика"),
- "",
- 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", "📱 Стоимость дополнительных устройств"),
- "",
- 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,
- )
-
diff --git a/app/keyboards/admin.py b/app/keyboards/admin.py
index c9142a8a..ee7729af 100644
--- a/app/keyboards/admin.py
+++ b/app/keyboards/admin.py
@@ -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)
diff --git a/app/localization/locales/ru.json b/app/localization/locales/ru.json
index dbb778d6..4ca51d14 100644
--- a/app/localization/locales/ru.json
+++ b/app/localization/locales/ru.json
@@ -20,8 +20,6 @@
"ADMIN_MESSAGES": "📨 Рассылки",
"ADMIN_MONITORING": "🔍 Мониторинг",
"ADMIN_PANEL": "\n⚙️ Административная панель\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": "🧩 Скидки на докупку доп. услуг включены.",
"ADMIN_PROMO_GROUP_ADDON_DISCOUNT_UPDATED_DISABLED": "🧩 Скидки на докупку доп. услуг отключены.",
- "ADMIN_PRICING_SUBSCRIPTIONS_BUTTON": "📅 Подписки",
- "ADMIN_PRICING_TRAFFIC_BUTTON": "📦 Трафик",
- "ADMIN_PRICING_DEVICES_BUTTON": "📱 Устройства",
- "ADMIN_PRICING_TITLE": "💰 Управление ценами",
- "ADMIN_PRICING_DESCRIPTION": "Выберите раздел для редактирования стоимости подписок, трафика и устройств.",
- "ADMIN_PRICING_OVERVIEW": "Ключевые значения:",
- "ADMIN_PRICING_DEVICE_SHORT": "дополнительное устройство",
- "ADMIN_PRICING_SUBSCRIPTIONS_TITLE": "📅 Стоимость подписок",
- "ADMIN_PRICING_SUBSCRIPTIONS_HELP": "Нажмите на период, чтобы изменить цену. Значения указаны в месяцах.",
- "ADMIN_PRICING_TRAFFIC_TITLE": "📦 Стоимость пакетов трафика",
- "ADMIN_PRICING_TRAFFIC_HELP": "Нажмите на пакет, чтобы обновить цену. Цена применяется за выбранный объём трафика.",
- "ADMIN_PRICING_DEVICES_TITLE": "📱 Стоимость дополнительных устройств",
- "ADMIN_PRICING_DEVICES_HELP": "Цена применяется за каждое устройство сверх базового лимита подписки.",
- "ADMIN_PRICING_EDIT_BUTTON": "✏️ Изменить цену",
- "ADMIN_PRICING_EDIT_TITLE": "💰 Изменение цены",
- "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": "Промогруппы не найдены.",
diff --git a/app/states.py b/app/states.py
index 25ceccd1..61015d48 100644
--- a/app/states.py
+++ b/app/states.py
@@ -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()