# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/subscriptions.py """ Subscription Public API Endpoints. Provides: - GET /public — Public package catalog with dynamic pricing resolution based on the user's organization country (or fallback to DEFAULT zone). """ from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.api import deps from app.db.session import get_db from app.models.core_logic import SubscriptionTier from app.models.identity import User from app.models.marketplace.organization import Organization from app.schemas.subscription import ( PublicSubscriptionTierResponse, SubscriptionTierResponse, PricingZoneModel, ) router = APIRouter() # ── Pricing Resolution ──────────────────────────────────────────────────────── DEFAULT_COUNTRY = "DEFAULT" def resolve_pricing( rules: dict, country_code: str, ) -> Optional[dict]: """ Feloldja a megfelelő árazást a `pricing_zones` JSONB mezőből a felhasználó országkódja alapján. **Logika:** 1. Ha a `rules` tartalmaz `pricing_zones`-t, megkeresi a country_code-nak megfelelő zónát. 2. Ha nincs pontos egyezés, visszaesik a "DEFAULT" zónára. 3. Ha a `pricing_zones` hiányzik, visszaesik a legacy `pricing` mezőre. 4. Ha semmi sincs, None-t ad vissza. """ if not rules or not isinstance(rules, dict): return None # 1. Try pricing_zones first pricing_zones = rules.get("pricing_zones") if pricing_zones and isinstance(pricing_zones, dict): # Exact match if country_code in pricing_zones: return pricing_zones[country_code] # Fallback to DEFAULT if "DEFAULT" in pricing_zones: return pricing_zones["DEFAULT"] # 2. Fallback to legacy pricing legacy_pricing = rules.get("pricing") if legacy_pricing and isinstance(legacy_pricing, dict): return { "monthly_price": legacy_pricing.get("monthly_price", 0), "yearly_price": legacy_pricing.get("yearly_price", 0), "currency": legacy_pricing.get("currency", "EUR"), "credit_price": legacy_pricing.get("credit_price"), } return None async def get_user_country_code( current_user: User, db: AsyncSession, ) -> str: """ Meghatározza a felhasználó országkódját. **Logika:** 1. Ha a felhasználónak van aktív szervezete (active_organization_id), lekéri annak country_code mezőjét. 2. Ha nincs aktív szervezet, a felhasználó Person rekordjának address országát próbálja meg lekérni. 3. Ha egyik sem elérhető, visszaadja a "DEFAULT" értéket. """ # 1. Try active organization active_org_id = getattr(current_user, "active_organization_id", None) if active_org_id: org_stmt = select(Organization.country_code).where( Organization.id == active_org_id, Organization.is_deleted == False, ) org_result = await db.execute(org_stmt) org_country = org_result.scalar_one_or_none() if org_country: return org_country.upper() # 2. Try user's person -> address country person = getattr(current_user, "person", None) if person: address = getattr(person, "address", None) if address: country = getattr(address, "country", None) if country: return country.upper() # 3. Fallback return DEFAULT_COUNTRY # ── Endpoints ───────────────────────────────────────────────────────────────── @router.get( "/public", response_model=List[PublicSubscriptionTierResponse], summary="Publikus csomagok listázása feloldott árazással", description=( "Visszaadja az összes publikus előfizetési csomagot, " "a felhasználó régiójára feloldott árazással (resolved_pricing). " "A feloldás a pricing_zones JSONB mezőből történik a felhasználó " "országkódja alapján." ), ) async def get_public_subscriptions( db: AsyncSession = Depends(get_db), current_user: User = Depends(deps.get_current_user), ): """ Publikus előfizetési csomagok listázása dinamikus árazással. **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. - 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 stmt = ( select(SubscriptionTier) .order_by(SubscriptionTier.id) ) result = await db.execute(stmt) tiers = result.scalars().all() # 3. Build response with resolved pricing response: List[PublicSubscriptionTierResponse] = [] for t in tiers: rules = t.rules or {} if not rules.get("lifecycle", {}).get("is_public", True): continue # Resolve pricing for this user's country resolved = resolve_pricing(rules, country_code) resolved_pricing = None if resolved: resolved_pricing = PricingZoneModel(**resolved) response.append( PublicSubscriptionTierResponse( id=t.id, name=t.name, rules=rules, is_custom=t.is_custom, resolved_pricing=resolved_pricing, ) ) return response