Merge pull request #257 from JustYay/dev

[FEAUTURE] Изменения прорядка касс через админ-панель
This commit is contained in:
Vladislav Lisitsyn
2026-02-07 01:52:09 +03:00
committed by GitHub
6 changed files with 228 additions and 11 deletions
+2
View File
@@ -6,6 +6,7 @@ from .settings.modes_config import MODES_CONFIG, load_modes_config, update_modes
from .settings.money_config import MONEY_CONFIG, load_money_config, update_money_config from .settings.money_config import MONEY_CONFIG, load_money_config, update_money_config
from .settings.notifications_config import NOTIFICATIONS_CONFIG, load_notifications_config, update_notifications_config from .settings.notifications_config import NOTIFICATIONS_CONFIG, load_notifications_config, update_notifications_config
from .settings.payments_config import PAYMENTS_CONFIG, load_payments_config, update_payments_config from .settings.payments_config import PAYMENTS_CONFIG, load_payments_config, update_payments_config
from .settings.providers_order_config import PROVIDERS_ORDER, load_providers_order, update_providers_order
from .settings.tariffs_config import TARIFFS_CONFIG, load_tariffs_config, update_tariffs_config from .settings.tariffs_config import TARIFFS_CONFIG, load_tariffs_config, update_tariffs_config
@@ -15,6 +16,7 @@ async def bootstrap() -> None:
await load_notifications_config(session) await load_notifications_config(session)
await load_modes_config(session) await load_modes_config(session)
await load_payments_config(session) await load_payments_config(session)
await load_providers_order(session)
await load_money_config(session) await load_money_config(session)
await load_management_config(session) await load_management_config(session)
await load_tariffs_config(session) await load_tariffs_config(session)
+38
View File
@@ -0,0 +1,38 @@
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Setting
PROVIDERS_ORDER: dict[str, int] = {}
async def load_providers_order(session: AsyncSession) -> None:
stmt = select(Setting).where(Setting.key == "PROVIDERS_ORDER")
result = await session.execute(stmt)
setting = result.scalar_one_or_none()
PROVIDERS_ORDER.clear()
if setting and isinstance(setting.value, dict):
PROVIDERS_ORDER.update({k: int(v) for k, v in setting.value.items()})
await session.flush()
async def update_providers_order(session: AsyncSession, new_order: dict[str, int]) -> None:
stmt = select(Setting).where(Setting.key == "PROVIDERS_ORDER")
result = await session.execute(stmt)
setting = result.scalar_one_or_none()
if setting is None:
setting = Setting(
key="PROVIDERS_ORDER",
value=new_order,
description="Порядок отображения платёжных провайдеров",
)
session.add(setting)
else:
setting.value = new_order
await session.commit()
PROVIDERS_ORDER.clear()
PROVIDERS_ORDER.update(new_order)
+49
View File
@@ -97,15 +97,64 @@ def build_settings_buttons_kb(buttons_state: dict[str, bool]) -> InlineKeyboardM
def build_settings_cashboxes_kb(providers_state: dict[str, bool]) -> InlineKeyboardMarkup: def build_settings_cashboxes_kb(providers_state: dict[str, bool]) -> InlineKeyboardMarkup:
order_button = InlineKeyboardButton(
text="📋 Порядок касс",
callback_data=AdminPanelCallback(action="settings_providers_order").pack(),
)
return build_toggle_section_keyboard( return build_toggle_section_keyboard(
titles=PAYMENT_PROVIDER_TITLES, titles=PAYMENT_PROVIDER_TITLES,
state=providers_state, state=providers_state,
action="settings_cashbox_toggle", action="settings_cashbox_toggle",
columns=2, columns=2,
back_action="settings", back_action="settings",
extra_rows=[[order_button]],
) )
def build_providers_order_kb(sorted_names: list[str]) -> InlineKeyboardMarkup:
"""Клавиатура для управления порядком отображения касс."""
builder = InlineKeyboardBuilder()
for idx, name in enumerate(sorted_names):
title = PAYMENT_PROVIDER_TITLES.get(name, name)
pos = idx + 1
builder.row(
InlineKeyboardButton(
text="⬆️",
callback_data=AdminPanelCallback(
action="settings_order_up", page=pos,
).pack(),
),
InlineKeyboardButton(
text=f"{pos}. {title}",
callback_data=AdminPanelCallback(
action="settings_providers_order",
).pack(),
),
InlineKeyboardButton(
text="⬇️",
callback_data=AdminPanelCallback(
action="settings_order_down", page=pos,
).pack(),
),
)
builder.row(
InlineKeyboardButton(
text="🔄 Сбросить порядок",
callback_data=AdminPanelCallback(action="settings_order_reset").pack(),
)
)
builder.row(
InlineKeyboardButton(
text="Назад",
callback_data=AdminPanelCallback(action="settings_cashboxes").pack(),
)
)
return builder.as_markup()
def build_settings_notifications_kb(notifications_state: dict[str, object]) -> InlineKeyboardMarkup: def build_settings_notifications_kb(notifications_state: dict[str, object]) -> InlineKeyboardMarkup:
intervals_button = InlineKeyboardButton( intervals_button = InlineKeyboardButton(
text="Интервалы", text="Интервалы",
+94 -1
View File
@@ -3,10 +3,12 @@ from aiogram.types import CallbackQuery
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from core.bootstrap import PAYMENTS_CONFIG, update_payments_config from core.bootstrap import PAYMENTS_CONFIG, update_payments_config
from core.settings.providers_order_config import PROVIDERS_ORDER, update_providers_order
from filters.admin import IsAdminFilter from filters.admin import IsAdminFilter
from handlers.payments.providers import PROVIDERS_BASE, _get_effective_order
from ..panel.keyboard import AdminPanelCallback from ..panel.keyboard import AdminPanelCallback
from .keyboard import PAYMENT_PROVIDER_TITLES, build_settings_cashboxes_kb from .keyboard import PAYMENT_PROVIDER_TITLES, build_providers_order_kb, build_settings_cashboxes_kb
router = Router(name="admin_settings_cashboxes") router = Router(name="admin_settings_cashboxes")
@@ -18,6 +20,15 @@ async def load_payment_providers_settings() -> dict[str, bool]:
return {k: bool(config.get(k, False)) for k in PAYMENT_PROVIDER_TITLES.keys()} return {k: bool(config.get(k, False)) for k in PAYMENT_PROVIDER_TITLES.keys()}
def _get_sorted_provider_names() -> list[str]:
"""Возвращает все провайдеры, отсортированные по текущему порядку."""
all_names = list(PROVIDERS_BASE.keys())
return sorted(
all_names,
key=lambda n: _get_effective_order(n, PROVIDERS_BASE.get(n, {})),
)
@router.callback_query(AdminPanelCallback.filter(F.action == "settings_cashboxes")) @router.callback_query(AdminPanelCallback.filter(F.action == "settings_cashboxes"))
async def open_settings_cashboxes_menu(callback: CallbackQuery, session: AsyncSession) -> None: async def open_settings_cashboxes_menu(callback: CallbackQuery, session: AsyncSession) -> None:
providers_state = await load_payment_providers_settings() providers_state = await load_payment_providers_settings()
@@ -56,3 +67,85 @@ async def toggle_cashbox_setting(
reply_markup=build_settings_cashboxes_kb(updated_state), reply_markup=build_settings_cashboxes_kb(updated_state),
) )
await callback.answer("Настройка обновлена") await callback.answer("Настройка обновлена")
@router.callback_query(AdminPanelCallback.filter(F.action == "settings_providers_order"))
async def open_providers_order_menu(callback: CallbackQuery, session: AsyncSession) -> None:
sorted_names = _get_sorted_provider_names()
text = (
"📋 <b>Порядок отображения касс</b>\n\n"
"⬆️ — поднять выше\n"
"⬇️ — опустить ниже\n\n"
"Порядок влияет на меню оплаты и fast flow."
)
await callback.message.edit_text(
text=text,
reply_markup=build_providers_order_kb(sorted_names),
)
await callback.answer()
@router.callback_query(AdminPanelCallback.filter(F.action == "settings_order_up"))
async def move_provider_up(
callback: CallbackQuery,
callback_data: AdminPanelCallback,
session: AsyncSession,
) -> None:
sorted_names = _get_sorted_provider_names()
idx = callback_data.page - 1
if idx <= 0:
await callback.answer("Уже на первом месте", show_alert=False)
return
if idx >= len(sorted_names):
await callback.answer("Неизвестная касса", show_alert=True)
return
sorted_names[idx], sorted_names[idx - 1] = sorted_names[idx - 1], sorted_names[idx]
new_order = {name: (i + 1) * 10 for i, name in enumerate(sorted_names)}
await update_providers_order(session, new_order)
await callback.message.edit_reply_markup(
reply_markup=build_providers_order_kb(sorted_names),
)
await callback.answer("✅ Перемещено выше")
@router.callback_query(AdminPanelCallback.filter(F.action == "settings_order_down"))
async def move_provider_down(
callback: CallbackQuery,
callback_data: AdminPanelCallback,
session: AsyncSession,
) -> None:
sorted_names = _get_sorted_provider_names()
idx = callback_data.page - 1
if idx >= len(sorted_names) - 1:
await callback.answer("Уже на последнем месте", show_alert=False)
return
if idx < 0:
await callback.answer("Неизвестная касса", show_alert=True)
return
sorted_names[idx], sorted_names[idx + 1] = sorted_names[idx + 1], sorted_names[idx]
new_order = {name: (i + 1) * 10 for i, name in enumerate(sorted_names)}
await update_providers_order(session, new_order)
await callback.message.edit_reply_markup(
reply_markup=build_providers_order_kb(sorted_names),
)
await callback.answer("✅ Перемещено ниже")
@router.callback_query(AdminPanelCallback.filter(F.action == "settings_order_reset"))
async def reset_providers_order(callback: CallbackQuery, session: AsyncSession) -> None:
await update_providers_order(session, {})
sorted_names = _get_sorted_provider_names()
await callback.message.edit_reply_markup(
reply_markup=build_providers_order_kb(sorted_names),
)
await callback.answer("✅ Порядок сброшен на дефолтный")
+13 -8
View File
@@ -27,7 +27,7 @@ from handlers.payments.currency_flow import (
shortfall_lead_text, shortfall_lead_text,
currency_label, currency_label,
) )
from handlers.payments.providers import get_providers_with_hooks from handlers.payments.providers import get_providers_with_hooks, sort_provider_names
from handlers.texts import FAST_PAY_CHOOSE_CURRENCY, FAST_PAY_CHOOSE_PROVIDER, FASTFLOW_COUPON_APPLIED_TEMPLATE from handlers.texts import FAST_PAY_CHOOSE_CURRENCY, FAST_PAY_CHOOSE_PROVIDER, FASTFLOW_COUPON_APPLIED_TEMPLATE
from handlers.utils import edit_or_send_message from handlers.utils import edit_or_send_message
from logger import logger from logger import logger
@@ -115,6 +115,7 @@ async def try_fast_payment_flow(
cfg = providers_map.get(p_up) or {} cfg = providers_map.get(p_up) or {}
if cfg.get("fast") and cfg.get("enabled", True): if cfg.get("fast") and cfg.get("enabled", True):
providers.append(p_up) providers.append(p_up)
providers = sort_provider_names(providers, providers_map)
mode, one_screen = get_currency_mode() mode, one_screen = get_currency_mode()
multicurrency_mode = mode == "RUB+USD" multicurrency_mode = mode == "RUB+USD"
@@ -425,6 +426,7 @@ async def fastflow_apply_coupon(message: Message, state: FSMContext, session: An
cfg = providers_map.get(p_up) or {} cfg = providers_map.get(p_up) or {}
if cfg.get("fast") and cfg.get("enabled", True): if cfg.get("fast") and cfg.get("enabled", True):
providers.append(p_up) providers.append(p_up)
providers = sort_provider_names(providers, providers_map)
mode, one_screen = get_currency_mode() mode, one_screen = get_currency_mode()
multicurrency_mode = mode == "RUB+USD" multicurrency_mode = mode == "RUB+USD"
@@ -541,13 +543,16 @@ async def choose_payment_currency(callback_query: CallbackQuery, state: FSMConte
configured = [str(p) for p in (USE_NEW_PAYMENT_FLOW or [])] configured = [str(p) for p in (USE_NEW_PAYMENT_FLOW or [])]
providers = [p.upper() for p in configured] providers = [p.upper() for p in configured]
filtered = [ filtered = sort_provider_names(
provider_upper [
for provider_upper in (p.upper() for p in providers) provider_upper
if (providers_map.get(provider_upper) or {}).get("currency") == currency for provider_upper in (p.upper() for p in providers)
and (providers_map.get(provider_upper) or {}).get("fast") if (providers_map.get(provider_upper) or {}).get("currency") == currency
and (providers_map.get(provider_upper) or {}).get("enabled", True) and (providers_map.get(provider_upper) or {}).get("fast")
] and (providers_map.get(provider_upper) or {}).get("enabled", True)
],
providers_map,
)
await state.update_data(chosen_currency=currency) await state.update_data(chosen_currency=currency)
if not filtered: if not filtered:
+32 -2
View File
@@ -2,63 +2,93 @@ from typing import Any
from hooks.hooks import run_hooks from hooks.hooks import run_hooks
PROVIDERS_BASE: dict[str, dict[str, Any]] = { PROVIDERS_BASE: dict[str, dict[str, Any]] = {
"YOOKASSA": { "YOOKASSA": {
"currency": "RUB", "currency": "RUB",
"value": "pay_yookassa", "value": "pay_yookassa",
"fast": "process_custom_amount_input", "fast": "process_custom_amount_input",
"order": 10,
}, },
"YOOMONEY": { "YOOMONEY": {
"currency": "RUB", "currency": "RUB",
"value": "pay_yoomoney", "value": "pay_yoomoney",
"fast": "process_custom_amount_input_yoomoney", "fast": "process_custom_amount_input_yoomoney",
"order": 20,
}, },
"ROBOKASSA": { "ROBOKASSA": {
"currency": "RUB", "currency": "RUB",
"value": "pay_robokassa", "value": "pay_robokassa",
"fast": "handle_custom_amount_input", "fast": "handle_custom_amount_input",
"order": 30,
}, },
"KASSAI_CARDS": { "KASSAI_CARDS": {
"currency": "RUB", "currency": "RUB",
"value": "pay_kassai_cards", "value": "pay_kassai_cards",
"fast": "handle_custom_amount_input_kassai_cards", "fast": "handle_custom_amount_input_kassai_cards",
"module": "kassai", "module": "kassai",
"order": 40,
}, },
"KASSAI_SBP": { "KASSAI_SBP": {
"currency": "RUB", "currency": "RUB",
"value": "pay_kassai_sbp", "value": "pay_kassai_sbp",
"fast": "handle_custom_amount_input_kassai_sbp", "fast": "handle_custom_amount_input_kassai_sbp",
"module": "kassai", "module": "kassai",
"order": 50,
}, },
"TRIBUTE": { "TRIBUTE": {
"currency": "RUB+USD", "currency": "RUB+USD",
"value": "pay_tribute", "value": "pay_tribute",
"fast": None, "fast": None,
"order": 60,
}, },
"HELEKET": { "HELEKET": {
"currency": "USD", "currency": "USD",
"value": "pay_heleket_crypto", "value": "pay_heleket_crypto",
"fast": "handle_custom_amount_input_heleket", "fast": "handle_custom_amount_input_heleket",
"order": 70,
}, },
"CRYPTOBOT": { "CRYPTOBOT": {
"currency": "USD", "currency": "USD",
"value": "pay_cryptobot", "value": "pay_cryptobot",
"fast": "process_custom_amount_input", "fast": "process_custom_amount_input",
"order": 80,
}, },
"FREEKASSA": { "FREEKASSA": {
"currency": "USD", "currency": "USD",
"value": "pay_freekassa", "value": "pay_freekassa",
"fast": None, "fast": None,
"order": 90,
}, },
"STARS": { "STARS": {
"currency": "STARS", "currency": "STARS",
"value": "pay_stars", "value": "pay_stars",
"fast": "process_custom_amount_input_stars", "fast": "process_custom_amount_input_stars",
"order": 100,
}, },
} }
def _get_effective_order(name: str, cfg: dict[str, Any]) -> int:
"""Возвращает эффективный порядок провайдера (админ > модуль > дефолт)."""
from core.settings.providers_order_config import PROVIDERS_ORDER
if name in PROVIDERS_ORDER:
return PROVIDERS_ORDER[name]
return cfg.get("order", 999)
def _sort_providers(providers: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
"""Сортирует провайдеров по полю 'order' (меньше = выше)."""
return dict(
sorted(providers.items(), key=lambda item: _get_effective_order(item[0], item[1]))
)
def sort_provider_names(names: list[str], providers_map: dict[str, dict[str, Any]]) -> list[str]:
"""Сортирует список имён провайдеров по их 'order' из providers_map."""
return sorted(names, key=lambda n: _get_effective_order(n, providers_map.get(n) or {}))
def get_providers(flags: dict[str, bool]) -> dict[str, dict[str, Any]]: def get_providers(flags: dict[str, bool]) -> dict[str, dict[str, Any]]:
providers: dict[str, dict[str, Any]] = {} providers: dict[str, dict[str, Any]] = {}
for name, base in PROVIDERS_BASE.items(): for name, base in PROVIDERS_BASE.items():
@@ -81,4 +111,4 @@ async def get_providers_with_hooks(flags: dict[str, bool]) -> dict[str, dict[str
base = dict(providers.get(name, {})) base = dict(providers.get(name, {}))
base.update(patch) base.update(patch)
providers[name] = base providers[name] = base
return providers return _sort_providers(providers)