diff --git a/app/handlers/admin/pricing.py b/app/handlers/admin/pricing.py
index 953caf85..dbccaa83 100644
--- a/app/handlers/admin/pricing.py
+++ b/app/handlers/admin/pricing.py
@@ -1,8 +1,6 @@
-import html
import logging
-from dataclasses import dataclass
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
-from typing import Any, Dict, Iterable, List, Optional, Tuple
+from typing import Iterable, List, Tuple
from aiogram import Bot, Dispatcher, F, types
from aiogram.fsm.context import FSMContext
@@ -22,425 +20,10 @@ logger = logging.getLogger(__name__)
PriceItem = Tuple[str, str, int]
-@dataclass(frozen=True)
-class SectionItem:
- key: str
- label: str
- value: Any
- display: str
- short_display: str
-
-
-@dataclass(frozen=True)
-class SectionSettingDefinition:
- key: str
- label_key: str
- label_default: str
- type: str
- summary_label_key: Optional[str] = None
- summary_label_default: Optional[str] = None
- prompt_key: Optional[str] = None
- prompt_default: Optional[str] = None
- include_in_summary: bool = True
-
-
-@dataclass(frozen=True)
-class CustomSectionConfig:
- title_key: str
- title_default: str
- button_key: str
- button_default: str
- summary_key: str
- summary_default: str
- items: Tuple[SectionSettingDefinition, ...]
-
-
-PRICE_KEY_PREFIXES: Tuple[str, ...] = ("PRICE_",)
-PRICE_KEY_EXTRAS: Tuple[str, ...] = ("BASE_SUBSCRIPTION_PRICE", "PRICE_PER_DEVICE")
-ALLOWED_PERIOD_VALUES: Tuple[int, ...] = (14, 30, 60, 90, 180, 360)
-
-
-CUSTOM_SECTIONS: Dict[str, CustomSectionConfig] = {
- "trial": CustomSectionConfig(
- title_key="ADMIN_PRICING_SECTION_TRIAL_TITLE",
- title_default="🎁 Пробный период",
- button_key="ADMIN_PRICING_BUTTON_TRIAL",
- button_default="🎁 Пробный период",
- summary_key="ADMIN_PRICING_MENU_SUMMARY_TRIAL",
- summary_default="• Пробный период: {summary}",
- items=(
- SectionSettingDefinition(
- key="TRIAL_DURATION_DAYS",
- label_key="ADMIN_PRICING_TRIAL_DURATION",
- label_default="Длительность пробного периода (дней)",
- type="int",
- summary_label_key="ADMIN_PRICING_TRIAL_SUMMARY_DURATION",
- summary_label_default="Дни",
- ),
- SectionSettingDefinition(
- key="TRIAL_TRAFFIC_LIMIT_GB",
- label_key="ADMIN_PRICING_TRIAL_TRAFFIC",
- label_default="Лимит трафика триала (ГБ)",
- type="int",
- summary_label_key="ADMIN_PRICING_TRIAL_SUMMARY_TRAFFIC",
- summary_label_default="Трафик",
- ),
- SectionSettingDefinition(
- key="TRIAL_DEVICE_LIMIT",
- label_key="ADMIN_PRICING_TRIAL_DEVICES",
- label_default="Количество устройств в триале",
- type="int",
- summary_label_key="ADMIN_PRICING_TRIAL_SUMMARY_DEVICES",
- summary_label_default="Устройства",
- ),
- SectionSettingDefinition(
- key="TRIAL_ADD_REMAINING_DAYS_TO_PAID",
- label_key="ADMIN_PRICING_TRIAL_ADD_REMAINING",
- label_default="Добавлять остаток триала к платной подписке",
- type="bool",
- summary_label_key="ADMIN_PRICING_TRIAL_SUMMARY_ADD_REMAINING",
- summary_label_default="Перенос дней",
- ),
- SectionSettingDefinition(
- key="TRIAL_SQUAD_UUID",
- label_key="ADMIN_PRICING_TRIAL_SQUAD",
- label_default="UUID сквада для пробного периода",
- type="text",
- include_in_summary=False,
- ),
- ),
- ),
- "subscription": CustomSectionConfig(
- title_key="ADMIN_PRICING_SECTION_SUBSCRIPTION_TITLE",
- title_default="⚙️ Параметры подписки",
- button_key="ADMIN_PRICING_BUTTON_SUBSCRIPTION",
- button_default="⚙️ Параметры подписки",
- summary_key="ADMIN_PRICING_MENU_SUMMARY_SUBSCRIPTION",
- summary_default="• Параметры подписки: {summary}",
- items=(
- SectionSettingDefinition(
- key="BASE_SUBSCRIPTION_PRICE",
- label_key="ADMIN_PRICING_SUBSCRIPTION_BASE_PRICE",
- label_default="Базовая стоимость подписки",
- type="price",
- summary_label_key="ADMIN_PRICING_SUBSCRIPTION_SUMMARY_PRICE",
- summary_label_default="База",
- ),
- SectionSettingDefinition(
- key="DEFAULT_DEVICE_LIMIT",
- label_key="ADMIN_PRICING_SUBSCRIPTION_DEFAULT_DEVICES",
- label_default="Устройств по умолчанию",
- type="int",
- summary_label_key="ADMIN_PRICING_SUBSCRIPTION_SUMMARY_DEFAULT_DEVICES",
- summary_label_default="Устройств",
- ),
- SectionSettingDefinition(
- key="MAX_DEVICES_LIMIT",
- label_key="ADMIN_PRICING_SUBSCRIPTION_MAX_DEVICES",
- label_default="Максимум устройств",
- type="int",
- summary_label_key="ADMIN_PRICING_SUBSCRIPTION_SUMMARY_MAX_DEVICES",
- summary_label_default="Макс устройств",
- ),
- ),
- ),
- "availability": CustomSectionConfig(
- title_key="ADMIN_PRICING_SECTION_AVAILABILITY_TITLE",
- title_default="📆 Выводимые периоды",
- button_key="ADMIN_PRICING_BUTTON_AVAILABILITY",
- button_default="📆 Выводимые периоды",
- summary_key="ADMIN_PRICING_MENU_SUMMARY_AVAILABILITY",
- summary_default="• Выводимые периоды: {summary}",
- items=(
- SectionSettingDefinition(
- key="AVAILABLE_SUBSCRIPTION_PERIODS",
- label_key="ADMIN_PRICING_AVAILABILITY_SUBSCRIPTIONS",
- label_default="Периоды подписки",
- type="periods",
- summary_label_key="ADMIN_PRICING_AVAILABILITY_SUMMARY_SUBSCRIPTIONS",
- summary_label_default="Подписка",
- ),
- SectionSettingDefinition(
- key="AVAILABLE_RENEWAL_PERIODS",
- label_key="ADMIN_PRICING_AVAILABILITY_RENEWALS",
- label_default="Периоды продления",
- type="periods",
- summary_label_key="ADMIN_PRICING_AVAILABILITY_SUMMARY_RENEWALS",
- summary_label_default="Продление",
- ),
- ),
- ),
- "traffic_settings": CustomSectionConfig(
- title_key="ADMIN_PRICING_SECTION_TRAFFIC_SETTINGS_TITLE",
- title_default="📊 Лимиты трафика",
- button_key="ADMIN_PRICING_BUTTON_TRAFFIC_SETTINGS",
- button_default="📊 Лимиты трафика",
- summary_key="ADMIN_PRICING_MENU_SUMMARY_TRAFFIC_SETTINGS",
- summary_default="• Лимиты трафика: {summary}",
- items=(
- SectionSettingDefinition(
- key="DEFAULT_TRAFFIC_LIMIT_GB",
- label_key="ADMIN_PRICING_TRAFFIC_DEFAULT_LIMIT",
- label_default="Лимит трафика по умолчанию (ГБ)",
- type="int",
- summary_label_key="ADMIN_PRICING_TRAFFIC_SUMMARY_DEFAULT",
- summary_label_default="По умолчанию",
- ),
- SectionSettingDefinition(
- key="TRAFFIC_SELECTION_MODE",
- label_key="ADMIN_PRICING_TRAFFIC_SELECTION_MODE",
- label_default="Режим выбора трафика",
- type="choice",
- summary_label_key="ADMIN_PRICING_TRAFFIC_SUMMARY_MODE",
- summary_label_default="Режим",
- ),
- SectionSettingDefinition(
- key="FIXED_TRAFFIC_LIMIT_GB",
- label_key="ADMIN_PRICING_TRAFFIC_FIXED_LIMIT",
- label_default="Фиксированный лимит (ГБ)",
- type="int",
- summary_label_key="ADMIN_PRICING_TRAFFIC_SUMMARY_FIXED",
- summary_label_default="Фикс",
- ),
- SectionSettingDefinition(
- key="DEFAULT_TRAFFIC_RESET_STRATEGY",
- label_key="ADMIN_PRICING_TRAFFIC_RESET_STRATEGY",
- label_default="Стратегия сброса трафика",
- type="choice",
- summary_label_key="ADMIN_PRICING_TRAFFIC_SUMMARY_RESET",
- summary_label_default="Сброс",
- ),
- SectionSettingDefinition(
- key="RESET_TRAFFIC_ON_PAYMENT",
- label_key="ADMIN_PRICING_TRAFFIC_RESET_ON_PAYMENT",
- label_default="Сбрасывать трафик при оплате",
- type="bool",
- summary_label_key="ADMIN_PRICING_TRAFFIC_SUMMARY_RESET_PAYMENT",
- summary_label_default="Сброс при оплате",
- ),
- ),
- ),
-}
-
-CUSTOM_SECTION_ORDER: Tuple[str, ...] = tuple(CUSTOM_SECTIONS.keys())
-
-CUSTOM_DEFINITION_BY_KEY: Dict[str, SectionSettingDefinition] = {}
-for _section_key in CUSTOM_SECTION_ORDER:
- _section_config = CUSTOM_SECTIONS[_section_key]
- for _definition in _section_config.items:
- CUSTOM_DEFINITION_BY_KEY[_definition.key] = _definition
-
-
def _language_code(language: str | None) -> str:
return (language or "ru").split("-")[0].lower()
-def _is_price_key(key: str) -> bool:
- return key in PRICE_KEY_EXTRAS or any(key.startswith(prefix) for prefix in PRICE_KEY_PREFIXES)
-
-
-def _get_item_type(key: str) -> str:
- definition = CUSTOM_DEFINITION_BY_KEY.get(key)
- if definition:
- return definition.type
- if _is_price_key(key):
- return "price"
- return "text"
-
-
-def _format_setting_value(key: str, language: str, *, short: bool = False) -> str:
- lang_code = _language_code(language)
- value = getattr(settings, key, None)
-
- if value is None:
- return "—"
-
- if isinstance(value, str):
- if not value.strip():
- return "—"
- if not short:
- return value
- return value if len(value) <= 20 else f"{value[:17]}…"
-
- if _is_price_key(key) and isinstance(value, (int, float)):
- return settings.format_price(int(value))
-
- if isinstance(value, bool):
- if short:
- if lang_code == "ru":
- return "✅ Вкл" if value else "❌ Выкл"
- return "✅ On" if value else "❌ Off"
- if lang_code == "ru":
- return "Включено" if value else "Выключено"
- return "Enabled" if value else "Disabled"
-
- if short:
- formatted = bot_configuration_service.format_value_for_list(key)
- if formatted:
- return formatted
-
- formatted_full = bot_configuration_service.format_value_human(key, value)
- return formatted_full if formatted_full else str(value)
-
-
-def _build_custom_section_items(section: str, language: str) -> List[SectionItem]:
- texts = get_texts(language)
- items: List[SectionItem] = []
-
- config = CUSTOM_SECTIONS[section]
- for definition in config.items:
- label = texts.t(definition.label_key, definition.label_default)
- value = getattr(settings, definition.key, None)
- display = _format_setting_value(definition.key, language)
- short_display = _format_setting_value(definition.key, language, short=True)
- items.append(
- SectionItem(
- key=definition.key,
- label=label,
- value=value,
- display=display,
- short_display=short_display,
- )
- )
-
- return items
-
-
-def _build_custom_summary(
- section: str,
- items: Iterable[SectionItem],
- language: str,
- fallback: str,
-) -> str:
- texts = get_texts(language)
- config = CUSTOM_SECTIONS[section]
- definitions: Dict[str, SectionSettingDefinition] = {
- definition.key: definition for definition in config.items
- }
-
- parts: List[str] = []
- for item in items:
- definition = definitions.get(item.key)
- if not definition or not definition.include_in_summary:
- continue
-
- label_default = definition.summary_label_default or definition.label_default
- if definition.summary_label_key:
- label = texts.t(definition.summary_label_key, label_default)
- else:
- label = label_default
-
- short_value = item.short_display or "—"
- parts.append(f"{label}: {short_value}")
-
- return ", ".join(parts) if parts else fallback
-
-
-def _build_instruction(
- definition: Optional[SectionSettingDefinition],
- item_type: str,
- key: str,
- language: str,
-) -> str:
- texts = get_texts(language)
-
- if definition and definition.prompt_key:
- return texts.t(
- definition.prompt_key,
- definition.prompt_default
- or texts.t(
- "ADMIN_PRICING_SETTING_PROMPT_GENERIC",
- "Введите новое значение. Для отмены отправьте «отмена».",
- ),
- )
-
- if item_type == "int":
- return texts.t(
- "ADMIN_PRICING_SETTING_PROMPT_INT",
- "Введите целое число. Для отмены отправьте «отмена».",
- )
-
- if item_type == "text":
- return texts.t(
- "ADMIN_PRICING_SETTING_PROMPT_TEXT",
- "Введите новое значение. Чтобы очистить параметр, отправьте «пусто». Для отмены — «отмена».",
- )
-
- if item_type == "choice":
- options = bot_configuration_service.get_choice_options(key)
- if options:
- readable = ", ".join(
- f"{option.label} ({option.value})" for option in options
- )
- return texts.t(
- "ADMIN_PRICING_SETTING_PROMPT_CHOICE",
- "Введите одно из значений: {options}. Для отмены — «отмена».",
- ).format(options=readable)
- return texts.t(
- "ADMIN_PRICING_SETTING_PROMPT_GENERIC",
- "Введите новое значение. Для отмены отправьте «отмена».",
- )
-
- if item_type == "periods":
- allowed = ", ".join(str(value) for value in ALLOWED_PERIOD_VALUES)
- return texts.t(
- "ADMIN_PRICING_SETTING_PROMPT_PERIODS",
- "Введите значения через запятую из допустимого набора: {values}. Для отмены — «отмена».",
- ).format(values=allowed)
-
- return texts.t(
- "ADMIN_PRICING_SETTING_PROMPT_GENERIC",
- "Введите новое значение. Для отмены отправьте «отмена».",
- )
-
-
-def _parse_periods_input(raw_value: str, language: str) -> str:
- texts = get_texts(language)
- cleaned = (raw_value or "").replace(" ", "").replace("\n", "").strip()
-
- if not cleaned:
- raise ValueError(
- texts.t(
- "ADMIN_PRICING_SETTING_PERIODS_EMPTY",
- "Список периодов не может быть пустым.",
- )
- )
-
- parts = [part for part in cleaned.split(",") if part]
- parsed: List[int] = []
-
- for part in parts:
- try:
- value = int(part)
- except ValueError as error:
- raise ValueError(
- texts.t(
- "ADMIN_PRICING_SETTING_PERIODS_INVALID_NUMBER",
- "Не удалось распознать значение «{value}». Укажите числа через запятую.",
- ).format(value=part)
- ) from error
-
- if value not in ALLOWED_PERIOD_VALUES:
- allowed = ", ".join(str(item) for item in ALLOWED_PERIOD_VALUES)
- raise ValueError(
- texts.t(
- "ADMIN_PRICING_SETTING_PERIODS_INVALID",
- "Недопустимое значение {value}. Доступны только: {allowed}.",
- ).format(value=value, allowed=allowed)
- )
-
- parsed.append(value)
-
- if not parsed:
- raise ValueError(
- texts.t(
- "ADMIN_PRICING_SETTING_PERIODS_EMPTY",
- "Список периодов не может быть пустым.",
- )
- )
-
- unique_sorted = sorted(set(parsed))
- return ",".join(str(value) for value in unique_sorted)
def _format_period_label(days: int, lang_code: str, short: bool = False) -> str:
if short:
suffix = "д" if lang_code == "ru" else "d"
@@ -554,75 +137,39 @@ def _build_overview(language: str) -> Tuple[str, types.InlineKeyboardMarkup]:
summary_traffic = _build_traffic_summary(traffic_items, lang_code, fallback)
summary_extra = _build_extra_summary(extra_items, fallback)
- custom_items: Dict[str, List[SectionItem]] = {}
- for section_key in CUSTOM_SECTION_ORDER:
- custom_items[section_key] = _build_custom_section_items(section_key, language)
-
- summary_lines: List[str] = [
- texts.t("ADMIN_PRICING_MENU_SUMMARY_PERIODS", "• Периоды: {summary}").format(
- summary=summary_periods
- ),
- texts.t("ADMIN_PRICING_MENU_SUMMARY_TRAFFIC", "• Трафик: {summary}").format(
- summary=summary_traffic
- ),
- texts.t("ADMIN_PRICING_MENU_SUMMARY_EXTRA", "• Дополнительно: {summary}").format(
- summary=summary_extra
- ),
- ]
-
- for section_key in CUSTOM_SECTION_ORDER:
- config = CUSTOM_SECTIONS[section_key]
- section_summary = _build_custom_summary(
- section_key, custom_items[section_key], language, fallback
- )
- summary_lines.append(
- texts.t(config.summary_key, config.summary_default).format(summary=section_summary)
- )
-
text = (
f"💰 {texts.t('ADMIN_PRICING_MENU_TITLE', 'Управление ценами')}\n\n"
f"{texts.t('ADMIN_PRICING_MENU_DESCRIPTION', 'Быстрый доступ к тарифам и пакетам.')}\n\n"
f"{texts.t('ADMIN_PRICING_MENU_SUMMARY', 'Краткая сводка:')}\n"
- + "\n".join(summary_lines)
- + "\n\n"
+ f"{texts.t('ADMIN_PRICING_MENU_SUMMARY_PERIODS', '• Периоды: {summary}').format(summary=summary_periods)}\n"
+ f"{texts.t('ADMIN_PRICING_MENU_SUMMARY_TRAFFIC', '• Трафик: {summary}').format(summary=summary_traffic)}\n"
+ f"{texts.t('ADMIN_PRICING_MENU_SUMMARY_EXTRA', '• Дополнительно: {summary}').format(summary=summary_extra)}\n\n"
f"{texts.t('ADMIN_PRICING_MENU_PROMPT', 'Выберите раздел для редактирования:')}"
)
- keyboard_rows: List[List[types.InlineKeyboardButton]] = [
- [
- types.InlineKeyboardButton(
- text=texts.t("ADMIN_PRICING_BUTTON_PERIODS", "🗓 Периоды подписки"),
- callback_data="admin_pricing_section:periods",
- )
- ],
- [
- types.InlineKeyboardButton(
- text=texts.t("ADMIN_PRICING_BUTTON_TRAFFIC", "📦 Пакеты трафика"),
- callback_data="admin_pricing_section:traffic",
- )
- ],
- [
- types.InlineKeyboardButton(
- text=texts.t("ADMIN_PRICING_BUTTON_EXTRA", "➕ Дополнительно"),
- callback_data="admin_pricing_section:extra",
- )
- ],
- ]
-
- for section_key in CUSTOM_SECTION_ORDER:
- config = CUSTOM_SECTIONS[section_key]
- keyboard_rows.append(
+ keyboard = types.InlineKeyboardMarkup(
+ inline_keyboard=[
[
types.InlineKeyboardButton(
- text=texts.t(config.button_key, config.button_default),
- callback_data=f"admin_pricing_section:{section_key}",
+ text=texts.t("ADMIN_PRICING_BUTTON_PERIODS", "🗓 Периоды подписки"),
+ callback_data="admin_pricing_section:periods",
)
- ]
- )
-
- keyboard_rows.append([types.InlineKeyboardButton(text=texts.BACK, callback_data="admin_panel")])
-
- keyboard = types.InlineKeyboardMarkup(inline_keyboard=keyboard_rows)
+ ],
+ [
+ types.InlineKeyboardButton(
+ text=texts.t("ADMIN_PRICING_BUTTON_TRAFFIC", "📦 Пакеты трафика"),
+ callback_data="admin_pricing_section:traffic",
+ )
+ ],
+ [
+ types.InlineKeyboardButton(
+ text=texts.t("ADMIN_PRICING_BUTTON_EXTRA", "➕ Дополнительно"),
+ callback_data="admin_pricing_section:extra",
+ )
+ ],
+ [types.InlineKeyboardButton(text=texts.BACK, callback_data="admin_panel")],
+ ]
+ )
return text, keyboard
@@ -635,69 +182,32 @@ def _build_section(
lang_code = _language_code(language)
if section == "periods":
- price_items = _get_period_items(lang_code)
- section_items = [
- SectionItem(
- key=key,
- label=label,
- value=price,
- display=settings.format_price(price),
- short_display=settings.format_price(price),
- )
- for key, label, price in price_items
- ]
+ items = _get_period_items(lang_code)
title = texts.t("ADMIN_PRICING_SECTION_PERIODS_TITLE", "🗓 Периоды подписки")
elif section == "traffic":
- price_items = _get_traffic_items(lang_code)
- section_items = [
- SectionItem(
- key=key,
- label=label,
- value=price,
- display=settings.format_price(price),
- short_display=settings.format_price(price),
- )
- for key, label, price in price_items
- ]
+ items = _get_traffic_items(lang_code)
title = texts.t("ADMIN_PRICING_SECTION_TRAFFIC_TITLE", "📦 Пакеты трафика")
- elif section == "extra":
- price_items = _get_extra_items(lang_code)
- section_items = [
- SectionItem(
- key=key,
- label=label,
- value=price,
- display=settings.format_price(price),
- short_display=settings.format_price(price),
- )
- for key, label, price in price_items
- ]
- title = texts.t("ADMIN_PRICING_SECTION_EXTRA_TITLE", "➕ Дополнительные опции")
- elif section in CUSTOM_SECTIONS:
- section_items = _build_custom_section_items(section, language)
- config = CUSTOM_SECTIONS[section]
- title = texts.t(config.title_key, config.title_default)
else:
- section_items = []
+ items = _get_extra_items(lang_code)
title = texts.t("ADMIN_PRICING_SECTION_EXTRA_TITLE", "➕ Дополнительные опции")
lines = [title, ""]
- if section_items:
- for item in section_items:
- lines.append(f"• {item.label} — {item.display}")
+ if items:
+ for key, label, price in items:
+ lines.append(f"• {label} — {settings.format_price(price)}")
lines.append("")
lines.append(texts.t("ADMIN_PRICING_SECTION_PROMPT", "Выберите что изменить:"))
else:
lines.append(texts.t("ADMIN_PRICING_SECTION_EMPTY", "Нет доступных значений."))
keyboard_rows: List[List[types.InlineKeyboardButton]] = []
- for item in section_items:
+ for key, label, price in items:
keyboard_rows.append(
[
types.InlineKeyboardButton(
- text=f"{item.label} • {item.short_display}",
- callback_data=f"admin_pricing_edit:{section}:{item.key}",
+ text=f"{label} • {settings.format_price(price)}",
+ callback_data=f"admin_pricing_edit:{section}:{key}",
)
]
)
@@ -761,11 +271,6 @@ def _parse_price_input(text: str) -> int:
def _resolve_label(section: str, key: str, language: str) -> str:
- custom_definition = CUSTOM_DEFINITION_BY_KEY.get(key)
- if custom_definition:
- texts = get_texts(language)
- return texts.t(custom_definition.label_key, custom_definition.label_default)
-
lang_code = _language_code(language)
if section == "periods" and key.startswith("PRICE_") and key.endswith("_DAYS"):
@@ -834,27 +339,6 @@ async def start_price_edit(
texts = get_texts(db_user.language)
label = _resolve_label(section, key, db_user.language)
- item_type = _get_item_type(key)
-
- if item_type == "bool":
- current_value = getattr(settings, key, False)
- new_value = not bool(current_value)
- await bot_configuration_service.set_value(db, key, new_value)
- await db.commit()
- await state.clear()
-
- value_text = _format_setting_value(key, db_user.language)
- success_text = texts.t(
- "ADMIN_PRICING_SETTING_SUCCESS",
- "Параметр {item} обновлен: {value}",
- ).format(item=label, value=value_text)
- await callback.message.answer(success_text)
-
- section_text, section_keyboard = _build_section(section, db_user.language)
- await _render_message(callback.message, section_text, section_keyboard)
- await callback.answer()
- return
-
await state.update_data(
pricing_key=key,
pricing_section=section,
@@ -862,23 +346,12 @@ async def start_price_edit(
)
await state.set_state(PricingStates.waiting_for_value)
- if item_type == "price":
- current_price = getattr(settings, key, 0)
- prompt = (
- f"💰 {texts.t('ADMIN_PRICING_EDIT_TITLE', 'Изменение цены')}\n\n"
- f"{texts.t('ADMIN_PRICING_EDIT_TARGET', 'Текущий тариф')}: {html.escape(label)}\n"
- f"{texts.t('ADMIN_PRICING_EDIT_CURRENT', 'Текущее значение')}: {settings.format_price(current_price)}\n\n"
- f"{texts.t('ADMIN_PRICING_EDIT_PROMPT', 'Введите новую стоимость в рублях (например 990 или 990.50). Для бесплатного тарифа укажите 0.')}"
- )
- else:
- current_value = _format_setting_value(key, db_user.language)
- instruction = _build_instruction(definition, item_type, key, db_user.language)
- prompt = (
- f"⚙️ {texts.t('ADMIN_PRICING_SETTING_EDIT_TITLE', 'Изменение параметра')}\n\n"
- f"{texts.t('ADMIN_PRICING_SETTING_EDIT_TARGET', 'Параметр')}: {html.escape(label)}\n"
- f"{texts.t('ADMIN_PRICING_SETTING_EDIT_CURRENT', 'Текущее значение')}: {html.escape(current_value)}\n\n"
- f"{html.escape(instruction)}"
- )
+ prompt = (
+ f"💰 {texts.t('ADMIN_PRICING_EDIT_TITLE', 'Изменение цены')}\n\n"
+ f"{texts.t('ADMIN_PRICING_EDIT_TARGET', 'Текущий тариф')}: {label}\n"
+ f"{texts.t('ADMIN_PRICING_EDIT_CURRENT', 'Текущее значение')}: {settings.format_price(getattr(settings, key, 0))}\n\n"
+ f"{texts.t('ADMIN_PRICING_EDIT_PROMPT', 'Введите новую стоимость в рублях (например 990 или 990.50). Для бесплатного тарифа укажите 0.')}"
+ )
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
@@ -928,60 +401,27 @@ async def process_price_input(
await message.answer(texts.t("ADMIN_PRICING_EDIT_CANCELLED", "Изменения отменены."))
return
- item_type = _get_item_type(key)
+ try:
+ price_kopeks = _parse_price_input(raw_value)
+ except ValueError:
+ await message.answer(
+ texts.t(
+ "ADMIN_PRICING_EDIT_INVALID",
+ "Не удалось распознать цену. Укажите число в рублях (например 990 или 990.50).",
+ )
+ )
+ return
- if item_type == "price":
- try:
- parsed_value: Any = _parse_price_input(raw_value)
- except ValueError:
- await message.answer(
- texts.t(
- "ADMIN_PRICING_EDIT_INVALID",
- "Не удалось распознать цену. Укажите число в рублях (например 990 или 990.50).",
- )
- )
- return
- elif item_type == "periods":
- try:
- parsed_value = _parse_periods_input(raw_value, db_user.language)
- except ValueError as error:
- reason = str(error).strip() or texts.t(
- "ADMIN_PRICING_SETTING_INVALID_GENERIC",
- "Не удалось обновить параметр. Проверьте ввод и попробуйте снова.",
- )
- await message.answer(
- texts.t("ADMIN_PRICING_SETTING_INVALID", "Ошибка: {reason}").format(reason=reason)
- )
- return
- else:
- try:
- parsed_value = bot_configuration_service.parse_user_value(key, raw_value)
- except ValueError as error:
- reason = str(error).strip() or texts.t(
- "ADMIN_PRICING_SETTING_INVALID_GENERIC",
- "Не удалось обновить параметр. Проверьте ввод и попробуйте снова.",
- )
- await message.answer(
- texts.t("ADMIN_PRICING_SETTING_INVALID", "Ошибка: {reason}").format(reason=reason)
- )
- return
-
- await bot_configuration_service.set_value(db, key, parsed_value)
+ await bot_configuration_service.set_value(db, key, price_kopeks)
await db.commit()
label = _resolve_label(section, key, db_user.language)
- value_text = _format_setting_value(key, db_user.language)
- success_template = (
- texts.t("ADMIN_PRICING_EDIT_SUCCESS", "Цена для {item} обновлена: {price}")
- if item_type == "price"
- else texts.t("ADMIN_PRICING_SETTING_SUCCESS", "Параметр {item} обновлен: {value}")
+ await message.answer(
+ texts.t("ADMIN_PRICING_EDIT_SUCCESS", "Цена для {item} обновлена: {price}").format(
+ item=label,
+ price=settings.format_price(price_kopeks),
+ )
)
- format_kwargs = {"item": label}
- if item_type == "price":
- format_kwargs["price"] = settings.format_price(parsed_value)
- else:
- format_kwargs["value"] = value_text
- await message.answer(success_template.format(**format_kwargs))
await state.clear()