259 lines
11 KiB
Python
Executable File
259 lines
11 KiB
Python
Executable File
# /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) |