refaktorálás javításai
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
72
backend/app/models/finance.py
Normal file
72
backend/app/models/finance.py
Normal 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.
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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
67
backend/app/models/identity.py
Executable file → Normal 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)
|
||||
234
backend/app/models/identity_1.0.py
Executable file
234
backend/app/models/identity_1.0.py
Executable 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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
124
backend/app/models/registry.py
Normal file
124
backend/app/models/registry.py
Normal 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']
|
||||
@@ -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))
|
||||
|
||||
@@ -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")
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
192
backend/app/models/vehicle.py
Normal file
192
backend/app/models/vehicle.py
Normal 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
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user