Improve GDPR implementation
This commit is contained in:
103
services/tenant/app/jobs/subscription_downgrade.py
Normal file
103
services/tenant/app/jobs/subscription_downgrade.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
Background job to process subscription downgrades at period end
|
||||
|
||||
Runs periodically to check for subscriptions with:
|
||||
- status = 'pending_cancellation'
|
||||
- cancellation_effective_date <= now()
|
||||
|
||||
Converts them to 'inactive' status
|
||||
"""
|
||||
|
||||
import structlog
|
||||
import asyncio
|
||||
from datetime import datetime, timezone
|
||||
from sqlalchemy import select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_async_session_factory
|
||||
from app.models.tenants import Subscription
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
async def process_pending_cancellations():
|
||||
"""
|
||||
Process all subscriptions that have reached their cancellation_effective_date
|
||||
"""
|
||||
async_session_factory = get_async_session_factory()
|
||||
|
||||
async with async_session_factory() as session:
|
||||
try:
|
||||
query = select(Subscription).where(
|
||||
Subscription.status == 'pending_cancellation',
|
||||
Subscription.cancellation_effective_date <= datetime.now(timezone.utc)
|
||||
)
|
||||
|
||||
result = await session.execute(query)
|
||||
subscriptions_to_downgrade = result.scalars().all()
|
||||
|
||||
downgraded_count = 0
|
||||
|
||||
for subscription in subscriptions_to_downgrade:
|
||||
subscription.status = 'inactive'
|
||||
subscription.plan = 'free'
|
||||
subscription.monthly_price = 0.0
|
||||
|
||||
logger.info(
|
||||
"subscription_downgraded_to_inactive",
|
||||
tenant_id=str(subscription.tenant_id),
|
||||
previous_plan=subscription.plan,
|
||||
cancellation_effective_date=subscription.cancellation_effective_date.isoformat()
|
||||
)
|
||||
|
||||
downgraded_count += 1
|
||||
|
||||
if downgraded_count > 0:
|
||||
await session.commit()
|
||||
logger.info(
|
||||
"subscriptions_downgraded",
|
||||
count=downgraded_count
|
||||
)
|
||||
else:
|
||||
logger.debug("no_subscriptions_to_downgrade")
|
||||
|
||||
return downgraded_count
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"subscription_downgrade_job_failed",
|
||||
error=str(e)
|
||||
)
|
||||
await session.rollback()
|
||||
raise
|
||||
|
||||
|
||||
async def run_subscription_downgrade_job():
|
||||
"""
|
||||
Main entry point for the subscription downgrade job
|
||||
Runs in a loop with configurable interval
|
||||
"""
|
||||
interval_seconds = 3600 # Run every hour
|
||||
|
||||
logger.info("subscription_downgrade_job_started", interval_seconds=interval_seconds)
|
||||
|
||||
while True:
|
||||
try:
|
||||
downgraded_count = await process_pending_cancellations()
|
||||
|
||||
logger.info(
|
||||
"subscription_downgrade_job_completed",
|
||||
downgraded_count=downgraded_count
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"subscription_downgrade_job_error",
|
||||
error=str(e)
|
||||
)
|
||||
|
||||
await asyncio.sleep(interval_seconds)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_subscription_downgrade_job())
|
||||
Reference in New Issue
Block a user