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)