refaktorálás javításai

This commit is contained in:
Roo
2026-03-13 10:22:41 +00:00
parent 2d8d23f469
commit f53e0b53df
140 changed files with 7316 additions and 4579 deletions

View File

@@ -11,6 +11,7 @@ from .address import Address, GeoPostalCode, GeoStreet, GeoStreetType, Rating
# 3. Jármű definíciók
from .vehicle_definitions import VehicleModelDefinition, VehicleType, FeatureDefinition, ModelFeatureMap
from .reference_data import ReferenceLookup
from .vehicle import CostCategory, VehicleCost
# 4. Szervezeti felépítés
from .organization import Organization, OrganizationMember, OrganizationFinancials, OrganizationSalesAssignment, OrgType, OrgUserRole, Branch
@@ -21,19 +22,23 @@ from .asset import Asset, AssetCatalog, AssetCost, AssetEvent, AssetFinancials,
# 6. Üzleti logika és előfizetések
from .core_logic import SubscriptionTier, OrganizationSubscription, CreditTransaction, ServiceSpecialty
from .payment import PaymentIntent, PaymentIntentStatus
from .finance import Issuer, IssuerType
# 7. Szolgáltatások és staging
from .service import ServiceProfile, ExpertiseTag, ServiceExpertise, ServiceStaging, DiscoveryParameter
# 8. Rendszer, Gamification és egyebek
# 8. Közösségi és értékelési modellek (Social 3)
from .social import ServiceProvider, Vote, Competition, UserScore, ServiceReview, ModerationStatus, SourceType
# 9. Rendszer, Gamification és egyebek
from .gamification import PointRule, LevelConfig, UserStats, Badge, UserBadge, PointsLedger
# --- 2.2 ÚJDONSÁG: InternalNotification hozzáadása ---
from .system import SystemParameter, InternalNotification
from .system import SystemParameter, InternalNotification
from .document import Document
from .translation import Translation
from .audit import SecurityAuditLog, ProcessLog, FinancialLedger
from .audit import SecurityAuditLog, ProcessLog, FinancialLedger
from .history import AuditLog, LogSeverity
from .security import PendingAction
from .legal import LegalDocument, LegalAcceptance
@@ -48,13 +53,16 @@ ServiceRecord = AssetEvent
__all__ = [
"Base", "User", "Person", "Wallet", "UserRole", "VerificationToken", "SocialAccount",
"Organization", "OrganizationMember", "OrganizationSalesAssignment", "OrgType", "OrgUserRole",
"Asset", "AssetCatalog", "AssetCost", "AssetEvent", "AssetFinancials",
"Asset", "AssetCatalog", "AssetCost", "AssetEvent", "AssetFinancials",
"AssetTelemetry", "AssetReview", "ExchangeRate", "CatalogDiscovery",
"Address", "GeoPostalCode", "GeoStreet", "GeoStreetType", "Branch",
"PointRule", "LevelConfig", "UserStats", "Badge", "UserBadge", "Rating", "PointsLedger",
# --- 2.2 ÚJDONSÁG KIEGÉSZÍTÉS ---
"SystemParameter", "InternalNotification",
"SystemParameter", "InternalNotification",
# Social models (Social 3)
"ServiceProvider", "Vote", "Competition", "UserScore", "ServiceReview", "ModerationStatus", "SourceType",
"Document", "Translation", "PendingAction",
"SubscriptionTier", "OrganizationSubscription", "CreditTransaction", "ServiceSpecialty",
@@ -64,6 +72,6 @@ __all__ = [
"ServiceProfile", "ExpertiseTag", "ServiceExpertise", "ServiceStaging", "DiscoveryParameter",
"Vehicle", "UserVehicle", "VehicleCatalog", "ServiceRecord", "VehicleModelDefinition", "ReferenceLookup",
"VehicleType", "FeatureDefinition", "ModelFeatureMap", "LegalDocument", "LegalAcceptance",
"Location", "LocationType"
"Location", "LocationType", "Issuer", "IssuerType", "CostCategory", "VehicleCost"
]
from app.models.payment import PaymentIntent, WithdrawalRequest

View File

@@ -12,7 +12,7 @@ from app.database import Base
class GeoPostalCode(Base):
"""Irányítószám alapú földrajzi kereső tábla."""
__tablename__ = "geo_postal_codes"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
country_code: Mapped[str] = mapped_column(String(5), default="HU")
@@ -22,16 +22,16 @@ class GeoPostalCode(Base):
class GeoStreet(Base):
"""Utcajegyzék tábla."""
__tablename__ = "geo_streets"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
postal_code_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.geo_postal_codes.id"))
postal_code_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("system.geo_postal_codes.id"))
name: Mapped[str] = mapped_column(String(200), nullable=False, index=True)
class GeoStreetType(Base):
"""Közterület jellege (utca, út, köz stb.)."""
__tablename__ = "geo_street_types"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
@@ -39,10 +39,10 @@ class GeoStreetType(Base):
class Address(Base):
"""Univerzális cím entitás GPS adatokkal kiegészítve."""
__tablename__ = "addresses"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
postal_code_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.geo_postal_codes.id"))
postal_code_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("system.geo_postal_codes.id"))
street_name: Mapped[str] = mapped_column(String(200), nullable=False)
street_type: Mapped[str] = mapped_column(String(50), nullable=False)
@@ -69,7 +69,7 @@ class Rating(Base):
Index('idx_rating_org', 'target_organization_id'),
Index('idx_rating_user', 'target_user_id'),
Index('idx_rating_branch', 'target_branch_id'),
{"schema": "data"}
{"schema": "marketplace"}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -77,9 +77,9 @@ class Rating(Base):
# MB 2.0: A felhasználók az identity sémában laknak!
author_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False)
target_organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.organizations.id"))
target_organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("fleet.organizations.id"))
target_user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
target_branch_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.branches.id"))
target_branch_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("fleet.branches.id"))
score: Mapped[float] = mapped_column(Numeric(3, 2), nullable=False)
comment: Mapped[Optional[str]] = mapped_column(Text)

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import uuid
from datetime import datetime
from typing import List, Optional, TYPE_CHECKING
from sqlalchemy import String, Boolean, DateTime, ForeignKey, Numeric, text, Text, UniqueConstraint, BigInteger, Integer
from sqlalchemy import String, Boolean, DateTime, ForeignKey, Numeric, text, Text, UniqueConstraint, BigInteger, Integer, Float
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB
from sqlalchemy.sql import func
@@ -14,10 +14,10 @@ class AssetCatalog(Base):
__tablename__ = "vehicle_catalog"
__table_args__ = (
UniqueConstraint('make', 'model', 'year_from', 'fuel_type', name='uix_vehicle_catalog_full'),
{"schema": "data"}
{"schema": "vehicle"}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
master_definition_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.vehicle_model_definitions.id"))
master_definition_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("vehicle.vehicle_model_definitions.id"))
make: Mapped[str] = mapped_column(String, index=True, nullable=False)
model: Mapped[str] = mapped_column(String, index=True, nullable=False)
@@ -36,7 +36,7 @@ class AssetCatalog(Base):
class Asset(Base):
""" A fizikai eszköz (Digital Twin) - Minden adat itt fut össze. """
__tablename__ = "assets"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
vin: Mapped[str] = mapped_column(String(17), unique=True, index=True, nullable=False)
@@ -54,14 +54,14 @@ class Asset(Base):
price: Mapped[Optional[float]] = mapped_column(Numeric(15, 2))
currency: Mapped[str] = mapped_column(String(3), default="EUR")
catalog_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.vehicle_catalog.id"))
current_organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.organizations.id"))
catalog_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("vehicle.vehicle_catalog.id"))
current_organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("fleet.organizations.id"))
# Identity kapcsolatok
owner_person_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id"))
owner_org_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.organizations.id"))
owner_org_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("fleet.organizations.id"))
operator_person_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id"))
operator_org_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.organizations.id"))
operator_org_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("fleet.organizations.id"))
status: Mapped[str] = mapped_column(String(20), default="active")
individual_equipment: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
@@ -83,9 +83,9 @@ class Asset(Base):
class AssetFinancials(Base):
""" I. Beszerzés és IV. Értékcsökkenés (Amortizáció). """
__tablename__ = "asset_financials"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.assets.id"), unique=True)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), unique=True)
purchase_price_net: Mapped[float] = mapped_column(Numeric(18, 2))
purchase_price_gross: Mapped[float] = mapped_column(Numeric(18, 2))
@@ -99,10 +99,10 @@ class AssetFinancials(Base):
class AssetCost(Base):
""" II. Üzemeltetés és TCO kimutatás. """
__tablename__ = "asset_costs"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.assets.id"), nullable=False)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.organizations.id"), nullable=False)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), nullable=False)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False)
cost_category: Mapped[str] = mapped_column(String(50), index=True)
amount_net: Mapped[float] = mapped_column(Numeric(18, 2), nullable=False)
@@ -117,15 +117,28 @@ class AssetCost(Base):
class VehicleLogbook(Base):
""" Útnyilvántartás (NAV, Kiküldetés, Munkábajárás). """
__tablename__ = "vehicle_logbook"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.assets.id"), nullable=False)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), nullable=False)
driver_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False)
trip_type: Mapped[str] = mapped_column(String(30), index=True)
trip_type: Mapped[str] = mapped_column(String(30), index=True)
is_reimbursable: Mapped[bool] = mapped_column(Boolean, default=False)
start_mileage: Mapped[int] = mapped_column(Integer)
end_mileage: Mapped[Optional[int]] = mapped_column(Integer)
distance_km: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), nullable=True)
# GPS koordináták
start_lat: Mapped[Optional[float]] = mapped_column(Numeric(10, 6), nullable=True)
start_lng: Mapped[Optional[float]] = mapped_column(Numeric(10, 6), nullable=True)
end_lat: Mapped[Optional[float]] = mapped_column(Numeric(10, 6), nullable=True)
end_lng: Mapped[Optional[float]] = mapped_column(Numeric(10, 6), nullable=True)
gps_calculated_distance: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), nullable=True)
# OBDII és telemetria
obd_verified: Mapped[bool] = mapped_column(Boolean, default=False)
max_acceleration: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
average_speed: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
asset: Mapped["Asset"] = relationship("Asset", back_populates="logbook")
driver: Mapped["User"] = relationship("User")
@@ -133,9 +146,9 @@ class VehicleLogbook(Base):
class AssetInspection(Base):
""" Napi ellenőrző lista és Biztonsági check. """
__tablename__ = "asset_inspections"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.assets.id"), nullable=False)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), nullable=False)
inspector_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False)
timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
@@ -148,9 +161,9 @@ class AssetInspection(Base):
class AssetReview(Base):
""" Jármű értékelések és visszajelzések. """
__tablename__ = "asset_reviews"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.assets.id"), nullable=False)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), nullable=False)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False)
overall_rating: Mapped[Optional[int]] = mapped_column(Integer) # 1-5 csillag
@@ -163,9 +176,9 @@ class AssetReview(Base):
class VehicleOwnership(Base):
""" Tulajdonosváltások története. """
__tablename__ = "vehicle_ownership_history"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.assets.id"), nullable=False)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), nullable=False)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False)
acquired_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
@@ -177,19 +190,19 @@ class VehicleOwnership(Base):
class AssetTelemetry(Base):
__tablename__ = "asset_telemetry"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.assets.id"), unique=True)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), unique=True)
current_mileage: Mapped[int] = mapped_column(Integer, default=0)
asset: Mapped["Asset"] = relationship("Asset", back_populates="telemetry")
class AssetAssignment(Base):
""" Eszköz-Szervezet összerendelés. """
__tablename__ = "asset_assignments"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "fleet"}
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.assets.id"), nullable=False)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.organizations.id"), nullable=False)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), nullable=False)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False)
status: Mapped[str] = mapped_column(String(30), default="active")
asset: Mapped["Asset"] = relationship("Asset", back_populates="assignments")
@@ -198,15 +211,15 @@ class AssetAssignment(Base):
class AssetEvent(Base):
""" Szerviz, baleset és egyéb jelentős események. """
__tablename__ = "asset_events"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.assets.id"), nullable=False)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), nullable=False)
event_type: Mapped[str] = mapped_column(String(50), nullable=False)
asset: Mapped["Asset"] = relationship("Asset", back_populates="events")
class ExchangeRate(Base):
__tablename__ = "exchange_rates"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "finance"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
rate: Mapped[float] = mapped_column(Numeric(18, 6), nullable=False)
@@ -216,7 +229,9 @@ class CatalogDiscovery(Base):
__table_args__ = (
# KIBŐVÍTETT EGYEDISÉGI SZABÁLY: Márka + Modell + Osztály + Piac + Évjárat
UniqueConstraint('make', 'model', 'vehicle_class', 'market', 'model_year', name='_make_model_market_year_uc'),
{"schema": "data"}
# Alapvető egyediség: make + model + vehicle_class (piac és évjárat nélkül)
UniqueConstraint('make', 'model', 'vehicle_class', name='uq_make_model_class'),
{"schema": "vehicle"}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
make: Mapped[str] = mapped_column(String(100), nullable=False, index=True)

View File

@@ -12,6 +12,7 @@ from app.database import Base
class SecurityAuditLog(Base):
""" Kiemelt biztonsági események és a 4-szem elv naplózása. """
__tablename__ = "security_audit_logs"
__table_args__ = {"schema": "audit"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
action: Mapped[Optional[str]] = mapped_column(String(50)) # 'ROLE_CHANGE', 'MANUAL_CREDIT_ADJUST'
@@ -28,6 +29,7 @@ class SecurityAuditLog(Base):
class OperationalLog(Base):
""" Felhasználói szintű napi üzemi események (Audit Trail). """
__tablename__ = "operational_logs"
__table_args__ = {"schema": "audit"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id", ondelete="SET NULL"))
@@ -41,6 +43,7 @@ class OperationalLog(Base):
class ProcessLog(Base):
""" Robotok és háttérfolyamatok futási naplója (A reggeli jelentésekhez). """
__tablename__ = "process_logs"
__table_args__ = {"schema": "audit"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
process_name: Mapped[str] = mapped_column(String(100), index=True) # 'Master-Enricher'
@@ -64,9 +67,18 @@ class WalletType(str, enum.Enum):
VOUCHER = "VOUCHER"
class LedgerStatus(str, enum.Enum):
PENDING = "PENDING"
SUCCESS = "SUCCESS"
FAILED = "FAILED"
REFUNDED = "REFUNDED"
REFUND = "REFUND"
class FinancialLedger(Base):
""" Minden pénz- és kreditmozgás központi naplója. Billing Engine alapja. """
__tablename__ = "financial_ledger"
__table_args__ = {"schema": "audit"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
@@ -87,6 +99,17 @@ class FinancialLedger(Base):
wallet_type: Mapped[Optional[WalletType]] = mapped_column(
PG_ENUM(WalletType, name="wallet_type", schema="audit")
)
# Economy 1: számlázási mezők
issuer_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("finance.issuers.id"), nullable=True)
invoice_status: Mapped[Optional[str]] = mapped_column(String(50), default="PENDING")
tax_amount: Mapped[Optional[float]] = mapped_column(Numeric(18, 4))
gross_amount: Mapped[Optional[float]] = mapped_column(Numeric(18, 4))
net_amount: Mapped[Optional[float]] = mapped_column(Numeric(18, 4))
transaction_id: Mapped[uuid.UUID] = mapped_column(
PG_UUID(as_uuid=True), default=uuid.uuid4, nullable=False, index=True
)
status: Mapped[LedgerStatus] = mapped_column(
PG_ENUM(LedgerStatus, name="ledger_status", schema="audit"),
default=LedgerStatus.SUCCESS,
nullable=False
)

View File

@@ -15,7 +15,7 @@ class SubscriptionTier(Base):
A csomagok határozzák meg a korlátokat (pl. max járműszám).
"""
__tablename__ = "subscription_tiers"
__table_args__ = {"schema": "data"}
__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'
@@ -27,15 +27,15 @@ class OrganizationSubscription(Base):
Szervezetek aktuális előfizetései és azok érvényessége.
"""
__tablename__ = "org_subscriptions"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "finance"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
# Kapcsolat a szervezettel (data séma)
org_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.organizations.id"), nullable=False)
# Kapcsolat a szervezettel (fleet séma)
org_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False)
# Kapcsolat a csomaggal (data séma)
tier_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.subscription_tiers.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)
@@ -46,12 +46,12 @@ class CreditTransaction(Base):
Kreditnapló (Pontok, kreditek vagy virtuális egyenleg követése).
"""
__tablename__ = "credit_logs"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "finance"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
# Kapcsolat a szervezettel (data séma)
org_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.organizations.id"), nullable=False)
# 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)
@@ -62,12 +62,12 @@ class ServiceSpecialty(Base):
Hierarchikus fa struktúra a szerviz szolgáltatásokhoz (pl. Motor -> Futómű).
"""
__tablename__ = "service_specialties"
__table_args__ = {"schema": "data"}
__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("data.service_specialties.id"))
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)

