import logging
import random
import string
import csv
import io
from aiogram import Router, F, types
from aiogram.filters import StateFilter
from aiogram.fsm.context import FSMContext
from datetime import datetime, timedelta, timezone
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from config.settings import Settings
from db.dal import promo_code_dal
from bot.states.admin_states import AdminStates
from bot.keyboards.inline.admin_keyboards import get_back_to_admin_panel_keyboard, get_admin_panel_keyboard
from aiogram.utils.keyboard import InlineKeyboardBuilder, InlineKeyboardButton
from bot.middlewares.i18n import JsonI18n
router = Router(name="promo_bulk_router")
async def create_bulk_promo_prompt_handler(callback: types.CallbackQuery,
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 callback.message:
await callback.answer("Error preparing bulk promo creation.",
show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
# Step 1: Ask for quantity
prompt_text = _(
"admin_bulk_promo_step1_quantity",
default="π ΠΠ°ΡΡΠΎΠ²ΠΎΠ΅ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²\n\nΠ¨Π°Π³ 1 ΠΈΠ· 4: ΠΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ\n\nΠΠ²Π΅Π΄ΠΈΡΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ² Π΄Π»Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ (1-100):"
)
try:
await callback.message.edit_text(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML")
except Exception as e:
logging.warning(
f"Could not edit message for bulk promo prompt: {e}. Sending new.")
await callback.message.answer(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML")
await callback.answer()
await state.set_state(AdminStates.waiting_for_bulk_promo_quantity)
def generate_unique_promo_code(length: int = 8) -> str:
"""Generate a unique random promo code"""
characters = string.ascii_uppercase + string.digits
return ''.join(random.choice(characters) for _ in range(length))
# Step 1: Process quantity
@router.message(AdminStates.waiting_for_bulk_promo_quantity, F.text)
async def process_bulk_promo_quantity_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)
try:
quantity = int(message.text.strip())
if not (1 <= quantity <= 100):
await message.answer(_(
"admin_bulk_promo_invalid_quantity",
default="β ΠΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ² Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±ΡΡΡ ΠΎΡ 1 Π΄ΠΎ 100"
))
return
await state.update_data(quantity=quantity)
# Step 2: Ask for bonus days
prompt_text = _(
"admin_bulk_promo_step2_bonus_days",
default="π ΠΠ°ΡΡΠΎΠ²ΠΎΠ΅ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²\n\nΠ¨Π°Π³ 2 ΠΈΠ· 4: ΠΠΎΠ½ΡΡΠ½ΡΠ΅ Π΄Π½ΠΈ\n\nΠΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ: {quantity}\n\nΠΠ²Π΅Π΄ΠΈΡΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ Π±ΠΎΠ½ΡΡΠ½ΡΡ
Π΄Π½Π΅ΠΉ Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄Π° (1-365):",
quantity=quantity
)
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_bulk_promo_bonus_days)
except ValueError:
await message.answer(_(
"admin_promo_invalid_number",
default="β ΠΠ²Π΅Π΄ΠΈΡΠ΅ ΠΊΠΎΡΡΠ΅ΠΊΡΠ½ΠΎΠ΅ ΡΠΈΡΠ»ΠΎ"
))
except Exception as e:
logging.error(f"Error processing bulk promo quantity: {e}")
await message.answer(_("error_occurred_try_again"))
# Step 2: Process bonus days
@router.message(AdminStates.waiting_for_bulk_promo_bonus_days, F.text)
async def process_bulk_promo_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)
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(bonus_days=bonus_days)
# Step 3: Ask for max activations
data = await state.get_data()
prompt_text = _(
"admin_bulk_promo_step3_max_activations",
default="π ΠΠ°ΡΡΠΎΠ²ΠΎΠ΅ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²\n\nΠ¨Π°Π³ 3 ΠΈΠ· 4: ΠΠΈΠΌΠΈΡ Π°ΠΊΡΠΈΠ²Π°ΡΠΈΠΉ\n\nΠΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ: {quantity}\nΠΠΎΠ½ΡΡΠ½ΡΠ΅ Π΄Π½ΠΈ: {bonus_days}\n\nΠΠ²Π΅Π΄ΠΈΡΠ΅ ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΠΎΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ Π°ΠΊΡΠΈΠ²Π°ΡΠΈΠΉ Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄Π° (1-10000):",
quantity=data.get("quantity"),
bonus_days=bonus_days
)
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_bulk_promo_max_activations)
except ValueError:
await message.answer(_(
"admin_promo_invalid_number",
default="β ΠΠ²Π΅Π΄ΠΈΡΠ΅ ΠΊΠΎΡΡΠ΅ΠΊΡΠ½ΠΎΠ΅ ΡΠΈΡΠ»ΠΎ"
))
except Exception as e:
logging.error(f"Error processing bulk promo bonus days: {e}")
await message.answer(_("error_occurred_try_again"))
# Step 3: Process max activations
@router.message(AdminStates.waiting_for_bulk_promo_max_activations, F.text)
async def process_bulk_promo_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)
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(max_activations=max_activations)
# Step 4: Ask for validity
data = await state.get_data()
prompt_text = _(
"admin_bulk_promo_step4_validity",
default="π ΠΠ°ΡΡΠΎΠ²ΠΎΠ΅ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²\n\nΠ¨Π°Π³ 4 ΠΈΠ· 4: Π‘ΡΠΎΠΊ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ\n\nΠΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ: {quantity}\nΠΠΎΠ½ΡΡΠ½ΡΠ΅ Π΄Π½ΠΈ: {bonus_days}\nΠΠ°ΠΊΡ. Π°ΠΊΡΠΈΠ²Π°ΡΠΈΠΉ: {max_activations}\n\nΠΡΠ±Π΅ΡΠΈΡΠ΅ ΡΡΠΎΠΊ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²:",
quantity=data.get("quantity"),
bonus_days=data.get("bonus_days"),
max_activations=max_activations
)
# Create keyboard for validity options
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text=_("admin_promo_unlimited_validity", default="π ΠΠ΅Π· ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠΉ"),
callback_data="bulk_promo_unlimited_validity"
)
)
builder.row(
InlineKeyboardButton(
text=_("admin_promo_set_validity_days", default="π
Π£ΠΊΠ°Π·Π°ΡΡ Π΄Π½ΠΈ"),
callback_data="bulk_promo_set_validity"
)
)
builder.row(
InlineKeyboardButton(
text=_("admin_back_to_panel", default="π Π Π°Π΄ΠΌΠΈΠ½ ΠΏΠ°Π½Π΅Π»Ρ"),
callback_data="admin_action:main"
)
)
await message.answer(
prompt_text,
reply_markup=builder.as_markup(),
parse_mode="HTML"
)
await state.set_state(AdminStates.waiting_for_bulk_promo_validity_days)
except ValueError:
await message.answer(_(
"admin_promo_invalid_number",
default="β ΠΠ²Π΅Π΄ΠΈΡΠ΅ ΠΊΠΎΡΡΠ΅ΠΊΡΠ½ΠΎΠ΅ ΡΠΈΡΠ»ΠΎ"
))
except Exception as e:
logging.error(f"Error processing bulk promo max activations: {e}")
await message.answer(_("error_occurred_try_again"))
# Step 4: Handle unlimited validity
@router.callback_query(F.data == "bulk_promo_unlimited_validity", StateFilter(AdminStates.waiting_for_bulk_promo_validity_days))
async def process_bulk_promo_unlimited_validity(callback: types.CallbackQuery,
state: FSMContext,
i18n_data: dict,
settings: Settings,
session: AsyncSession):
await state.update_data(validity_days=None)
await create_bulk_promo_codes_final(callback, state, i18n_data, settings, session)
# Step 4: Handle set validity
@router.callback_query(F.data == "bulk_promo_set_validity", StateFilter(AdminStates.waiting_for_bulk_promo_validity_days))
async def process_bulk_promo_set_validity(callback: types.CallbackQuery,
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 or not callback.message:
await callback.answer("Error processing validity.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
data = await state.get_data()
prompt_text = _(
"admin_bulk_promo_enter_validity_days",
default="π ΠΠ°ΡΡΠΎΠ²ΠΎΠ΅ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²\n\nΠ¨Π°Π³ 4 ΠΈΠ· 4: Π‘ΡΠΎΠΊ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ\n\nΠΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ: {quantity}\nΠΠΎΠ½ΡΡΠ½ΡΠ΅ Π΄Π½ΠΈ: {bonus_days}\nΠΠ°ΠΊΡ. Π°ΠΊΡΠΈΠ²Π°ΡΠΈΠΉ: {max_activations}\n\nΠΠ²Π΅Π΄ΠΈΡΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ Π΄Π½Π΅ΠΉ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ² (1-365):",
quantity=data.get("quantity"),
bonus_days=data.get("bonus_days"),
max_activations=data.get("max_activations")
)
try:
await callback.message.edit_text(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML"
)
except Exception:
await callback.message.answer(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML"
)
await callback.answer()
# Step 4: Process validity days
@router.message(AdminStates.waiting_for_bulk_promo_validity_days, F.text)
async def process_bulk_promo_validity_days_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)
try:
validity_days = int(message.text.strip())
if not (1 <= validity_days <= 365):
await message.answer(_(
"admin_promo_invalid_validity_days",
default="β Π‘ΡΠΎΠΊ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±ΡΡΡ ΠΎΡ 1 Π΄ΠΎ 365 Π΄Π½Π΅ΠΉ"
))
return
await state.update_data(validity_days=validity_days)
await create_bulk_promo_codes_final(message, state, i18n_data, settings, session)
except ValueError:
await message.answer(_(
"admin_promo_invalid_number",
default="β ΠΠ²Π΅Π΄ΠΈΡΠ΅ ΠΊΠΎΡΡΠ΅ΠΊΡΠ½ΠΎΠ΅ ΡΠΈΡΠ»ΠΎ"
))
except Exception as e:
logging.error(f"Error processing bulk promo validity days: {e}")
await message.answer(_("error_occurred_try_again"))
async def create_bulk_promo_codes_final(callback_or_message,
state: FSMContext,
i18n_data: dict,
settings: Settings,
session: AsyncSession):
"""Final step - create multiple promo codes in database"""
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
try:
data = await state.get_data()
quantity = data["quantity"]
# Show progress message
progress_text = _(
"admin_bulk_promo_creating",
default="π Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ {quantity} ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²...",
quantity=quantity
)
if hasattr(callback_or_message, 'message'): # CallbackQuery
try:
await callback_or_message.message.edit_text(progress_text, parse_mode="HTML")
except Exception:
await callback_or_message.message.answer(progress_text, parse_mode="HTML")
await callback_or_message.answer()
else: # Message
await callback_or_message.answer(progress_text, parse_mode="HTML")
# Generate and create promo codes
created_codes = []
failed_codes = []
for i in range(quantity):
try:
# Generate unique code
attempts = 0
while attempts < 10: # Max 10 attempts to generate unique code
promo_code = generate_unique_promo_code()
existing_promo = await promo_code_dal.get_promo_code_by_code(session, promo_code)
if not existing_promo:
break
attempts += 1
if attempts >= 10:
failed_codes.append(f"ΠΠΎΠ΄ #{i+1} (Π½Π΅ ΡΠ΄Π°Π»ΠΎΡΡ ΡΠ³Π΅Π½Π΅ΡΠΈΡΠΎΠ²Π°ΡΡ ΡΠ½ΠΈΠΊΠ°Π»ΡΠ½ΡΠΉ)")
continue
# Prepare promo code data
promo_data = {
"code": promo_code,
"bonus_days": data["bonus_days"],
"max_activations": data["max_activations"],
"current_activations": 0,
"is_active": True,
"created_by_admin_id": callback_or_message.from_user.id,
"created_at": datetime.now(timezone.utc)
}
# Set validity
if data.get("validity_days"):
promo_data["valid_until"] = datetime.now(timezone.utc) + timedelta(days=data["validity_days"])
else:
promo_data["valid_until"] = None
# Create promo code
created_promo = await promo_code_dal.create_promo_code(session, promo_data)
created_codes.append(created_promo.code)
except Exception as e:
logging.error(f"Error creating bulk promo code #{i+1}: {e}")
failed_codes.append(f"ΠΠΎΠ΄ #{i+1} ({str(e)[:50]})")
await session.commit()
# Success message
success_lines = [
_(
"admin_bulk_promo_created_title",
default="β
ΠΠ°ΡΡΠΎΠ²ΠΎΠ΅ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π·Π°Π²Π΅ΡΡΠ΅Π½ΠΎ!\n"
),
_(
"admin_bulk_promo_created_stats",
default="π Π‘ΠΎΠ·Π΄Π°Π½ΠΎ: {created} ΠΈΠ· {total}",
created=len(created_codes),
total=quantity
)
]
if data.get("validity_days"):
validity_text = f"{data['validity_days']} Π΄Π½Π΅ΠΉ"
else:
validity_text = _("admin_promo_unlimited", default="ΠΠ΅Π· ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠΉ")
success_lines.append(
_(
"admin_bulk_promo_settings",
default="π ΠΠΎΠ½ΡΡΠ½ΡΠ΅ Π΄Π½ΠΈ: {bonus_days}\n"
"π ΠΠ°ΠΊΡ. Π°ΠΊΡΠΈΠ²Π°ΡΠΈΠΉ: {max_activations}\n"
"β° Π‘ΡΠΎΠΊ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ: {validity}",
bonus_days=data["bonus_days"],
max_activations=data["max_activations"],
validity=validity_text
)
)
# Create CSV file with promo codes if any were created
csv_file = None
if created_codes:
success_lines.append(f"\nπ Π‘ΠΎΠ·Π΄Π°Π½ΠΎ {len(created_codes)} ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²")
success_lines.append("π CSV ΡΠ°ΠΉΠ» Ρ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄Π°ΠΌΠΈ ΠΎΡΠΏΡΠ°Π²Π»Π΅Π½ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΌ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ΠΌ")
# Create CSV file
output = io.StringIO()
writer = csv.writer(output)
# CSV headers
writer.writerow([
"ΠΡΠΎΠΌΠΎΠΊΠΎΠ΄", "ΠΠΎΠ½ΡΡΠ½ΡΠ΅ Π΄Π½ΠΈ", "ΠΠ°ΠΊΡ. Π°ΠΊΡΠΈΠ²Π°ΡΠΈΠΈ", "ΠΠ΅ΠΉΡΡΠ²ΠΈΡΠ΅Π»Π΅Π½ Π΄ΠΎ",
"ΠΠΎΠΌΠ°Π½Π΄Π° Π΄Π»Ρ ΡΡΠ°ΡΡΠ°", "Π‘ΡΡΠ»ΠΊΠ° Π΄Π»Ρ Π°ΠΊΡΠΈΠ²Π°ΡΠΈΠΈ"
])
# Get real bot username
bot_username = 'your_bot' # fallback
try:
if hasattr(callback_or_message, 'message'):
bot = callback_or_message.message.bot
else:
bot = callback_or_message.bot
bot_info = await bot.get_me()
bot_username = bot_info.username or 'your_bot'
except Exception as e:
logging.error(f"Failed to get bot username for CSV links: {e}")
bot_username = 'your_bot'
for code in created_codes:
# Determine validity info
if data.get("validity_days"):
valid_until = (datetime.now(timezone.utc) + timedelta(days=data["validity_days"])).strftime("%Y-%m-%d %H:%M:%S")
else:
valid_until = "ΠΠ΅Π· ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠΉ"
start_command = f"/start promo_{code}"
telegram_link = f"https://t.me/{bot_username}?start=promo_{code}"
writer.writerow([
code,
data["bonus_days"],
data["max_activations"],
valid_until,
start_command,
telegram_link
])
output.seek(0)
# Create file for sending
filename = f"bulk_promo_codes_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
csv_file = types.BufferedInputFile(
output.getvalue().encode('utf-8-sig'), # BOM for correct Excel display
filename=filename
)
if failed_codes:
success_lines.append(f"\nβ ΠΡΠΈΠ±ΠΊΠΈ ({len(failed_codes)}):")
for error in failed_codes[:5]: # Show first 5 errors
success_lines.append(error)
if len(failed_codes) > 5:
success_lines.append(f"... ΠΈ Π΅ΡΠ΅ {len(failed_codes) - 5} ΠΎΡΠΈΠ±ΠΎΠΊ")
success_text = "\n".join(success_lines)
if hasattr(callback_or_message, 'message'): # CallbackQuery
try:
await callback_or_message.message.edit_text(
success_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML"
)
message_obj = callback_or_message.message
except Exception:
message_obj = await callback_or_message.message.answer(
success_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML"
)
await callback_or_message.answer()
else: # Message
message_obj = await callback_or_message.answer(
success_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML"
)
# Send CSV file if created
if csv_file:
csv_caption = f"π ΠΡΠΎΠΌΠΎΠΊΠΎΠ΄Ρ Π΄Π»Ρ ΠΌΠ°ΡΡΠΎΠ²ΠΎΠ³ΠΎ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ\nπ« ΠΡΠ΅Π³ΠΎ: {len(created_codes)} ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²\nπ ΠΠΎΠ½ΡΡ: {data['bonus_days']} Π΄Π½Π΅ΠΉ ΠΊΠ°ΠΆΠ΄ΡΠΉ"
await message_obj.answer_document(csv_file, caption=csv_caption)
await state.clear()
except Exception as e:
logging.error(f"Error creating bulk promo codes: {e}")
error_text = _("error_occurred_try_again", default="β ΠΡΠΎΠΈΠ·ΠΎΡΠ»Π° ΠΎΡΠΈΠ±ΠΊΠ°. ΠΠΎΠΏΡΠΎΠ±ΡΠΉΡΠ΅ ΡΠ½ΠΎΠ²Π°.")
if hasattr(callback_or_message, 'message'): # CallbackQuery
await callback_or_message.message.answer(error_text)
else: # Message
await callback_or_message.answer(error_text)
await state.clear()
# Cancel bulk promo creation
@router.callback_query(
F.data == "admin_action:main",
StateFilter(
AdminStates.waiting_for_bulk_promo_quantity,
AdminStates.waiting_for_bulk_promo_bonus_days,
AdminStates.waiting_for_bulk_promo_max_activations,
AdminStates.waiting_for_bulk_promo_validity_days,
),
)
async def cancel_bulk_promo_creation_state_to_menu(callback: types.CallbackQuery,
state: FSMContext,
settings: Settings,
i18n_data: dict,
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 callback.message:
await callback.answer("Error cancelling.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
try:
await callback.message.edit_text(
_(key="admin_panel_title"),
reply_markup=get_admin_panel_keyboard(i18n, current_lang, settings)
)
except Exception:
await callback.message.answer(
_(key="admin_panel_title"),
reply_markup=get_admin_panel_keyboard(i18n, current_lang, settings)
)
await callback.answer(_("admin_bulk_promo_creation_cancelled", default="ΠΠ°ΡΡΠΎΠ²ΠΎΠ΅ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ² ΠΎΡΠΌΠ΅Π½Π΅Π½ΠΎ"))
await state.clear()