import logging from datetime import datetime, timedelta from aiogram import Dispatcher, types, F from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.states import AdminStates from app.database.models import PromoCode, PromoCodeUse, PromoCodeType, User from app.keyboards.admin import ( get_admin_promocodes_keyboard, get_promocode_type_keyboard, get_admin_pagination_keyboard, get_confirmation_keyboard ) from app.localization.texts import get_texts from app.database.crud.promocode import ( get_promocodes_list, get_promocodes_count, create_promocode, get_promocode_statistics, get_promocode_by_code, update_promocode, delete_promocode, get_promocode_by_id ) from app.database.crud.promo_group import get_promo_group_by_id, get_promo_groups_with_counts from app.utils.decorators import admin_required, error_handler from app.utils.formatters import format_datetime logger = logging.getLogger(__name__) @admin_required @error_handler async def show_promocodes_menu( callback: types.CallbackQuery, db_user: User, db: AsyncSession ): total_codes = await get_promocodes_count(db) active_codes = await get_promocodes_count(db, is_active=True) text = f""" 🎫 Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°ΠΌΠΈ πŸ“Š Бтатистика: - ВсСго ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²: {total_codes} - Активных: {active_codes} - НСактивных: {total_codes - active_codes} Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ дСйствиС: """ await callback.message.edit_text( text, reply_markup=get_admin_promocodes_keyboard(db_user.language) ) await callback.answer() @admin_required @error_handler async def show_promocodes_list( callback: types.CallbackQuery, db_user: User, db: AsyncSession, page: int = 1 ): limit = 10 offset = (page - 1) * limit promocodes = await get_promocodes_list(db, offset=offset, limit=limit) total_count = await get_promocodes_count(db) total_pages = (total_count + limit - 1) // limit if not promocodes: await callback.message.edit_text( "🎫 ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Ρ‹ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹", reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_promocodes")] ]) ) await callback.answer() return text = f"🎫 Бписок ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ² (стр. {page}/{total_pages})\n\n" keyboard = [] for promo in promocodes: status_emoji = "βœ…" if promo.is_active else "❌" type_emoji = { "balance": "πŸ’°", "subscription_days": "πŸ“…", "trial_subscription": "🎁", "promo_group": "🏷️" }.get(promo.type, "🎫") text += f"{status_emoji} {type_emoji} {promo.code}\n" text += f"πŸ“Š Использований: {promo.current_uses}/{promo.max_uses}\n" if promo.type == PromoCodeType.BALANCE.value: text += f"πŸ’° Бонус: {settings.format_price(promo.balance_bonus_kopeks)}\n" elif promo.type == PromoCodeType.SUBSCRIPTION_DAYS.value: text += f"πŸ“… Π”Π½Π΅ΠΉ: {promo.subscription_days}\n" elif promo.type == PromoCodeType.PROMO_GROUP.value: if promo.promo_group: text += f"🏷️ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ°: {promo.promo_group.name}\n" if promo.valid_until: text += f"⏰ Π”ΠΎ: {format_datetime(promo.valid_until)}\n" keyboard.append([ types.InlineKeyboardButton( text=f"🎫 {promo.code}", callback_data=f"promo_manage_{promo.id}" ) ]) text += "\n" if total_pages > 1: pagination_row = get_admin_pagination_keyboard( page, total_pages, "admin_promo_list", "admin_promocodes", db_user.language ).inline_keyboard[0] keyboard.append(pagination_row) keyboard.extend([ [types.InlineKeyboardButton(text="βž• Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ", callback_data="admin_promo_create")], [types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_promocodes")] ]) await callback.message.edit_text( text, reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard) ) await callback.answer() @admin_required @error_handler async def show_promocodes_list_page( callback: types.CallbackQuery, db_user: User, db: AsyncSession ): """ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΏΠ°Π³ΠΈΠ½Π°Ρ†ΠΈΠΈ списка ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ².""" try: page = int(callback.data.split('_')[-1]) except (ValueError, IndexError): page = 1 await show_promocodes_list(callback, db_user, db, page=page) @admin_required @error_handler async def show_promocode_management( callback: types.CallbackQuery, db_user: User, db: AsyncSession ): promo_id = int(callback.data.split('_')[-1]) promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½", show_alert=True) return status_emoji = "βœ…" if promo.is_active else "❌" type_emoji = { "balance": "πŸ’°", "subscription_days": "πŸ“…", "trial_subscription": "🎁", "promo_group": "🏷️" }.get(promo.type, "🎫") text = f""" 🎫 Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠΌ {type_emoji} Код: {promo.code} {status_emoji} Бтатус: {'АктивСн' if promo.is_active else 'НСактивСн'} πŸ“Š Использований: {promo.current_uses}/{promo.max_uses} """ if promo.type == PromoCodeType.BALANCE.value: text += f"πŸ’° Бонус: {settings.format_price(promo.balance_bonus_kopeks)}\n" elif promo.type == PromoCodeType.SUBSCRIPTION_DAYS.value: text += f"πŸ“… Π”Π½Π΅ΠΉ: {promo.subscription_days}\n" elif promo.type == PromoCodeType.PROMO_GROUP.value: if promo.promo_group: text += f"🏷️ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ°: {promo.promo_group.name} (ΠΏΡ€ΠΈΠΎΡ€ΠΈΡ‚Π΅Ρ‚: {promo.promo_group.priority})\n" elif promo.promo_group_id: text += f"🏷️ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ° ID: {promo.promo_group_id} (Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Π°)\n" if promo.valid_until: text += f"⏰ ДСйствуСт Π΄ΠΎ: {format_datetime(promo.valid_until)}\n" first_purchase_only = getattr(promo, 'first_purchase_only', False) first_purchase_emoji = "βœ…" if first_purchase_only else "❌" text += f"πŸ†• Волько пСрвая ΠΏΠΎΠΊΡƒΠΏΠΊΠ°: {first_purchase_emoji}\n" text += f"πŸ“… Π‘ΠΎΠ·Π΄Π°Π½: {format_datetime(promo.created_at)}\n" first_purchase_btn_text = "πŸ†• ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΊΡƒΠΏΠΊΠ°: βœ…" if first_purchase_only else "πŸ†• ΠŸΠ΅Ρ€Π²Π°Ρ ΠΏΠΎΠΊΡƒΠΏΠΊΠ°: ❌" keyboard = [ [ types.InlineKeyboardButton( text="✏️ Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ", callback_data=f"promo_edit_{promo.id}" ), types.InlineKeyboardButton( text="πŸ”„ ΠŸΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ статус", callback_data=f"promo_toggle_{promo.id}" ) ], [ types.InlineKeyboardButton( text=first_purchase_btn_text, callback_data=f"promo_toggle_first_{promo.id}" ) ], [ types.InlineKeyboardButton( text="πŸ“Š Бтатистика", callback_data=f"promo_stats_{promo.id}" ), types.InlineKeyboardButton( text="πŸ—‘οΈ Π£Π΄Π°Π»ΠΈΡ‚ΡŒ", callback_data=f"promo_delete_{promo.id}" ) ], [ types.InlineKeyboardButton(text="⬅️ К списку", callback_data="admin_promo_list") ] ] await callback.message.edit_text( text, reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard) ) await callback.answer() @admin_required @error_handler async def show_promocode_edit_menu( callback: types.CallbackQuery, db_user: User, db: AsyncSession ): try: promo_id = int(callback.data.split('_')[-1]) except (ValueError, IndexError): await callback.answer("❌ Ошибка получСния ID ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°", show_alert=True) return promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½", show_alert=True) return text = f""" ✏️ Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° {promo.code} πŸ’° Π’Π΅ΠΊΡƒΡ‰ΠΈΠ΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹: """ if promo.type == PromoCodeType.BALANCE.value: text += f"β€’ Бонус: {settings.format_price(promo.balance_bonus_kopeks)}\n" elif promo.type in [PromoCodeType.SUBSCRIPTION_DAYS.value, PromoCodeType.TRIAL_SUBSCRIPTION.value]: text += f"β€’ Π”Π½Π΅ΠΉ: {promo.subscription_days}\n" text += f"β€’ Использований: {promo.current_uses}/{promo.max_uses}\n" if promo.valid_until: text += f"β€’ Π”ΠΎ: {format_datetime(promo.valid_until)}\n" else: text += f"β€’ Π‘Ρ€ΠΎΠΊ: бСссрочно\n" text += f"\nΠ’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ для измСнСния:" keyboard = [ [ types.InlineKeyboardButton( text="πŸ“… Π”Π°Ρ‚Π° окончания", callback_data=f"promo_edit_date_{promo.id}" ) ], [ types.InlineKeyboardButton( text="πŸ“Š ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ использований", callback_data=f"promo_edit_uses_{promo.id}" ) ] ] if promo.type == PromoCodeType.BALANCE.value: keyboard.insert(1, [ types.InlineKeyboardButton( text="πŸ’° Π‘ΡƒΠΌΠΌΠ° бонуса", callback_data=f"promo_edit_amount_{promo.id}" ) ]) elif promo.type in [PromoCodeType.SUBSCRIPTION_DAYS.value, PromoCodeType.TRIAL_SUBSCRIPTION.value]: keyboard.insert(1, [ types.InlineKeyboardButton( text="πŸ“… ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π΄Π½Π΅ΠΉ", callback_data=f"promo_edit_days_{promo.id}" ) ]) keyboard.extend([ [ types.InlineKeyboardButton( text="⬅️ Назад", callback_data=f"promo_manage_{promo.id}" ) ] ]) await callback.message.edit_text( text, reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard) ) await callback.answer() @admin_required @error_handler async def start_edit_promocode_date( callback: types.CallbackQuery, db_user: User, state: FSMContext ): try: promo_id = int(callback.data.split('_')[-1]) except (ValueError, IndexError): await callback.answer("❌ Ошибка получСния ID ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°", show_alert=True) return await state.update_data( editing_promo_id=promo_id, edit_action="date" ) text = f""" πŸ“… ИзмСнСниС Π΄Π°Ρ‚Ρ‹ окончания ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° Π’Π²Π΅Π΄ΠΈΡ‚Π΅ количСство Π΄Π½Π΅ΠΉ Π΄ΠΎ окончания (ΠΎΡ‚ Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ ΠΌΠΎΠΌΠ΅Π½Ρ‚Π°): β€’ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ 0 для бСссрочного ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° β€’ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ΅ число для установки срока НапримСр: 30 (ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ Π±ΡƒΠ΄Π΅Ρ‚ Π΄Π΅ΠΉΡΡ‚Π²ΠΎΠ²Π°Ρ‚ΡŒ 30 Π΄Π½Π΅ΠΉ) ID ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°: {promo_id} """ keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="❌ ΠžΡ‚ΠΌΠ΅Π½Π°", callback_data=f"promo_edit_{promo_id}")] ]) await callback.message.edit_text(text, reply_markup=keyboard) await state.set_state(AdminStates.setting_promocode_expiry) await callback.answer() @admin_required @error_handler async def start_edit_promocode_amount( callback: types.CallbackQuery, db_user: User, state: FSMContext ): try: promo_id = int(callback.data.split('_')[-1]) except (ValueError, IndexError): await callback.answer("❌ Ошибка получСния ID ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°", show_alert=True) return await state.update_data( editing_promo_id=promo_id, edit_action="amount" ) text = f""" πŸ’° ИзмСнСниС суммы бонуса ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π½ΠΎΠ²ΡƒΡŽ сумму Π² рублях: НапримСр: 500 ID ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°: {promo_id} """ keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="❌ ΠžΡ‚ΠΌΠ΅Π½Π°", callback_data=f"promo_edit_{promo_id}")] ]) await callback.message.edit_text(text, reply_markup=keyboard) await state.set_state(AdminStates.setting_promocode_value) await callback.answer() @admin_required @error_handler async def start_edit_promocode_days( callback: types.CallbackQuery, db_user: User, state: FSMContext ): # Π˜Π‘ΠŸΠ ΠΠ’Π›Π•ΠΠ˜Π•: Π±Π΅Ρ€Π΅ΠΌ послСдний элСмСнт ΠΊΠ°ΠΊ ID try: promo_id = int(callback.data.split('_')[-1]) except (ValueError, IndexError): await callback.answer("❌ Ошибка получСния ID ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°", show_alert=True) return await state.update_data( editing_promo_id=promo_id, edit_action="days" ) text = f""" πŸ“… ИзмСнСниС количСства Π΄Π½Π΅ΠΉ подписки Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π½ΠΎΠ²ΠΎΠ΅ количСство Π΄Π½Π΅ΠΉ: НапримСр: 30 ID ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°: {promo_id} """ keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="❌ ΠžΡ‚ΠΌΠ΅Π½Π°", callback_data=f"promo_edit_{promo_id}")] ]) await callback.message.edit_text(text, reply_markup=keyboard) await state.set_state(AdminStates.setting_promocode_value) await callback.answer() @admin_required @error_handler async def start_edit_promocode_uses( callback: types.CallbackQuery, db_user: User, state: FSMContext ): try: promo_id = int(callback.data.split('_')[-1]) except (ValueError, IndexError): await callback.answer("❌ Ошибка получСния ID ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°", show_alert=True) return await state.update_data( editing_promo_id=promo_id, edit_action="uses" ) text = f""" πŸ“Š ИзмСнСниС максимального количСства использований Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π½ΠΎΠ²ΠΎΠ΅ количСство использований: β€’ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ 0 для Π±Π΅Π·Π»ΠΈΠΌΠΈΡ‚Π½Ρ‹Ρ… использований β€’ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΏΠΎΠ»ΠΎΠΆΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ΅ число для ограничСния НапримСр: 100 ID ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°: {promo_id} """ keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="❌ ΠžΡ‚ΠΌΠ΅Π½Π°", callback_data=f"promo_edit_{promo_id}")] ]) await callback.message.edit_text(text, reply_markup=keyboard) await state.set_state(AdminStates.setting_promocode_uses) await callback.answer() @admin_required @error_handler async def start_promocode_creation( callback: types.CallbackQuery, db_user: User, state: FSMContext ): await callback.message.edit_text( "🎫 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\n" "Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ Ρ‚ΠΈΠΏ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°:", reply_markup=get_promocode_type_keyboard(db_user.language) ) await callback.answer() @admin_required @error_handler async def select_promocode_type( callback: types.CallbackQuery, db_user: User, state: FSMContext ): promo_type = callback.data.split('_')[-1] type_names = { "balance": "πŸ’° ПополнСниС баланса", "days": "πŸ“… Π”Π½ΠΈ подписки", "trial": "🎁 ВСстовая подписка", "group": "🏷️ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ°" } await state.update_data(promocode_type=promo_type) await callback.message.edit_text( f"🎫 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\n" f"Π’ΠΈΠΏ: {type_names.get(promo_type, promo_type)}\n\n" f"Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΠ΄ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ латинскиС Π±ΡƒΠΊΠ²Ρ‹ ΠΈ Ρ†ΠΈΡ„Ρ€Ρ‹):", reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="❌ ΠžΡ‚ΠΌΠ΅Π½Π°", callback_data="admin_promocodes")] ]) ) await state.set_state(AdminStates.creating_promocode) await callback.answer() @admin_required @error_handler async def process_promocode_code( message: types.Message, db_user: User, state: FSMContext, db: AsyncSession ): code = message.text.strip().upper() if not code.isalnum() or len(code) < 3 or len(code) > 20: await message.answer("❌ Код Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ латинскиС Π±ΡƒΠΊΠ²Ρ‹ ΠΈ Ρ†ΠΈΡ„Ρ€Ρ‹ (3-20 символов)") return existing = await get_promocode_by_code(db, code) if existing: await message.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ с Ρ‚Π°ΠΊΠΈΠΌ ΠΊΠΎΠ΄ΠΎΠΌ ΡƒΠΆΠ΅ сущСствуСт") return await state.update_data(promocode_code=code) data = await state.get_data() promo_type = data.get('promocode_type') if promo_type == "balance": await message.answer( f"πŸ’° ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄: {code}\n\n" f"Π’Π²Π΅Π΄ΠΈΡ‚Π΅ сумму пополнСния баланса (Π² рублях):" ) await state.set_state(AdminStates.setting_promocode_value) elif promo_type == "days": await message.answer( f"πŸ“… ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄: {code}\n\n" f"Π’Π²Π΅Π΄ΠΈΡ‚Π΅ количСство Π΄Π½Π΅ΠΉ подписки:" ) await state.set_state(AdminStates.setting_promocode_value) elif promo_type == "trial": await message.answer( f"🎁 ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄: {code}\n\n" f"Π’Π²Π΅Π΄ΠΈΡ‚Π΅ количСство Π΄Π½Π΅ΠΉ тСстовой подписки:" ) await state.set_state(AdminStates.setting_promocode_value) elif promo_type == "group": # Show promo group selection groups_with_counts = await get_promo_groups_with_counts(db, limit=50) if not groups_with_counts: await message.answer( "❌ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΡ‹ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ хотя Π±Ρ‹ ΠΎΠ΄Π½Ρƒ ΠΏΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΡƒ.", reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_promocodes")] ]) ) await state.clear() return keyboard = [] text = f"🏷️ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄: {code}\n\nΠ’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ ΠΏΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΡƒ для назначСния:\n\n" for promo_group, user_count in groups_with_counts: text += f"β€’ {promo_group.name} (ΠΏΡ€ΠΈΠΎΡ€ΠΈΡ‚Π΅Ρ‚: {promo_group.priority}, ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ: {user_count})\n" keyboard.append([ types.InlineKeyboardButton( text=f"{promo_group.name} (↑{promo_group.priority})", callback_data=f"promo_select_group_{promo_group.id}" ) ]) keyboard.append([ types.InlineKeyboardButton(text="❌ ΠžΡ‚ΠΌΠ΅Π½Π°", callback_data="admin_promocodes") ]) await message.answer( text, reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard) ) await state.set_state(AdminStates.selecting_promo_group) @admin_required @error_handler async def process_promo_group_selection( callback: types.CallbackQuery, db_user: User, state: FSMContext, db: AsyncSession ): """Handle promo group selection for promocode""" try: promo_group_id = int(callback.data.split('_')[-1]) except (ValueError, IndexError): await callback.answer("❌ Ошибка получСния ID ΠΏΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΡ‹", show_alert=True) return promo_group = await get_promo_group_by_id(db, promo_group_id) if not promo_group: await callback.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ° Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Π°", show_alert=True) return await state.update_data( promo_group_id=promo_group_id, promo_group_name=promo_group.name ) await callback.message.edit_text( f"🏷️ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ для ΠΏΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΡ‹\n\n" f"ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ°: {promo_group.name}\n" f"ΠŸΡ€ΠΈΠΎΡ€ΠΈΡ‚Π΅Ρ‚: {promo_group.priority}\n\n" f"πŸ“Š Π’Π²Π΅Π΄ΠΈΡ‚Π΅ количСство использований ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° (ΠΈΠ»ΠΈ 0 для Π±Π΅Π·Π»ΠΈΠΌΠΈΡ‚Π°):" ) await state.set_state(AdminStates.setting_promocode_uses) await callback.answer() @admin_required @error_handler async def process_promocode_value( message: types.Message, db_user: User, state: FSMContext, db: AsyncSession ): data = await state.get_data() if data.get('editing_promo_id'): await handle_edit_value(message, db_user, state, db) return try: value = int(message.text.strip()) promo_type = data.get('promocode_type') if promo_type == "balance" and (value < 1 or value > 10000): await message.answer("❌ Π‘ΡƒΠΌΠΌΠ° Π΄ΠΎΠ»ΠΆΠ½Π° Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 1 Π΄ΠΎ 10,000 Ρ€ΡƒΠ±Π»Π΅ΠΉ") return elif promo_type in ["days", "trial"] and (value < 1 or value > 3650): await message.answer("❌ ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π΄Π½Π΅ΠΉ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 1 Π΄ΠΎ 3650") return await state.update_data(promocode_value=value) await message.answer( f"πŸ“Š Π’Π²Π΅Π΄ΠΈΡ‚Π΅ количСство использований ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° (ΠΈΠ»ΠΈ 0 для Π±Π΅Π·Π»ΠΈΠΌΠΈΡ‚Π°):" ) await state.set_state(AdminStates.setting_promocode_uses) except ValueError: await message.answer("❌ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠ΅ число") async def handle_edit_value( message: types.Message, db_user: User, state: FSMContext, db: AsyncSession ): data = await state.get_data() promo_id = data.get('editing_promo_id') edit_action = data.get('edit_action') promo = await get_promocode_by_id(db, promo_id) if not promo: await message.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½") await state.clear() return try: value = int(message.text.strip()) if edit_action == "amount": if value < 1 or value > 10000: await message.answer("❌ Π‘ΡƒΠΌΠΌΠ° Π΄ΠΎΠ»ΠΆΠ½Π° Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 1 Π΄ΠΎ 10,000 Ρ€ΡƒΠ±Π»Π΅ΠΉ") return await update_promocode(db, promo, balance_bonus_kopeks=value * 100) await message.answer( f"βœ… Π‘ΡƒΠΌΠΌΠ° бонуса ΠΈΠ·ΠΌΠ΅Π½Π΅Π½Π° Π½Π° {value}β‚½", reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="🎫 К ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Ρƒ", callback_data=f"promo_manage_{promo_id}")] ]) ) elif edit_action == "days": if value < 1 or value > 3650: await message.answer("❌ ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π΄Π½Π΅ΠΉ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 1 Π΄ΠΎ 3650") return await update_promocode(db, promo, subscription_days=value) await message.answer( f"βœ… ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π΄Π½Π΅ΠΉ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΎ Π½Π° {value}", reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="🎫 К ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Ρƒ", callback_data=f"promo_manage_{promo_id}")] ]) ) await state.clear() logger.info(f"ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ {promo.code} ΠΎΡ‚Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ администратором {db_user.telegram_id}: {edit_action} = {value}") except ValueError: await message.answer("❌ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠ΅ число") @admin_required @error_handler async def process_promocode_uses( message: types.Message, db_user: User, state: FSMContext, db: AsyncSession ): data = await state.get_data() if data.get('editing_promo_id'): await handle_edit_uses(message, db_user, state, db) return try: max_uses = int(message.text.strip()) if max_uses < 0 or max_uses > 100000: await message.answer("❌ ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ использований Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 0 Π΄ΠΎ 100,000") return if max_uses == 0: max_uses = 999999 await state.update_data(promocode_max_uses=max_uses) await message.answer( f"⏰ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ срок дСйствия ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° Π² днях (ΠΈΠ»ΠΈ 0 для бСссрочного):" ) await state.set_state(AdminStates.setting_promocode_expiry) except ValueError: await message.answer("❌ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠ΅ число") async def handle_edit_uses( message: types.Message, db_user: User, state: FSMContext, db: AsyncSession ): data = await state.get_data() promo_id = data.get('editing_promo_id') promo = await get_promocode_by_id(db, promo_id) if not promo: await message.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½") await state.clear() return try: max_uses = int(message.text.strip()) if max_uses < 0 or max_uses > 100000: await message.answer("❌ ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ использований Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 0 Π΄ΠΎ 100,000") return if max_uses == 0: max_uses = 999999 if max_uses < promo.current_uses: await message.answer( f"❌ Новый Π»ΠΈΠΌΠΈΡ‚ ({max_uses}) Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ мСньшС Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΡ… использований ({promo.current_uses})" ) return await update_promocode(db, promo, max_uses=max_uses) uses_text = "Π±Π΅Π·Π»ΠΈΠΌΠΈΡ‚Π½ΠΎΠ΅" if max_uses == 999999 else str(max_uses) await message.answer( f"βœ… МаксимальноС количСство использований ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΎ Π½Π° {uses_text}", reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="🎫 К ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Ρƒ", callback_data=f"promo_manage_{promo_id}")] ]) ) await state.clear() logger.info(f"ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ {promo.code} ΠΎΡ‚Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ администратором {db_user.telegram_id}: max_uses = {max_uses}") except ValueError: await message.answer("❌ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠ΅ число") @admin_required @error_handler async def process_promocode_expiry( message: types.Message, db_user: User, state: FSMContext, db: AsyncSession ): data = await state.get_data() if data.get('editing_promo_id'): await handle_edit_expiry(message, db_user, state, db) return try: expiry_days = int(message.text.strip()) if expiry_days < 0 or expiry_days > 3650: await message.answer("❌ Π‘Ρ€ΠΎΠΊ дСйствия Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 0 Π΄ΠΎ 3650 Π΄Π½Π΅ΠΉ") return code = data.get('promocode_code') promo_type = data.get('promocode_type') value = data.get('promocode_value', 0) max_uses = data.get('promocode_max_uses', 1) promo_group_id = data.get('promo_group_id') promo_group_name = data.get('promo_group_name') valid_until = None if expiry_days > 0: valid_until = datetime.utcnow() + timedelta(days=expiry_days) type_map = { "balance": PromoCodeType.BALANCE, "days": PromoCodeType.SUBSCRIPTION_DAYS, "trial": PromoCodeType.TRIAL_SUBSCRIPTION, "group": PromoCodeType.PROMO_GROUP } promocode = await create_promocode( db=db, code=code, type=type_map[promo_type], balance_bonus_kopeks=value * 100 if promo_type == "balance" else 0, subscription_days=value if promo_type in ["days", "trial"] else 0, max_uses=max_uses, valid_until=valid_until, created_by=db_user.id, promo_group_id=promo_group_id if promo_type == "group" else None ) type_names = { "balance": "ПополнСниС баланса", "days": "Π”Π½ΠΈ подписки", "trial": "ВСстовая подписка", "group": "ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ°" } summary_text = f""" βœ… ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ создан! 🎫 Код: {promocode.code} πŸ“ Π’ΠΈΠΏ: {type_names.get(promo_type)} """ if promo_type == "balance": summary_text += f"πŸ’° Π‘ΡƒΠΌΠΌΠ°: {settings.format_price(promocode.balance_bonus_kopeks)}\n" elif promo_type in ["days", "trial"]: summary_text += f"πŸ“… Π”Π½Π΅ΠΉ: {promocode.subscription_days}\n" elif promo_type == "group" and promo_group_name: summary_text += f"🏷️ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ°: {promo_group_name}\n" summary_text += f"πŸ“Š Использований: {promocode.max_uses}\n" if promocode.valid_until: summary_text += f"⏰ ДСйствуСт Π΄ΠΎ: {format_datetime(promocode.valid_until)}\n" await message.answer( summary_text, reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="🎫 К ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°ΠΌ", callback_data="admin_promocodes")] ]) ) await state.clear() logger.info(f"Π‘ΠΎΠ·Π΄Π°Π½ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ {code} администратором {db_user.telegram_id}") except ValueError: await message.answer("❌ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠ΅ число Π΄Π½Π΅ΠΉ") async def handle_edit_expiry( message: types.Message, db_user: User, state: FSMContext, db: AsyncSession ): data = await state.get_data() promo_id = data.get('editing_promo_id') promo = await get_promocode_by_id(db, promo_id) if not promo: await message.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½") await state.clear() return try: expiry_days = int(message.text.strip()) if expiry_days < 0 or expiry_days > 3650: await message.answer("❌ Π‘Ρ€ΠΎΠΊ дСйствия Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 0 Π΄ΠΎ 3650 Π΄Π½Π΅ΠΉ") return valid_until = None if expiry_days > 0: valid_until = datetime.utcnow() + timedelta(days=expiry_days) await update_promocode(db, promo, valid_until=valid_until) if valid_until: expiry_text = f"Π΄ΠΎ {format_datetime(valid_until)}" else: expiry_text = "бСссрочно" await message.answer( f"βœ… Π‘Ρ€ΠΎΠΊ дСйствия ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½: {expiry_text}", reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text="🎫 К ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Ρƒ", callback_data=f"promo_manage_{promo_id}")] ]) ) await state.clear() logger.info(f"ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ {promo.code} ΠΎΡ‚Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ администратором {db_user.telegram_id}: expiry = {expiry_days} Π΄Π½Π΅ΠΉ") except ValueError: await message.answer("❌ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠ΅ число Π΄Π½Π΅ΠΉ") @admin_required @error_handler async def toggle_promocode_status( callback: types.CallbackQuery, db_user: User, db: AsyncSession ): promo_id = int(callback.data.split('_')[-1]) promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½", show_alert=True) return new_status = not promo.is_active await update_promocode(db, promo, is_active=new_status) status_text = "Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Π½" if new_status else "Π΄Π΅Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Π½" await callback.answer(f"βœ… ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ {status_text}", show_alert=True) await show_promocode_management(callback, db_user, db) @admin_required @error_handler async def toggle_promocode_first_purchase( callback: types.CallbackQuery, db_user: User, db: AsyncSession ): """ΠŸΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ Ρ€Π΅ΠΆΠΈΠΌ 'Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для ΠΏΠ΅Ρ€Π²ΠΎΠΉ ΠΏΠΎΠΊΡƒΠΏΠΊΠΈ'.""" promo_id = int(callback.data.split('_')[-1]) promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½", show_alert=True) return new_status = not getattr(promo, 'first_purchase_only', False) await update_promocode(db, promo, first_purchase_only=new_status) status_text = "Π²ΠΊΠ»ΡŽΡ‡Ρ‘Π½" if new_status else "Π²Ρ‹ΠΊΠ»ΡŽΡ‡Π΅Π½" await callback.answer(f"βœ… Π Π΅ΠΆΠΈΠΌ 'пСрвая ΠΏΠΎΠΊΡƒΠΏΠΊΠ°' {status_text}", show_alert=True) await show_promocode_management(callback, db_user, db) @admin_required @error_handler async def confirm_delete_promocode( callback: types.CallbackQuery, db_user: User, db: AsyncSession ): try: promo_id = int(callback.data.split('_')[-1]) except (ValueError, IndexError): await callback.answer("❌ Ошибка получСния ID ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°", show_alert=True) return promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½", show_alert=True) return text = f""" ⚠️ ΠŸΠΎΠ΄Ρ‚Π²Π΅Ρ€ΠΆΠ΄Π΅Π½ΠΈΠ΅ удалСния Π’Ρ‹ Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ {promo.code}? πŸ“Š Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π΅: β€’ Использований: {promo.current_uses}/{promo.max_uses} β€’ Бтатус: {'АктивСн' if promo.is_active else 'НСактивСн'} ⚠️ Π’Π½ΠΈΠΌΠ°Π½ΠΈΠ΅: Π­Ρ‚ΠΎ дСйствиС нСльзя ΠΎΡ‚ΠΌΠ΅Π½ΠΈΡ‚ΡŒ! ID: {promo_id} """ keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [ types.InlineKeyboardButton( text="βœ… Π”Π°, ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ", callback_data=f"promo_delete_confirm_{promo.id}" ), types.InlineKeyboardButton( text="❌ ΠžΡ‚ΠΌΠ΅Π½Π°", callback_data=f"promo_manage_{promo.id}" ) ] ]) await callback.message.edit_text(text, reply_markup=keyboard) await callback.answer() @admin_required @error_handler async def delete_promocode_confirmed( callback: types.CallbackQuery, db_user: User, db: AsyncSession ): try: promo_id = int(callback.data.split('_')[-1]) except (ValueError, IndexError): await callback.answer("❌ Ошибка получСния ID ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°", show_alert=True) return promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½", show_alert=True) return code = promo.code success = await delete_promocode(db, promo) if success: await callback.answer(f"βœ… ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ {code} ΡƒΠ΄Π°Π»Π΅Π½", show_alert=True) await show_promocodes_list(callback, db_user, db) else: await callback.answer("❌ Ошибка удалСния ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°", show_alert=True) @admin_required @error_handler async def show_promocode_stats( callback: types.CallbackQuery, db_user: User, db: AsyncSession ): promo_id = int(callback.data.split('_')[-1]) promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½", show_alert=True) return stats = await get_promocode_statistics(db, promo_id) text = f""" πŸ“Š Бтатистика ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° {promo.code} πŸ“ˆ ΠžΠ±Ρ‰Π°Ρ статистика: - ВсСго использований: {stats['total_uses']} - Использований сСгодня: {stats['today_uses']} - ΠžΡΡ‚Π°Π»ΠΎΡΡŒ использований: {promo.max_uses - promo.current_uses} πŸ“… ПослСдниС использования: """ if stats['recent_uses']: for use in stats['recent_uses'][:5]: use_date = format_datetime(use.used_at) if hasattr(use, 'user_username') and use.user_username: user_display = f"@{use.user_username}" elif hasattr(use, 'user_full_name') and use.user_full_name: user_display = use.user_full_name elif hasattr(use, 'user_telegram_id'): user_display = f"ID{use.user_telegram_id}" else: user_display = f"ID{use.user_id}" text += f"- {use_date} | {user_display}\n" else: text += "- Пока Π½Π΅ Π±Ρ‹Π»ΠΎ использований\n" keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [ types.InlineKeyboardButton( text="⬅️ Назад", callback_data=f"promo_manage_{promo.id}" ) ] ]) await callback.message.edit_text(text, reply_markup=keyboard) await callback.answer() @admin_required @error_handler async def show_general_promocode_stats( callback: types.CallbackQuery, db_user: User, db: AsyncSession ): total_codes = await get_promocodes_count(db) active_codes = await get_promocodes_count(db, is_active=True) text = f""" πŸ“Š ΠžΠ±Ρ‰Π°Ρ статистика ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ² πŸ“ˆ ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΏΠΎΠΊΠ°Π·Π°Ρ‚Π΅Π»ΠΈ: - ВсСго ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²: {total_codes} - Активных: {active_codes} - НСактивных: {total_codes - active_codes} Для Π΄Π΅Ρ‚Π°Π»ΡŒΠ½ΠΎΠΉ статистики Π²Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹ΠΉ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ ΠΈΠ· списка. """ keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [ types.InlineKeyboardButton(text="🎫 К ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°ΠΌ", callback_data="admin_promo_list") ], [ types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_promocodes") ] ]) await callback.message.edit_text(text, reply_markup=keyboard) await callback.answer() def register_handlers(dp: Dispatcher): dp.callback_query.register(show_promocodes_menu, F.data == "admin_promocodes") dp.callback_query.register(show_promocodes_list, F.data == "admin_promo_list") dp.callback_query.register(show_promocodes_list_page, F.data.startswith("admin_promo_list_page_")) dp.callback_query.register(start_promocode_creation, F.data == "admin_promo_create") dp.callback_query.register(select_promocode_type, F.data.startswith("promo_type_")) dp.callback_query.register(process_promo_group_selection, F.data.startswith("promo_select_group_")) dp.callback_query.register(show_promocode_management, F.data.startswith("promo_manage_")) dp.callback_query.register(toggle_promocode_first_purchase, F.data.startswith("promo_toggle_first_")) dp.callback_query.register(toggle_promocode_status, F.data.startswith("promo_toggle_")) dp.callback_query.register(show_promocode_stats, F.data.startswith("promo_stats_")) dp.callback_query.register(start_edit_promocode_date, F.data.startswith("promo_edit_date_")) dp.callback_query.register(start_edit_promocode_amount, F.data.startswith("promo_edit_amount_")) dp.callback_query.register(start_edit_promocode_days, F.data.startswith("promo_edit_days_")) dp.callback_query.register(start_edit_promocode_uses, F.data.startswith("promo_edit_uses_")) dp.callback_query.register(show_general_promocode_stats, F.data == "admin_promo_general_stats") dp.callback_query.register( show_promocode_edit_menu, F.data.regexp(r"^promo_edit_\d+$") ) dp.callback_query.register(delete_promocode_confirmed, F.data.startswith("promo_delete_confirm_")) dp.callback_query.register(confirm_delete_promocode, F.data.startswith("promo_delete_")) dp.message.register(process_promocode_code, AdminStates.creating_promocode) dp.message.register(process_promocode_value, AdminStates.setting_promocode_value) dp.message.register(process_promocode_uses, AdminStates.setting_promocode_uses) dp.message.register(process_promocode_expiry, AdminStates.setting_promocode_expiry)