feat: complete subscription billing system phases 6-10

Phase 6 - Database-driven tiers:
- Update subscription_service to query database first with legacy fallback
- Add get_tier_info() db parameter and _get_tier_from_legacy() method

Phase 7 - Platform health integration:
- Add get_subscription_capacity() for theoretical vs actual capacity
- Include subscription capacity in full health report

Phase 8 - Background subscription tasks:
- Add reset_period_counters() for billing period resets
- Add check_trial_expirations() for trial management
- Add sync_stripe_status() for Stripe synchronization
- Add cleanup_stale_subscriptions() for maintenance
- Add capture_capacity_snapshot() for daily metrics

Phase 10 - Capacity planning & forecasting:
- Add CapacitySnapshot model for historical tracking
- Create capacity_forecast_service with growth trends
- Add /subscription-capacity, /trends, /recommendations endpoints
- Add /snapshot endpoint for manual captures

Also includes billing API enhancements from phase 4:
- Add upcoming-invoice, change-tier, addon purchase/cancel endpoints
- Add UsageSummary schema for billing page
- Enhance billing.js with addon management functions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-26 20:51:13 +01:00
parent b717c23787
commit c6e7f4087f
20 changed files with 1895 additions and 29 deletions

View File

@@ -166,6 +166,101 @@ class PlatformHealthService:
"active_vendors": active_vendors,
}
def get_subscription_capacity(self, db: Session) -> dict:
"""
Calculate theoretical capacity based on all vendor subscriptions.
Returns aggregated limits and current usage for capacity planning.
"""
from models.database.subscription import VendorSubscription
from models.database.vendor import VendorUser
# Get all active subscriptions with their limits
subscriptions = (
db.query(VendorSubscription)
.filter(VendorSubscription.status.in_(["active", "trial"]))
.all()
)
# Aggregate theoretical limits
total_products_limit = 0
total_orders_limit = 0
total_team_limit = 0
unlimited_products = 0
unlimited_orders = 0
unlimited_team = 0
tier_distribution = {}
for sub in subscriptions:
# Track tier distribution
tier = sub.tier or "unknown"
tier_distribution[tier] = tier_distribution.get(tier, 0) + 1
# Aggregate limits
if sub.products_limit is None:
unlimited_products += 1
else:
total_products_limit += sub.products_limit
if sub.orders_limit is None:
unlimited_orders += 1
else:
total_orders_limit += sub.orders_limit
if sub.team_members_limit is None:
unlimited_team += 1
else:
total_team_limit += sub.team_members_limit
# Get actual usage
actual_products = db.query(func.count(Product.id)).scalar() or 0
actual_team = (
db.query(func.count(VendorUser.id))
.filter(VendorUser.is_active == True) # noqa: E712
.scalar()
or 0
)
# Orders this period (aggregate across all subscriptions)
total_orders_used = sum(s.orders_this_period for s in subscriptions)
def calc_utilization(actual: int, limit: int, unlimited: int) -> dict:
if unlimited > 0:
# Some subscriptions have unlimited - can't calculate true %
return {
"actual": actual,
"theoretical_limit": limit,
"unlimited_count": unlimited,
"utilization_percent": None,
"has_unlimited": True,
}
elif limit > 0:
return {
"actual": actual,
"theoretical_limit": limit,
"unlimited_count": 0,
"utilization_percent": round((actual / limit) * 100, 1),
"headroom": limit - actual,
"has_unlimited": False,
}
else:
return {
"actual": actual,
"theoretical_limit": 0,
"unlimited_count": 0,
"utilization_percent": 0,
"has_unlimited": False,
}
return {
"total_subscriptions": len(subscriptions),
"tier_distribution": tier_distribution,
"products": calc_utilization(actual_products, total_products_limit, unlimited_products),
"orders_monthly": calc_utilization(total_orders_used, total_orders_limit, unlimited_orders),
"team_members": calc_utilization(actual_team, total_team_limit, unlimited_team),
}
def get_full_health_report(self, db: Session) -> dict:
"""Get comprehensive platform health report."""
# System metrics
@@ -177,6 +272,9 @@ class PlatformHealthService:
# Image storage metrics
image_storage = self.get_image_storage_metrics()
# Subscription capacity
subscription_capacity = self.get_subscription_capacity(db)
# Calculate thresholds
thresholds = self._calculate_thresholds(system, database, image_storage)
@@ -197,6 +295,7 @@ class PlatformHealthService:
"system": system,
"database": database,
"image_storage": image_storage,
"subscription_capacity": subscription_capacity,
"thresholds": thresholds,
"recommendations": recommendations,
"infrastructure_tier": tier,