Files
service-finder/backend/app/models/core_logic.py

237 lines
10 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
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).
"""
__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 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__ = {"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)
credit_cost: Mapped[int] = mapped_column(Integer, default=0) # alapértelmezett ár kreditben
is_active: Mapped[bool] = mapped_column(Boolean, default=True)