View File

@@ -11,7 +11,7 @@ from app.db.base_class import Base
class Document(Base):
""" NAS alapú dokumentumtár metaadatai. """
__tablename__ = "documents"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
parent_type: Mapped[str] = mapped_column(String(20)) # 'organization' vagy 'asset'

View File

@@ -0,0 +1,72 @@
# /opt/docker/dev/service_finder/backend/app/models/finance.py
"""
Finance modellek: Issuer (Kibocsátó) és FinancialLedger (Pénzügyi főkönyv) bővítése.
"""
import enum
import uuid
from datetime import datetime
from typing import Any, Optional
from sqlalchemy import String, DateTime, JSON, ForeignKey, Numeric, Boolean, Integer, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, ENUM as PG_ENUM
from sqlalchemy.sql import func
from app.database import Base
class IssuerType(str, enum.Enum):
"""Kibocsátó típusok (jogi forma)."""
KFT = "KFT"
EV = "EV"
BT = "BT"
ZRT = "ZRT"
OTHER = "OTHER"
class Issuer(Base):
"""
Kibocsátó (számlakibocsátó) entitás.
A rendszerben a számlákat kibocsátó jogi személyek vagy vállalkozások.
Például: KFT, EV, stb. A revenue_limit meghatározza az adóhatár összegét.
"""
__tablename__ = "issuers"
__table_args__ = {"schema": "finance"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
# Név és adószám
name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
tax_id: Mapped[Optional[str]] = mapped_column(String(50), unique=True, index=True)
# Típus
type: Mapped[IssuerType] = mapped_column(
PG_ENUM(IssuerType, name="issuer_type", schema="finance"),
default=IssuerType.OTHER,
nullable=False
)
# Bevételi limit (pl. KATA határ)
revenue_limit: Mapped[float] = mapped_column(Numeric(18, 4), default=19500000.0)
current_revenue: Mapped[float] = mapped_column(Numeric(18, 4), default=0.0)
# Aktív-e
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
# API konfiguráció (pl. számlázó rendszer integráció)
api_config: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
# Időbélyegek
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
def __repr__(self) -> str:
return f"<Issuer {self.id}: {self.name} ({self.type})>"
# Import FinancialLedger from audit module? We'll keep it separate.
# The FinancialLedger class remains in audit.py, but we add fields there.
# For completeness, we could also define it here, but to avoid duplication,
# we'll just import it if needed.
# Instead, we'll add a relationship from FinancialLedger to Issuer in audit.py.

View File

@@ -12,7 +12,7 @@ if TYPE_CHECKING:
class PointRule(Base):
__tablename__ = "point_rules"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
action_key: Mapped[str] = mapped_column(String, unique=True, index=True)
@@ -22,7 +22,7 @@ class PointRule(Base):
class LevelConfig(Base):
__tablename__ = "level_configs"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
level_number: Mapped[int] = mapped_column(Integer, unique=True)
@@ -31,7 +31,7 @@ class LevelConfig(Base):
class PointsLedger(Base):
__tablename__ = "points_ledger"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
@@ -47,7 +47,7 @@ class PointsLedger(Base):
class UserStats(Base):
__tablename__ = "user_stats"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
# MB 2.0: User az identity sémában lakik!
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), primary_key=True)
@@ -64,7 +64,7 @@ class UserStats(Base):
class Badge(Base):
__tablename__ = "badges"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
name: Mapped[str] = mapped_column(String, unique=True)
@@ -73,13 +73,13 @@ class Badge(Base):
class UserBadge(Base):
__tablename__ = "user_badges"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
# MB 2.0: User az identity sémában lakik!
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"))
badge_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.badges.id"))
badge_id: Mapped[int] = mapped_column(Integer, ForeignKey("system.badges.id"))
earned_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

