refakotorálás előtti állapot
This commit is contained in:
140
backend/app/workers/system/subscription_worker.py
Normal file
140
backend/app/workers/system/subscription_worker.py
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
🤖 Subscription Lifecycle Worker (Robot-20)
|
||||
A 20-as Gitea kártya implementációja: Lejárt előfizetések automatikus kezelése.
|
||||
|
||||
Folyamat:
|
||||
1. Lekérdezés: Azokat a User-eket, ahol subscription_expires_at < NOW() ÉS subscription_plan != 'FREE'
|
||||
2. Downgrade: subscription_plan = "FREE", is_vip = False
|
||||
3. Naplózás: Főkönyvi bejegyzés (SUBSCRIPTION_EXPIRED)
|
||||
4. Értesítés: NotificationService küldése a downgrade-ről
|
||||
|
||||
Futtatás:
|
||||
- Napi egyszer (cron) vagy manuálisan: docker exec sf_api python -m app.workers.system.subscription_worker
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import List
|
||||
|
||||
from sqlalchemy import select, update, and_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.models.identity import User
|
||||
from app.models.audit import FinancialLedger, LedgerEntryType, WalletType
|
||||
from app.services.billing_engine import record_ledger_entry
|
||||
from app.services.notification_service import NotificationService
|
||||
|
||||
logger = logging.getLogger("subscription-worker")
|
||||
|
||||
async def process_expired_subscriptions(db: AsyncSession) -> dict:
|
||||
"""
|
||||
Atomikus tranzakcióban feldolgozza a lejárt előfizetéseket.
|
||||
|
||||
Returns:
|
||||
dict: Statisztikák (processed_count, downgraded_users, errors)
|
||||
"""
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
# 1. Lekérdezés: Lejárt előfizetések, amelyek még nem FREE-ek
|
||||
stmt = select(User).where(
|
||||
and_(
|
||||
User.subscription_expires_at < now,
|
||||
User.subscription_plan != 'FREE',
|
||||
User.is_deleted == False,
|
||||
User.is_active == True
|
||||
)
|
||||
).with_for_update(skip_locked=True) # Atomikus zárolás
|
||||
|
||||
result = await db.execute(stmt)
|
||||
expired_users: List[User] = result.scalars().all()
|
||||
|
||||
stats = {
|
||||
"processed_count": len(expired_users),
|
||||
"downgraded_users": [],
|
||||
"errors": []
|
||||
}
|
||||
|
||||
for user in expired_users:
|
||||
try:
|
||||
# 2. Downgrade
|
||||
old_plan = user.subscription_plan
|
||||
user.subscription_plan = "FREE"
|
||||
user.is_vip = False
|
||||
# subscription_expires_at marad null vagy régi érték (lejárt)
|
||||
|
||||
# 3. Főkönyvi bejegyzés (SUBSCRIPTION_EXPIRED)
|
||||
# Megjegyzés: amount = 0, mert nem történik pénzmozgás, csak naplózás
|
||||
ledger_entry = await record_ledger_entry(
|
||||
db=db,
|
||||
user_id=user.id,
|
||||
amount=0.0,
|
||||
entry_type=LedgerEntryType.DEBIT,
|
||||
wallet_type=WalletType.SYSTEM,
|
||||
transaction_type="SUBSCRIPTION_EXPIRED",
|
||||
description=f"Előfizetés lejárt: {old_plan} → FREE",
|
||||
reference_type="subscription",
|
||||
reference_id=user.id
|
||||
)
|
||||
|
||||
# 4. Értesítés küldése
|
||||
await NotificationService.send_notification(
|
||||
db=db,
|
||||
user_id=user.id,
|
||||
title="Előfizetésed lejárt",
|
||||
message=f"A(z) {old_plan} előfizetésed lejárt, ezért átállítottuk a FREE csomagra. További előnyökért frissíts előfizetést!",
|
||||
category="billing",
|
||||
priority="medium",
|
||||
data={
|
||||
"old_plan": old_plan,
|
||||
"new_plan": "FREE",
|
||||
"user_id": user.id,
|
||||
"expired_at": user.subscription_expires_at.isoformat() if user.subscription_expires_at else None
|
||||
},
|
||||
send_email=True,
|
||||
email_template="subscription_expired",
|
||||
email_vars={
|
||||
"recipient": user.email,
|
||||
"first_name": user.person.first_name if user.person else "Partnerünk",
|
||||
"old_plan": old_plan,
|
||||
"new_plan": "FREE",
|
||||
"lang": user.preferred_language
|
||||
}
|
||||
)
|
||||
|
||||
stats["downgraded_users"].append({
|
||||
"user_id": user.id,
|
||||
"email": user.email,
|
||||
"old_plan": old_plan,
|
||||
"new_plan": "FREE"
|
||||
})
|
||||
|
||||
logger.info(f"User {user.id} ({user.email}) subscription downgraded from {old_plan} to FREE")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing user {user.id}: {e}", exc_info=True)
|
||||
stats["errors"].append({"user_id": user.id, "error": str(e)})
|
||||
|
||||
# Commit a tranzakció
|
||||
await db.commit()
|
||||
|
||||
return stats
|
||||
|
||||
async def main():
|
||||
"""Fő futtató függvény, amelyet a cron hív."""
|
||||
logger.info("Starting subscription worker...")
|
||||
|
||||
async with AsyncSessionLocal() as db:
|
||||
try:
|
||||
stats = await process_expired_subscriptions(db)
|
||||
logger.info(f"Subscription worker completed. Stats: {stats}")
|
||||
print(f"✅ Subscription worker completed. Processed: {stats['processed_count']}, Downgraded: {len(stats['downgraded_users'])}")
|
||||
except Exception as e:
|
||||
logger.error(f"Subscription worker failed: {e}", exc_info=True)
|
||||
await db.rollback()
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user