feat(subscriptions): add subscription change management with upgrade/downgrade scheduling
- Add subscription_change_pending_tier and subscription_change_pending_date fields to UserProfile - Create migration 019_add_subscription_change_pending - Add apply_pending_subscription_changes(), request_subscription_change(), cancel_pending_subscription_change() utilities - Add POST /api/subscriptions/change and DELETE /api/subscriptions/change endpoints - Update GET /api/subscriptions/my to apply pending changes and return pending change info - Update subscription view to apply pending changes and pass period_start + pending info - Update subscription.html: per-tier action buttons (upgrade/downgrade/cancel), pending-change banner, period start date - Add Celery daily task apply_pending_subscription_changes_all at 00:05 UTC - Add 19 new tests covering all new utility functions and API endpoints Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
"""Celery task to apply pending subscription changes that have become due.
|
||||
|
||||
Runs daily to ensure that scheduled downgrades are applied on time.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from app.celery_app import celery
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@celery.task(name="app.tasks.subscription_tasks.apply_pending_subscription_changes_all")
|
||||
def apply_pending_subscription_changes_all() -> dict[str, int]:
|
||||
"""Apply all pending subscription changes whose effective date has arrived.
|
||||
|
||||
Iterates over every ``UserProfile`` that has a pending change and calls
|
||||
:func:`app.utils.subscription.apply_pending_subscription_changes` for
|
||||
each one.
|
||||
|
||||
Returns:
|
||||
A dict with ``{"applied": <count>, "checked": <count>}``.
|
||||
"""
|
||||
from app.database import SessionLocal
|
||||
from app.models import UserProfile
|
||||
from app.utils.subscription import apply_pending_subscription_changes
|
||||
|
||||
db = SessionLocal()
|
||||
applied = 0
|
||||
checked = 0
|
||||
try:
|
||||
profiles = (
|
||||
db.query(UserProfile)
|
||||
.filter(
|
||||
UserProfile.subscription_change_pending_tier.isnot(None),
|
||||
UserProfile.subscription_change_pending_date.isnot(None),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
for profile in profiles:
|
||||
checked += 1
|
||||
if apply_pending_subscription_changes(db, profile.user_id):
|
||||
applied += 1
|
||||
except Exception as exc:
|
||||
logger.error("Error in apply_pending_subscription_changes_all: %s", exc)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
logger.info("apply_pending_subscription_changes_all: checked=%d applied=%d", checked, applied)
|
||||
return {"checked": checked, "applied": applied}
|
||||
Reference in New Issue
Block a user