View File

@@ -21,7 +21,7 @@ class LogSeverity(str, enum.Enum):
class AuditLog(Base):
""" Rendszerszintű műveletnapló. """
__tablename__ = "audit_logs"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "audit"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
@@ -29,7 +29,7 @@ class AuditLog(Base):
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
severity: Mapped[LogSeverity] = mapped_column(
PG_ENUM(LogSeverity, name="log_severity", schema="data"),
PG_ENUM(LogSeverity, name="log_severity", schema="audit"),
default=LogSeverity.info
)

67
backend/app/models/identity.py Executable file → Normal file
View File

@@ -1,4 +1,3 @@
# /opt/docker/dev/service_finder/backend/app/models/identity.py
from __future__ import annotations
import uuid
import enum
@@ -16,6 +15,8 @@ if TYPE_CHECKING:
from .organization import Organization, OrganizationMember
from .asset import VehicleOwnership
from .gamification import UserStats
from .payment import PaymentIntent, WithdrawalRequest
from .social import ServiceReview, SocialAccount
class UserRole(str, enum.Enum):
superadmin = "superadmin"
@@ -40,11 +41,10 @@ class Person(Base):
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, index=True)
id_uuid: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), default=uuid.uuid4, unique=True, nullable=False)
# A lakcím a 'data' sémában marad
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.addresses.id"))
# A lakcím a 'system' sémában van
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("system.addresses.id"))
# Kritikus azonosító: Név + Anyja neve + Szül.idő hash-elve.
# Ezzel ismerjük fel a személyt akkor is, ha új User accountot hoz létre.
identity_hash: Mapped[Optional[str]] = mapped_column(String(64), unique=True, index=True)
last_name: Mapped[str] = mapped_column(String, nullable=False)
@@ -73,9 +73,6 @@ class Person(Base):
# --- KAPCSOLATOK ---
users: Mapped[List["User"]] = relationship("User", back_populates="person")
memberships: Mapped[List["OrganizationMember"]] = relationship("OrganizationMember", back_populates="person")
# MB 2.0 KIEGÉSZÍTÉS: A személy által birtokolt üzleti entitások (Cégek/Szolgáltatók)
# Ez a lista megmarad akkor is, ha az Organization deaktiválódik.
owned_business_entities: Mapped[List["Organization"]] = relationship("Organization", back_populates="legal_owner")
class User(Base):
@@ -117,33 +114,24 @@ class User(Base):
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
# Kapcsolatok
# --- KAPCSOLATOK ---
person: Mapped[Optional["Person"]] = relationship("Person", back_populates="users")
wallet: Mapped[Optional["Wallet"]] = relationship("Wallet", back_populates="user", uselist=False)
# JAVÍTÁS: Ez a sor KELL az OCR robot és a Trust Engine működéséhez
trust_profile: Mapped[Optional["UserTrustProfile"]] = relationship("UserTrustProfile", back_populates="user", uselist=False, cascade="all, delete-orphan")
social_accounts: Mapped[List["SocialAccount"]] = relationship("SocialAccount", back_populates="user", cascade="all, delete-orphan")
owned_organizations: Mapped[List["Organization"]] = relationship("Organization", back_populates="owner")
stats: Mapped[Optional["UserStats"]] = relationship("UserStats", back_populates="user", uselist=False, cascade="all, delete-orphan")
ownership_history: Mapped[List["VehicleOwnership"]] = relationship("VehicleOwnership", back_populates="user")
# PaymentIntent kapcsolatok
payment_intents_as_payer: Mapped[List["PaymentIntent"]] = relationship(
"PaymentIntent",
foreign_keys="[PaymentIntent.payer_id]",
back_populates="payer"
)
# Pénzügyi és egyéb kapcsolatok
withdrawal_requests: Mapped[List["WithdrawalRequest"]] = relationship("WithdrawalRequest", foreign_keys="[WithdrawalRequest.user_id]", back_populates="user", cascade="all, delete-orphan")
payment_intents_as_beneficiary: Mapped[List["PaymentIntent"]] = relationship(
"PaymentIntent",
foreign_keys="[PaymentIntent.beneficiary_id]",
back_populates="beneficiary"
)
@property
def tier_name(self) -> str:
"""Kompatibilitási mező a keresőhöz: a 'FREE' -> 'free' konverzióhoz"""
return (self.subscription_plan or "free").lower()
service_reviews: Mapped[List["ServiceReview"]] = relationship("ServiceReview", back_populates="user", cascade="all, delete-orphan")
class Wallet(Base):
""" Felhasználói pénztárca. """
__tablename__ = "wallets"
__table_args__ = {"schema": "identity"}
@@ -159,6 +147,7 @@ class Wallet(Base):
active_vouchers: Mapped[List["ActiveVoucher"]] = relationship("ActiveVoucher", back_populates="wallet", cascade="all, delete-orphan")
class VerificationToken(Base):
""" E-mail és egyéb verifikációs tokenek. """
__tablename__ = "verification_tokens"
__table_args__ = {"schema": "identity"}
@@ -171,6 +160,7 @@ class VerificationToken(Base):
is_used: Mapped[bool] = mapped_column(Boolean, default=False)
class SocialAccount(Base):
""" Közösségi bejelentkezési adatok (Google, Facebook, stb). """
__tablename__ = "social_accounts"
__table_args__ = (
UniqueConstraint('provider', 'social_id', name='uix_social_provider_id'),
@@ -187,9 +177,8 @@ class SocialAccount(Base):
user: Mapped["User"] = relationship("User", back_populates="social_accounts")
class ActiveVoucher(Base):
"""Aktív, le nem járt voucher-ek tárolása FIFO elv szerint."""
""" Aktív, le nem járt voucher-ek tárolása FIFO elv szerint. """
__tablename__ = "active_vouchers"
__table_args__ = {"schema": "identity"}
@@ -200,5 +189,27 @@ class ActiveVoucher(Base):
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
# Kapcsolatok
wallet: Mapped["Wallet"] = relationship("Wallet", back_populates="active_vouchers")
wallet: Mapped["Wallet"] = relationship("Wallet", back_populates="active_vouchers")
class UserTrustProfile(Base):
""" Gondos Gazda Index (Trust Score) tárolása. """
__tablename__ = "user_trust_profiles"
__table_args__ = {"schema": "identity"}
user_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("identity.users.id", ondelete="CASCADE"),
primary_key=True,
index=True
)
trust_score: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
maintenance_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0.0, nullable=False)
quality_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0.0, nullable=False)
preventive_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0.0, nullable=False)
last_calculated: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
nullable=False
)
user: Mapped["User"] = relationship("User", back_populates="trust_profile", uselist=False)

