pénzügyi modul továbbfejlesztése (csomagkezelés)
This commit is contained in:
@@ -139,28 +139,61 @@ async def get_public_subscriptions(
|
||||
**Szabály:**
|
||||
- Csak azokat a csomagokat adja vissza, ahol a `rules.lifecycle.is_public` = true
|
||||
(JSONB mező alapján szűrve). Ha a mező hiányzik, alapértelmezés szerint publikus.
|
||||
- Defense-in-Depth: A `rules.type` mező alapján szűrjük a csomagokat a felhasználó
|
||||
aktuális kontextusa szerint. Ha a user aktív szervezete `individual` típusú (vagy
|
||||
nincs aktív szervezet), csak `private`/`consumer` típusú csomagokat adunk vissza.
|
||||
Ha a szervezet business típusú, csak `corporate`/`business` típusú csomagokat.
|
||||
- Minden csomaghoz tartozik egy `resolved_pricing` mező, amely a felhasználó
|
||||
országkódja alapján feloldott árazást tartalmazza.
|
||||
- A frontend tovább szűrhet a `name` mező alapján (private_ vs corp_ előtag).
|
||||
"""
|
||||
# 1. Determine user's country code
|
||||
country_code = await get_user_country_code(current_user, db)
|
||||
|
||||
# 2. Fetch all public tiers
|
||||
# 2. Determine if user is in corporate context (defense-in-depth)
|
||||
active_org_id = getattr(current_user, "active_organization_id", None)
|
||||
is_corporate = False
|
||||
if active_org_id:
|
||||
org_type_stmt = select(Organization.org_type).where(
|
||||
Organization.id == active_org_id,
|
||||
Organization.is_deleted == False,
|
||||
)
|
||||
org_type_result = await db.execute(org_type_stmt)
|
||||
org_type = org_type_result.scalar_one_or_none()
|
||||
if org_type and org_type != "individual":
|
||||
is_corporate = True
|
||||
|
||||
# 3. Fetch all public tiers (filter out non-public at SQL level)
|
||||
stmt = (
|
||||
select(SubscriptionTier)
|
||||
.where(
|
||||
# Only show tiers where lifecycle.is_public is NOT explicitly 'false'
|
||||
# Using SQLAlchemy JSONB path query (same approach as admin_packages.py line 134)
|
||||
~SubscriptionTier.rules["lifecycle"]["is_public"].as_string().in_(["false"])
|
||||
)
|
||||
.order_by(SubscriptionTier.id)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
tiers = result.scalars().all()
|
||||
|
||||
# 3. Build response with resolved pricing
|
||||
# 4. Build response with resolved pricing and type filtering
|
||||
response: List[PublicSubscriptionTierResponse] = []
|
||||
for t in tiers:
|
||||
rules = t.rules or {}
|
||||
if not rules.get("lifecycle", {}).get("is_public", True):
|
||||
continue
|
||||
|
||||
# Defense-in-Depth: Filter by target audience type
|
||||
rules_type = rules.get("type", None)
|
||||
if rules_type:
|
||||
if is_corporate:
|
||||
# In corporate context: only show corporate/business packages
|
||||
if rules_type not in ("corporate", "business"):
|
||||
continue
|
||||
else:
|
||||
# In private context: only show private/consumer packages
|
||||
if rules_type in ("corporate", "business"):
|
||||
continue
|
||||
|
||||
# Resolve pricing for this user's country
|
||||
resolved = resolve_pricing(rules, country_code)
|
||||
resolved_pricing = None
|
||||
@@ -253,12 +286,57 @@ async def get_my_subscription(
|
||||
user_tier = await SubscriptionService.get_user_tier(db, user_id)
|
||||
feature_flags = await SubscriptionService.get_user_feature_flags(db, user_id)
|
||||
|
||||
# ── P0 PENDING DOWNGRADE FIELDS (Issue #429) ──
|
||||
# Check if there's a pending downgrade on the current subscription
|
||||
has_pending_downgrade = False
|
||||
pending_tier_name = None
|
||||
pending_effective_date = None
|
||||
|
||||
if subscription_data:
|
||||
pending_sub = None
|
||||
if source == "organization" and active_org_id:
|
||||
pending_stmt = select(OrganizationSubscription).options(
|
||||
selectinload(OrganizationSubscription.pending_tier)
|
||||
).where(
|
||||
OrganizationSubscription.org_id == active_org_id,
|
||||
OrganizationSubscription.is_active == True,
|
||||
OrganizationSubscription.pending_tier_id.is_not(None),
|
||||
).order_by(OrganizationSubscription.id.desc()).limit(1)
|
||||
pending_result = await db.execute(pending_stmt)
|
||||
pending_sub = pending_result.scalar_one_or_none()
|
||||
elif source == "user":
|
||||
pending_stmt = select(UserSubscription).options(
|
||||
selectinload(UserSubscription.pending_tier)
|
||||
).where(
|
||||
UserSubscription.user_id == user_id,
|
||||
UserSubscription.is_active == True,
|
||||
UserSubscription.pending_tier_id.is_not(None),
|
||||
).order_by(UserSubscription.id.desc()).limit(1)
|
||||
pending_result = await db.execute(pending_stmt)
|
||||
pending_sub = pending_result.scalar_one_or_none()
|
||||
|
||||
if pending_sub and pending_sub.pending_tier:
|
||||
has_pending_downgrade = True
|
||||
pt = pending_sub.pending_tier
|
||||
pending_tier_name = (
|
||||
pt.rules.get("display_name", pt.name)
|
||||
if pt.rules else pt.name
|
||||
)
|
||||
pending_effective_date = (
|
||||
pending_sub.valid_until.isoformat()
|
||||
if pending_sub.valid_until else None
|
||||
)
|
||||
|
||||
return {
|
||||
"subscription": subscription_data,
|
||||
"tier": user_tier,
|
||||
"features": feature_flags.get("features", {}),
|
||||
"expires_at": feature_flags.get("expires_at"),
|
||||
"source": source,
|
||||
# ── P0 Pending downgrade fields ──
|
||||
"has_pending_downgrade": has_pending_downgrade,
|
||||
"pending_tier_name": pending_tier_name,
|
||||
"pending_effective_date": pending_effective_date,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user