Revert "Add trial and tariff settings to admin pricing"

This commit is contained in:
Egor
2025-10-04 05:57:49 +03:00
committed by GitHub
parent 2b79de7d82
commit e623f9df3a

View File

@@ -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"💰 <b>{texts.t('ADMIN_PRICING_MENU_TITLE', 'Управление ценами')}</b>\n\n"
f"{texts.t('ADMIN_PRICING_MENU_DESCRIPTION', 'Быстрый доступ к тарифам и пакетам.')}\n\n"
f"<b>{texts.t('ADMIN_PRICING_MENU_SUMMARY', 'Краткая сводка:')}</b>\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"💰 <b>{texts.t('ADMIN_PRICING_EDIT_TITLE', 'Изменение цены')}</b>\n\n"
f"{texts.t('ADMIN_PRICING_EDIT_TARGET', 'Текущий тариф')}: <b>{html.escape(label)}</b>\n"
f"{texts.t('ADMIN_PRICING_EDIT_CURRENT', 'Текущее значение')}: <b>{settings.format_price(current_price)}</b>\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"⚙️ <b>{texts.t('ADMIN_PRICING_SETTING_EDIT_TITLE', 'Изменение параметра')}</b>\n\n"
f"{texts.t('ADMIN_PRICING_SETTING_EDIT_TARGET', 'Параметр')}: <b>{html.escape(label)}</b>\n"
f"{texts.t('ADMIN_PRICING_SETTING_EDIT_CURRENT', 'Текущее значение')}: <b>{html.escape(current_value)}</b>\n\n"
f"{html.escape(instruction)}"
)
prompt = (
f"💰 <b>{texts.t('ADMIN_PRICING_EDIT_TITLE', 'Изменение цены')}</b>\n\n"
f"{texts.t('ADMIN_PRICING_EDIT_TARGET', 'Текущий тариф')}: <b>{label}</b>\n"
f"{texts.t('ADMIN_PRICING_EDIT_CURRENT', 'Текущее значение')}: <b>{settings.format_price(getattr(settings, key, 0))}</b>\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()