From cf9fbc5ccc4a205e0bde8a7c999be82f5af952f9 Mon Sep 17 00:00:00 2001 From: Egor Date: Fri, 19 Sep 2025 13:15:17 +0300 Subject: [PATCH] Fix campaign deletion confirmation and add editing workflow --- app/handlers/admin/campaigns.py | 864 +++++++++++++++++++++++++++++++- app/keyboards/admin.py | 112 ++++- app/states.py | 8 + 3 files changed, 965 insertions(+), 19 deletions(-) diff --git a/app/handlers/admin/campaigns.py b/app/handlers/admin/campaigns.py index 68708b67..482221fa 100644 --- a/app/handlers/admin/campaigns.py +++ b/app/handlers/admin/campaigns.py @@ -2,7 +2,7 @@ import logging import re from typing import List -from aiogram import Dispatcher, types, F +from aiogram import Bot, Dispatcher, types, F from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession @@ -24,6 +24,7 @@ from app.keyboards.admin import ( get_admin_campaigns_keyboard, get_admin_pagination_keyboard, get_campaign_bonus_type_keyboard, + get_campaign_edit_keyboard, get_campaign_management_keyboard, get_confirmation_keyboard, ) @@ -78,7 +79,12 @@ async def _get_bot_deep_link_from_message( def _build_campaign_servers_keyboard( - servers, selected_uuids: List[str] + servers, + selected_uuids: List[str], + *, + toggle_prefix: str = "campaign_toggle_server_", + save_callback: str = "campaign_servers_save", + back_callback: str = "admin_campaigns", ) -> types.InlineKeyboardMarkup: keyboard: List[List[types.InlineKeyboardButton]] = [] @@ -89,7 +95,7 @@ def _build_campaign_servers_keyboard( keyboard.append( [ types.InlineKeyboardButton( - text=text, callback_data=f"campaign_toggle_server_{server.id}" + text=text, callback_data=f"{toggle_prefix}{server.id}" ) ] ) @@ -97,15 +103,44 @@ def _build_campaign_servers_keyboard( keyboard.append( [ types.InlineKeyboardButton( - text="✅ Сохранить", callback_data="campaign_servers_save" + text="✅ Сохранить", callback_data=save_callback + ), + types.InlineKeyboardButton( + text="⬅️ Назад", callback_data=back_callback ), - types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_campaigns"), ] ) return types.InlineKeyboardMarkup(inline_keyboard=keyboard) +async def _render_campaign_edit_menu( + bot: Bot, + chat_id: int, + message_id: int, + campaign, + language: str, +): + texts = get_texts(language) + text = ( + "✏️ Редактирование кампании\n\n" + f"{_format_campaign_summary(campaign, texts)}\n" + "Выберите, что изменить:" + ) + + await bot.edit_message_text( + text=text, + chat_id=chat_id, + message_id=message_id, + reply_markup=get_campaign_edit_keyboard( + campaign.id, + is_balance_bonus=campaign.is_balance_bonus, + language=language, + ), + parse_mode="HTML", + ) + + @admin_required @error_handler async def show_campaigns_menu( @@ -300,6 +335,761 @@ async def show_campaign_detail( await callback.answer() +@admin_required +@error_handler +async def show_campaign_edit_menu( + callback: types.CallbackQuery, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + campaign_id = int(callback.data.split("_")[-1]) + campaign = await get_campaign_by_id(db, campaign_id) + + if not campaign: + await state.clear() + await callback.answer("❌ Кампания не найдена", show_alert=True) + return + + await state.clear() + + await _render_campaign_edit_menu( + callback.bot, + callback.message.chat.id, + callback.message.message_id, + campaign, + db_user.language, + ) + await callback.answer() + + +@admin_required +@error_handler +async def start_edit_campaign_name( + callback: types.CallbackQuery, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + campaign_id = int(callback.data.split("_")[-1]) + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await callback.answer("❌ Кампания не найдена", show_alert=True) + return + + await state.clear() + await state.set_state(AdminStates.editing_campaign_name) + await state.update_data( + editing_campaign_id=campaign_id, + campaign_edit_message_id=callback.message.message_id, + ) + + await callback.message.edit_text( + ( + "✏️ Изменение названия кампании\n\n" + f"Текущее название: {campaign.name}\n" + "Введите новое название (3-100 символов):" + ), + reply_markup=types.InlineKeyboardMarkup( + inline_keyboard=[ + [ + types.InlineKeyboardButton( + text="❌ Отмена", + callback_data=f"admin_campaign_edit_{campaign_id}", + ) + ] + ] + ), + ) + await callback.answer() + + +@admin_required +@error_handler +async def process_edit_campaign_name( + message: types.Message, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + data = await state.get_data() + campaign_id = data.get("editing_campaign_id") + if not campaign_id: + await message.answer("❌ Сессия редактирования устарела. Попробуйте снова.") + await state.clear() + return + + new_name = message.text.strip() + if len(new_name) < 3 or len(new_name) > 100: + await message.answer( + "❌ Название должно содержать от 3 до 100 символов. Попробуйте снова." + ) + return + + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await message.answer("❌ Кампания не найдена") + await state.clear() + return + + await update_campaign(db, campaign, name=new_name) + await state.clear() + + await message.answer("✅ Название обновлено.") + + edit_message_id = data.get("campaign_edit_message_id") + if edit_message_id: + await _render_campaign_edit_menu( + message.bot, + message.chat.id, + edit_message_id, + campaign, + db_user.language, + ) + + +@admin_required +@error_handler +async def start_edit_campaign_start_parameter( + callback: types.CallbackQuery, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + campaign_id = int(callback.data.split("_")[-1]) + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await callback.answer("❌ Кампания не найдена", show_alert=True) + return + + await state.clear() + await state.set_state(AdminStates.editing_campaign_start) + await state.update_data( + editing_campaign_id=campaign_id, + campaign_edit_message_id=callback.message.message_id, + ) + + await callback.message.edit_text( + ( + "🔗 Изменение стартового параметра\n\n" + f"Текущий параметр: {campaign.start_parameter}\n" + "Введите новый параметр (латинские буквы, цифры, - или _, 3-32 символа):" + ), + reply_markup=types.InlineKeyboardMarkup( + inline_keyboard=[ + [ + types.InlineKeyboardButton( + text="❌ Отмена", + callback_data=f"admin_campaign_edit_{campaign_id}", + ) + ] + ] + ), + ) + await callback.answer() + + +@admin_required +@error_handler +async def process_edit_campaign_start_parameter( + message: types.Message, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + data = await state.get_data() + campaign_id = data.get("editing_campaign_id") + if not campaign_id: + await message.answer("❌ Сессия редактирования устарела. Попробуйте снова.") + await state.clear() + return + + new_param = message.text.strip() + if not _CAMPAIGN_PARAM_REGEX.match(new_param): + await message.answer( + "❌ Разрешены только латинские буквы, цифры, символы - и _. Длина 3-32 символа." + ) + return + + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await message.answer("❌ Кампания не найдена") + await state.clear() + return + + existing = await get_campaign_by_start_parameter(db, new_param) + if existing and existing.id != campaign_id: + await message.answer("❌ Такой параметр уже используется. Введите другой вариант.") + return + + await update_campaign(db, campaign, start_parameter=new_param) + await state.clear() + + await message.answer("✅ Стартовый параметр обновлен.") + + edit_message_id = data.get("campaign_edit_message_id") + if edit_message_id: + await _render_campaign_edit_menu( + message.bot, + message.chat.id, + edit_message_id, + campaign, + db_user.language, + ) + + +@admin_required +@error_handler +async def start_edit_campaign_balance_bonus( + callback: types.CallbackQuery, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + campaign_id = int(callback.data.split("_")[-1]) + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await callback.answer("❌ Кампания не найдена", show_alert=True) + return + + if not campaign.is_balance_bonus: + await callback.answer("❌ У кампании другой тип бонуса", show_alert=True) + return + + await state.clear() + await state.set_state(AdminStates.editing_campaign_balance) + await state.update_data( + editing_campaign_id=campaign_id, + campaign_edit_message_id=callback.message.message_id, + ) + + await callback.message.edit_text( + ( + "💰 Изменение бонуса на баланс\n\n" + f"Текущий бонус: {get_texts(db_user.language).format_price(campaign.balance_bonus_kopeks)}\n" + "Введите новую сумму в рублях (например, 100 или 99.5):" + ), + reply_markup=types.InlineKeyboardMarkup( + inline_keyboard=[ + [ + types.InlineKeyboardButton( + text="❌ Отмена", + callback_data=f"admin_campaign_edit_{campaign_id}", + ) + ] + ] + ), + ) + await callback.answer() + + +@admin_required +@error_handler +async def process_edit_campaign_balance_bonus( + message: types.Message, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + data = await state.get_data() + campaign_id = data.get("editing_campaign_id") + if not campaign_id: + await message.answer("❌ Сессия редактирования устарела. Попробуйте снова.") + await state.clear() + return + + try: + amount_rubles = float(message.text.replace(",", ".")) + except ValueError: + await message.answer("❌ Введите корректную сумму (например, 100 или 99.5)") + return + + if amount_rubles <= 0: + await message.answer("❌ Сумма должна быть больше нуля") + return + + amount_kopeks = int(round(amount_rubles * 100)) + + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await message.answer("❌ Кампания не найдена") + await state.clear() + return + + if not campaign.is_balance_bonus: + await message.answer("❌ У кампании другой тип бонуса") + await state.clear() + return + + await update_campaign(db, campaign, balance_bonus_kopeks=amount_kopeks) + await state.clear() + + await message.answer("✅ Бонус обновлен.") + + edit_message_id = data.get("campaign_edit_message_id") + if edit_message_id: + await _render_campaign_edit_menu( + message.bot, + message.chat.id, + edit_message_id, + campaign, + db_user.language, + ) + + +async def _ensure_subscription_campaign(message_or_callback, campaign) -> bool: + if campaign.is_balance_bonus: + if isinstance(message_or_callback, types.CallbackQuery): + await message_or_callback.answer( + "❌ Для этой кампании доступен только бонус на баланс", + show_alert=True, + ) + else: + await message_or_callback.answer( + "❌ Для этой кампании нельзя изменить параметры подписки" + ) + return False + return True + + +@admin_required +@error_handler +async def start_edit_campaign_subscription_days( + callback: types.CallbackQuery, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + campaign_id = int(callback.data.split("_")[-1]) + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await callback.answer("❌ Кампания не найдена", show_alert=True) + return + + if not await _ensure_subscription_campaign(callback, campaign): + return + + await state.clear() + await state.set_state(AdminStates.editing_campaign_subscription_days) + await state.update_data( + editing_campaign_id=campaign_id, + campaign_edit_message_id=callback.message.message_id, + ) + + await callback.message.edit_text( + ( + "📅 Изменение длительности подписки\n\n" + f"Текущее значение: {campaign.subscription_duration_days or 0} д.\n" + "Введите новое количество дней (1-730):" + ), + reply_markup=types.InlineKeyboardMarkup( + inline_keyboard=[ + [ + types.InlineKeyboardButton( + text="❌ Отмена", + callback_data=f"admin_campaign_edit_{campaign_id}", + ) + ] + ] + ), + ) + await callback.answer() + + +@admin_required +@error_handler +async def process_edit_campaign_subscription_days( + message: types.Message, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + data = await state.get_data() + campaign_id = data.get("editing_campaign_id") + if not campaign_id: + await message.answer("❌ Сессия редактирования устарела. Попробуйте снова.") + await state.clear() + return + + try: + days = int(message.text.strip()) + except ValueError: + await message.answer("❌ Введите число дней (1-730)") + return + + if days <= 0 or days > 730: + await message.answer("❌ Длительность должна быть от 1 до 730 дней") + return + + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await message.answer("❌ Кампания не найдена") + await state.clear() + return + + if not await _ensure_subscription_campaign(message, campaign): + await state.clear() + return + + await update_campaign(db, campaign, subscription_duration_days=days) + await state.clear() + + await message.answer("✅ Длительность подписки обновлена.") + + edit_message_id = data.get("campaign_edit_message_id") + if edit_message_id: + await _render_campaign_edit_menu( + message.bot, + message.chat.id, + edit_message_id, + campaign, + db_user.language, + ) + + +@admin_required +@error_handler +async def start_edit_campaign_subscription_traffic( + callback: types.CallbackQuery, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + campaign_id = int(callback.data.split("_")[-1]) + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await callback.answer("❌ Кампания не найдена", show_alert=True) + return + + if not await _ensure_subscription_campaign(callback, campaign): + return + + await state.clear() + await state.set_state(AdminStates.editing_campaign_subscription_traffic) + await state.update_data( + editing_campaign_id=campaign_id, + campaign_edit_message_id=callback.message.message_id, + ) + + current_traffic = campaign.subscription_traffic_gb or 0 + traffic_text = "безлимит" if current_traffic == 0 else f"{current_traffic} ГБ" + + await callback.message.edit_text( + ( + "🌐 Изменение лимита трафика\n\n" + f"Текущее значение: {traffic_text}\n" + "Введите новый лимит в ГБ (0 = безлимит, максимум 10000):" + ), + reply_markup=types.InlineKeyboardMarkup( + inline_keyboard=[ + [ + types.InlineKeyboardButton( + text="❌ Отмена", + callback_data=f"admin_campaign_edit_{campaign_id}", + ) + ] + ] + ), + ) + await callback.answer() + + +@admin_required +@error_handler +async def process_edit_campaign_subscription_traffic( + message: types.Message, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + data = await state.get_data() + campaign_id = data.get("editing_campaign_id") + if not campaign_id: + await message.answer("❌ Сессия редактирования устарела. Попробуйте снова.") + await state.clear() + return + + try: + traffic = int(message.text.strip()) + except ValueError: + await message.answer("❌ Введите целое число (0 или больше)") + return + + if traffic < 0 or traffic > 10000: + await message.answer("❌ Лимит трафика должен быть от 0 до 10000 ГБ") + return + + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await message.answer("❌ Кампания не найдена") + await state.clear() + return + + if not await _ensure_subscription_campaign(message, campaign): + await state.clear() + return + + await update_campaign(db, campaign, subscription_traffic_gb=traffic) + await state.clear() + + await message.answer("✅ Лимит трафика обновлен.") + + edit_message_id = data.get("campaign_edit_message_id") + if edit_message_id: + await _render_campaign_edit_menu( + message.bot, + message.chat.id, + edit_message_id, + campaign, + db_user.language, + ) + + +@admin_required +@error_handler +async def start_edit_campaign_subscription_devices( + callback: types.CallbackQuery, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + campaign_id = int(callback.data.split("_")[-1]) + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await callback.answer("❌ Кампания не найдена", show_alert=True) + return + + if not await _ensure_subscription_campaign(callback, campaign): + return + + await state.clear() + await state.set_state(AdminStates.editing_campaign_subscription_devices) + await state.update_data( + editing_campaign_id=campaign_id, + campaign_edit_message_id=callback.message.message_id, + ) + + current_devices = campaign.subscription_device_limit or settings.DEFAULT_DEVICE_LIMIT + + await callback.message.edit_text( + ( + "📱 Изменение лимита устройств\n\n" + f"Текущее значение: {current_devices}\n" + f"Введите новое количество (1-{settings.MAX_DEVICES_LIMIT}):" + ), + reply_markup=types.InlineKeyboardMarkup( + inline_keyboard=[ + [ + types.InlineKeyboardButton( + text="❌ Отмена", + callback_data=f"admin_campaign_edit_{campaign_id}", + ) + ] + ] + ), + ) + await callback.answer() + + +@admin_required +@error_handler +async def process_edit_campaign_subscription_devices( + message: types.Message, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + data = await state.get_data() + campaign_id = data.get("editing_campaign_id") + if not campaign_id: + await message.answer("❌ Сессия редактирования устарела. Попробуйте снова.") + await state.clear() + return + + try: + devices = int(message.text.strip()) + except ValueError: + await message.answer("❌ Введите целое число устройств") + return + + if devices < 1 or devices > settings.MAX_DEVICES_LIMIT: + await message.answer( + f"❌ Количество устройств должно быть от 1 до {settings.MAX_DEVICES_LIMIT}" + ) + return + + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await message.answer("❌ Кампания не найдена") + await state.clear() + return + + if not await _ensure_subscription_campaign(message, campaign): + await state.clear() + return + + await update_campaign(db, campaign, subscription_device_limit=devices) + await state.clear() + + await message.answer("✅ Лимит устройств обновлен.") + + edit_message_id = data.get("campaign_edit_message_id") + if edit_message_id: + await _render_campaign_edit_menu( + message.bot, + message.chat.id, + edit_message_id, + campaign, + db_user.language, + ) + + +@admin_required +@error_handler +async def start_edit_campaign_subscription_servers( + callback: types.CallbackQuery, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + campaign_id = int(callback.data.split("_")[-1]) + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await callback.answer("❌ Кампания не найдена", show_alert=True) + return + + if not await _ensure_subscription_campaign(callback, campaign): + return + + servers, _ = await get_all_server_squads(db, available_only=False) + if not servers: + await callback.answer( + "❌ Не найдены доступные серверы. Добавьте серверы перед изменением.", + show_alert=True, + ) + return + + selected = list(campaign.subscription_squads or []) + + await state.clear() + await state.set_state(AdminStates.editing_campaign_subscription_servers) + await state.update_data( + editing_campaign_id=campaign_id, + campaign_edit_message_id=callback.message.message_id, + campaign_subscription_squads=selected, + ) + + keyboard = _build_campaign_servers_keyboard( + servers, + selected, + toggle_prefix=f"campaign_edit_toggle_{campaign_id}_", + save_callback=f"campaign_edit_servers_save_{campaign_id}", + back_callback=f"admin_campaign_edit_{campaign_id}", + ) + + await callback.message.edit_text( + ( + "🌍 Редактирование доступных серверов\n\n" + "Нажмите на сервер, чтобы добавить или убрать его из кампании.\n" + "После выбора нажмите \"✅ Сохранить\"." + ), + reply_markup=keyboard, + ) + await callback.answer() + + +@admin_required +@error_handler +async def toggle_edit_campaign_server( + callback: types.CallbackQuery, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + parts = callback.data.split("_") + try: + server_id = int(parts[-1]) + except (ValueError, IndexError): + await callback.answer("❌ Не удалось определить сервер", show_alert=True) + return + + data = await state.get_data() + campaign_id = data.get("editing_campaign_id") + if not campaign_id: + await callback.answer("❌ Сессия редактирования устарела", show_alert=True) + await state.clear() + return + + server = await get_server_squad_by_id(db, server_id) + if not server: + await callback.answer("❌ Сервер не найден", show_alert=True) + return + + selected = list(data.get("campaign_subscription_squads", [])) + + if server.squad_uuid in selected: + selected.remove(server.squad_uuid) + else: + selected.append(server.squad_uuid) + + await state.update_data(campaign_subscription_squads=selected) + + servers, _ = await get_all_server_squads(db, available_only=False) + keyboard = _build_campaign_servers_keyboard( + servers, + selected, + toggle_prefix=f"campaign_edit_toggle_{campaign_id}_", + save_callback=f"campaign_edit_servers_save_{campaign_id}", + back_callback=f"admin_campaign_edit_{campaign_id}", + ) + + await callback.message.edit_reply_markup(reply_markup=keyboard) + await callback.answer() + + +@admin_required +@error_handler +async def save_edit_campaign_subscription_servers( + callback: types.CallbackQuery, + db_user: User, + state: FSMContext, + db: AsyncSession, +): + data = await state.get_data() + campaign_id = data.get("editing_campaign_id") + if not campaign_id: + await callback.answer("❌ Сессия редактирования устарела", show_alert=True) + await state.clear() + return + + selected = list(data.get("campaign_subscription_squads", [])) + if not selected: + await callback.answer("❗ Выберите хотя бы один сервер", show_alert=True) + return + + campaign = await get_campaign_by_id(db, campaign_id) + if not campaign: + await state.clear() + await callback.answer("❌ Кампания не найдена", show_alert=True) + return + + if not await _ensure_subscription_campaign(callback, campaign): + await state.clear() + return + + await update_campaign(db, campaign, subscription_squads=selected) + await state.clear() + + await _render_campaign_edit_menu( + callback.bot, + callback.message.chat.id, + callback.message.message_id, + campaign, + db_user.language, + ) + await callback.answer("✅ Сохранено") + + @admin_required @error_handler async def toggle_campaign_status( @@ -386,8 +1176,8 @@ async def confirm_delete_campaign( await callback.message.edit_text( text, reply_markup=get_confirmation_keyboard( - confirm_callback=f"admin_campaign_delete_confirm_{campaign_id}", - cancel_callback=f"admin_campaign_manage_{campaign_id}", + confirm_action=f"admin_campaign_delete_confirm_{campaign_id}", + cancel_action=f"admin_campaign_manage_{campaign_id}", ), ) await callback.answer() @@ -765,6 +1555,43 @@ def register_handlers(dp: Dispatcher): dp.callback_query.register( show_campaign_detail, F.data.startswith("admin_campaign_manage_") ) + dp.callback_query.register( + start_edit_campaign_name, F.data.startswith("admin_campaign_edit_name_") + ) + dp.callback_query.register( + start_edit_campaign_start_parameter, + F.data.startswith("admin_campaign_edit_start_"), + ) + dp.callback_query.register( + start_edit_campaign_balance_bonus, + F.data.startswith("admin_campaign_edit_balance_"), + ) + dp.callback_query.register( + start_edit_campaign_subscription_days, + F.data.startswith("admin_campaign_edit_sub_days_"), + ) + dp.callback_query.register( + start_edit_campaign_subscription_traffic, + F.data.startswith("admin_campaign_edit_sub_traffic_"), + ) + dp.callback_query.register( + start_edit_campaign_subscription_devices, + F.data.startswith("admin_campaign_edit_sub_devices_"), + ) + dp.callback_query.register( + start_edit_campaign_subscription_servers, + F.data.startswith("admin_campaign_edit_sub_servers_"), + ) + dp.callback_query.register( + save_edit_campaign_subscription_servers, + F.data.startswith("campaign_edit_servers_save_"), + ) + dp.callback_query.register( + toggle_edit_campaign_server, F.data.startswith("campaign_edit_toggle_") + ) + dp.callback_query.register( + show_campaign_edit_menu, F.data.startswith("admin_campaign_edit_") + ) dp.callback_query.register( delete_campaign_confirmed, F.data.startswith("admin_campaign_delete_confirm_") ) @@ -803,3 +1630,26 @@ def register_handlers(dp: Dispatcher): process_campaign_subscription_devices, AdminStates.creating_campaign_subscription_devices, ) + dp.message.register( + process_edit_campaign_name, AdminStates.editing_campaign_name + ) + dp.message.register( + process_edit_campaign_start_parameter, + AdminStates.editing_campaign_start, + ) + dp.message.register( + process_edit_campaign_balance_bonus, + AdminStates.editing_campaign_balance, + ) + dp.message.register( + process_edit_campaign_subscription_days, + AdminStates.editing_campaign_subscription_days, + ) + dp.message.register( + process_edit_campaign_subscription_traffic, + AdminStates.editing_campaign_subscription_traffic, + ) + dp.message.register( + process_edit_campaign_subscription_devices, + AdminStates.editing_campaign_subscription_devices, + ) diff --git a/app/keyboards/admin.py b/app/keyboards/admin.py index d49daf82..3ecee4a5 100644 --- a/app/keyboards/admin.py +++ b/app/keyboards/admin.py @@ -167,21 +167,109 @@ def get_admin_campaigns_keyboard(language: str = "ru") -> InlineKeyboardMarkup: ]) -def get_campaign_management_keyboard(campaign_id: int, is_active: bool, language: str = "ru") -> InlineKeyboardMarkup: +def get_campaign_management_keyboard( + campaign_id: int, is_active: bool, language: str = "ru" +) -> InlineKeyboardMarkup: status_text = "🔴 Выключить" if is_active else "🟢 Включить" - return InlineKeyboardMarkup(inline_keyboard=[ - [ - InlineKeyboardButton(text="📊 Статистика", callback_data=f"admin_campaign_stats_{campaign_id}"), - InlineKeyboardButton(text=status_text, callback_data=f"admin_campaign_toggle_{campaign_id}") - ], - [ - InlineKeyboardButton(text="🗑️ Удалить", callback_data=f"admin_campaign_delete_{campaign_id}") - ], - [ - InlineKeyboardButton(text="⬅️ К списку", callback_data="admin_campaigns_list") + return InlineKeyboardMarkup( + inline_keyboard=[ + [ + InlineKeyboardButton( + text="📊 Статистика", + callback_data=f"admin_campaign_stats_{campaign_id}", + ), + InlineKeyboardButton( + text=status_text, + callback_data=f"admin_campaign_toggle_{campaign_id}", + ), + ], + [ + InlineKeyboardButton( + text="✏️ Редактировать", + callback_data=f"admin_campaign_edit_{campaign_id}", + ) + ], + [ + InlineKeyboardButton( + text="🗑️ Удалить", + callback_data=f"admin_campaign_delete_{campaign_id}", + ) + ], + [ + InlineKeyboardButton( + text="⬅️ К списку", callback_data="admin_campaigns_list" + ) + ], ] - ]) + ) + + +def get_campaign_edit_keyboard( + campaign_id: int, + *, + is_balance_bonus: bool, + language: str = "ru", +) -> InlineKeyboardMarkup: + texts = get_texts(language) + + keyboard: List[List[InlineKeyboardButton]] = [ + [ + InlineKeyboardButton( + text="✏️ Название", + callback_data=f"admin_campaign_edit_name_{campaign_id}", + ), + InlineKeyboardButton( + text="🔗 Параметр", + callback_data=f"admin_campaign_edit_start_{campaign_id}", + ), + ] + ] + + if is_balance_bonus: + keyboard.append( + [ + InlineKeyboardButton( + text="💰 Бонус на баланс", + callback_data=f"admin_campaign_edit_balance_{campaign_id}", + ) + ] + ) + else: + keyboard.extend( + [ + [ + InlineKeyboardButton( + text="📅 Длительность", + callback_data=f"admin_campaign_edit_sub_days_{campaign_id}", + ), + InlineKeyboardButton( + text="🌐 Трафик", + callback_data=f"admin_campaign_edit_sub_traffic_{campaign_id}", + ), + ], + [ + InlineKeyboardButton( + text="📱 Устройства", + callback_data=f"admin_campaign_edit_sub_devices_{campaign_id}", + ), + InlineKeyboardButton( + text="🌍 Серверы", + callback_data=f"admin_campaign_edit_sub_servers_{campaign_id}", + ), + ], + ] + ) + + keyboard.append( + [ + InlineKeyboardButton( + text=texts.BACK, callback_data=f"admin_campaign_manage_{campaign_id}" + ) + ] + ) + + return InlineKeyboardMarkup(inline_keyboard=keyboard) def get_campaign_bonus_type_keyboard(language: str = "ru") -> InlineKeyboardMarkup: diff --git a/app/states.py b/app/states.py index 35f461e0..ac1f76f7 100644 --- a/app/states.py +++ b/app/states.py @@ -50,6 +50,14 @@ class AdminStates(StatesGroup): creating_campaign_subscription_traffic = State() creating_campaign_subscription_devices = State() creating_campaign_subscription_servers = State() + + editing_campaign_name = State() + editing_campaign_start = State() + editing_campaign_balance = State() + editing_campaign_subscription_days = State() + editing_campaign_subscription_traffic = State() + editing_campaign_subscription_devices = State() + editing_campaign_subscription_servers = State() waiting_for_broadcast_message = State() waiting_for_broadcast_media = State()