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()