View File

@@ -0,0 +1,234 @@
# /opt/docker/dev/service_finder/backend/app/models/identity.py
from __future__ import annotations
import uuid
import enum
from datetime import datetime
from typing import Any, List, Optional, TYPE_CHECKING
from sqlalchemy import String, Boolean, DateTime, ForeignKey, JSON, Numeric, text, Integer, BigInteger, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, ENUM as PG_ENUM
from sqlalchemy.sql import func
# MB 2.0: Központi aszinkron adatbázis motorból húzzuk be a Base-t
from app.database import Base
if TYPE_CHECKING:
from .organization import Organization, OrganizationMember
from .asset import VehicleOwnership
from .gamification import UserStats
class UserRole(str, enum.Enum):
superadmin = "superadmin"
admin = "admin"
region_admin = "region_admin"
country_admin = "country_admin"
moderator = "moderator"
sales_agent = "sales_agent"
user = "user"
service_owner = "service_owner"
fleet_manager = "fleet_manager"
driver = "driver"
class Person(Base):
"""
Természetes személy identitása. A DNS szint.
Minden identitás adat az 'identity' sémába kerül.
"""
__tablename__ = "persons"
__table_args__ = {"schema": "identity"}
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, index=True)
id_uuid: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), default=uuid.uuid4, unique=True, nullable=False)
# A lakcím a 'data' sémában marad
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("system.addresses.id"))
# Kritikus azonosító: Név + Anyja neve + Szül.idő hash-elve.
# Ezzel ismerjük fel a személyt akkor is, ha új User accountot hoz létre.
identity_hash: Mapped[Optional[str]] = mapped_column(String(64), unique=True, index=True)
last_name: Mapped[str] = mapped_column(String, nullable=False)
first_name: Mapped[str] = mapped_column(String, nullable=False)
phone: Mapped[Optional[str]] = mapped_column(String)
mothers_last_name: Mapped[Optional[str]] = mapped_column(String)
mothers_first_name: Mapped[Optional[str]] = mapped_column(String)
birth_place: Mapped[Optional[str]] = mapped_column(String)
birth_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
identity_docs: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
ice_contact: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
lifetime_xp: Mapped[int] = mapped_column(BigInteger, server_default=text("0"))
penalty_points: Mapped[int] = mapped_column(Integer, server_default=text("0"))
social_reputation: Mapped[float] = mapped_column(Numeric(3, 2), server_default=text("1.00"))
is_sales_agent: Mapped[bool] = mapped_column(Boolean, server_default=text("false"))
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
is_ghost: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
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())
# --- KAPCSOLATOK ---
users: Mapped[List["User"]] = relationship("User", back_populates="person")
memberships: Mapped[List["OrganizationMember"]] = relationship("OrganizationMember", back_populates="person")
# MB 2.0 KIEGÉSZÍTÉS: A személy által birtokolt üzleti entitások (Cégek/Szolgáltatók)
# Ez a lista megmarad akkor is, ha az Organization deaktiválódik.
owned_business_entities: Mapped[List["Organization"]] = relationship("Organization", back_populates="legal_owner")
class User(Base):
""" Login entitás. Bármikor törölhető (GDPR), de Person-höz kötött. """
__tablename__ = "users"
__table_args__ = {"schema": "identity"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
email: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False)
hashed_password: Mapped[Optional[str]] = mapped_column(String)
role: Mapped[UserRole] = mapped_column(
PG_ENUM(UserRole, name="userrole", schema="identity"),
default=UserRole.user
)
person_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id"))
trust_profile: Mapped[Optional["UserTrustProfile"]] = relationship("UserTrustProfile", back_populates="user", uselist=False, cascade="all, delete-orphan")
subscription_plan: Mapped[str] = mapped_column(String(30), server_default=text("'FREE'"))
subscription_expires_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
is_vip: Mapped[bool] = mapped_column(Boolean, server_default=text("false"))
referral_code: Mapped[Optional[str]] = mapped_column(String(20), unique=True)
referred_by_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
current_sales_agent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
is_active: Mapped[bool] = mapped_column(Boolean, default=False)
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)
folder_slug: Mapped[Optional[str]] = mapped_column(String(12), unique=True, index=True)
preferred_language: Mapped[str] = mapped_column(String(5), server_default="hu")
region_code: Mapped[str] = mapped_column(String(5), server_default="HU")
preferred_currency: Mapped[str] = mapped_column(String(3), server_default="HUF")
scope_level: Mapped[str] = mapped_column(String(30), server_default="individual")
scope_id: Mapped[Optional[str]] = mapped_column(String(50))
custom_permissions: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
# Kapcsolatok
person: Mapped[Optional["Person"]] = relationship("Person", back_populates="users")
wallet: Mapped[Optional["Wallet"]] = relationship("Wallet", back_populates="user", uselist=False)
social_accounts: Mapped[List["SocialAccount"]] = relationship("SocialAccount", back_populates="user", cascade="all, delete-orphan")
owned_organizations: Mapped[List["Organization"]] = relationship("Organization", back_populates="owner")
stats: Mapped[Optional["UserStats"]] = relationship("UserStats", back_populates="user", uselist=False, cascade="all, delete-orphan")
ownership_history: Mapped[List["VehicleOwnership"]] = relationship("VehicleOwnership", back_populates="user")
# PaymentIntent kapcsolatok
payment_intents_as_payer: Mapped[List["PaymentIntent"]] = relationship(
"PaymentIntent",
foreign_keys="[PaymentIntent.payer_id]",
back_populates="payer"
)
withdrawal_requests: Mapped[List["WithdrawalRequest"]] = relationship("WithdrawalRequest", foreign_keys="[WithdrawalRequest.user_id]", back_populates="user", cascade="all, delete-orphan")
payment_intents_as_beneficiary: Mapped[List["PaymentIntent"]] = relationship(
"PaymentIntent",
foreign_keys="[PaymentIntent.beneficiary_id]",
back_populates="beneficiary"
)
# Service reviews
service_reviews: Mapped[List["ServiceReview"]] = relationship("ServiceReview", back_populates="user", cascade="all, delete-orphan")
@property
def tier_name(self) -> str:
"""Kompatibilitási mező a keresőhöz: a 'FREE' -> 'free' konverzióhoz"""
return (self.subscription_plan or "free").lower()
class Wallet(Base):
__tablename__ = "wallets"
__table_args__ = {"schema": "identity"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), unique=True)
earned_credits: Mapped[float] = mapped_column(Numeric(18, 4), server_default=text("0"))
purchased_credits: Mapped[float] = mapped_column(Numeric(18, 4), server_default=text("0"))
service_coins: Mapped[float] = mapped_column(Numeric(18, 4), server_default=text("0"))
currency: Mapped[str] = mapped_column(String(3), default="HUF")
user: Mapped["User"] = relationship("User", back_populates="wallet")
active_vouchers: Mapped[List["ActiveVoucher"]] = relationship("ActiveVoucher", back_populates="wallet", cascade="all, delete-orphan")
class VerificationToken(Base):
__tablename__ = "verification_tokens"
__table_args__ = {"schema": "identity"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
token: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), default=uuid.uuid4, unique=True, nullable=False)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id", ondelete="CASCADE"), nullable=False)
token_type: Mapped[str] = mapped_column(String(20), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
is_used: Mapped[bool] = mapped_column(Boolean, default=False)
class SocialAccount(Base):
__tablename__ = "social_accounts"
__table_args__ = (
UniqueConstraint('provider', 'social_id', name='uix_social_provider_id'),
{"schema": "identity"}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id", ondelete="CASCADE"), nullable=False)
provider: Mapped[str] = mapped_column(String(50), nullable=False)
social_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
email: Mapped[str] = mapped_column(String(255), nullable=False)
extra_data: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
user: Mapped["User"] = relationship("User", back_populates="social_accounts")
class ActiveVoucher(Base):
"""Aktív, le nem járt voucher-ek tárolása FIFO elv szerint."""
__tablename__ = "active_vouchers"
__table_args__ = {"schema": "identity"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
wallet_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.wallets.id", ondelete="CASCADE"), nullable=False)
amount: Mapped[float] = mapped_column(Numeric(18, 4), nullable=False)
original_amount: Mapped[float] = mapped_column(Numeric(18, 4), nullable=False)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
# Kapcsolatok
wallet: Mapped["Wallet"] = relationship("Wallet", back_populates="active_vouchers")
class UserTrustProfile(Base):
"""
Gondos Gazda Index (Trust Score) tárolása felhasználónként.
A pontszámot a trust_engine számolja dinamikusan a SystemParameter-ek alapján.
"""
__tablename__ = "user_trust_profiles"
__table_args__ = {"schema": "identity"}
user_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("identity.users.id", ondelete="CASCADE"),
primary_key=True,
index=True
)
trust_score: Mapped[int] = mapped_column(Integer, default=0, nullable=False) # 0-100 pont
maintenance_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0.0, nullable=False) # 0.0-1.0
quality_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0.0, nullable=False) # 0.0-1.0
preventive_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0.0, nullable=False) # 0.0-1.0
last_calculated: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
nullable=False
)
# Kapcsolatok
user: Mapped["User"] = relationship("User", back_populates="trust_profile", uselist=False)

