""" 📩 Subscription Tier Pydantic Schemas A subscription_tiers JSONB rules mezƑjĂ©nek erƑsen tipizĂĄlt Pydantic modelljei. A rules struktĂșra a seed_packages.py-ban definiĂĄlt formĂĄtumot követi: { "type": "private" | "corporate", "pricing": {"monthly_price": float, "yearly_price": float, "currency": "EUR"}, "allowances": {"max_vehicles": int, "max_garages": int, "monthly_free_credits": int}, "entitlements": ["SRV_..."], "affiliate": {"commission_rate_percent": int, "referral_bonus_credits": int} } """ from __future__ import annotations from datetime import datetime from decimal import Decimal from typing import Any, Dict, List, Optional from pydantic import BaseModel, ConfigDict, Field, field_validator # ── BelsƑ (embedded) modellek a rules JSONB struktĂșrĂĄhoz ────────────────────── class PricingModel(BaseModel): """ÁrazĂĄsi informĂĄciĂłk egy elƑfizetĂ©si csomaghoz.""" monthly_price: float = Field(..., ge=0, description="Havi elƑfizetĂ©si dĂ­j (EUR)") yearly_price: float = Field(..., ge=0, description="Éves elƑfizetĂ©si dĂ­j (EUR)") currency: str = Field(default="EUR", min_length=3, max_length=3, description="PĂ©nznem ISO 4217 kĂłdja") credit_price: Optional[int] = Field(default=None, ge=0, description="KreditĂĄr (havi kredit költsĂ©g a csomaghoz)") class AllowancesModel(BaseModel): """KorlĂĄtok Ă©s keretek a csomagban.""" max_vehicles: int = Field(..., ge=0, description="MaximĂĄlis jĂĄrmƱszĂĄm") max_garages: int = Field(..., ge=0, description="MaximĂĄlis garĂĄzsok szĂĄma") monthly_free_credits: int = Field(..., ge=0, description="Havi ingyenes kreditek szĂĄma") class LifecycleModel(BaseModel): """Életciklus ĂĄllapotok egy elƑfizetĂ©si csomaghoz.""" is_public: bool = Field(default=True, description="Publikus-e a csomag (lĂĄtszik-e a felhasznĂĄlĂłknak)") available_until: Optional[datetime] = Field(default=None, description="Meddig Ă©rhetƑ el a csomag (None = nincs lejĂĄrat)") class AffiliateModel(BaseModel): """Partnerprogram (affiliate) beĂĄllĂ­tĂĄsok.""" commission_rate_percent: int = Field(..., ge=0, le=100, description="JutalĂ©k mĂ©rtĂ©ke szĂĄzalĂ©kban") referral_bonus_credits: int = Field(..., ge=0, description="AjĂĄnlĂĄsi bĂłnusz kreditek szĂĄma") class SubscriptionRulesModel(BaseModel): """ A subscription_tiers.rules JSONB oszlopĂĄnak erƑsen tipizĂĄlt modellje. Fields: type: "private" (magĂĄnszemĂ©ly) vagy "corporate" (vĂĄllalati) display_name: Emberi olvasĂĄsra szĂĄnt megjelenĂ­tĂ©si nĂ©v (pl. "PrivĂĄt Ingyenes") pricing: ÁrazĂĄsi adatok (havi/Ă©ves dĂ­j, pĂ©nznem) allowances: KorlĂĄtok (max jĂĄrmƱ, garĂĄzs, kredit) entitlements: JogosultsĂĄgok listĂĄja (pl. SRV_DATA_EXPORT) affiliate: Partnerprogram beĂĄllĂ­tĂĄsok lifecycle: Életciklus ĂĄllapotok (opcionĂĄlis, pl. is_public) """ type: str = Field(..., pattern=r"^(private|corporate)$", description="Csomag tĂ­pusa") display_name: Optional[str] = Field( default=None, description="Emberi olvasĂĄsra szĂĄnt megjelenĂ­tĂ©si nĂ©v (pl. 'PrivĂĄt Ingyenes')", ) pricing: Optional[PricingModel] = Field( default=None, description="ÁrazĂĄsi adatok (havi/Ă©ves dĂ­j, pĂ©nznem). OpcionĂĄlis a hiĂĄnyos seed adatok miatt." ) allowances: Optional[AllowancesModel] = Field( default=None, description="KorlĂĄtok (max jĂĄrmƱ, garĂĄzs, kredit). OpcionĂĄlis a hiĂĄnyos seed adatok miatt." ) entitlements: List[str] = Field(default_factory=list, description="JogosultsĂĄgok (entitlement) listĂĄja") affiliate: Optional[AffiliateModel] = Field( default=None, description="Partnerprogram beĂĄllĂ­tĂĄsok. OpcionĂĄlis a hiĂĄnyos seed adatok miatt." ) lifecycle: Optional[LifecycleModel] = Field( default=None, description="Életciklus ĂĄllapotok (is_public, available_until)" ) @field_validator("type") @classmethod def validate_type(cls, v: str) -> str: if v.lower() not in ("private", "corporate"): raise ValueError("A 'type' mezƑ csak 'private' vagy 'corporate' lehet.") return v.lower() # ── API Response / Request modellek ─────────────────────────────────────────── class SubscriptionTierResponse(BaseModel): """Teljes SubscriptionTier rekord visszaadĂĄsa (GET /, GET /{id}).""" id: int name: str rules: SubscriptionRulesModel is_custom: bool model_config = ConfigDict(from_attributes=True) class SubscriptionTierCreate(BaseModel): """Új elƑfizetĂ©si csomag lĂ©trehozĂĄsa (POST /).""" name: str = Field(..., min_length=2, max_length=100, description="Csomag neve (pl. private_pro_v2)") rules: SubscriptionRulesModel is_custom: bool = Field(default=False, description="Egyedi (custom) csomag-e") @field_validator("name") @classmethod def validate_name(cls, v: str) -> str: if not v.strip(): raise ValueError("A csomag neve nem lehet ĂŒres.") return v.strip().lower() class SubscriptionTierUpdate(BaseModel): """MeglĂ©vƑ elƑfizetĂ©si csomag rĂ©szleges frissĂ­tĂ©se (PATCH /{tier_id}).""" name: Optional[str] = Field(default=None, min_length=2, max_length=100, description="Új csomagnĂ©v") rules: Optional[SubscriptionRulesModel] = Field(default=None, description="FrissĂ­tett rules JSONB") is_custom: Optional[bool] = Field(default=None, description="Egyedi csomag jelölĂ©s") @field_validator("name") @classmethod def validate_name(cls, v: Optional[str]) -> Optional[str]: if v is not None and not v.strip(): raise ValueError("A csomag neve nem lehet ĂŒres.") return v.strip().lower() if v else v class SubscriptionTierListResponse(BaseModel): """Csomagok listĂĄzĂĄsĂĄnak vĂĄlaszformĂĄtuma (GET /).""" total: int tiers: List[SubscriptionTierResponse]