fix(subscriptions): fix subscription UI and API after store→merchant migration
Store detail page now shows all platform subscriptions instead of always "No Subscription Found". Subscriptions listing page renamed from Store to Merchant throughout (template, JS, menu, i18n) with Platform column added. Tiers API supports platform_id filtering. Merchant detail page no longer hardcodes 'oms' platform — loads all platforms, shows subscription cards per platform with labels, and the Create Subscription modal includes a platform selector with platform-filtered tiers. Create button always accessible in Quick Actions. Edit modal on /admin/subscriptions loads tiers from API filtered by platform instead of hardcoded options, sends tier_code (not tier) to match PATCH schema, and shows platform context. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api, require_module_access
|
||||
from app.core.database import get_db
|
||||
from app.exceptions import ResourceNotFoundException
|
||||
from app.modules.billing.services import admin_subscription_service, subscription_service
|
||||
from app.modules.enums import FrontendType
|
||||
from app.modules.billing.schemas import (
|
||||
@@ -51,11 +52,12 @@ admin_router = APIRouter(
|
||||
@admin_router.get("/tiers", response_model=SubscriptionTierListResponse)
|
||||
def list_subscription_tiers(
|
||||
include_inactive: bool = Query(False, description="Include inactive tiers"),
|
||||
platform_id: int | None = Query(None, description="Filter tiers by platform"),
|
||||
current_user: UserContext = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""List all subscription tiers."""
|
||||
tiers = admin_subscription_service.get_tiers(db, include_inactive=include_inactive)
|
||||
tiers = admin_subscription_service.get_tiers(db, include_inactive=include_inactive, platform_id=platform_id)
|
||||
|
||||
return SubscriptionTierListResponse(
|
||||
tiers=[SubscriptionTierResponse.model_validate(t) for t in tiers],
|
||||
@@ -133,15 +135,18 @@ def list_merchant_subscriptions(
|
||||
db, page=page, per_page=per_page, status=status, tier=tier, search=search
|
||||
)
|
||||
|
||||
from app.modules.tenancy.models import Platform
|
||||
|
||||
subscriptions = []
|
||||
for sub, merchant in data["results"]:
|
||||
sub_resp = MerchantSubscriptionAdminResponse.model_validate(sub)
|
||||
tier_name = sub.tier.name if sub.tier else None
|
||||
platform = db.query(Platform).filter(Platform.id == sub.platform_id).first()
|
||||
subscriptions.append(
|
||||
MerchantSubscriptionWithMerchant(
|
||||
**sub_resp.model_dump(),
|
||||
merchant_name=merchant.name,
|
||||
platform_name="", # Platform name can be resolved if needed
|
||||
platform_name=platform.name if platform else "",
|
||||
tier_name=tier_name,
|
||||
)
|
||||
)
|
||||
@@ -244,63 +249,64 @@ def get_subscription_for_store(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Get subscription + feature usage for a store (resolves to merchant).
|
||||
Get subscriptions + feature usage for a store (resolves to merchant).
|
||||
|
||||
Convenience endpoint for the admin store detail page. Resolves
|
||||
store -> merchant -> subscription internally and returns subscription
|
||||
info with feature usage metrics.
|
||||
store -> merchant -> all platform subscriptions and returns a list
|
||||
of subscription entries with feature usage metrics.
|
||||
"""
|
||||
from app.modules.billing.services.feature_service import feature_service
|
||||
from app.modules.billing.schemas.subscription import FeatureSummaryResponse
|
||||
from app.modules.tenancy.models import Platform
|
||||
|
||||
# Resolve store to merchant
|
||||
merchant_id, platform_id = feature_service._get_merchant_for_store(db, store_id)
|
||||
if merchant_id is None or platform_id is None:
|
||||
raise HTTPException(status_code=404, detail="Store not found or has no merchant association")
|
||||
# Resolve store to merchant + all platform IDs
|
||||
merchant_id, platform_ids = feature_service._get_merchant_and_platforms_for_store(db, store_id)
|
||||
if merchant_id is None or not platform_ids:
|
||||
raise HTTPException(status_code=404, detail="Store not found or has no platform association")
|
||||
|
||||
# Get subscription
|
||||
try:
|
||||
sub, merchant = admin_subscription_service.get_subscription(
|
||||
db, merchant_id, platform_id
|
||||
)
|
||||
except Exception:
|
||||
return {
|
||||
"subscription": None,
|
||||
"tier": None,
|
||||
"features": [],
|
||||
}
|
||||
results = []
|
||||
for pid in platform_ids:
|
||||
try:
|
||||
sub, merchant = admin_subscription_service.get_subscription(db, merchant_id, pid)
|
||||
except ResourceNotFoundException:
|
||||
continue
|
||||
|
||||
# Get feature summary
|
||||
features_summary = feature_service.get_merchant_features_summary(db, merchant_id, platform_id)
|
||||
# Get feature summary
|
||||
features_summary = feature_service.get_merchant_features_summary(db, merchant_id, pid)
|
||||
|
||||
# Build tier info
|
||||
tier_info = None
|
||||
if sub.tier:
|
||||
tier_info = {
|
||||
"code": sub.tier.code,
|
||||
"name": sub.tier.name,
|
||||
"feature_codes": [fl.feature_code for fl in (sub.tier.feature_limits or [])],
|
||||
}
|
||||
# Build tier info
|
||||
tier_info = None
|
||||
if sub.tier:
|
||||
tier_info = {
|
||||
"code": sub.tier.code,
|
||||
"name": sub.tier.name,
|
||||
"feature_codes": [fl.feature_code for fl in (sub.tier.feature_limits or [])],
|
||||
}
|
||||
|
||||
# Build usage metrics (quantitative features only)
|
||||
usage_metrics = []
|
||||
for fs in features_summary:
|
||||
if fs.feature_type == "quantitative" and fs.enabled:
|
||||
usage_metrics.append({
|
||||
"name": fs.name_key.replace("_", " ").title(),
|
||||
"current": fs.current or 0,
|
||||
"limit": fs.limit,
|
||||
"percentage": fs.percent_used or 0,
|
||||
"is_unlimited": fs.limit is None,
|
||||
"is_at_limit": fs.remaining == 0 if fs.remaining is not None else False,
|
||||
"is_approaching_limit": (fs.percent_used or 0) >= 80,
|
||||
})
|
||||
# Build usage metrics (quantitative features only)
|
||||
usage_metrics = []
|
||||
for fs in features_summary:
|
||||
if fs.feature_type == "quantitative" and fs.enabled:
|
||||
usage_metrics.append({
|
||||
"name": fs.name_key.replace("_", " ").title(),
|
||||
"current": fs.current or 0,
|
||||
"limit": fs.limit,
|
||||
"percentage": fs.percent_used or 0,
|
||||
"is_unlimited": fs.limit is None,
|
||||
"is_at_limit": fs.remaining == 0 if fs.remaining is not None else False,
|
||||
"is_approaching_limit": (fs.percent_used or 0) >= 80,
|
||||
})
|
||||
|
||||
return {
|
||||
"subscription": MerchantSubscriptionAdminResponse.model_validate(sub).model_dump(),
|
||||
"tier": tier_info,
|
||||
"features": usage_metrics,
|
||||
}
|
||||
# Resolve platform name
|
||||
platform = db.query(Platform).filter(Platform.id == pid).first()
|
||||
|
||||
results.append({
|
||||
"subscription": MerchantSubscriptionAdminResponse.model_validate(sub).model_dump(),
|
||||
"tier": tier_info,
|
||||
"features": usage_metrics,
|
||||
"platform_name": platform.name if platform else "",
|
||||
})
|
||||
|
||||
return {"subscriptions": results}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user