diff --git a/app/handlers/admin/promocodes.py b/app/handlers/admin/promocodes.py index 13ea48a4..11e340c7 100644 --- a/app/handlers/admin/promocodes.py +++ b/app/handlers/admin/promocodes.py @@ -86,7 +86,8 @@ async def show_promocodes_list( "balance": "πŸ’°", "subscription_days": "πŸ“…", "trial_subscription": "🎁", - "promo_group": "🏷️" + "promo_group": "🏷️", + "discount": "πŸ’Έ" }.get(promo.type, "🎫") text += f"{status_emoji} {type_emoji} {promo.code}\n" @@ -99,6 +100,12 @@ async def show_promocodes_list( elif promo.type == PromoCodeType.PROMO_GROUP.value: if promo.promo_group: text += f"🏷️ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ°: {promo.promo_group.name}\n" + elif promo.type == PromoCodeType.DISCOUNT.value: + discount_hours = promo.subscription_days + if discount_hours > 0: + text += f"πŸ’Έ Π‘ΠΊΠΈΠ΄ΠΊΠ°: {promo.balance_bonus_kopeks}% ({discount_hours} Ρ‡.)\n" + else: + text += f"πŸ’Έ Π‘ΠΊΠΈΠ΄ΠΊΠ°: {promo.balance_bonus_kopeks}% (Π΄ΠΎ ΠΏΠΎΠΊΡƒΠΏΠΊΠΈ)\n" if promo.valid_until: text += f"⏰ Π”ΠΎ: {format_datetime(promo.valid_until)}\n" @@ -164,7 +171,8 @@ async def show_promocode_management( "balance": "πŸ’°", "subscription_days": "πŸ“…", "trial_subscription": "🎁", - "promo_group": "🏷️" + "promo_group": "🏷️", + "discount": "πŸ’Έ" }.get(promo.type, "🎫") text = f""" @@ -184,6 +192,12 @@ async def show_promocode_management( text += f"🏷️ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ°: {promo.promo_group.name} (ΠΏΡ€ΠΈΠΎΡ€ΠΈΡ‚Π΅Ρ‚: {promo.promo_group.priority})\n" elif promo.promo_group_id: text += f"🏷️ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ° ID: {promo.promo_group_id} (Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Π°)\n" + elif promo.type == PromoCodeType.DISCOUNT.value: + discount_hours = promo.subscription_days + if discount_hours > 0: + text += f"πŸ’Έ Π‘ΠΊΠΈΠ΄ΠΊΠ°: {promo.balance_bonus_kopeks}% (срок: {discount_hours} Ρ‡.)\n" + else: + text += f"πŸ’Έ Π‘ΠΊΠΈΠ΄ΠΊΠ°: {promo.balance_bonus_kopeks}% (Π΄ΠΎ ΠΏΠ΅Ρ€Π²ΠΎΠΉ ΠΏΠΎΠΊΡƒΠΏΠΊΠΈ)\n" if promo.valid_until: text += f"⏰ ДСйствуСт Π΄ΠΎ: {format_datetime(promo.valid_until)}\n" @@ -496,7 +510,8 @@ async def select_promocode_type( "balance": "πŸ’° ПополнСниС баланса", "days": "πŸ“… Π”Π½ΠΈ подписки", "trial": "🎁 ВСстовая подписка", - "group": "🏷️ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ°" + "group": "🏷️ ΠŸΡ€ΠΎΠΌΠΎΠ³Ρ€ΡƒΠΏΠΏΠ°", + "discount": "πŸ’Έ ΠžΠ΄Π½ΠΎΡ€Π°Π·ΠΎΠ²Π°Ρ скидка" } await state.update_data(promocode_type=promo_type) @@ -556,6 +571,12 @@ async def process_promocode_code( f"Π’Π²Π΅Π΄ΠΈΡ‚Π΅ количСство Π΄Π½Π΅ΠΉ тСстовой подписки:" ) await state.set_state(AdminStates.setting_promocode_value) + elif promo_type == "discount": + await message.answer( + f"πŸ’Έ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄: {code}\n\n" + f"Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΏΡ€ΠΎΡ†Π΅Π½Ρ‚ скидки (1-100):" + ) + 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) @@ -654,6 +675,9 @@ async def process_promocode_value( elif promo_type in ["days", "trial"] and (value < 1 or value > 3650): await message.answer("❌ ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π΄Π½Π΅ΠΉ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 1 Π΄ΠΎ 3650") return + elif promo_type == "discount" and (value < 1 or value > 100): + await message.answer("❌ ΠŸΡ€ΠΎΡ†Π΅Π½Ρ‚ скидки Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 1 Π΄ΠΎ 100") + return await state.update_data(promocode_value=value) @@ -821,7 +845,7 @@ async def process_promocode_expiry( 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) @@ -829,6 +853,17 @@ async def process_promocode_expiry( promo_group_id = data.get('promo_group_id') promo_group_name = data.get('promo_group_name') + # Для DISCOUNT Ρ‚ΠΈΠΏΠ° Π½ΡƒΠΆΠ½ΠΎ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ ΡΠΏΡ€ΠΎΡΠΈΡ‚ΡŒ срок дСйствия скидки Π² часах + if promo_type == "discount": + await state.update_data(promocode_expiry_days=expiry_days) + await message.answer( + f"⏰ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄: {code}\n\n" + f"Π’Π²Π΅Π΄ΠΈΡ‚Π΅ срок дСйствия скидки Π² часах (0-8760):\n" + f"0 = бСссрочно Π΄ΠΎ ΠΏΠ΅Ρ€Π²ΠΎΠΉ ΠΏΠΎΠΊΡƒΠΏΠΊΠΈ" + ) + await state.set_state(AdminStates.setting_discount_hours) + return + valid_until = None if expiry_days > 0: valid_until = datetime.utcnow() + timedelta(days=expiry_days) @@ -892,6 +927,80 @@ async def process_promocode_expiry( await message.answer("❌ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠ΅ число Π΄Π½Π΅ΠΉ") +@admin_required +@error_handler +async def process_discount_hours( + message: types.Message, + db_user: User, + state: FSMContext, + db: AsyncSession +): + """ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ Π²Π²ΠΎΠ΄Π° срока дСйствия скидки Π² часах для DISCOUNT ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°.""" + data = await state.get_data() + + try: + discount_hours = int(message.text.strip()) + + if discount_hours < 0 or discount_hours > 8760: + await message.answer("❌ Π‘Ρ€ΠΎΠΊ дСйствия скидки Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 0 Π΄ΠΎ 8760 часов") + return + + code = data.get('promocode_code') + value = data.get('promocode_value', 0) # ΠŸΡ€ΠΎΡ†Π΅Π½Ρ‚ скидки + max_uses = data.get('promocode_max_uses', 1) + expiry_days = data.get('promocode_expiry_days', 0) + + valid_until = None + if expiry_days > 0: + valid_until = datetime.utcnow() + timedelta(days=expiry_days) + + # Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ DISCOUNT ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ + # balance_bonus_kopeks = ΠΏΡ€ΠΎΡ†Π΅Π½Ρ‚ скидки (НЕ ΠΊΠΎΠΏΠ΅ΠΉΠΊΠΈ!) + # subscription_days = срок дСйствия скидки Π² часах (НЕ Π΄Π½ΠΈ!) + promocode = await create_promocode( + db=db, + code=code, + type=PromoCodeType.DISCOUNT, + balance_bonus_kopeks=value, # ΠŸΡ€ΠΎΡ†Π΅Π½Ρ‚ (1-100) + subscription_days=discount_hours, # Часы (0-8760) + max_uses=max_uses, + valid_until=valid_until, + created_by=db_user.id, + promo_group_id=None + ) + + summary_text = f""" +βœ… ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ создан! + +🎫 Код: {promocode.code} +πŸ“ Π’ΠΈΠΏ: ΠžΠ΄Π½ΠΎΡ€Π°Π·ΠΎΠ²Π°Ρ скидка +πŸ’Έ Π‘ΠΊΠΈΠ΄ΠΊΠ°: {promocode.balance_bonus_kopeks}% +""" + + if discount_hours > 0: + summary_text += f"⏰ Π‘Ρ€ΠΎΠΊ скидки: {discount_hours} Ρ‡.\n" + else: + summary_text += f"⏰ Π‘Ρ€ΠΎΠΊ скидки: Π΄ΠΎ ΠΏΠ΅Ρ€Π²ΠΎΠΉ ΠΏΠΎΠΊΡƒΠΏΠΊΠΈ\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"Π‘ΠΎΠ·Π΄Π°Π½ DISCOUNT ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ {code} ({value}%, {discount_hours}Ρ‡) администратором {db_user.telegram_id}") + + except ValueError: + await message.answer("❌ Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠ΅ число часов") + + async def handle_edit_expiry( message: types.Message, db_user: User, @@ -1182,4 +1291,5 @@ def register_handlers(dp: Dispatcher): 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_discount_hours, AdminStates.setting_discount_hours) diff --git a/app/states.py b/app/states.py index b338f836..e847e6b4 100644 --- a/app/states.py +++ b/app/states.py @@ -57,6 +57,7 @@ class AdminStates(StatesGroup): setting_promocode_value = State() setting_promocode_uses = State() setting_promocode_expiry = State() + setting_discount_hours = State() # Для DISCOUNT: Π²Π²ΠΎΠ΄ срока дСйствия скидки Π² часах selecting_promo_group = State() creating_campaign_name = State()