View File

@@ -8,6 +8,7 @@ from app.db.base_class import Base
class LegalDocument(Base):
__tablename__ = "legal_documents"
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
title: Mapped[Optional[str]] = mapped_column(String(255))
@@ -22,10 +23,11 @@ class LegalDocument(Base):
class LegalAcceptance(Base):
__tablename__ = "legal_acceptances"
__table_args__ = {"schema": "identity"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"))
document_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.legal_documents.id"))
document_id: Mapped[int] = mapped_column(Integer, ForeignKey("system.legal_documents.id"))
accepted_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
ip_address: Mapped[Optional[str]] = mapped_column(String(45))
user_agent: Mapped[Optional[str]] = mapped_column(Text)
user_agent: Mapped[Optional[str]] = mapped_column(Text)

View File

@@ -13,6 +13,7 @@ class LocationType(str, enum.Enum):
class Location(Base):
__tablename__ = "locations"
__table_args__ = {"schema": "fleet"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
name: Mapped[str] = mapped_column(String)
@@ -21,6 +22,6 @@ class Location(Base):
nullable=False
)
coordinates: Mapped[Optional[str]] = mapped_column(String)
coordinates: Mapped[Optional[str]] = mapped_column(String)
address_full: Mapped[Optional[str]] = mapped_column(String)
capacity: Mapped[Optional[int]] = mapped_column(Integer)
capacity: Mapped[Optional[int]] = mapped_column(Integer)

View File

@@ -35,7 +35,7 @@ class Organization(Base):
a jármű-életút adatok megmaradnak az eredeti Person-höz kötve.
"""
__tablename__ = "organizations"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "fleet"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
@@ -60,7 +60,7 @@ class Organization(Base):
lifecycle_index: Mapped[int] = mapped_column(Integer, default=1, server_default=text("1"))
# --- 🏢 ALAPADATOK (MEGŐRIZVE) ---
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.addresses.id"))
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("system.addresses.id"))
is_anonymized: Mapped[bool] = mapped_column(Boolean, default=False, server_default=text("false"))
anonymized_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
@@ -85,7 +85,7 @@ class Organization(Base):
reg_number: Mapped[Optional[str]] = mapped_column(String(50))
org_type: Mapped[OrgType] = mapped_column(
PG_ENUM(OrgType, name="orgtype", schema="data"),
PG_ENUM(OrgType, name="orgtype", schema="fleet"),
default=OrgType.individual
)
@@ -125,13 +125,16 @@ class Organization(Base):
# Kapcsolat az örök személy rekordhoz
legal_owner: Mapped[Optional["Person"]] = relationship("Person", back_populates="owned_business_entities")
# Kapcsolat a jármű költségekhez (TCO rendszer)
vehicle_costs: Mapped[List["VehicleCost"]] = relationship("VehicleCost", back_populates="organization")
class OrganizationFinancials(Base):
__tablename__ = "organization_financials"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "fleet"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.organizations.id"), nullable=False)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False)
year: Mapped[int] = mapped_column(Integer, nullable=False)
turnover: Mapped[Optional[float]] = mapped_column(Numeric(18, 2))
profit: Mapped[Optional[float]] = mapped_column(Numeric(18, 2))
@@ -143,16 +146,16 @@ class OrganizationFinancials(Base):
class OrganizationMember(Base):
__tablename__ = "organization_members"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "fleet"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.organizations.id"), nullable=False)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False)
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
person_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id"))
role: Mapped[OrgUserRole] = mapped_column(
PG_ENUM(OrgUserRole, name="orguserrole", schema="data"),
PG_ENUM(OrgUserRole, name="orguserrole", schema="fleet"),
default=OrgUserRole.DRIVER
)
permissions: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
@@ -165,10 +168,10 @@ class OrganizationMember(Base):
class OrganizationSalesAssignment(Base):
__tablename__ = "org_sales_assignments"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "fleet"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.organizations.id"))
organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("fleet.organizations.id"))
agent_user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
assigned_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
@@ -179,11 +182,11 @@ class Branch(Base):
Telephely entitás. A fizikai helyszín, ahol a szolgáltatás vagy flotta-kezelés zajlik.
"""
__tablename__ = "branches"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "fleet"}
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.organizations.id"), nullable=False)
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("data.addresses.id"))
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=False)
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("system.addresses.id"))
name: Mapped[str] = mapped_column(String(100), nullable=False)
is_main: Mapped[bool] = mapped_column(Boolean, default=False)

View File

