This commit is contained in:
Vladless
2025-08-19 01:02:24 +03:00
8 changed files with 486 additions and 101 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ async def get_hot_leads(session: AsyncSession):
"""
Возвращает пользователей, у которых есть успешные оплаты, но нет активных ключей.
"""
subquery = select(Key.tg_id).where(Key.expiry_time > func.extract("epoch", func.now()) * 1000).distinct()
subquery = select(Key.tg_id).distinct()
stmt = (
select(Payment.tg_id)
+1
View File
@@ -87,6 +87,7 @@ class Tariff(DictLikeMixin, Base):
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow)
subgroup_title = Column(String, nullable=True)
sort_order = Column(Integer, nullable=True)
class Server(DictLikeMixin, Base):
+131 -6
View File
@@ -1,8 +1,9 @@
import hashlib
from collections import defaultdict
from datetime import datetime
from sqlalchemy import delete, insert, select, update
from sqlalchemy import delete, func, insert, select, update
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
@@ -34,16 +35,48 @@ async def find_subgroup_by_hash(session: AsyncSession, subgroup_hash: str, group
return None
async def get_tariffs(session: AsyncSession, tariff_id: int = None, group_code: str = None):
async def get_tariffs(session: AsyncSession, tariff_id: int = None, group_code: str = None, with_subgroup_weights: bool = False):
try:
if tariff_id:
result = await session.execute(select(Tariff).where(Tariff.id == tariff_id))
elif group_code:
result = await session.execute(select(Tariff).where(Tariff.group_code == group_code).order_by(Tariff.id))
result = await session.execute(
select(Tariff)
.where(Tariff.group_code == group_code)
.order_by(Tariff.sort_order, Tariff.id)
)
else:
result = await session.execute(select(Tariff))
result = await session.execute(select(Tariff).order_by(Tariff.sort_order, Tariff.id))
return [dict(r.__dict__) for r in result.scalars().all()]
tariffs = [dict(r.__dict__) for r in result.scalars().all()]
if with_subgroup_weights and group_code:
tariffs_without_order = [t for t in tariffs if t.get("sort_order") is None]
if tariffs_without_order:
max_order = max((t.get("sort_order") or 0 for t in tariffs), default=0)
for i, tariff in enumerate(tariffs_without_order, start=max_order + 1):
tariff["sort_order"] = i
await session.execute(
update(Tariff).where(Tariff.id == tariff["id"]).values(sort_order=i)
)
await session.commit()
grouped = defaultdict(list)
for t in tariffs:
grouped[t.get("subgroup_title")].append(t)
subgroup_weights = {}
for subgroup, tariffs_list in grouped.items():
if subgroup:
total_weight = sum(t.get("sort_order", 1) for t in tariffs_list)
subgroup_weights[subgroup] = total_weight
return {
'tariffs': tariffs,
'subgroup_weights': subgroup_weights
}
return tariffs
except SQLAlchemyError as e:
logger.error(f"[TARIFF] Ошибка при получении тарифов: {e}")
return []
@@ -77,7 +110,9 @@ async def get_tariffs_for_cluster(session: AsyncSession, cluster_name: str):
group_code = row[0]
result = await session.execute(
select(Tariff).where(Tariff.group_code == group_code, Tariff.is_active.is_(True)).order_by(Tariff.id)
select(Tariff)
.where(Tariff.group_code == group_code, Tariff.is_active.is_(True))
.order_by(Tariff.sort_order, Tariff.id)
)
return [dict(r.__dict__) for r in result.scalars().all()]
except SQLAlchemyError as e:
@@ -90,6 +125,20 @@ async def create_tariff(session: AsyncSession, data: dict):
data["created_at"] = datetime.utcnow()
data["updated_at"] = datetime.utcnow()
if "sort_order" not in data:
group_code = data.get("group_code")
if group_code:
result = await session.execute(
select(func.max(Tariff.sort_order))
.where(Tariff.group_code == group_code)
)
max_order = result.scalar() or 0
else:
result = await session.execute(select(func.max(Tariff.sort_order)))
max_order = result.scalar() or 0
data["sort_order"] = max_order + 1
stmt = insert(Tariff).values(**data).returning(Tariff)
result = await session.execute(stmt)
await session.commit()
@@ -136,3 +185,79 @@ async def check_tariff_exists(session: AsyncSession, tariff_id: int):
except SQLAlchemyError as e:
logger.error(f"[TARIFF] Ошибка при проверке тарифа {tariff_id}: {e}")
return False
async def get_tariff_sort_order(session: AsyncSession, tariff_id: int) -> int:
try:
result = await session.execute(
select(Tariff.sort_order).where(Tariff.id == tariff_id)
)
sort_order = result.scalar_one_or_none()
if sort_order is None:
await session.execute(
update(Tariff).where(Tariff.id == tariff_id).values(sort_order=1)
)
await session.commit()
return 1
return sort_order
except SQLAlchemyError as e:
logger.error(f"[TARIFF] Ошибка при получении sort_order для тарифа {tariff_id}: {e}")
return None
async def move_tariff_up(session: AsyncSession, tariff_id: int) -> bool:
try:
current_order = await get_tariff_sort_order(session, tariff_id)
new_order = max(1, current_order - 1)
await session.execute(
update(Tariff).where(Tariff.id == tariff_id).values(sort_order=new_order)
)
await session.commit()
return True
except SQLAlchemyError as e:
logger.error(f"[TARIFF] Ошибка при перемещении тарифа {tariff_id} вверх: {e}")
await session.rollback()
return False
async def move_tariff_down(session: AsyncSession, tariff_id: int) -> bool:
try:
current_order = await get_tariff_sort_order(session, tariff_id)
new_order = current_order + 1
await session.execute(
update(Tariff).where(Tariff.id == tariff_id).values(sort_order=new_order)
)
await session.commit()
return True
except SQLAlchemyError as e:
logger.error(f"[TARIFF] Ошибка при перемещении тарифа {tariff_id} вниз: {e}")
await session.rollback()
return False
async def initialize_tariff_sort_orders(session: AsyncSession, group_code: str) -> bool:
try:
result = await session.execute(
select(Tariff).where(Tariff.group_code == group_code).order_by(Tariff.id)
)
tariffs = result.scalars().all()
if not tariffs:
return True
for i, tariff in enumerate(tariffs):
new_sort_order = 1 + i
await session.execute(
update(Tariff).where(Tariff.id == tariff.id).values(sort_order=new_sort_order)
)
await session.commit()
return True
except SQLAlchemyError as e:
logger.error(f"[TARIFF] Ошибка при инициализации sort_order для группы {group_code}: {e}")
await session.rollback()
return False
+28 -26
View File
@@ -3,11 +3,12 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
from collections import defaultdict
from sqlalchemy import delete, func, select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Gift, GiftUsage, Tariff
from database.tariffs import create_subgroup_hash, find_subgroup_by_hash
from database.tariffs import create_subgroup_hash, find_subgroup_by_hash, get_tariffs
from handlers.utils import edit_or_send_message, format_days, format_months
from logger import logger
@@ -32,9 +33,9 @@ async def admin_gift_menu(callback: CallbackQuery):
@router.callback_query(F.data == "admin_gift_create")
async def admin_create_gift_step1(callback: CallbackQuery, session: AsyncSession):
stmt = select(Tariff).where(Tariff.group_code == "gifts", Tariff.is_active.is_(True)).order_by(Tariff.duration_days)
result = await session.execute(stmt)
tariffs = result.scalars().all()
tariffs_data = await get_tariffs(session, group_code="gifts", with_subgroup_weights=True)
tariffs = [t for t in tariffs_data['tariffs'] if t.get('is_active')]
subgroup_weights = tariffs_data['subgroup_weights']
if not tariffs:
builder = InlineKeyboardBuilder()
@@ -42,23 +43,31 @@ async def admin_create_gift_step1(callback: CallbackQuery, session: AsyncSession
await callback.message.edit_text("❌ Нет активных тарифов в группе 'gifts'.", reply_markup=builder.as_markup())
return
from collections import defaultdict
grouped_tariffs = defaultdict(list)
for t in tariffs:
grouped_tariffs[t.subgroup_title].append(t)
grouped_tariffs[t.get('subgroup_title')].append(t)
builder = InlineKeyboardBuilder()
for t in grouped_tariffs.get(None, []):
if t.duration_days % 30 == 0:
duration_text = format_months(t.duration_days // 30)
if t.get('duration_days') % 30 == 0:
duration_text = format_months(t.get('duration_days') // 30)
else:
duration_text = format_days(t.duration_days)
duration_text = format_days(t.get('duration_days'))
builder.button(text=f"{t.name} {duration_text}", callback_data=f"admin_gift_select|{t.id}")
builder.row(
types.InlineKeyboardButton(
text=f"{t.get('name')} {duration_text}",
callback_data=f"admin_gift_select|{t.get('id')}"
)
)
for subgroup in sorted(k for k in grouped_tariffs if k):
sorted_subgroups = sorted(
[k for k in grouped_tariffs if k],
key=lambda x: (subgroup_weights.get(x, 999999), x)
)
for subgroup in sorted_subgroups:
subgroup_hash = create_subgroup_hash(subgroup, "gifts")
builder.row(
types.InlineKeyboardButton(
@@ -82,30 +91,23 @@ async def admin_gift_show_tariffs_in_subgroup(callback: CallbackQuery, session:
await callback.message.edit_text("❌ Подгруппа не найдена.")
return
stmt = (
select(Tariff)
.where(Tariff.group_code == "gifts", Tariff.is_active.is_(True))
.order_by(Tariff.duration_days)
)
result = await session.execute(stmt)
tariffs = result.scalars().all()
filtered = [t for t in tariffs if t.subgroup_title == subgroup]
tariffs = await get_tariffs(session, group_code="gifts")
filtered = [t for t in tariffs if t.get('subgroup_title') == subgroup and t.get('is_active')]
if not filtered:
await callback.message.edit_text("❌ В этой подгруппе пока нет тарифов.")
return
builder = InlineKeyboardBuilder()
for t in filtered:
if t.duration_days % 30 == 0:
duration_text = format_months(t.duration_days // 30)
if t.get('duration_days') % 30 == 0:
duration_text = format_months(t.get('duration_days') // 30)
else:
duration_text = format_days(t.duration_days)
duration_text = format_days(t.get('duration_days'))
builder.row(
types.InlineKeyboardButton(
text=f"{t.name} {duration_text}",
callback_data=f"admin_gift_select|{t.id}",
text=f"{t.get('name')} {duration_text}",
callback_data=f"admin_gift_select|{t.get('id')}",
)
)
+99 -9
View File
@@ -27,6 +27,12 @@ def build_tariff_menu_kb() -> InlineKeyboardMarkup:
callback_data=AdminTariffCallback(action="list").pack(),
)
)
builder.row(
InlineKeyboardButton(
text="🔢 Расположение тарифов",
callback_data=AdminTariffCallback(action="arrange").pack(),
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=AdminPanelCallback(action="admin").pack()))
return builder.as_markup()
@@ -37,6 +43,76 @@ def build_cancel_kb() -> InlineKeyboardMarkup:
)
def build_tariff_arrangement_groups_kb(groups: list[str]) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
row = []
for i, group in enumerate(groups):
row.append(
InlineKeyboardButton(
text=group,
callback_data=AdminTariffCallback(action=f"arrange_group|{group}").pack(),
)
)
if len(row) == 2 or i == len(groups) - 1:
builder.row(*row)
row = []
builder.row(
InlineKeyboardButton(
text="⬅️ Назад",
callback_data=AdminTariffCallback(action="list").pack(),
)
)
return builder.as_markup()
def build_tariffs_arrangement_kb(group_code: str, tariffs: list) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
grouped_tariffs = defaultdict(list)
for t in tariffs:
grouped_tariffs[t.get("subgroup_title")].append(t)
for subgroup in grouped_tariffs:
grouped_tariffs[subgroup].sort(key=lambda x: x.get("sort_order"))
if grouped_tariffs.get(None):
for t in grouped_tariffs[None]:
builder.row(
InlineKeyboardButton(text="⬆️", callback_data=AdminTariffCallback(action=f"quick_move_up|{t.get('id')}|{group_code}").pack()),
InlineKeyboardButton(text=f" {t.get('name')} ", callback_data=AdminTariffCallback(action=f"view|{t.get('id')}").pack()),
InlineKeyboardButton(text="⬇️", callback_data=AdminTariffCallback(action=f"quick_move_down|{t.get('id')}|{group_code}").pack())
)
for subgroup, tariffs_list in grouped_tariffs.items():
if subgroup:
builder.row(
InlineKeyboardButton(text=f"📁 {subgroup}", callback_data=AdminTariffCallback(action="arrange").pack())
)
for t in tariffs_list:
builder.row(
InlineKeyboardButton(text="⬆️", callback_data=AdminTariffCallback(action=f"quick_move_up|{t.get('id')}|{group_code}").pack()),
InlineKeyboardButton(text=f" {t.get('name')} ", callback_data=AdminTariffCallback(action=f"view|{t.get('id')}").pack()),
InlineKeyboardButton(text="⬇️", callback_data=AdminTariffCallback(action=f"quick_move_down|{t.get('id')}|{group_code}").pack())
)
builder.row(
InlineKeyboardButton(
text="⬅️ Назад",
callback_data=AdminPanelCallback(action="tariffs").pack(),
)
)
builder.row(
InlineKeyboardButton(
text="🏠 Главное меню",
callback_data=AdminPanelCallback(action="admin").pack(),
)
)
return builder.as_markup()
def build_tariff_groups_kb(groups: list[str]) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
row = []
@@ -73,14 +149,18 @@ def build_tariff_list_kb(tariffs: list[dict]) -> InlineKeyboardMarkup:
subgroup = t.get("subgroup_title")
grouped[subgroup].append(t)
for subgroup_title, _items in grouped.items():
if subgroup_title:
subgroup_hash = create_subgroup_hash(subgroup_title, group_code)
builder.row(
InlineKeyboardButton(
text=f"{subgroup_title}", callback_data=f"view_subgroup|{subgroup_hash}|{group_code}"
)
sorted_subgroups = sorted(
[k for k in grouped if k],
key=lambda x: (sum(t.get("sort_order", 1) for t in grouped[x]), x)
)
for subgroup_title in sorted_subgroups:
subgroup_hash = create_subgroup_hash(subgroup_title, group_code)
builder.row(
InlineKeyboardButton(
text=f"{subgroup_title}", callback_data=f"view_subgroup|{subgroup_hash}|{group_code}"
)
)
for t in grouped.get(None, []):
title = f"{t['name']}{t['price_rub']}"
@@ -110,7 +190,7 @@ def build_tariff_list_kb(tariffs: list[dict]) -> InlineKeyboardMarkup:
return builder.as_markup()
def build_single_tariff_kb(tariff_id: int) -> InlineKeyboardMarkup:
def build_single_tariff_kb(tariff_id: int, group_code: str = None) -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(
inline_keyboard=[
[
@@ -123,10 +203,20 @@ def build_single_tariff_kb(tariff_id: int) -> InlineKeyboardMarkup:
callback_data=AdminTariffCallback(action=f"delete|{tariff_id}").pack(),
),
],
[
InlineKeyboardButton(
text="⬆️ Выше",
callback_data=AdminTariffCallback(action=f"move_up|{tariff_id}").pack(),
),
InlineKeyboardButton(
text="⬇️ Ниже",
callback_data=AdminTariffCallback(action=f"move_down|{tariff_id}").pack(),
),
],
[
InlineKeyboardButton(
text="⬅️ Назад",
callback_data=AdminTariffCallback(action="list").pack(),
callback_data=AdminTariffCallback(action=f"group|{group_code}").pack() if group_code else AdminTariffCallback(action="list").pack(),
)
],
]
+195 -53
View File
@@ -1,6 +1,8 @@
import re
from collections import defaultdict
from datetime import datetime
import pytz
from aiogram import F, Router
from aiogram.fsm.context import FSMContext
@@ -17,7 +19,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
from database import create_tariff
from database.models import Gift, Key, Server, Tariff
from database.tariffs import create_subgroup_hash, find_subgroup_by_hash
from database.tariffs import (
create_subgroup_hash,
find_subgroup_by_hash,
get_tariffs,
move_tariff_up as db_move_tariff_up,
move_tariff_down as db_move_tariff_down
)
from filters.admin import IsAdminFilter
from ..panel.keyboard import AdminPanelCallback
@@ -29,6 +37,8 @@ from .keyboard import (
build_tariff_groups_kb,
build_tariff_list_kb,
build_tariff_menu_kb,
build_tariff_arrangement_groups_kb,
build_tariffs_arrangement_kb,
)
@@ -291,27 +301,54 @@ async def show_tariff_groups(callback: CallbackQuery, session: AsyncSession):
await callback.message.edit_text(text, reply_markup=build_tariff_groups_kb(groups))
def tariff_to_dict(tariff: Tariff) -> dict:
@router.callback_query(AdminTariffCallback.filter(F.action == "arrange"), IsAdminFilter())
async def show_tariff_arrangement_menu(callback: CallbackQuery, session: AsyncSession):
result = await session.execute(
select(distinct(Tariff.group_code)).where(Tariff.group_code.isnot(None)).order_by(Tariff.group_code)
)
groups = [row[0] for row in result.fetchall()]
if not groups:
await callback.message.edit_text("❌ Нет доступных групп тарифов.")
return
await callback.message.edit_text(
"🔢 <b>Управление расположением тарифов</b>\n\n"
"📋 <b>Как это работает:</b>\n"
"• Тарифы отображаются в порядке их расположения\n"
"• Меньший номер = выше в списке\n"
"• Новые тарифы добавляются в конец списка\n"
"• ⬆️ поднимает тариф выше (номер уменьшается)\n"
"• ⬇️ опускает тариф ниже (номер увеличивается)\n"
"• Подгруппы сортируются по общей сумме тарифов внутри\n\n"
"Выберите группу для управления расположением:",
reply_markup=build_tariff_arrangement_groups_kb(groups),
)
def tariff_to_dict(tariff) -> dict:
if isinstance(tariff, dict):
return tariff
return {
"id": tariff.id,
"name": tariff.name,
"price_rub": tariff.price_rub,
"group_code": tariff.group_code,
"subgroup_title": tariff.subgroup_title,
"sort_order": tariff.sort_order,
}
@router.callback_query(AdminTariffCallback.filter(F.action.startswith("group|")), IsAdminFilter())
async def show_tariffs_in_group(callback: CallbackQuery, callback_data: AdminTariffCallback, session: AsyncSession):
group_code = callback_data.action.split("|", 1)[1]
group_code = callback_data.action.split("|")[1]
result = await session.execute(select(Tariff).where(Tariff.group_code == group_code).order_by(Tariff.id))
tariffs = result.scalars().all()
tariffs = await get_tariffs(session, group_code=group_code)
if not tariffs:
await callback.message.edit_text("❌ В этой группе пока нет тарифов.")
return
tariff_dicts = [tariff_to_dict(t) for t in tariffs]
await callback.message.edit_text(
@@ -320,9 +357,61 @@ async def show_tariffs_in_group(callback: CallbackQuery, callback_data: AdminTar
)
@router.callback_query(AdminTariffCallback.filter(F.action.startswith("arrange_group|")), IsAdminFilter())
async def show_tariffs_arrangement(callback: CallbackQuery, callback_data: AdminTariffCallback, session: AsyncSession):
group_code = callback_data.action.split("|")[1]
tariffs_data = await get_tariffs(session, group_code=group_code, with_subgroup_weights=True)
tariffs = [t for t in tariffs_data['tariffs'] if t.get('is_active')]
subgroup_weights = tariffs_data['subgroup_weights']
if not tariffs:
await callback.message.edit_text("❌ В этой группе пока нет активных тарифов.")
return
grouped_tariffs = defaultdict(list)
for t in tariffs:
grouped_tariffs[t.get("subgroup_title")].append(t)
sorted_subgroups = sorted(
[k for k in grouped_tariffs if k],
key=lambda x: (subgroup_weights.get(x, 999999), x)
)
moscow_tz = pytz.timezone("Europe/Moscow")
now = datetime.now(moscow_tz)
current_time = now.strftime("%d.%m.%y %H:%M:%S МСК")
text = f"🔢 <b>Итоговая сортировка тарифов в группе: {group_code}</b>\n\n"
if grouped_tariffs.get(None):
text += "<b>📋 Основные тарифы:</b>\n"
for t in grouped_tariffs[None]:
sort_order = t.get('sort_order', 1)
text += f"{t.get('name')} <code>[позиция: {sort_order}]</code>\n"
text += "\n"
if sorted_subgroups:
text += "<b>📁 Подгруппы:</b>\n"
for subgroup in sorted_subgroups:
subgroup_weight = subgroup_weights.get(subgroup, 999999)
text += f"• <b>{subgroup}</b> <code>[вес группы: {subgroup_weight}]</code>\n"
for t in grouped_tariffs[subgroup]:
sort_order = t.get('sort_order', 1)
text += f"{t.get('name')} <code>[позиция: {sort_order}]</code>\n"
text += "\n"
text += f"\n{current_time}"
await callback.message.edit_text(
text,
reply_markup=build_tariffs_arrangement_kb(group_code, tariffs),
)
@router.callback_query(AdminTariffCallback.filter(F.action.startswith("view|")), IsAdminFilter())
async def view_tariff(callback: CallbackQuery, callback_data: AdminTariffCallback, session: AsyncSession):
tariff_id = int(callback_data.action.split("|", 1)[1])
tariff_id = int(callback_data.action.split("|")[1])
result = await session.execute(select(Tariff).where(Tariff.id == tariff_id))
tariff = result.scalar_one_or_none()
@@ -337,7 +426,7 @@ async def view_tariff(callback: CallbackQuery, callback_data: AdminTariffCallbac
@router.callback_query(AdminTariffCallback.filter(F.action.startswith("delete|")), IsAdminFilter())
async def confirm_tariff_deletion(callback: CallbackQuery, callback_data: AdminTariffCallback, session: AsyncSession):
tariff_id = int(callback_data.action.split("|", 1)[1])
tariff_id = int(callback_data.action.split("|")[1])
result = await session.execute(select(Tariff).where(Tariff.id == tariff_id))
tariff = result.scalar_one_or_none()
@@ -567,6 +656,7 @@ async def start_tariff_creation_existing_group(
def render_tariff_card(tariff: Tariff) -> tuple[str, InlineKeyboardMarkup]:
traffic_text = f"{tariff.traffic_limit} ГБ" if tariff.traffic_limit else "Безлимит"
device_text = f"{tariff.device_limit}" if tariff.device_limit is not None else "Безлимит"
sort_order = getattr(tariff, 'sort_order', 1)
text = (
f"<b>📄 Тариф: {tariff.name}</b>\n\n"
@@ -575,22 +665,19 @@ def render_tariff_card(tariff: Tariff) -> tuple[str, InlineKeyboardMarkup]:
f"💰 Стоимость: <b>{tariff.price_rub}₽</b>\n"
f"📦 Трафик: <b>{traffic_text}</b>\n"
f"📱 Устройств: <b>{device_text}</b>\n"
f"🔢 Позиция: <b>{sort_order}</b>\n"
f"{'✅ Активен' if tariff.is_active else '⛔ Отключен'}"
)
return text, build_single_tariff_kb(tariff.id)
return text, build_single_tariff_kb(tariff.id, tariff.group_code)
@router.callback_query(F.data.startswith("start_subgrouping|"), IsAdminFilter())
async def start_subgrouping(callback: CallbackQuery, state: FSMContext, session: AsyncSession):
group_code = callback.data.split("|", 1)[1]
result = await session.execute(
select(Tariff)
.where(Tariff.group_code == group_code, (Tariff.subgroup_title.is_(None) | (Tariff.subgroup_title == "")))
.order_by(Tariff.id)
)
tariffs = result.scalars().all()
tariffs = await get_tariffs(session, group_code=group_code)
tariffs = [t for t in tariffs if not t.get("subgroup_title") or t.get("subgroup_title") == ""]
if not tariffs:
await callback.message.edit_text(
@@ -612,7 +699,7 @@ async def start_subgrouping(callback: CallbackQuery, state: FSMContext, session:
builder = InlineKeyboardBuilder()
for tariff in tariffs:
builder.row(InlineKeyboardButton(text=f"{tariff.name}", callback_data=f"sub_select|{tariff.id}"))
builder.row(InlineKeyboardButton(text=f"{tariff.get('name')}", callback_data=f"sub_select|{tariff.get('id')}"))
builder.row(
InlineKeyboardButton(text="➡️ Продолжить", callback_data="subgroup_continue"),
@@ -638,18 +725,14 @@ async def toggle_tariff_subgroup_selection(callback: CallbackQuery, state: FSMCo
await state.update_data(selected_tariff_ids=list(selected))
group_code = data["group_code"]
result = await session.execute(
select(Tariff)
.where(Tariff.group_code == group_code, (Tariff.subgroup_title.is_(None) | (Tariff.subgroup_title == "")))
.order_by(Tariff.id)
)
tariffs = result.scalars().all()
tariffs = await get_tariffs(session, group_code=group_code)
tariffs = [t for t in tariffs if not t.get("subgroup_title") or t.get("subgroup_title") == ""]
builder = InlineKeyboardBuilder()
for tariff in tariffs:
is_selected = tariff.id in selected
is_selected = tariff.get("id") in selected
prefix = "" if is_selected else ""
builder.row(InlineKeyboardButton(text=f"{prefix}{tariff.name}", callback_data=f"sub_select|{tariff.id}"))
builder.row(InlineKeyboardButton(text=f"{prefix}{tariff.get('name')}", callback_data=f"sub_select|{tariff.get('id')}"))
builder.row(
InlineKeyboardButton(text="➡️ Продолжить", callback_data="subgroup_continue"),
@@ -732,12 +815,8 @@ async def view_subgroup_tariffs(callback: CallbackQuery, session: AsyncSession):
await callback.message.edit_text("❌ Подгруппа не найдена.")
return
result = await session.execute(
select(Tariff)
.where(Tariff.group_code == group_code, Tariff.subgroup_title == subgroup_title)
.order_by(Tariff.id)
)
tariffs = result.scalars().all()
tariffs = await get_tariffs(session, group_code=group_code)
tariffs = [t for t in tariffs if t.get("subgroup_title") == subgroup_title]
if not tariffs:
await callback.message.edit_text("❌ В этой подгруппе пока нет тарифов.")
@@ -938,17 +1017,10 @@ async def start_edit_subgroup_tariffs(callback: CallbackQuery, state: FSMContext
await callback.message.edit_text("❌ Подгруппа не найдена.")
return
result = await session.execute(
select(Tariff)
.where(
Tariff.group_code == group_code,
or_(Tariff.subgroup_title == subgroup_title, Tariff.subgroup_title.is_(None), Tariff.subgroup_title == ""),
)
.order_by(Tariff.id)
)
all_tariffs_to_show = result.scalars().all()
all_tariffs_to_show = await get_tariffs(session, group_code=group_code)
all_tariffs_to_show = [t for t in all_tariffs_to_show if t.get("subgroup_title") == subgroup_title or not t.get("subgroup_title") or t.get("subgroup_title") == ""]
subgroup_tariff_ids = {t.id for t in all_tariffs_to_show if t.subgroup_title == subgroup_title}
subgroup_tariff_ids = {t.get("id") for t in all_tariffs_to_show if t.get("subgroup_title") == subgroup_title}
if not all_tariffs_to_show:
await callback.message.edit_text(
@@ -971,9 +1043,9 @@ async def start_edit_subgroup_tariffs(callback: CallbackQuery, state: FSMContext
builder = InlineKeyboardBuilder()
for tariff in all_tariffs_to_show:
is_in_subgroup = tariff.id in subgroup_tariff_ids
is_in_subgroup = tariff.get("id") in subgroup_tariff_ids
prefix = "" if is_in_subgroup else ""
builder.row(InlineKeyboardButton(text=f"{prefix}{tariff.name}", callback_data=f"edit_sub_toggle|{tariff.id}"))
builder.row(InlineKeyboardButton(text=f"{prefix}{tariff.get('name')}", callback_data=f"edit_sub_toggle|{tariff.get('id')}"))
builder.row(
InlineKeyboardButton(text="💾 Сохранить", callback_data="edit_sub_save"),
@@ -1005,21 +1077,14 @@ async def toggle_tariff_in_subgroup_edit(callback: CallbackQuery, state: FSMCont
group_code = data["group_code"]
subgroup_hash = data["subgroup_hash"]
result = await session.execute(
select(Tariff)
.where(
Tariff.group_code == group_code,
or_(Tariff.subgroup_title == subgroup_title, Tariff.subgroup_title.is_(None), Tariff.subgroup_title == ""),
)
.order_by(Tariff.id)
)
all_tariffs_to_show = result.scalars().all()
all_tariffs_to_show = await get_tariffs(session, group_code=group_code)
all_tariffs_to_show = [t for t in all_tariffs_to_show if t.get("subgroup_title") == subgroup_title or not t.get("subgroup_title") or t.get("subgroup_title") == ""]
builder = InlineKeyboardBuilder()
for tariff in all_tariffs_to_show:
is_selected = tariff.id in selected_ids
is_selected = tariff.get("id") in selected_ids
prefix = "" if is_selected else ""
builder.row(InlineKeyboardButton(text=f"{prefix}{tariff.name}", callback_data=f"edit_sub_toggle|{tariff.id}"))
builder.row(InlineKeyboardButton(text=f"{prefix}{tariff.get('name')}", callback_data=f"edit_sub_toggle|{tariff.get('id')}"))
builder.row(
InlineKeyboardButton(text="💾 Сохранить", callback_data="edit_sub_save"),
@@ -1111,3 +1176,80 @@ async def save_subgroup_tariffs_changes(callback: CallbackQuery, state: FSMConte
]
),
)
@router.callback_query(AdminTariffCallback.filter(F.action.startswith("move_up|")), IsAdminFilter())
async def move_tariff_up(callback: CallbackQuery, callback_data: AdminTariffCallback, session: AsyncSession):
tariff_id = int(callback_data.action.split("|")[1])
success = await db_move_tariff_up(session, tariff_id)
if not success:
await callback.answer("❌ Ошибка при перемещении тарифа", show_alert=True)
return
result = await session.execute(select(Tariff).where(Tariff.id == tariff_id))
tariff = result.scalar_one_or_none()
if not tariff:
await callback.answer("❌ Тариф не найден", show_alert=True)
return
text, markup = render_tariff_card(tariff)
await callback.message.edit_text(text=text, reply_markup=markup)
await callback.answer("✅ Тариф перемещен выше (-1)")
@router.callback_query(AdminTariffCallback.filter(F.action.startswith("move_down|")), IsAdminFilter())
async def move_tariff_down(callback: CallbackQuery, callback_data: AdminTariffCallback, session: AsyncSession):
tariff_id = int(callback_data.action.split("|")[1])
success = await db_move_tariff_down(session, tariff_id)
if not success:
await callback.answer("❌ Ошибка при перемещении тарифа", show_alert=True)
return
result = await session.execute(select(Tariff).where(Tariff.id == tariff_id))
tariff = result.scalar_one_or_none()
if not tariff:
await callback.answer("❌ Тариф не найден", show_alert=True)
return
text, markup = render_tariff_card(tariff)
await callback.message.edit_text(text=text, reply_markup=markup)
await callback.answer("✅ Тариф перемещен ниже (+1)")
@router.callback_query(AdminTariffCallback.filter(F.action.startswith("quick_move_up|")), IsAdminFilter())
async def quick_move_tariff_up(callback: CallbackQuery, callback_data: AdminTariffCallback, session: AsyncSession):
parts = callback_data.action.split("|")
tariff_id = int(parts[1])
group_code = parts[2]
success = await db_move_tariff_up(session, tariff_id)
if not success:
await callback.answer("❌ Ошибка при перемещении тарифа", show_alert=True)
return
await callback.answer("✅ Тариф перемещен выше (-1)")
new_callback_data = AdminTariffCallback(action=f"arrange_group|{group_code}")
await show_tariffs_arrangement(callback, new_callback_data, session)
@router.callback_query(AdminTariffCallback.filter(F.action.startswith("quick_move_down|")), IsAdminFilter())
async def quick_move_tariff_down(callback: CallbackQuery, callback_data: AdminTariffCallback, session: AsyncSession):
parts = callback_data.action.split("|")
tariff_id = int(parts[1])
group_code = parts[2]
success = await db_move_tariff_down(session, tariff_id)
if not success:
await callback.answer("❌ Ошибка при перемещении тарифа", show_alert=True)
return
await callback.answer("✅ Тариф перемещен ниже (+1)")
new_callback_data = AdminTariffCallback(action=f"arrange_group|{group_code}")
await show_tariffs_arrangement(callback, new_callback_data, session)
+20 -2
View File
@@ -142,6 +142,13 @@ async def handle_key_creation(
tariffs = await get_tariffs_for_cluster(session, cluster_name)
if tariffs:
group_code = tariffs[0].get("group_code")
if group_code:
tariffs_data = await get_tariffs(session, group_code=group_code, with_subgroup_weights=True)
tariffs = [t for t in tariffs_data['tariffs'] if t.get('is_active')]
subgroup_weights = tariffs_data['subgroup_weights']
if not tariffs:
result = await session.execute(select(Admin).where(Admin.tg_id == tg_id))
is_admin = result.scalar_one_or_none() is not None
@@ -202,7 +209,12 @@ async def handle_key_creation(
)
)
for subgroup in sorted(k for k in grouped_tariffs if k):
sorted_subgroups = sorted(
[k for k in grouped_tariffs if k],
key=lambda x: (subgroup_weights.get(x, 999999), x)
)
for subgroup in sorted_subgroups:
subgroup_hash = create_subgroup_hash(subgroup, group_code)
builder.row(
InlineKeyboardButton(
@@ -245,7 +257,13 @@ async def show_tariffs_in_subgroup_user(callback: CallbackQuery, state: FSMConte
return
tariffs = await get_tariffs_for_cluster(session, cluster_name)
filtered = [t for t in tariffs if t.get("subgroup_title") == subgroup]
filtered = []
if tariffs:
group_code = tariffs[0].get("group_code")
if group_code:
tariffs = await get_tariffs(session, group_code=group_code)
filtered = [t for t in tariffs if t.get("subgroup_title") == subgroup and t.get('is_active')]
if not filtered:
await edit_or_send_message(
+11 -4
View File
@@ -26,7 +26,7 @@ from database import (
update_key_expiry,
)
from database.models import Key, Server
from database.tariffs import create_subgroup_hash, find_subgroup_by_hash
from database.tariffs import create_subgroup_hash, find_subgroup_by_hash, get_tariffs
from handlers.buttons import BACK, MAIN_MENU, MY_SUB, PAYMENT
from handlers.keys.operations import renew_key_in_cluster
from handlers.payments.robokassa_pay import handle_custom_amount_input
@@ -97,8 +97,10 @@ async def process_callback_renew_key(callback_query: CallbackQuery, state: FSMCo
if current_tariff["group_code"] not in ["discounts", "discounts_max", "gifts", "trial"]:
group_code = current_tariff["group_code"]
tariffs = await get_tariffs(session, group_code=group_code)
tariffs = [t for t in tariffs if t["is_active"]]
tariffs_data = await get_tariffs(session, group_code=group_code, with_subgroup_weights=True)
tariffs = [t for t in tariffs_data['tariffs'] if t.get('is_active')]
subgroup_weights = tariffs_data['subgroup_weights']
if not tariffs:
await callback_query.message.answer("❌ Нет доступных тарифов для продления.")
return
@@ -118,7 +120,12 @@ async def process_callback_renew_key(callback_query: CallbackQuery, state: FSMCo
)
)
for subgroup in sorted(k for k in grouped_tariffs if k):
sorted_subgroups = sorted(
[k for k in grouped_tariffs if k],
key=lambda x: (subgroup_weights.get(x, 999999), x)
)
for subgroup in sorted_subgroups:
subgroup_hash = create_subgroup_hash(subgroup, group_code)
builder.row(
InlineKeyboardButton(