From 1e79b153511ed9a01de94414257940fc7eafdff2 Mon Sep 17 00:00:00 2001 From: VAQYBIN Date: Sun, 25 Jan 2026 20:43:01 +0500 Subject: [PATCH] =?UTF-8?q?fix(promo):=20=D0=98=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=81=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=D0=BC=20=D0=BF=D1=80=D0=BE=D0=BC=D0=BE=D0=BA=D0=BE=D0=B4?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D1=81=D0=BA=D0=B8=D0=B4=D0=BA=D1=83=20?= =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B3,=20=D0=BA=D0=BE=D0=B3=D0=B4=D0=B0=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=BC=D0=BE=D0=BA=D0=BE=D0=B4=20=D0=BD=D0=B0=20=D1=81?= =?UTF-8?q?=D0=BA=D0=B8=D0=B4=D0=BA=D1=83=20=D0=BD=D0=B5=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B3=20=D0=B1=D1=8B=D1=82=D1=8C=20=D1=83=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D1=91=D0=BD,=20=D0=B5=D1=81=D0=BB=D0=B8=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=20=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B4=D0=BA=D1=83,=20=D0=BD=D0=BE=20=D0=BD=D0=B5=20=D0=B2?= =?UTF-8?q?=D0=BE=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BB=D1=81=D1=8F=20=D0=B5=D0=B9.=20=D0=94=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=B8=D1=81=D1=82=D0=B5=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=BC=D0=BE=D0=BA=D0=BE=D0=B4=D0=B0,=20?= =?UTF-8?q?=D0=B4=D0=B0=D0=B6=D0=B5=20=D0=B5=D1=81=D0=BB=D0=B8=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=BC=D0=BE=D0=BA=D0=BE=D0=B4=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=81=D0=BA=D0=B8=D0=B4=D0=BA=D1=83=20=D1=83=D0=B6=D0=B5=20?= =?UTF-8?q?=D0=B1=D1=8B=D0=BB=20=D0=B2=D0=B2=D0=B5=D0=B4=D1=91=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/services/promo_code_service.py | 12 +++++++++++- db/dal/active_discount_dal.py | 17 +++++++++++++++++ db/dal/promo_code_dal.py | 17 +++++++++++++++-- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/bot/services/promo_code_service.py b/bot/services/promo_code_service.py index 5e477e8..e56cea9 100644 --- a/bot/services/promo_code_service.py +++ b/bot/services/promo_code_service.py @@ -1,5 +1,5 @@ import logging -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy.ext.asyncio import AsyncSession from typing import Optional, Tuple, Dict from aiogram import Bot @@ -168,6 +168,16 @@ class PromoCodeService: await active_discount_dal.clear_active_discount(session, user_id) return None + # Check if promo code has expired + if promo.valid_until and promo.valid_until <= datetime.now(timezone.utc): + # Promo code expired - clear the discount + logging.info( + f"Promo code {promo.code} expired (valid_until: {promo.valid_until}). " + f"Clearing active discount for user {user_id}" + ) + await active_discount_dal.clear_active_discount(session, user_id) + return None + return (active_discount.discount_percentage, promo.code) def calculate_discounted_price( diff --git a/db/dal/active_discount_dal.py b/db/dal/active_discount_dal.py index 846bd80..5b99c51 100644 --- a/db/dal/active_discount_dal.py +++ b/db/dal/active_discount_dal.py @@ -69,3 +69,20 @@ async def clear_active_discount( if cleared: logging.info(f"Active discount cleared for user {user_id}") return cleared + + +async def clear_active_discounts_by_promo_code( + session: AsyncSession, + promo_code_id: int +) -> int: + """ + Clear all active discounts associated with a specific promo code. + Returns the number of discounts cleared. + """ + stmt = delete(ActiveDiscount).where(ActiveDiscount.promo_code_id == promo_code_id) + result = await session.execute(stmt) + await session.flush() + count = result.rowcount + if count > 0: + logging.info(f"Cleared {count} active discount(s) for promo_code_id={promo_code_id}") + return count diff --git a/db/dal/promo_code_dal.py b/db/dal/promo_code_dal.py index 0f6b174..1f1902e 100644 --- a/db/dal/promo_code_dal.py +++ b/db/dal/promo_code_dal.py @@ -132,16 +132,29 @@ async def update_promo_code(session: AsyncSession, promo_id: int, async def delete_promo_code(session: AsyncSession, promo_id: int) -> Optional[PromoCode]: + from db.dal import active_discount_dal + promo = await get_promo_code_by_id(session, promo_id) if not promo: return None - # First, delete related activations due to foreign key constraint + + # 1. Clear all active discounts referencing this promo code + await active_discount_dal.clear_active_discounts_by_promo_code(session, promo_id) + + # 2. Set promo_code_id to NULL in payments table to avoid FK violation + stmt = update(Payment).where(Payment.promo_code_id == promo_id).values(promo_code_id=None) + await session.execute(stmt) + + # 3. Delete related activations activations = await get_promo_activations_by_code_id(session, promo_id) for activation in activations: await session.delete(activation) - + + # 4. Delete the promo code itself await session.delete(promo) await session.flush() + + logging.info(f"Promo code '{promo.code}' (ID: {promo_id}) deleted successfully") return promo