@@ -41,7 +41,7 @@ class PaymentIntent(Base):
- gross_amount: net_amount + handling_fee (Stripe-nak küldött összeg)
"""
__tablename__ = "payment_intents"
__table_args__ = {"schema": "audit"}
__table_args__ = {"schema": "finance"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -60,7 +60,7 @@ class PaymentIntent(Base):
# Cél pénztárca típusa
target_wallet_type: Mapped[WalletType] = mapped_column(
PG_ENUM(WalletType, name="wallet_type", schema="audit"),
PG_ENUM(WalletType, name="wallet_type", schema="finance"),
nullable=False
)
@@ -73,7 +73,7 @@ class PaymentIntent(Base):
# Státusz
status: Mapped[PaymentIntentStatus] = mapped_column(
PG_ENUM(PaymentIntentStatus, name="payment_intent_status", schema="audit"),
PG_ENUM(PaymentIntentStatus, name="payment_intent_status", schema="finance"),
default=PaymentIntentStatus.PENDING,
nullable=False,
index=True
@@ -152,7 +152,7 @@ class WithdrawalRequest(Base):
Ha 14 napon belül nem kerül jóváhagyásra, automatikusan REJECTED lesz és a pénz visszakerül a Earned zsebbe.
"""
__tablename__ = "withdrawal_requests"
__table_args__ = {"schema": "audit"}
__table_args__ = {"schema": "finance"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -166,13 +166,13 @@ class WithdrawalRequest(Base):
# Kifizetési mód
payout_method: Mapped[WithdrawalPayoutMethod] = mapped_column(
PG_ENUM(WithdrawalPayoutMethod, name="withdrawal_payout_method", schema="audit"),
PG_ENUM(WithdrawalPayoutMethod, name="withdrawal_payout_method", schema="finance"),
nullable=False
)
# Státusz
status: Mapped[WithdrawalRequestStatus] = mapped_column(
PG_ENUM(WithdrawalRequestStatus, name="withdrawal_request_status", schema="audit"),
PG_ENUM(WithdrawalRequestStatus, name="withdrawal_request_status", schema="finance"),
default=WithdrawalRequestStatus.PENDING,
nullable=False,
index=True

View File

@@ -5,7 +5,7 @@ from app.database import Base
class ReferenceLookup(Base):
__tablename__ = "reference_lookup"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id = Column(Integer, primary_key=True, index=True)
make = Column(String, nullable=False, index=True)
@@ -13,7 +13,7 @@ class ReferenceLookup(Base):
year = Column(Integer, nullable=True, index=True)
# Itt tároljuk az egységesített adatokat
specs = Column(JSONB, nullable=False)
specs = Column(JSONB, nullable=False)
source = Column(String, nullable=False) # pl: 'os-vehicle-db', 'wikidata'
source_id = Column(String, nullable=True)

View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""
Central Model Registry for Service Finder
Automatically discovers and imports all SQLAlchemy models from the models directory,
ensuring Base.metadata is fully populated with tables, constraints, and indexes.
Usage:
from app.models.registry import Base, get_all_models, ensure_models_loaded
"""
import importlib
import os
import sys
from pathlib import Path
from typing import Dict, List, Type
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.orm import DeclarativeBase
# Import the Base from database (circular dependency will be resolved later)
# We'll define our own Base if needed, but better to reuse existing one.
# We'll import after path setup.
# Add backend to path if not already
backend_dir = Path(__file__).parent.parent.parent
if str(backend_dir) not in sys.path:
sys.path.insert(0, str(backend_dir))
# Import Base from database (this will be the same Base used everywhere)
from app.database import Base
def discover_model_files() -> List[Path]:
"""
Walk through models directory and collect all .py files except __init__.py and registry.py.
"""
models_dir = Path(__file__).parent
model_files = []
for root, _, files in os.walk(models_dir):
for file in files:
if file.endswith('.py') and file not in ('__init__.py', 'registry.py'):
full_path = Path(root) / file
model_files.append(full_path)
return model_files
def import_module_from_file(file_path: Path) -> str:
"""
Import a Python module from its file path.
Returns the module name.
"""
# Compute module name relative to backend/app
rel_path = file_path.relative_to(backend_dir)
module_name = str(rel_path).replace(os.sep, '.').replace('.py', '')
try:
spec = importlib.util.spec_from_file_location(module_name, file_path)
if spec is None:
raise ImportError(f"Could not load spec for {module_name}")
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module_name
except Exception as e:
# Silently skip import errors (maybe due to missing dependencies)
# but log for debugging
print(f"⚠️ Could not import {module_name}: {e}", file=sys.stderr)
return None
def load_all_models() -> List[str]:
"""
Dynamically import all model files to populate Base.metadata.
Returns list of successfully imported module names.
"""
model_files = discover_model_files()
imported = []
for file in model_files:
module_name = import_module_from_file(file)
if module_name:
imported.append(module_name)
# Also ensure the __init__.py is loaded (it imports many models manually)
try:
import app.models
imported.append('app.models')
except ImportError:
pass
print(f"✅ Registry loaded {len(imported)} model modules. Total tables in metadata: {len(Base.metadata.tables)}")
return imported
def get_all_models() -> Dict[str, Type[DeclarativeMeta]]:
"""
Return a mapping of class name to model class for all registered SQLAlchemy models.
This works only after models have been imported.
"""
# This is a heuristic: find all subclasses of Base in loaded modules
from sqlalchemy.orm import DeclarativeBase
models = {}
for cls in Base.__subclasses__():
models[cls.__name__] = cls
# Also check deeper inheritance (if models inherit from other models that inherit from Base)
for module_name, module in sys.modules.items():
if module_name.startswith('app.models.'):
for attr_name in dir(module):
attr = getattr(module, attr_name)
if isinstance(attr, type) and issubclass(attr, Base) and attr is not Base:
models[attr.__name__] = attr
return models
def ensure_models_loaded():
"""
Ensure that all models are loaded into Base.metadata.
This is idempotent and can be called multiple times.
"""
if len(Base.metadata.tables) == 0:
load_all_models()
else:
# Already loaded
pass
# Auto-load models when this module is imported (optional, but useful)
# We'll make it explicit via a function call to avoid side effects.
# Instead, we'll provide a function to trigger loading.
# Export
__all__ = ['Base', 'discover_model_files', 'load_all_models', 'get_all_models', 'ensure_models_loaded']

View File

@@ -16,12 +16,12 @@ class ServiceProfile(Base):
__tablename__ = "service_profiles"
__table_args__ = (
Index('idx_service_fingerprint', 'fingerprint', unique=True),
{"schema": "data"}
{"schema": "marketplace"}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.organizations.id"), unique=True)
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.service_profiles.id"))
organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), unique=True)
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("marketplace.service_profiles.id"))
fingerprint: Mapped[str] = mapped_column(String(255), index=True, nullable=False)
location: Mapped[Any] = mapped_column(Geometry(geometry_type='POINT', srid=4326, spatial_index=False), index=True)
@@ -33,6 +33,15 @@ class ServiceProfile(Base):
rating: Mapped[Optional[float]] = mapped_column(Float)
user_ratings_total: Mapped[Optional[int]] = mapped_column(Integer)
# Aggregated verified review ratings (Social 3)
rating_verified_count: Mapped[Optional[int]] = mapped_column(Integer, server_default=text("0"))
rating_price_avg: Mapped[Optional[float]] = mapped_column(Float)
rating_quality_avg: Mapped[Optional[float]] = mapped_column(Float)
rating_time_avg: Mapped[Optional[float]] = mapped_column(Float)
rating_communication_avg: Mapped[Optional[float]] = mapped_column(Float)
rating_overall: Mapped[Optional[float]] = mapped_column(Float)
last_review_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
vibe_analysis: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
social_links: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
specialization_tags: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
@@ -50,6 +59,7 @@ class ServiceProfile(Base):
# Kapcsolatok
organization: Mapped["Organization"] = relationship("Organization", back_populates="service_profile")
expertises: Mapped[List["ServiceExpertise"]] = relationship("ServiceExpertise", back_populates="service")
reviews: Mapped[List["ServiceReview"]] = relationship("ServiceReview", back_populates="service")
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())
@@ -60,7 +70,7 @@ class ExpertiseTag(Base):
Ez a tábla vezérli a robotok keresését és a Gamification pontozást is.
"""
__tablename__ = "expertise_tags"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "marketplace"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -114,11 +124,11 @@ class ServiceExpertise(Base):
Itt tároljuk, hogy az adott szerviznél mennyire validált egy szakma.
"""
__tablename__ = "service_expertises"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "marketplace"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
service_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.service_profiles.id", ondelete="CASCADE"))
expertise_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.expertise_tags.id", ondelete="CASCADE"))
service_id: Mapped[int] = mapped_column(Integer, ForeignKey("marketplace.service_profiles.id", ondelete="CASCADE"))
expertise_id: Mapped[int] = mapped_column(Integer, ForeignKey("marketplace.expertise_tags.id", ondelete="CASCADE"))
# Mennyire biztos ez a tudás? (0: robot találta, 1: júzer mondta, 2: igazolt szakma)
confidence_level: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"))
@@ -134,7 +144,7 @@ class ServiceStaging(Base):
__tablename__ = "service_staging"
__table_args__ = (
Index('idx_staging_fingerprint', 'fingerprint', unique=True),
{"schema": "data"}
{"schema": "marketplace"}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
@@ -150,7 +160,7 @@ class ServiceStaging(Base):
class DiscoveryParameter(Base):
""" Robot vezérlési paraméterek adminból. """
__tablename__ = "discovery_parameters"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "marketplace"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
city: Mapped[str] = mapped_column(String(100))

View File

@@ -1,12 +1,13 @@
# /opt/docker/dev/service_finder/backend/app/models/social.py
import enum
import uuid
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Integer, ForeignKey, DateTime, Boolean, Text, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import ENUM as PG_ENUM
from sqlalchemy.dialects.postgresql import ENUM as PG_ENUM, UUID as PG_UUID
from sqlalchemy.sql import func
from app.db.base_class import Base
from app.database import Base
class ModerationStatus(str, enum.Enum):
pending = "pending"
@@ -21,6 +22,7 @@ class SourceType(str, enum.Enum):
class ServiceProvider(Base):
""" Közösség által beküldött szolgáltatók (v1.3.1). """
__tablename__ = "service_providers"
__table_args__ = {"schema": "marketplace"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
name: Mapped[str] = mapped_column(String, nullable=False)
@@ -28,11 +30,11 @@ class ServiceProvider(Base):
category: Mapped[Optional[str]] = mapped_column(String)
status: Mapped[ModerationStatus] = mapped_column(
PG_ENUM(ModerationStatus, name="moderation_status", inherit_schema=True),
PG_ENUM(ModerationStatus, name="moderation_status", inherit_schema=True),
default=ModerationStatus.pending
)
source: Mapped[SourceType] = mapped_column(
PG_ENUM(SourceType, name="source_type", inherit_schema=True),
PG_ENUM(SourceType, name="source_type", inherit_schema=True),
default=SourceType.manual
)
@@ -46,16 +48,18 @@ class Vote(Base):
__tablename__ = "votes"
__table_args__ = (
UniqueConstraint('user_id', 'provider_id', name='uq_user_provider_vote'),
{"schema": "marketplace"}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False)
provider_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.service_providers.id"), nullable=False)
provider_id: Mapped[int] = mapped_column(Integer, ForeignKey("marketplace.service_providers.id"), nullable=False)
vote_value: Mapped[int] = mapped_column(Integer, nullable=False) # +1 vagy -1
class Competition(Base):
""" Gamifikált versenyek (pl. Januári Feltöltő Verseny). """
__tablename__ = "competitions"
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str] = mapped_column(String, nullable=False)
@@ -69,10 +73,44 @@ class UserScore(Base):
__tablename__ = "user_scores"
__table_args__ = (
UniqueConstraint('user_id', 'competition_id', name='uq_user_competition_score'),
{"schema": "system"}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"))
competition_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.competitions.id"))
competition_id: Mapped[int] = mapped_column(Integer, ForeignKey("system.competitions.id"))
points: Mapped[int] = mapped_column(Integer, default=0)
last_updated: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
last_updated: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
class ServiceReview(Base):
"""
Verifikált szerviz értékelések (Social 3).
Csak igazolt pénzügyi tranzakció után lehet értékelni.
"""
__tablename__ = "service_reviews"
__table_args__ = (
UniqueConstraint('transaction_id', name='uq_service_review_transaction'),
{"schema": "marketplace"}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
service_id: Mapped[int] = mapped_column(Integer, ForeignKey("marketplace.service_profiles.id", ondelete="CASCADE"), nullable=False)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id", ondelete="SET NULL"), nullable=False)
transaction_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), nullable=False, index=True)
# Rating dimensions (1-10)
price_rating: Mapped[int] = mapped_column(Integer, nullable=False) # 1-10
quality_rating: Mapped[int] = mapped_column(Integer, nullable=False) # 1-10
time_rating: Mapped[int] = mapped_column(Integer, nullable=False) # 1-10
communication_rating: Mapped[int] = mapped_column(Integer, nullable=False) # 1-10
comment: Mapped[Optional[str]] = mapped_column(Text)
is_verified: Mapped[bool] = mapped_column(Boolean, default=True, server_default=text("true"))
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())
# Relationships
service: Mapped["ServiceProfile"] = relationship("ServiceProfile", back_populates="reviews")
user: Mapped["User"] = relationship("User", back_populates="service_reviews")

View File

@@ -10,7 +10,7 @@ from app.db.base_class import Base
class StagedVehicleData(Base):
""" Robot 2.1 (Researcher) nyers adatgyűjtője. """
__tablename__ = "staged_vehicle_data"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
source_url: Mapped[Optional[str]] = mapped_column(String)
@@ -24,7 +24,7 @@ class StagedVehicleData(Base):
class ServiceStaging(Base):
""" Robot 1.3 (Scout) által talált nyers szerviz adatok. """
__tablename__ = "service_staging"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str] = mapped_column(String(255), index=True)
@@ -47,10 +47,10 @@ class ServiceStaging(Base):
class DiscoveryParameter(Base):
""" Felderítési paraméterek (Városok, ahol a Scout keres). """
__tablename__ = "discovery_parameters"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "marketplace"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
city: Mapped[str] = mapped_column(String(100), unique=True, index=True)
country_code: Mapped[str] = mapped_column(String(5), server_default=text("'HU'"))
keyword: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
last_run_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))

View File

@@ -1,19 +1,26 @@
# /opt/docker/dev/service_finder/backend/app/models/system.py
import uuid
from datetime import datetime
from enum import Enum
from typing import Optional
from sqlalchemy import String, Integer, Boolean, DateTime, text, UniqueConstraint, ForeignKey, Text
from sqlalchemy import String, Integer, Boolean, DateTime, text, UniqueConstraint, ForeignKey, Text, Enum as SQLEnum
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.sql import func
from app.db.base_class import Base
from app.database import Base
class ParameterScope(str, Enum):
GLOBAL = "global"
COUNTRY = "country"
REGION = "region"
USER = "user"
class SystemParameter(Base):
""" Dinamikus konfigurációs motor (Global -> Org -> User). """
__tablename__ = "system_parameters"
__table_args__ = (
UniqueConstraint('key', 'scope_level', 'scope_id', name='uix_param_scope'),
{"extend_existing": True}
{"schema": "system", "extend_existing": True}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
@@ -21,8 +28,8 @@ class SystemParameter(Base):
category: Mapped[str] = mapped_column(String, server_default="general", index=True)
value: Mapped[dict] = mapped_column(JSONB, nullable=False)
scope_level: Mapped[str] = mapped_column(String(30), server_default=text("'global'"), index=True)
scope_id: Mapped[Optional[str]] = mapped_column(String(50))
scope_level: Mapped[ParameterScope] = mapped_column(SQLEnum(ParameterScope, name="parameter_scope"), server_default=ParameterScope.GLOBAL.value, index=True)
scope_id: Mapped[Optional[str]] = mapped_column(String(50))
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
description: Mapped[Optional[str]] = mapped_column(String)
@@ -35,7 +42,7 @@ class InternalNotification(Base):
Ezek az üzenetek várják a felhasználót belépéskor.
"""
__tablename__ = "internal_notifications"
__table_args__ = ({"schema": "data", "extend_existing": True})
__table_args__ = ({"schema": "system", "extend_existing": True})
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id: Mapped[int] = mapped_column(ForeignKey("identity.users.id", ondelete="CASCADE"), nullable=False, index=True)

View File

@@ -10,7 +10,7 @@ class Translation(Base):
Többnyelvűséget támogató tábla a felületi elemekhez és dinamikus tartalmakhoz.
"""
__tablename__ = "translations"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)

View File

@@ -0,0 +1,192 @@
# /opt/docker/dev/service_finder/backend/app/models/vehicle.py
"""
TCO (Total Cost of Ownership) alapmodelljei a 'vehicle' sémában.
- CostCategory: Standardizált költségkategóriák hierarchiája
- VehicleCost: Járműhöz kapcsolódó tényleges költségnapló
"""
from __future__ import annotations
from datetime import datetime
from typing import Optional
import uuid
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Text, Numeric, UniqueConstraint, Float, CheckConstraint
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func
from app.database import Base
class CostCategory(Base):
"""
Standardizált költségkategóriák hierarchikus fája.
Rendszerkategóriák (is_system=True) nem törölhetők, csak felhasználói kategóriák.
"""
__tablename__ = "cost_categories"
__table_args__ = {"schema": "vehicle"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
parent_id: Mapped[Optional[int]] = mapped_column(
Integer,
ForeignKey("vehicle.cost_categories.id", ondelete="SET NULL"),
nullable=True,
index=True
)
code: Mapped[str] = mapped_column(String(50), unique=True, index=True, nullable=False)
name: Mapped[str] = mapped_column(String(100), nullable=False)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
is_system: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
# Hierarchikus kapcsolatok
parent: Mapped[Optional["CostCategory"]] = relationship(
"CostCategory",
remote_side=[id],
back_populates="children",
foreign_keys=[parent_id]
)
children: Mapped[list["CostCategory"]] = relationship(
"CostCategory",
back_populates="parent",
foreign_keys=[parent_id]
)
# Kapcsolódó költségek
costs: Mapped[list["VehicleCost"]] = relationship("VehicleCost", back_populates="category")
def __repr__(self) -> str:
return f"CostCategory(id={self.id}, code='{self.code}', name='{self.name}')"
class VehicleCost(Base):
"""
Járműhöz kapcsolódó tényleges költségnapló.
Minden költséghez kötelező az odometer állás (km) és a dátum.
Az organization_id az Univerzális Flotta hivatkozás (fleet.organizations).
"""
__tablename__ = "costs"
__table_args__ = (
UniqueConstraint("vehicle_id", "category_id", "date", "odometer", name="uq_cost_unique_entry"),
{"schema": "vehicle"}
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
vehicle_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("vehicle.vehicle_model_definitions.id", ondelete="CASCADE"),
nullable=False,
index=True
)
organization_id: Mapped[Optional[int]] = mapped_column(
Integer,
ForeignKey("fleet.organizations.id", ondelete="SET NULL"),
nullable=True,
index=True
)
category_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("vehicle.cost_categories.id", ondelete="RESTRICT"),
nullable=False,
index=True
)
amount: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False) # Összeg
currency: Mapped[str] = mapped_column(String(3), default="HUF", server_default="'HUF'") # ISO valutakód
odometer: Mapped[int] = mapped_column(Integer, nullable=False) # Kilométeróra állás (km)
date: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
# Kapcsolatok
vehicle: Mapped["VehicleModelDefinition"] = relationship("VehicleModelDefinition", back_populates="costs")
organization: Mapped[Optional["Organization"]] = relationship("Organization", back_populates="vehicle_costs")
category: Mapped["CostCategory"] = relationship("CostCategory", back_populates="costs")
def __repr__(self) -> str:
return f"VehicleCost(id={self.id}, vehicle_id={self.vehicle_id}, amount={self.amount} {self.currency})"
class VehicleOdometerState(Base):
"""
Jármű kilométeróra állapotának és becslésének tárolása.
Adminisztrátor által paraméterezhető algoritmusokkal működik.
"""
__tablename__ = "vehicle_odometer_states"
__table_args__ = {"schema": "vehicle"}
vehicle_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("vehicle.vehicle_model_definitions.id", ondelete="CASCADE"),
primary_key=True,
nullable=False
)
last_recorded_odometer: Mapped[int] = mapped_column(Integer, nullable=False)
last_recorded_date: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
daily_avg_distance: Mapped[float] = mapped_column(Numeric(10, 2), nullable=False)
estimated_current_odometer: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False)
confidence_score: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
manual_override_avg: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
# Kapcsolat a jármű definícióval
vehicle: Mapped["VehicleModelDefinition"] = relationship("VehicleModelDefinition", back_populates="odometer_state")
def __repr__(self) -> str:
return f"VehicleOdometerState(vehicle_id={self.vehicle_id}, estimated={self.estimated_current_odometer}, confidence={self.confidence_score})"
class VehicleUserRating(Base):
"""
Jármű értékelési rendszer - User -> Vehicle kapcsolat.
Egy felhasználó csak egyszer értékelhet egy adott járművet.
Értékelés 4 dimenzióban 1-10 skálán.
"""
__tablename__ = "vehicle_user_ratings"
__table_args__ = (
UniqueConstraint("vehicle_id", "user_id", name="uq_vehicle_user_rating_unique"),
CheckConstraint("driving_experience BETWEEN 1 AND 10", name="ck_driving_experience_range"),
CheckConstraint("reliability BETWEEN 1 AND 10", name="ck_reliability_range"),
CheckConstraint("comfort BETWEEN 1 AND 10", name="ck_comfort_range"),
CheckConstraint("consumption_satisfaction BETWEEN 1 AND 10", name="ck_consumption_satisfaction_range"),
{"schema": "vehicle"}
)
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
server_default=func.gen_random_uuid()
)
vehicle_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("vehicle.vehicle_model_definitions.id", ondelete="CASCADE"),
nullable=False,
index=True
)
user_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("identity.users.id", ondelete="CASCADE"),
nullable=False,
index=True
)
driving_experience: Mapped[int] = mapped_column(Integer, nullable=False)
reliability: Mapped[int] = mapped_column(Integer, nullable=False)
comfort: Mapped[int] = mapped_column(Integer, nullable=False)
consumption_satisfaction: Mapped[int] = mapped_column(Integer, nullable=False)
comment: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
# Kapcsolatok
vehicle: Mapped["VehicleModelDefinition"] = relationship("VehicleModelDefinition", back_populates="ratings")
user: Mapped["User"] = relationship("User", back_populates="vehicle_ratings")
def __repr__(self) -> str:
return f"VehicleUserRating(id={self.id}, vehicle_id={self.vehicle_id}, user_id={self.user_id})"
@property
def average_score(self) -> float:
"""Számított átlagpontszám a 4 dimenzióból."""
scores = [self.driving_experience, self.reliability, self.comfort, self.consumption_satisfaction]
return sum(scores) / 4.0

View File

@@ -13,7 +13,7 @@ from app.database import Base
class VehicleType(Base):
""" Jármű kategóriák (pl. Személyautó, Motorkerékpár, Teherautó, Hajó) """
__tablename__ = "vehicle_types"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
code: Mapped[str] = mapped_column(String(30), unique=True, index=True)
@@ -29,10 +29,10 @@ class VehicleType(Base):
class FeatureDefinition(Base):
""" Felszereltségi elemek definíciója (pl. ABS, Klíma, LED fényszóró) """
__tablename__ = "feature_definitions"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
vehicle_type_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.vehicle_types.id"))
vehicle_type_id: Mapped[int] = mapped_column(Integer, ForeignKey("vehicle.vehicle_types.id"))
code: Mapped[str] = mapped_column(String(50), index=True)
name: Mapped[str] = mapped_column(String(100))
category: Mapped[str] = mapped_column(String(50), index=True)
@@ -48,7 +48,7 @@ class VehicleModelDefinition(Base):
Az ökoszisztéma technikai igazságforrása.
"""
__tablename__ = "vehicle_model_definitions"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
make: Mapped[str] = mapped_column(String(100), index=True)
@@ -89,7 +89,7 @@ class VehicleModelDefinition(Base):
# --- SPECIFIKÁCIÓK ---
vehicle_type_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.vehicle_types.id"))
vehicle_type_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("vehicle.vehicle_types.id"))
vehicle_class: Mapped[Optional[str]] = mapped_column(String(50), index=True)
body_type: Mapped[Optional[str]] = mapped_column(String(100))
fuel_type: Mapped[Optional[str]] = mapped_column(String(50), index=True)
@@ -130,7 +130,7 @@ class VehicleModelDefinition(Base):
UniqueConstraint('make', 'normalized_name', 'variant_code', 'version_code', 'fuel_type', 'market', 'year_from', name='uix_vmd_precision_v2'),
Index('idx_vmd_lookup_fast', 'make', 'normalized_name'),
Index('idx_vmd_engine_bridge', 'make', 'engine_code'),
{"schema": "data"}
{"schema": "vehicle"}
)
# KAPCSOLATOK
@@ -138,18 +138,23 @@ class VehicleModelDefinition(Base):
feature_maps: Mapped[List["ModelFeatureMap"]] = relationship("ModelFeatureMap", back_populates="model_definition")
# Hivatkozás az asset.py-ban lévő osztályra
# Megjegyzés: Ha az AssetCatalog nincs itt importálva, húzzal adjuk meg a nevet
# Megjegyzés: Ha az AssetCatalog nincs itt importálva, húzzal adjuk meg a neve
variants: Mapped[List["AssetCatalog"]] = relationship("AssetCatalog", back_populates="master_definition")
# TCO költségnapló kapcsolata
costs: Mapped[List["VehicleCost"]] = relationship("VehicleCost", back_populates="vehicle")
# Kilométeróra állapot kapcsolata
odometer_state: Mapped["VehicleOdometerState"] = relationship("VehicleOdometerState", back_populates="vehicle")
class ModelFeatureMap(Base):
""" Kapcsolótábla a modellek és az alapfelszereltség között """
__tablename__ = "model_feature_maps"
__table_args__ = {"schema": "data"}
__table_args__ = {"schema": "vehicle"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
model_definition_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.vehicle_model_definitions.id"))
feature_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.feature_definitions.id"))
model_definition_id: Mapped[int] = mapped_column(Integer, ForeignKey("vehicle.vehicle_model_definitions.id"))
feature_id: Mapped[int] = mapped_column(Integer, ForeignKey("vehicle.feature_definitions.id"))
is_standard: Mapped[bool] = mapped_column(Boolean, default=True)
model_definition: Mapped["VehicleModelDefinition"] = relationship("VehicleModelDefinition", back_populates="feature_maps")