Enhance promo code editing functionality in admin panel

- Implemented a four-step process for editing promo codes, allowing admins to update code, bonus days, max activations, and validity period with improved user prompts.
- Added new states to manage each step of the editing process, ensuring better flow and validation.
- Updated localization files to include new messages and prompts for the editing steps in both English and Russian, enhancing user experience.
This commit is contained in:
machka-pasla
2025-08-03 22:12:49 +03:00
parent fc48cd7a09
commit b13883c039
4 changed files with 270 additions and 55 deletions
+240 -45
View File
@@ -653,63 +653,258 @@ async def promo_edit_select_handler(callback: types.CallbackQuery, state: FSMCon
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
await state.update_data(edit_promo_id=promo_id)
await state.set_state(AdminStates.waiting_for_promo_edit_details)
await state.update_data(
edit_promo_id=promo_id,
original_code=promo.code,
original_bonus_days=promo.bonus_days,
original_max_activations=promo.max_activations,
original_valid_until=promo.valid_until
)
# Step 1: Edit code
prompt_text = _(
"admin_promo_edit_step1_code",
default="✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 1 из 4:</b> Код промокода\n\nТекущий код: <b>{current_code}</b>\n\nВведите новый код промокода (3-30 символов, только буквы и цифры) или отправьте текущий для сохранения:",
current_code=promo.code
)
await state.set_state(AdminStates.waiting_for_promo_edit_code)
await callback.message.edit_text(
_("admin_promo_edit_prompt", code=promo.code),
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML")
await callback.answer()
@router.message(AdminStates.waiting_for_promo_edit_details, F.text)
async def process_promo_edit_details(message: types.Message, state: FSMContext,
i18n_data: dict, settings: Settings,
session: AsyncSession):
data = await state.get_data()
promo_id = data.get("edit_promo_id")
# Step 1: Process edited code
@router.message(AdminStates.waiting_for_promo_edit_code, F.text)
async def process_promo_edit_code_handler(message: types.Message, state: FSMContext,
i18n_data: dict, settings: Settings,
session: AsyncSession):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n or not promo_id:
await message.answer("Error")
await state.clear()
if not i18n:
await message.reply("Language service error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
parts = message.text.strip().split()
if not (3 <= len(parts) <= 4):
await message.answer(_("admin_promo_invalid_format"))
return
data = await state.get_data()
original_code = data.get("original_code")
try:
code_str = parts[0].upper()
bonus = int(parts[1])
max_act = int(parts[2])
valid_until = None
if len(parts) == 4:
days = int(parts[3])
valid_until = datetime.now(timezone.utc) + timedelta(days=days)
except Exception:
await message.answer(_("admin_promo_invalid_format_general"))
return
update_data = {
"code": code_str,
"bonus_days": bonus,
"max_activations": max_act,
"valid_until": valid_until,
}
updated = await promo_code_dal.update_promo_code(session, promo_id, update_data)
if updated:
await session.commit()
await message.answer(
_("admin_promo_updated_success", code=code_str),
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML",
code_str = message.text.strip().upper()
if not (3 <= len(code_str) <= 30 and code_str.isalnum()):
await message.answer(_(
"admin_promo_invalid_code_format",
default="❌ Код промокода должен содержать 3-30 символов (только буквы и цифры)"
))
return
# Check if code already exists (except current promo)
if code_str != original_code:
existing_promo = await promo_code_dal.get_promo_code_by_code(session, code_str)
if existing_promo:
await message.answer(_(
"admin_promo_code_already_exists",
default="❌ Промокод с таким кодом уже существует"
))
return
await state.update_data(new_code=code_str)
# Step 2: Ask for bonus days
prompt_text = _(
"admin_promo_edit_step2_bonus_days",
default="✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 2 из 4:</b> Бонусные дни\n\nКод: <b>{code}</b>\nТекущие дни: <b>{current_days}</b>\n\nВведите количество бонусных дней (1-365):",
code=code_str,
current_days=data.get("original_bonus_days")
)
else:
await session.rollback()
await message.answer(_("admin_promo_not_found"))
await state.clear()
await message.answer(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML"
)
await state.set_state(AdminStates.waiting_for_promo_edit_bonus_days)
except ValueError:
await message.answer(_(
"admin_promo_invalid_code_format",
default="❌ Код промокода должен содержать 3-30 символов (только буквы и цифры)"
))
# Step 2: Process edited bonus days
@router.message(AdminStates.waiting_for_promo_edit_bonus_days, F.text)
async def process_promo_edit_bonus_days_handler(message: types.Message, state: FSMContext,
i18n_data: dict, settings: Settings):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await message.reply("Language service error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
data = await state.get_data()
try:
bonus_days = int(message.text.strip())
if not (1 <= bonus_days <= 365):
await message.answer(_(
"admin_promo_invalid_bonus_days",
default="❌ Количество дней должно быть от 1 до 365"
))
return
await state.update_data(new_bonus_days=bonus_days)
# Step 3: Ask for max activations
prompt_text = _(
"admin_promo_edit_step3_max_activations",
default="✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 3 из 4:</b> Максимальные активации\n\nКод: <b>{code}</b>\nДни: <b>{days}</b>\nТекущие активации: <b>{current_max}</b>\n\nВведите максимальное количество активаций (1-10000):",
code=data.get("new_code"),
days=bonus_days,
current_max=data.get("original_max_activations")
)
await message.answer(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML"
)
await state.set_state(AdminStates.waiting_for_promo_edit_max_activations)
except ValueError:
await message.answer(_(
"admin_promo_invalid_bonus_days",
default="❌ Количество дней должно быть от 1 до 365"
))
# Step 3: Process edited max activations
@router.message(AdminStates.waiting_for_promo_edit_max_activations, F.text)
async def process_promo_edit_max_activations_handler(message: types.Message, state: FSMContext,
i18n_data: dict, settings: Settings):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await message.reply("Language service error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
data = await state.get_data()
try:
max_activations = int(message.text.strip())
if not (1 <= max_activations <= 10000):
await message.answer(_(
"admin_promo_invalid_max_activations",
default="❌ Количество активаций должно быть от 1 до 10000"
))
return
await state.update_data(new_max_activations=max_activations)
# Step 4: Ask for validity days
current_valid_until = data.get("original_valid_until")
current_validity_str = _("admin_promo_valid_indefinitely", default="Неограниченно")
if current_valid_until:
current_validity_str = current_valid_until.strftime('%Y-%m-%d')
prompt_text = _(
"admin_promo_edit_step4_validity",
default="✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 4 из 4:</b> Срок действия\n\nКод: <b>{code}</b>\nДни: <b>{days}</b>\nАктивации: <b>{max_act}</b>\nТекущий срок: <b>{current_validity}</b>\n\nВведите количество дней действия промокода от сегодня (1-365) или 0 для неограниченного срока:",
code=data.get("new_code"),
days=data.get("new_bonus_days"),
max_act=max_activations,
current_validity=current_validity_str
)
await message.answer(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML"
)
await state.set_state(AdminStates.waiting_for_promo_edit_validity_days)
except ValueError:
await message.answer(_(
"admin_promo_invalid_max_activations",
default="❌ Количество активаций должно быть от 1 до 10000"
))
# Step 4: Process edited validity and finalize
@router.message(AdminStates.waiting_for_promo_edit_validity_days, F.text)
async def process_promo_edit_validity_handler(message: types.Message, state: FSMContext,
i18n_data: dict, settings: Settings,
session: AsyncSession):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await message.reply("Language service error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
data = await state.get_data()
promo_id = data.get("edit_promo_id")
try:
validity_days = int(message.text.strip())
if not (0 <= validity_days <= 365):
await message.answer(_(
"admin_promo_invalid_validity_days",
default="❌ Срок действия должен быть от 0 до 365 дней (0 = неограниченно)"
))
return
# Calculate validity date
valid_until_date = None
valid_until_str_display = _("admin_promo_valid_indefinitely", default="Неограниченно")
if validity_days > 0:
valid_until_date = datetime.now(timezone.utc) + timedelta(days=validity_days)
valid_until_str_display = valid_until_date.strftime('%Y-%m-%d')
# Update promo code
update_data = {
"code": data.get("new_code"),
"bonus_days": data.get("new_bonus_days"),
"max_activations": data.get("new_max_activations"),
"valid_until": valid_until_date,
}
updated = await promo_code_dal.update_promo_code(session, promo_id, update_data)
if updated:
await session.commit()
success_text = _(
"admin_promo_updated_success",
default="✅ Промокод обновлен!\n\n🎟 Код: <b>{code}</b>\n🎁 Бонусные дни: <b>{days}</b>\n🔢 Максимальные активации: <b>{max_act}</b>\n⏰ Действует до: <b>{validity}</b>",
code=data.get("new_code"),
days=data.get("new_bonus_days"),
max_act=data.get("new_max_activations"),
validity=valid_until_str_display
)
await message.answer(
success_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML"
)
else:
await session.rollback()
await message.answer(_(
"admin_promo_update_failed",
default="❌ Ошибка обновления промокода"
))
await state.clear()
except ValueError:
await message.answer(_(
"admin_promo_invalid_validity_days",
default="❌ Срок действия должен быть от 0 до 365 дней (0 = неограниченно)"
))
@router.callback_query(F.data.startswith("promo_delete:"))
+4
View File
@@ -11,6 +11,10 @@ class AdminStates(StatesGroup):
waiting_for_promo_max_activations = State()
waiting_for_promo_validity_days = State()
waiting_for_promo_edit_details = State()
waiting_for_promo_edit_code = State()
waiting_for_promo_edit_bonus_days = State()
waiting_for_promo_edit_max_activations = State()
waiting_for_promo_edit_validity_days = State()
waiting_for_bulk_promo_quantity = State()
waiting_for_bulk_promo_bonus_days = State()
waiting_for_bulk_promo_max_activations = State()
+13 -5
View File
@@ -104,11 +104,11 @@
"admin_sync_panel_button": "🔄 Sync",
"admin_unknown_action": "Unknown admin action.",
"admin_stats_and_monitoring_section": "📊 Statistics & Monitoring",
"admin_user_management_section": "👥 User Management",
"admin_promo_marketing_section": "🎁 Promos & Marketing",
"admin_system_functions_section": "⚙️ System Functions",
"admin_ban_management_section": "🚫 Ban Management",
"admin_stats_and_monitoring_section": "📊 Statistics",
"admin_user_management_section": "👥 Users",
"admin_promo_marketing_section": "🎁 Promos",
"admin_system_functions_section": "⚙️ System",
"admin_ban_management_section": "🚫 Bans",
"admin_users_management_button": "👤 User Management",
"back_to_user_management_button": "⬅️ Back to User Management",
"admin_action_cancelled_default": "Action cancelled. Returning to menu.",
@@ -308,6 +308,14 @@
"admin_bulk_promo_created_success": "✅ Created {count} promo codes for {days} days!",
"admin_bulk_promo_codes_file": "📄 All created promo codes in file ({count} pcs.)",
"admin_promo_edit_step1_code": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 1 of 4:</b> Promo Code\n\nCurrent code: <b>{current_code}</b>\n\nEnter new promo code (3-30 characters, letters and numbers only) or send current one to keep:",
"admin_promo_edit_step2_bonus_days": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 2 of 4:</b> Bonus Days\n\nCode: <b>{code}</b>\nCurrent days: <b>{current_days}</b>\n\nEnter number of bonus days (1-365):",
"admin_promo_edit_step3_max_activations": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 3 of 4:</b> Max Activations\n\nCode: <b>{code}</b>\nDays: <b>{days}</b>\nCurrent activations: <b>{current_max}</b>\n\nEnter maximum number of activations (1-10000):",
"admin_promo_edit_step4_validity": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 4 of 4:</b> Validity Period\n\nCode: <b>{code}</b>\nDays: <b>{days}</b>\nActivations: <b>{max_act}</b>\nCurrent validity: <b>{current_validity}</b>\n\nEnter number of days the promo code is valid from today (1-365) or 0 for unlimited:",
"admin_promo_updated_success": "✅ Promo code updated!\n\n🎟 Code: <b>{code}</b>\n🎁 Bonus days: <b>{days}</b>\n🔢 Max activations: <b>{max_act}</b>\n⏰ Valid until: <b>{validity}</b>",
"admin_promo_update_failed": "❌ Error updating promo code",
"admin_promo_invalid_validity_days": "❌ Validity days must be from 0 to 365 days (0 = unlimited)",
"admin_logs_export_csv": "📄 Export to CSV",
"admin_logs_csv_export_started": "📄 Starting log export to CSV...",
"admin_logs_csv_export_success": "✅ Logs exported! File attached above.",
+13 -5
View File
@@ -104,11 +104,11 @@
"admin_sync_panel_button": "🔄 Синхронизация",
"admin_unknown_action": "Неизвестное действие администратора.",
"admin_stats_and_monitoring_section": "📊 Статистика и мониторинг",
"admin_user_management_section": "👥 Управление пользователями",
"admin_promo_marketing_section": "🎁 Промокоды и маркетинг",
"admin_system_functions_section": "⚙️ Системные функции",
"admin_ban_management_section": "🚫 Управление блокировками",
"admin_stats_and_monitoring_section": "📊 Статистика",
"admin_user_management_section": "👥 Пользователи",
"admin_promo_marketing_section": "🎁 Промокоды",
"admin_system_functions_section": "⚙️ Система",
"admin_ban_management_section": "🚫 Блокировки",
"admin_users_management_button": "👤 Управление пользователями",
"back_to_user_management_button": "⬅️ К управлению пользователями",
"admin_action_cancelled_default": "Действие отменено. Возврат в меню.",
@@ -309,6 +309,14 @@
"admin_bulk_promo_created_success": "✅ Создано {count} промокодов на {days} дней!",
"admin_bulk_promo_codes_file": "📄 Все созданные промокоды в файле ({count} шт.)",
"admin_promo_edit_step1_code": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 1 из 4:</b> Код промокода\n\nТекущий код: <b>{current_code}</b>\n\nВведите новый код промокода (3-30 символов, только буквы и цифры) или отправьте текущий для сохранения:",
"admin_promo_edit_step2_bonus_days": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 2 из 4:</b> Бонусные дни\n\nКод: <b>{code}</b>\nТекущие дни: <b>{current_days}</b>\n\nВведите количество бонусных дней (1-365):",
"admin_promo_edit_step3_max_activations": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 3 из 4:</b> Максимальные активации\n\nКод: <b>{code}</b>\nДни: <b>{days}</b>\nТекущие активации: <b>{current_max}</b>\n\nВведите максимальное количество активаций (1-10000):",
"admin_promo_edit_step4_validity": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 4 из 4:</b> Срок действия\n\nКод: <b>{code}</b>\nДни: <b>{days}</b>\nАктивации: <b>{max_act}</b>\nТекущий срок: <b>{current_validity}</b>\n\nВведите количество дней действия промокода от сегодня (1-365) или 0 для неограниченного срока:",
"admin_promo_updated_success": "✅ Промокод обновлен!\n\n🎟 Код: <b>{code}</b>\n🎁 Бонусные дни: <b>{days}</b>\n🔢 Максимальные активации: <b>{max_act}</b>\n⏰ Действует до: <b>{validity}</b>",
"admin_promo_update_failed": "❌ Ошибка обновления промокода",
"admin_promo_invalid_validity_days": "❌ Срок действия должен быть от 0 до 365 дней (0 = неограниченно)",
"admin_logs_export_csv": "📄 Экспорт в CSV",
"admin_logs_csv_export_started": "📄 Начинаю экспорт логов в CSV...",
"admin_logs_csv_export_success": "✅ Логи экспортированы! Файл прикреплен выше.",