144 lines
6.0 KiB
Python
144 lines
6.0 KiB
Python
"""
|
|
📦 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]
|