changing the tariff through the admin panel without recreating it

This commit is contained in:
Vladless
2025-12-26 20:23:50 +03:00
parent c7563096e9
commit 9d02ad6fbc
7 changed files with 717 additions and 144 deletions
+2 -1
View File
@@ -1,6 +1,6 @@
from aiogram import Router
from . import users_balance, users_bans, users_hwid, users_keys, users_manage
from . import users_balance, users_bans, users_hwid, users_keys, users_manage, users_tariffs
router = Router()
@@ -9,3 +9,4 @@ router.include_router(users_balance.router)
router.include_router(users_hwid.router)
router.include_router(users_keys.router)
router.include_router(users_bans.router)
router.include_router(users_tariffs.router)
+1 -135
View File
@@ -3,7 +3,7 @@ import time
import uuid
from datetime import datetime, timedelta, timezone
from typing import Any
from core.settings.tariffs_config import normalize_tariff_config
import pytz
@@ -178,140 +178,6 @@ async def handle_key_edit(
)
@router.callback_query(F.data == "back:renew", IsAdminFilter())
async def handle_back_to_key_menu(
callback_query: CallbackQuery,
session: AsyncSession,
state: FSMContext,
):
data = await state.get_data()
email = data["email"]
tg_id = data["tg_id"]
await state.clear()
callback_data = AdminUserEditorCallback(action="users_key_edit", data=email, tg_id=tg_id)
await handle_key_edit(
callback_query=callback_query,
callback_data=callback_data,
session=session,
update=False,
)
@router.callback_query(
AdminUserEditorCallback.filter(F.action == "users_renew"),
IsAdminFilter(),
)
async def handle_user_choose_tariff_group(
callback_query: CallbackQuery,
callback_data: AdminUserEditorCallback,
session: AsyncSession,
state: FSMContext,
):
email = callback_data.data
tg_id = callback_data.tg_id
await state.set_state(RenewTariffState.selecting_group)
await state.update_data(email=email, tg_id=tg_id)
result = await session.execute(select(Tariff.group_code).distinct())
groups = [row[0] for row in result.fetchall()]
builder = InlineKeyboardBuilder()
for group_code in groups:
builder.button(text=group_code, callback_data=f"group:{group_code}")
builder.button(text="🔙 Назад", callback_data="back:renew")
builder.adjust(1)
await callback_query.message.edit_text(
text="📁 <b>Выберите тарифную группу:</b>",
reply_markup=builder.as_markup(),
)
@router.callback_query(F.data.startswith("group:"), IsAdminFilter())
async def handle_user_choose_tariff(
callback_query: CallbackQuery,
session: AsyncSession,
state: FSMContext,
):
group_code = callback_query.data.split(":", 1)[1]
await state.update_data(group_code=group_code)
await state.set_state(RenewTariffState.selecting_tariff)
result = await session.execute(
select(Tariff).where(Tariff.group_code == group_code, Tariff.is_active.is_(True)).order_by(Tariff.id)
)
tariffs = result.scalars().all()
if not tariffs:
await callback_query.message.edit_text("❌ Нет активных тарифов в группе.")
return
builder = InlineKeyboardBuilder()
for tariff in tariffs:
builder.button(text=f"{tariff.name} {int(tariff.price_rub)}", callback_data=f"confirm:{tariff.id}")
builder.button(text="🔙 Назад", callback_data="back:group")
builder.adjust(1)
await callback_query.message.edit_text(
text=f"📦 <b>Выберите тариф для группы <code>{group_code}</code>:</b>",
reply_markup=builder.as_markup(),
)
@router.callback_query(F.data.startswith("confirm:"), IsAdminFilter())
async def handle_user_renew_confirm(
callback_query: CallbackQuery,
session: AsyncSession,
state: FSMContext,
):
tariff_id = int(callback_query.data.split(":")[1])
data = await state.get_data()
email = data["email"]
tg_id = data["tg_id"]
stmt = update(Key).where(Key.tg_id == tg_id, Key.email == email).values(tariff_id=tariff_id)
await session.execute(stmt)
await session.commit()
await update_subscription(tg_id=tg_id, email=email, session=session)
await state.clear()
callback_data = AdminUserEditorCallback(action="users_key_edit", data=email, tg_id=tg_id)
await handle_key_edit(
callback_query=callback_query,
callback_data=callback_data,
session=session,
update=False,
)
@router.callback_query(F.data == "back:group", IsAdminFilter())
async def handle_back_to_group(
callback_query: CallbackQuery,
state: FSMContext,
session: AsyncSession,
):
await state.get_data()
result = await session.execute(select(Tariff.group_code).distinct())
groups = [row[0] for row in result.fetchall()]
builder = InlineKeyboardBuilder()
for group_code in groups:
builder.button(text=group_code, callback_data=f"group:{group_code}")
builder.button(text="🔙 Назад", callback_data="back:renew")
builder.adjust(1)
await callback_query.message.edit_text(
text="📁 <b>Выберите тарифную группу:</b>",
reply_markup=builder.as_markup(),
)
await state.set_state(RenewTariffState.selecting_group)
@router.callback_query(
AdminUserEditorCallback.filter(F.action == "users_expiry_edit"),
IsAdminFilter(),
+697
View File
@@ -0,0 +1,697 @@
from datetime import datetime
from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from core.settings.tariffs_config import normalize_tariff_config
from database import get_tariff_by_id
from database.models import Key, Tariff
from filters.admin import IsAdminFilter
from handlers.keys.operations import renew_key_in_cluster
from logger import logger
from .keyboard import AdminUserEditorCallback
from .users_states import RenewTariffState
from .users_keys import handle_key_edit
router = Router()
@router.callback_query(F.data == "back:renew", IsAdminFilter())
async def handle_back_to_key_menu(
callback_query: CallbackQuery,
session: AsyncSession,
state: FSMContext,
):
data = await state.get_data()
email = data.get("email")
tg_id = data.get("tg_id")
await state.clear()
if not email or not tg_id:
await callback_query.message.edit_text("❌ Не найдены данные сессии.")
return
callback_data = AdminUserEditorCallback(action="users_key_edit", data=email, tg_id=tg_id)
await handle_key_edit(
callback_query=callback_query,
callback_data=callback_data,
session=session,
update=False,
)
@router.callback_query(
AdminUserEditorCallback.filter(F.action == "users_renew"),
IsAdminFilter(),
)
async def handle_user_choose_tariff_group(
callback_query: CallbackQuery,
callback_data: AdminUserEditorCallback,
session: AsyncSession,
state: FSMContext,
):
email = callback_data.data
tg_id = callback_data.tg_id
await state.set_state(RenewTariffState.selecting_group)
await state.update_data(email=email, tg_id=tg_id)
result = await session.execute(select(Tariff.group_code).distinct())
groups = [row[0] for row in result.fetchall()]
builder = InlineKeyboardBuilder()
for group_code in groups:
builder.button(text=group_code, callback_data=f"group:{group_code}")
builder.button(text="🔙 Назад", callback_data="back:renew")
builder.adjust(1)
await callback_query.message.edit_text(
text="📁 <b>Выберите тарифную группу:</b>",
reply_markup=builder.as_markup(),
)
@router.callback_query(F.data.startswith("group:"), IsAdminFilter())
async def handle_user_choose_tariff(
callback_query: CallbackQuery,
session: AsyncSession,
state: FSMContext,
):
group_code = callback_query.data.split(":", 1)[1]
await state.update_data(group_code=group_code)
await state.set_state(RenewTariffState.selecting_tariff)
result = await session.execute(
select(Tariff).where(Tariff.group_code == group_code, Tariff.is_active.is_(True)).order_by(Tariff.id)
)
tariffs = result.scalars().all()
if not tariffs:
await callback_query.message.edit_text("❌ Нет активных тарифов в группе.")
return
builder = InlineKeyboardBuilder()
for tariff in tariffs:
builder.button(text=f"{tariff.name} {int(tariff.price_rub)}", callback_data=f"confirm:{tariff.id}")
builder.button(text="🔙 Назад", callback_data="back:group")
builder.adjust(1)
await callback_query.message.edit_text(
text=f"📦 <b>Выберите тариф для группы <code>{group_code}</code>:</b>",
reply_markup=builder.as_markup(),
)
@router.callback_query(F.data.startswith("confirm:"), IsAdminFilter())
async def handle_user_renew_confirm(
callback_query: CallbackQuery,
session: AsyncSession,
state: FSMContext,
):
tariff_id = int(callback_query.data.split(":")[1])
data = await state.get_data()
email = data.get("email")
tg_id = data.get("tg_id")
if not email or not tg_id:
await callback_query.message.edit_text("❌ Не найдены данные сессии.")
await state.clear()
return
tariff = await get_tariff_by_id(session, tariff_id)
if not tariff:
await callback_query.message.edit_text("❌ Тариф не найден.")
await state.clear()
return
result = await session.execute(select(Key).where(Key.email == email, Key.tg_id == tg_id))
key_obj: Key | None = result.scalar_one_or_none()
if not key_obj:
await callback_query.message.edit_text("❌ Ключ не найден.")
await state.clear()
return
if tariff.get("configurable"):
raw_device_options = tariff.get("device_options")
raw_traffic_options = tariff.get("traffic_options_gb")
raw_device_options = raw_device_options if isinstance(raw_device_options, list) else []
raw_traffic_options = raw_traffic_options if isinstance(raw_traffic_options, list) else []
try:
device_options = sorted(raw_device_options, key=lambda v: (int(v) == 0, int(v)))
except (TypeError, ValueError):
device_options = raw_device_options
try:
traffic_options = sorted(raw_traffic_options, key=lambda v: (int(v) == 0, int(v)))
except (TypeError, ValueError):
traffic_options = raw_traffic_options
device_int_options: list[int] = []
for value in device_options:
try:
device_int_options.append(int(value))
except (TypeError, ValueError):
continue
traffic_int_options: list[int] = []
for value in traffic_options:
try:
traffic_int_options.append(int(value))
except (TypeError, ValueError):
continue
if not device_int_options and not traffic_int_options:
await callback_query.message.edit_text(
"❌ Конфигуратор для этого тарифа не настроен. Попробуйте выбрать другой тариф."
)
await state.clear()
return
cfg = normalize_tariff_config(tariff)
positive_device_values = [v for v in device_int_options if v > 0]
positive_traffic_values = [v for v in traffic_int_options if v > 0]
base_device_limit = cfg.get("base_device_limit")
if base_device_limit is None:
base_device_limit = tariff.get("device_limit")
if base_device_limit is None:
if positive_device_values:
base_device_limit = min(positive_device_values)
elif device_int_options:
base_device_limit = device_int_options[0]
base_device_limit = int(base_device_limit) if base_device_limit is not None else None
base_traffic_gb = cfg.get("base_traffic_gb")
if base_traffic_gb is None:
traffic_limit_raw = tariff.get("traffic_limit")
if traffic_limit_raw is not None:
try:
base_traffic_gb = int(traffic_limit_raw)
except (TypeError, ValueError):
base_traffic_gb = None
if base_traffic_gb is None:
if positive_traffic_values:
base_traffic_gb = min(positive_traffic_values)
elif traffic_int_options:
base_traffic_gb = traffic_int_options[0]
base_traffic_gb = int(base_traffic_gb) if base_traffic_gb is not None else None
selected_devices = (
base_device_limit if base_device_limit is not None else (device_int_options[0] if device_int_options else None)
)
selected_traffic_gb = (
base_traffic_gb if base_traffic_gb is not None else (traffic_int_options[0] if traffic_int_options else None)
)
await state.update_data(
renew_tariff_id=tariff_id,
renew_selected_device_limit=selected_devices,
renew_selected_traffic_gb=selected_traffic_gb,
renew_mode="renew",
)
builder = InlineKeyboardBuilder()
device_buttons: list[InlineKeyboardButton] = []
traffic_buttons: list[InlineKeyboardButton] = []
if device_int_options and len(device_int_options) > 1:
sel = int(selected_devices or 0)
for value in device_int_options:
mark = "" if value == sel else ""
caption = "Безлимит устройств" if value == 0 else f"{value} устройств"
device_buttons.append(
InlineKeyboardButton(
text=f"{caption}{mark}",
callback_data=f"cfg_renew_devices|{tariff_id}|{value}",
)
)
if traffic_int_options and len(traffic_int_options) > 1:
sel = int(selected_traffic_gb or 0)
for value in traffic_int_options:
mark = "" if value == sel else ""
caption = "Безлимит трафика" if value == 0 else f"{value} ГБ"
traffic_buttons.append(
InlineKeyboardButton(
text=f"{caption}{mark}",
callback_data=f"cfg_renew_traffic|{tariff_id}|{value}",
)
)
if device_buttons and traffic_buttons:
max_len = max(len(device_buttons), len(traffic_buttons))
for i in range(max_len):
row = []
if i < len(device_buttons):
row.append(device_buttons[i])
if i < len(traffic_buttons):
row.append(traffic_buttons[i])
builder.row(*row)
elif device_buttons:
for b in device_buttons:
builder.row(b)
elif traffic_buttons:
for b in traffic_buttons:
builder.row(b)
builder.row(InlineKeyboardButton(text="✅ Применить", callback_data=f"cfg_renew_apply|{tariff_id}"))
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="back:group"))
devices_label = (
"Безлимит устройств"
if (selected_devices is not None and int(selected_devices) <= 0)
else (f"{int(selected_devices)} устройств" if selected_devices is not None else "")
)
traffic_label = (
"Безлимит трафика"
if (selected_traffic_gb is not None and int(selected_traffic_gb) <= 0)
else (f"{int(selected_traffic_gb)} ГБ" if selected_traffic_gb is not None else "")
)
await callback_query.message.edit_text(
text=(
"🧩 <b>Выбор конфигурации тарифа</b>\n\n"
f"📦 <b>Тариф:</b> {tariff.get('name', '')}\n"
f"📱 <b>Устройства:</b> {devices_label}\n"
f"📊 <b>Трафик:</b> {traffic_label}\n\n"
"Выберите параметры и нажмите «✅ Применить»."
),
reply_markup=builder.as_markup(),
)
return
device_limit = int(tariff.get("device_limit") or 0)
raw_traffic_limit = tariff.get("traffic_limit")
traffic_gb = 0
if raw_traffic_limit is not None:
try:
traffic_gb = int(raw_traffic_limit)
except (TypeError, ValueError):
traffic_gb = 0
old_tariff_id = key_obj.tariff_id
old_subgroup = None
if old_tariff_id:
old_subgroup = (
await session.execute(select(Tariff.subgroup_title).where(Tariff.id == old_tariff_id))
).scalar_one_or_none()
new_subgroup = (
await session.execute(select(Tariff.subgroup_title).where(Tariff.id == tariff_id))
).scalar_one_or_none()
new_expiry_time = int(key_obj.expiry_time or 0) or int(datetime.utcnow().timestamp() * 1000)
await session.execute(
update(Key)
.where(Key.tg_id == tg_id, Key.email == email)
.values(
tariff_id=tariff_id,
selected_device_limit=None,
current_device_limit=None,
selected_traffic_limit=None,
current_traffic_limit=None,
selected_price_rub=None,
)
)
await session.commit()
try:
ok = await renew_key_in_cluster(
cluster_id=key_obj.server_id,
email=email,
client_id=key_obj.client_id,
new_expiry_time=new_expiry_time,
total_gb=traffic_gb,
session=session,
hwid_device_limit=device_limit,
reset_traffic=False,
target_subgroup=new_subgroup,
old_subgroup=old_subgroup,
plan=tariff_id,
)
except Exception as e:
logger.error(f"[AdminRenew] renew_key_in_cluster failed: tg_id={tg_id} email={email} tariff_id={tariff_id}: {e}")
ok = False
await state.clear()
if not ok:
await callback_query.message.answer("❌ Не удалось обновить подписку на серверах (renew).")
callback_data_back = AdminUserEditorCallback(action="users_key_edit", data=email, tg_id=tg_id)
await handle_key_edit(callback_query=callback_query, callback_data=callback_data_back, session=session, update=False)
@router.callback_query(F.data.startswith("cfg_renew_devices|"), IsAdminFilter())
async def handle_cfg_renew_devices(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession):
_, tariff_id_str, value_str = callback_query.data.split("|", 2)
tariff_id = int(tariff_id_str)
value = int(value_str)
data = await state.get_data()
if int(data.get("renew_tariff_id") or 0) != tariff_id:
await callback_query.answer("⚠️ Сессия устарела", show_alert=True)
return
await state.update_data(renew_selected_device_limit=value)
tariff = await get_tariff_by_id(session, tariff_id)
if not tariff:
await callback_query.message.edit_text("❌ Тариф не найден.")
await state.clear()
return
selected_devices = int((await state.get_data()).get("renew_selected_device_limit") or 0)
selected_traffic_gb = (await state.get_data()).get("renew_selected_traffic_gb")
raw_device_options = tariff.get("device_options")
raw_traffic_options = tariff.get("traffic_options_gb")
raw_device_options = raw_device_options if isinstance(raw_device_options, list) else []
raw_traffic_options = raw_traffic_options if isinstance(raw_traffic_options, list) else []
try:
device_options = sorted(raw_device_options, key=lambda v: (int(v) == 0, int(v)))
except (TypeError, ValueError):
device_options = raw_device_options
try:
traffic_options = sorted(raw_traffic_options, key=lambda v: (int(v) == 0, int(v)))
except (TypeError, ValueError):
traffic_options = raw_traffic_options
device_int_options: list[int] = []
for v in device_options:
try:
device_int_options.append(int(v))
except (TypeError, ValueError):
continue
traffic_int_options: list[int] = []
for v in traffic_options:
try:
traffic_int_options.append(int(v))
except (TypeError, ValueError):
continue
builder = InlineKeyboardBuilder()
device_buttons: list[InlineKeyboardButton] = []
traffic_buttons: list[InlineKeyboardButton] = []
if device_int_options and len(device_int_options) > 1:
for v in device_int_options:
mark = "" if v == selected_devices else ""
caption = "Безлимит устройств" if v == 0 else f"{v} устройств"
device_buttons.append(
InlineKeyboardButton(text=f"{caption}{mark}", callback_data=f"cfg_renew_devices|{tariff_id}|{v}")
)
if traffic_int_options and len(traffic_int_options) > 1:
sel_tr = int(selected_traffic_gb or 0)
for v in traffic_int_options:
mark = "" if v == sel_tr else ""
caption = "Безлимит трафика" if v == 0 else f"{v} ГБ"
traffic_buttons.append(
InlineKeyboardButton(text=f"{caption}{mark}", callback_data=f"cfg_renew_traffic|{tariff_id}|{v}")
)
if device_buttons and traffic_buttons:
max_len = max(len(device_buttons), len(traffic_buttons))
for i in range(max_len):
row = []
if i < len(device_buttons):
row.append(device_buttons[i])
if i < len(traffic_buttons):
row.append(traffic_buttons[i])
builder.row(*row)
elif device_buttons:
for b in device_buttons:
builder.row(b)
elif traffic_buttons:
for b in traffic_buttons:
builder.row(b)
builder.row(InlineKeyboardButton(text="✅ Применить", callback_data=f"cfg_renew_apply|{tariff_id}"))
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="back:group"))
devices_label = "Безлимит устройств" if selected_devices <= 0 else f"{selected_devices} устройств"
traffic_label = (
"Безлимит трафика"
if (selected_traffic_gb is not None and int(selected_traffic_gb) <= 0)
else (f"{int(selected_traffic_gb)} ГБ" if selected_traffic_gb is not None else "")
)
await callback_query.message.edit_text(
text=(
"🧩 <b>Выбор конфигурации тарифа</b>\n\n"
f"📦 <b>Тариф:</b> {tariff.get('name', '')}\n"
f"📱 <b>Устройства:</b> {devices_label}\n"
f"📊 <b>Трафик:</b> {traffic_label}\n\n"
"Выберите параметры и нажмите «✅ Применить»."
),
reply_markup=builder.as_markup(),
)
await callback_query.answer()
@router.callback_query(F.data.startswith("cfg_renew_traffic|"), IsAdminFilter())
async def handle_cfg_renew_traffic(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession):
_, tariff_id_str, value_str = callback_query.data.split("|", 2)
tariff_id = int(tariff_id_str)
value = int(value_str)
data = await state.get_data()
if int(data.get("renew_tariff_id") or 0) != tariff_id:
await callback_query.answer("⚠️ Сессия устарела", show_alert=True)
return
await state.update_data(renew_selected_traffic_gb=value)
tariff = await get_tariff_by_id(session, tariff_id)
if not tariff:
await callback_query.message.edit_text("❌ Тариф не найден.")
await state.clear()
return
selected_devices = (await state.get_data()).get("renew_selected_device_limit")
selected_traffic_gb = int((await state.get_data()).get("renew_selected_traffic_gb") or 0)
raw_device_options = tariff.get("device_options")
raw_traffic_options = tariff.get("traffic_options_gb")
raw_device_options = raw_device_options if isinstance(raw_device_options, list) else []
raw_traffic_options = raw_traffic_options if isinstance(raw_traffic_options, list) else []
try:
device_options = sorted(raw_device_options, key=lambda v: (int(v) == 0, int(v)))
except (TypeError, ValueError):
device_options = raw_device_options
try:
traffic_options = sorted(raw_traffic_options, key=lambda v: (int(v) == 0, int(v)))
except (TypeError, ValueError):
traffic_options = raw_traffic_options
device_int_options: list[int] = []
for v in device_options:
try:
device_int_options.append(int(v))
except (TypeError, ValueError):
continue
traffic_int_options: list[int] = []
for v in traffic_options:
try:
traffic_int_options.append(int(v))
except (TypeError, ValueError):
continue
builder = InlineKeyboardBuilder()
device_buttons: list[InlineKeyboardButton] = []
traffic_buttons: list[InlineKeyboardButton] = []
if device_int_options and len(device_int_options) > 1:
sel_dev = int(selected_devices or 0)
for v in device_int_options:
mark = "" if v == sel_dev else ""
caption = "Безлимит устройств" if v == 0 else f"{v} устройств"
device_buttons.append(
InlineKeyboardButton(text=f"{caption}{mark}", callback_data=f"cfg_renew_devices|{tariff_id}|{v}")
)
if traffic_int_options and len(traffic_int_options) > 1:
for v in traffic_int_options:
mark = "" if v == selected_traffic_gb else ""
caption = "Безлимит трафика" if v == 0 else f"{v} ГБ"
traffic_buttons.append(
InlineKeyboardButton(text=f"{caption}{mark}", callback_data=f"cfg_renew_traffic|{tariff_id}|{v}")
)
if device_buttons and traffic_buttons:
max_len = max(len(device_buttons), len(traffic_buttons))
for i in range(max_len):
row = []
if i < len(device_buttons):
row.append(device_buttons[i])
if i < len(traffic_buttons):
row.append(traffic_buttons[i])
builder.row(*row)
elif device_buttons:
for b in device_buttons:
builder.row(b)
elif traffic_buttons:
for b in traffic_buttons:
builder.row(b)
builder.row(InlineKeyboardButton(text="✅ Применить", callback_data=f"cfg_renew_apply|{tariff_id}"))
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="back:group"))
devices_label = (
"Безлимит устройств"
if (selected_devices is not None and int(selected_devices) <= 0)
else (f"{int(selected_devices)} устройств" if selected_devices is not None else "")
)
traffic_label = "Безлимит трафика" if selected_traffic_gb <= 0 else f"{selected_traffic_gb} ГБ"
await callback_query.message.edit_text(
text=(
"🧩 <b>Выбор конфигурации тарифа</b>\n\n"
f"📦 <b>Тариф:</b> {tariff.get('name', '')}\n"
f"📱 <b>Устройства:</b> {devices_label}\n"
f"📊 <b>Трафик:</b> {traffic_label}\n\n"
"Выберите параметры и нажмите «✅ Применить»."
),
reply_markup=builder.as_markup(),
)
await callback_query.answer()
@router.callback_query(F.data.startswith("cfg_renew_apply|"), IsAdminFilter())
async def handle_cfg_renew_apply(callback_query: CallbackQuery, session: AsyncSession, state: FSMContext):
_, tariff_id_str = callback_query.data.split("|", 1)
tariff_id = int(tariff_id_str)
data = await state.get_data()
email = data.get("email")
tg_id = data.get("tg_id")
if not email or not tg_id:
await callback_query.message.edit_text("❌ Не найдены данные сессии.")
await state.clear()
return
if int(data.get("renew_tariff_id") or 0) != tariff_id:
await callback_query.message.edit_text("❌ Сессия устарела. Выберите тариф заново.")
await state.clear()
return
selected_devices = data.get("renew_selected_device_limit")
selected_traffic_gb = data.get("renew_selected_traffic_gb")
if selected_devices is None and selected_traffic_gb is None:
await callback_query.message.edit_text("❌ Не выбраны параметры конфигурации.")
await state.clear()
return
result = await session.execute(select(Key).where(Key.email == email, Key.tg_id == tg_id))
key_obj: Key | None = result.scalar_one_or_none()
if not key_obj:
await callback_query.message.edit_text("❌ Ключ не найден.")
await state.clear()
return
old_tariff_id = key_obj.tariff_id
old_subgroup = None
if old_tariff_id:
old_subgroup = (
await session.execute(select(Tariff.subgroup_title).where(Tariff.id == old_tariff_id))
).scalar_one_or_none()
new_subgroup = (
await session.execute(select(Tariff.subgroup_title).where(Tariff.id == tariff_id))
).scalar_one_or_none()
new_expiry_time = int(key_obj.expiry_time or 0) or int(datetime.utcnow().timestamp() * 1000)
await session.execute(
update(Key)
.where(Key.tg_id == tg_id, Key.email == email)
.values(
tariff_id=tariff_id,
selected_device_limit=int(selected_devices) if selected_devices is not None else None,
current_device_limit=int(selected_devices) if selected_devices is not None else None,
selected_traffic_limit=int(selected_traffic_gb)
if (selected_traffic_gb is not None and int(selected_traffic_gb) > 0)
else None,
current_traffic_limit=int(selected_traffic_gb)
if (selected_traffic_gb is not None and int(selected_traffic_gb) > 0)
else None,
selected_price_rub=None,
)
)
await session.commit()
try:
ok = await renew_key_in_cluster(
cluster_id=key_obj.server_id,
email=email,
client_id=key_obj.client_id,
new_expiry_time=new_expiry_time,
total_gb=int(selected_traffic_gb or 0),
session=session,
hwid_device_limit=int(selected_devices or 0),
reset_traffic=False,
target_subgroup=new_subgroup,
old_subgroup=old_subgroup,
plan=tariff_id,
)
except Exception as e:
logger.error(f"[AdminRenewCfg] renew_key_in_cluster failed: tg_id={tg_id} email={email} tariff_id={tariff_id}: {e}")
ok = False
await state.clear()
if not ok:
await callback_query.message.answer("❌ Не удалось обновить подписку на серверах (renew).")
callback_data_back = AdminUserEditorCallback(action="users_key_edit", data=email, tg_id=int(tg_id))
await handle_key_edit(callback_query=callback_query, callback_data=callback_data_back, session=session, update=False)
@router.callback_query(F.data == "back:group", IsAdminFilter())
async def handle_back_to_group(
callback_query: CallbackQuery,
state: FSMContext,
session: AsyncSession,
):
result = await session.execute(select(Tariff.group_code).distinct())
groups = [row[0] for row in result.fetchall()]
builder = InlineKeyboardBuilder()
for group_code in groups:
builder.button(text=group_code, callback_data=f"group:{group_code}")
builder.button(text="🔙 Назад", callback_data="back:renew")
builder.adjust(1)
await callback_query.message.edit_text(
text="📁 <b>Выберите тарифную группу:</b>",
reply_markup=builder.as_markup(),
)
await state.set_state(RenewTariffState.selecting_group)
+13 -4
View File
@@ -464,17 +464,26 @@ async def process_callback_renew_plan(callback_query: CallbackQuery, state: FSMC
selected_devices_db = record.get("selected_device_limit")
selected_traffic_db = record.get("selected_traffic_limit")
traffic_default = next(
(x for x in traffic_options_sorted if x != 0),
(traffic_options_sorted[0] if traffic_options_sorted else None),
)
device_default = next(
(x for x in device_options_sorted if x != 0),
(device_options_sorted[0] if device_options_sorted else None),
)
if traffic_options_sorted:
selected_traffic_db = int(selected_traffic_db) if selected_traffic_db is not None else traffic_options_sorted[0]
selected_traffic_db = int(selected_traffic_db) if selected_traffic_db is not None else traffic_default
if selected_traffic_db not in traffic_options_sorted:
selected_traffic_db = traffic_options_sorted[0]
selected_traffic_db = traffic_default
else:
selected_traffic_db = None
if device_options_sorted:
selected_devices_db = int(selected_devices_db) if selected_devices_db is not None else device_options_sorted[0]
selected_devices_db = int(selected_devices_db) if selected_devices_db is not None else device_default
if selected_devices_db not in device_options_sorted:
selected_devices_db = device_options_sorted[0]
selected_devices_db = device_default
else:
selected_devices_db = None
+1 -1
View File
@@ -25,7 +25,7 @@ from panels._3xui import extend_client_key, get_xui_instance
from panels.remnawave import RemnawaveAPI
from .aggregated_links import make_aggregated_link
from .subgroup_migration import migrate_between_subgroups
from ...tariffs.subgroup_migration import migrate_between_subgroups
async def resolve_cluster(session: AsyncSession, cluster_id: str):
@@ -15,8 +15,8 @@ from logger import (
from panels._3xui import ClientConfig, add_client, extend_client_key, get_xui_instance
from panels.remnawave import RemnawaveAPI
from .deletion import delete_on_3xui, delete_on_remnawave
from .utils import bytes_from_gb, norm_name, split_by_panel
from ..keys.operations.deletion import delete_on_3xui, delete_on_remnawave
from ..keys.operations.utils import bytes_from_gb, norm_name, split_by_panel
async def ensure_on_remnawave(
+1 -1
View File
@@ -92,4 +92,4 @@ def get_git_commit_number() -> str:
def get_version() -> str:
return f"v.5.1-b221229 {get_git_commit_number()}"
return f"v.5.1-b261235 {get_git_commit_number()}"