# /opt/docker/dev/service_finder/backend/app/models/core_logic.py from typing import Optional, List, Any from datetime import datetime # Python saját típusa a típusjelöléshez from sqlalchemy import String, Integer, ForeignKey, Boolean, DateTime, Numeric, text, Float, Index from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.sql import func # MB 2.0: A központi aszinkron adatbázis motorból húzzuk be a Base-t from app.database import Base # ═══════════════════════════════════════════════════════════════════════════════ # P0 SYSTEM ARCHITECTURE: Global Region Registry # ═══════════════════════════════════════════════════════════════════════════════ class RegionConfig(Base): """ 🌍 Global Region Configuration Registry. Central table for L10n/i18n configuration. Drives all UI formatting (dates, numbers, currencies) and financial calculations (VAT, pricing). Each row represents a country/region with its own locale, currency, VAT rate, and timezone settings. Schema: system.region_config """ __tablename__ = "region_config" __table_args__ = {"schema": "system"} country_code: Mapped[str] = mapped_column( String(10), primary_key=True, index=True, comment="ISO 3166-1 alpha-2 country code, e.g. 'HU', 'GB', or 'DEFAULT' for fallback" ) name: Mapped[str] = mapped_column( String, nullable=False, comment="Human-readable region name, e.g. 'Hungary', 'United Kingdom'" ) currency: Mapped[str] = mapped_column( String(3), nullable=False, comment="ISO 4217 currency code, e.g. 'HUF', 'GBP', 'EUR'" ) default_vat_rate: Mapped[float] = mapped_column( Float, nullable=False, default=0.0, comment="Default VAT rate as decimal, e.g. 27.0 for 27%" ) locale_code: Mapped[str] = mapped_column( String(10), nullable=False, comment="BCP 47 locale code, e.g. 'hu-HU', 'en-GB', 'de-DE'" ) timezone: Mapped[str] = mapped_column( String(50), nullable=False, comment="IANA timezone, e.g. 'Europe/Budapest', 'Europe/London'" ) is_active: Mapped[bool] = mapped_column( Boolean, nullable=False, default=True, server_default=text("true"), comment="Soft-toggle for region availability" ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now() ) updated_at: Mapped[Optional[datetime]] = mapped_column( DateTime(timezone=True), onupdate=func.now() ) class SubscriptionTier(Base): """ Előfizetési csomagok definíciója (pl. Free, Premium, VIP). A csomagok határozzák meg a korlátokat (pl. max járműszám). P0 MULTI-SUBSCRIPTION AGGREGATION: A `type` mező különbözteti meg a Base tier-eket ('private', 'corporate', 'business') az Add-on tier-ektől ('addon'). Ez lehetővé teszi az "1 Base + N Add-on" modellt. """ __tablename__ = "subscription_tiers" __table_args__ = {"schema": "system"} id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String, unique=True, index=True) # pl. 'premium' rules: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) # pl. {"max_vehicles": 5} is_custom: Mapped[bool] = mapped_column(Boolean, default=False) # ── P0 MULTI-SUBSCRIPTION AGGREGATION: Base vs Add-on típus ── # 'base' = core tier (individual, corporate, business) # 'addon' = add-on tier (extra vehicles, branches, users) # None/empty = treated as 'base' for backward compatibility type: Mapped[Optional[str]] = mapped_column( String(20), nullable=True, default=None, server_default=text("NULL"), comment="Tier type: 'base' for core subscriptions, 'addon' for add-on tiers. NULL = base (backward compat)." ) # ── P0 3D CAPABILITY MATRIX: feature_capabilities JSONB ── # Defines what features/capabilities this subscription tier unlocks. # Example: {"can_export_data": True, "can_use_analytics": True, "max_vehicles": 50} feature_capabilities: Mapped[dict] = mapped_column( JSONB, nullable=False, default=dict, server_default=text("'{}'::jsonb") ) # ── P0 AUDIT FIX: Database-driven tier level instead of hardcoded string mapping ── # Numeric level for the entitlement hierarchy: 0=free, 1=premium, 2=enterprise. # This replaces the hardcoded _map_tier_name() string-matching logic. # New tiers can be added with any name; the tier_level determines their position. tier_level: Mapped[int] = mapped_column( Integer, nullable=False, default=0, server_default=text("0"), comment="Entitlement hierarchy level: 0=free, 1=premium, 2=enterprise" ) # ── P0 SINGLETON PACKAGE RULES: is_default_fallback ── # Ha True, ez a csomag az alapértelmezett fallback csomag, amit a # subscription_service használ, ha a user előfizetése lejárt. # MAXIMUM EGY csomag lehet True értékkel (singleton). is_default_fallback: Mapped[bool] = mapped_column( Boolean, nullable=False, default=False, server_default=text("false"), comment="Singleton: maximum one tier can be the default fallback package" ) # ── P0 SINGLETON PACKAGE RULES: trial_days_on_signup ── # Ha > 0, ez a csomag a próbaidős csomag, amit új regisztrációnál # automatikusan hozzárendelünk a felhasználóhoz. # MAXIMUM EGY csomag lehet > 0 értékkel (singleton). trial_days_on_signup: Mapped[int] = mapped_column( Integer, nullable=False, default=0, server_default=text("0"), comment="Singleton: maximum one tier can have trial_days_on_signup > 0" ) class OrganizationSubscription(Base): """ Szervezetek aktuális előfizetései és azok érvényessége. """ __tablename__ = "org_subscriptions" __table_args__ = {"schema": "finance"} id: Mapped[int] = mapped_column(Integer, primary_key=True) # Kapcsolat a szervezettel (fleet séma) org_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False) # Kapcsolat a csomaggal (system séma) tier_id: Mapped[int] = mapped_column(Integer, ForeignKey("system.subscription_tiers.id"), nullable=False) valid_from: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) valid_until: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True) # ── P0 BOOSTER ARCHITECTURE: extra_allowances JSONB ── # Tárolja a darabáron vett extra kvótákat a csomag keretein felül. # Példa: {"extra_vehicles": 1, "extra_garages": 1, "extra_credits": 100} extra_allowances: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"), default=dict) # ── P0 FEATURE FLAG: relationship a SubscriptionTier-hez ── tier: Mapped[Optional["SubscriptionTier"]] = relationship( "SubscriptionTier", foreign_keys=[tier_id], lazy="selectin", ) class UserSubscription(Base): """ Felhasználók aktuális előfizetései és azok érvényessége. Tábla: finance.user_subscriptions """ __tablename__ = "user_subscriptions" __table_args__ = {"schema": "finance"} id: Mapped[int] = mapped_column(Integer, primary_key=True) # Kapcsolat a felhasználóval (identity séma) user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False) # Kapcsolat a csomaggal (system séma) tier_id: Mapped[int] = mapped_column(Integer, ForeignKey("system.subscription_tiers.id"), nullable=False) valid_from: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) valid_until: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True) # ── P0 FEATURE FLAG: relationship a SubscriptionTier-hez ── tier: Mapped[Optional["SubscriptionTier"]] = relationship( "SubscriptionTier", foreign_keys=[tier_id], lazy="selectin", ) class CreditTransaction(Base): """ Kreditnapló (Pontok, kreditek vagy virtuális egyenleg követése). """ __tablename__ = "credit_logs" __table_args__ = {"schema": "finance"} id: Mapped[int] = mapped_column(Integer, primary_key=True) # Kapcsolat a szervezettel (fleet séma) org_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False) amount: Mapped[float] = mapped_column(Numeric(10, 2), nullable=False) description: Mapped[Optional[str]] = mapped_column(String) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) class ServiceSpecialty(Base): """ Hierarchikus fa struktúra a szerviz szolgáltatásokhoz (pl. Motor -> Futómű). """ __tablename__ = "service_specialties" __table_args__ = {"schema": "marketplace"} id: Mapped[int] = mapped_column(Integer, primary_key=True) # Önmagára mutató idegen kulcs a hierarchiához parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("marketplace.service_specialties.id")) name: Mapped[str] = mapped_column(String, nullable=False) slug: Mapped[str] = mapped_column(String, unique=True, index=True) # Kapcsolat az ős-szolgáltatással (Self-referential relationship) parent: Mapped[Optional["ServiceSpecialty"]] = relationship("ServiceSpecialty", remote_side=[id], backref="children") class ServiceCatalog(Base): """ Szolgáltatás Katalógus (Service Catalog). A rendszer által kínált szolgáltatások definíciói, pl. 'SRV_DATA_EXPORT'. Tábla: system.service_catalog """ __tablename__ = "service_catalog" __table_args__ = ( Index('idx_service_catalog_name_i18n', 'name_i18n', postgresql_using='gin'), {"schema": "system"} ) id: Mapped[int] = mapped_column(Integer, primary_key=True) service_code: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False) # pl. 'SRV_DATA_EXPORT' name: Mapped[str] = mapped_column(String, nullable=False) description: Mapped[Optional[str]] = mapped_column(String) # --- 🏗️ i18n JSONB Migration (Phase 1) --- name_i18n: Mapped[dict] = mapped_column(JSONB, nullable=False, server_default=text("'{\"hu\": \"\"}'::jsonb")) description_i18n: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True) credit_cost: Mapped[int] = mapped_column(Integer, default=0) # alapértelmezett ár kreditben is_active: Mapped[bool] = mapped_column(Boolean, default=True)