diff --git a/app/handlers/admin/promocodes.py b/app/handlers/admin/promocodes.py
index 4394fcfa..f7032f40 100644
--- a/app/handlers/admin/promocodes.py
+++ b/app/handlers/admin/promocodes.py
@@ -183,372 +183,6 @@ async def show_promocode_management(
await callback.answer()
-
-@admin_required
-@error_handler
-async def show_promocode_edit_menu(
- callback: types.CallbackQuery,
- db_user: User,
- db: AsyncSession
-):
- """Показать меню редактирования промокода"""
- promo_id = int(callback.data.split('_')[-1])
-
- promo = await db.get(PromoCode, promo_id)
- if not promo:
- await callback.answer("❌ Промокод не найден", show_alert=True)
- return
-
- text = f"""
-✏️ Редактирование промокода {promo.code}
-
-Выберите параметр для изменения:
-"""
-
- 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
-):
- promo_id = int(callback.data.split('_')[-1])
-
- await state.update_data(
- editing_promo_id=promo_id,
- edit_type="date"
- )
-
- text = """
-📅 Изменение даты окончания промокода
-
-Введите количество дней до окончания (от текущего момента):
-• Введите 0 для бессрочного промокода
-• Введите положительное число для установки срока
-
-Например: 30 (промокод будет действовать 30 дней)
-"""
-
- 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
-):
- """Начать редактирование суммы промокода"""
- promo_id = int(callback.data.split('_')[-1])
-
- await state.update_data(
- editing_promo_id=promo_id,
- edit_type="amount"
- )
-
- text = """
-💰 Изменение суммы бонуса промокода
-
-Введите новую сумму в рублях:
-Например: 500
-"""
-
- 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
-):
- """Начать редактирование количества дней промокода"""
- promo_id = int(callback.data.split('_')[-1])
-
- await state.update_data(
- editing_promo_id=promo_id,
- edit_type="days"
- )
-
- text = """
-📅 Изменение количества дней подписки
-
-Введите новое количество дней:
-Например: 30
-"""
-
- 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
-):
- """Начать редактирование количества использований промокода"""
- promo_id = int(callback.data.split('_')[-1])
-
- await state.update_data(
- editing_promo_id=promo_id,
- edit_type="uses"
- )
-
- text = """
-📊 Изменение максимального количества использований
-
-Введите новое количество использований:
-• Введите 0 для безлимитных использований
-• Введите положительное число для ограничения
-
-Например: 100
-"""
-
- 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 process_promocode_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_type = data.get('edit_type')
-
- if not promo_id or not edit_type:
- await message.answer("❌ Ошибка: данные сессии утеряны")
- await state.clear()
- return
-
- promo = await db.get(PromoCode, promo_id)
- if not promo:
- await message.answer("❌ Промокод не найден")
- await state.clear()
- return
-
- try:
- value = int(message.text.strip())
-
- if edit_type == "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_type == "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_type} = {value}")
-
- except ValueError:
- await message.answer("❌ Введите корректное число")
-
-
-@admin_required
-@error_handler
-async def process_promocode_edit_uses(
- message: types.Message,
- db_user: User,
- state: FSMContext,
- db: AsyncSession
-):
- """Обработать новое количество использований"""
- data = await state.get_data()
- promo_id = data.get('editing_promo_id')
-
- if not promo_id:
- await message.answer("❌ Ошибка: данные сессии утеряны")
- await state.clear()
- return
-
- promo = await db.get(PromoCode, 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_edit_expiry(
- message: types.Message,
- db_user: User,
- state: FSMContext,
- db: AsyncSession
-):
- """Обработать новую дату окончания"""
- data = await state.get_data()
- promo_id = data.get('editing_promo_id')
-
- if not promo_id:
- await message.answer("❌ Ошибка: данные сессии утеряны")
- await state.clear()
- return
-
- promo = await db.get(PromoCode, 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(
@@ -765,7 +399,149 @@ async def process_promocode_code(
)
await state.set_state(AdminStates.setting_promocode_value)
elif promo_type == "trial":
- await message
+ await message.answer(
+ f"🎁 Промокод: {code}\n\n"
+ f"Введите количество дней тестовой подписки:"
+ )
+ await state.set_state(AdminStates.setting_promocode_value)
+
+
+@admin_required
+@error_handler
+async def process_promocode_value(
+ message: types.Message,
+ db_user: User,
+ state: FSMContext
+):
+ try:
+ value = int(message.text.strip())
+
+ data = await state.get_data()
+ 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("❌ Введите корректное число")
+
+
+@admin_required
+@error_handler
+async def process_promocode_uses(
+ message: types.Message,
+ db_user: User,
+ state: FSMContext
+):
+ 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("❌ Введите корректное число")
+
+
+@admin_required
+@error_handler
+async def process_promocode_expiry(
+ message: types.Message,
+ db_user: User,
+ state: FSMContext,
+ db: AsyncSession
+):
+ try:
+ expiry_days = int(message.text.strip())
+
+ if expiry_days < 0 or expiry_days > 3650:
+ await message.answer("❌ Срок действия должен быть от 0 до 3650 дней")
+ return
+
+ data = await state.get_data()
+ 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)
+
+ 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
+ }
+
+ 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
+ )
+
+ type_names = {
+ "balance": "Пополнение баланса",
+ "days": "Дни подписки",
+ "trial": "Тестовая подписка"
+ }
+
+ 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"
+
+ 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("❌ Введите корректное число дней")
def register_handlers(dp: Dispatcher):
@@ -780,17 +556,7 @@ def register_handlers(dp: Dispatcher):
dp.callback_query.register(delete_promocode_confirmed, F.data.startswith("promo_delete_confirm_"))
dp.callback_query.register(show_promocode_stats, F.data.startswith("promo_stats_"))
- dp.callback_query.register(show_promocode_edit_menu, F.data.startswith("promo_edit_"))
- 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.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)
-
- dp.message.register(process_promocode_edit_value, AdminStates.setting_promocode_value)
- dp.message.register(process_promocode_edit_uses, AdminStates.setting_promocode_uses)
- dp.message.register(process_promocode_edit_expiry, AdminStates.setting_promocode_expiry)