Initial commit: Robot ökoszisztéma v2.0 - Stabilizált jármű és szerviz robotok
This commit is contained in:
65
backend/app/models/__init__.py
Executable file
65
backend/app/models/__init__.py
Executable file
@@ -0,0 +1,65 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/__init__.py
|
||||
# MB 2.0: Kritikus javítás - Mindenki az app.database.Base-t használja!
|
||||
from app.database import Base
|
||||
|
||||
# 1. Alapvető identitás és szerepkörök
|
||||
from .identity import Person, User, Wallet, VerificationToken, SocialAccount, UserRole
|
||||
|
||||
# 2. Földrajzi adatok és címek
|
||||
from .address import Address, GeoPostalCode, GeoStreet, GeoStreetType, Rating
|
||||
|
||||
# 3. Jármű definíciók
|
||||
from .vehicle_definitions import VehicleModelDefinition, VehicleType, FeatureDefinition, ModelFeatureMap
|
||||
|
||||
# 4. Szervezeti felépítés
|
||||
from .organization import Organization, OrganizationMember, OrganizationFinancials, OrganizationSalesAssignment, OrgType, OrgUserRole, Branch
|
||||
|
||||
# 5. Eszközök és katalógusok
|
||||
from .asset import Asset, AssetCatalog, AssetCost, AssetEvent, AssetFinancials, AssetTelemetry, AssetReview, ExchangeRate, CatalogDiscovery, VehicleOwnership
|
||||
|
||||
# 6. Üzleti logika és előfizetések
|
||||
from .core_logic import SubscriptionTier, OrganizationSubscription, CreditTransaction, ServiceSpecialty
|
||||
|
||||
# 7. Szolgáltatások és staging
|
||||
from .service import ServiceProfile, ExpertiseTag, ServiceExpertise, ServiceStaging, DiscoveryParameter
|
||||
|
||||
# 8. 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 .document import Document
|
||||
from .translation import Translation
|
||||
from .audit import SecurityAuditLog, ProcessLog, FinancialLedger
|
||||
from .history import AuditLog, LogSeverity
|
||||
from .security import PendingAction
|
||||
from .legal import LegalDocument, LegalAcceptance
|
||||
from .logistics import Location, LocationType
|
||||
|
||||
# Aliasok a Digital Twin kompatibilitáshoz
|
||||
Vehicle = Asset
|
||||
UserVehicle = Asset
|
||||
VehicleCatalog = AssetCatalog
|
||||
ServiceRecord = AssetEvent
|
||||
|
||||
__all__ = [
|
||||
"Base", "User", "Person", "Wallet", "UserRole", "VerificationToken", "SocialAccount",
|
||||
"Organization", "OrganizationMember", "OrganizationSalesAssignment", "OrgType", "OrgUserRole",
|
||||
"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",
|
||||
|
||||
"Document", "Translation", "PendingAction",
|
||||
"SubscriptionTier", "OrganizationSubscription", "CreditTransaction", "ServiceSpecialty",
|
||||
"AuditLog", "VehicleOwnership", "LogSeverity",
|
||||
"SecurityAuditLog", "ProcessLog", "FinancialLedger",
|
||||
"ServiceProfile", "ExpertiseTag", "ServiceExpertise", "ServiceStaging", "DiscoveryParameter",
|
||||
"Vehicle", "UserVehicle", "VehicleCatalog", "ServiceRecord", "VehicleModelDefinition",
|
||||
"VehicleType", "FeatureDefinition", "ModelFeatureMap", "LegalDocument", "LegalAcceptance",
|
||||
"Location", "LocationType"
|
||||
]
|
||||
89
backend/app/models/address.py
Executable file
89
backend/app/models/address.py
Executable file
@@ -0,0 +1,89 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/address.py
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, List, Optional
|
||||
from sqlalchemy import String, Integer, ForeignKey, Text, DateTime, Float, Boolean, text, func, Numeric, Index, and_
|
||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship, foreign
|
||||
|
||||
# MB 2.0: Kritikus javítás - a központi metadata-t használjuk az app.database-ből
|
||||
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"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
country_code: Mapped[str] = mapped_column(String(5), default="HU")
|
||||
zip_code: Mapped[str] = mapped_column(String(10), nullable=False, index=True)
|
||||
city: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
||||
|
||||
class GeoStreet(Base):
|
||||
"""Utcajegyzék tábla."""
|
||||
__tablename__ = "geo_streets"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
postal_code_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.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"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
|
||||
|
||||
class Address(Base):
|
||||
"""Univerzális cím entitás GPS adatokkal kiegészítve."""
|
||||
__tablename__ = "addresses"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
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"))
|
||||
|
||||
street_name: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
street_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
house_number: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
|
||||
stairwell: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
floor: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
door: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
parcel_id: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
full_address_text: Mapped[Optional[str]] = mapped_column(Text)
|
||||
|
||||
# Robot és térképes funkciók számára
|
||||
latitude: Mapped[Optional[float]] = mapped_column(Float)
|
||||
longitude: Mapped[Optional[float]] = mapped_column(Float)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
|
||||
class Rating(Base):
|
||||
"""Univerzális értékelési rendszer - v1.3.1"""
|
||||
__tablename__ = "ratings"
|
||||
__table_args__ = (
|
||||
Index('idx_rating_org', 'target_organization_id'),
|
||||
Index('idx_rating_user', 'target_user_id'),
|
||||
Index('idx_rating_branch', 'target_branch_id'),
|
||||
{"schema": "data"}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
|
||||
# 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_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"))
|
||||
|
||||
score: Mapped[float] = mapped_column(Numeric(3, 2), nullable=False)
|
||||
comment: Mapped[Optional[str]] = mapped_column(Text)
|
||||
images: Mapped[Any] = mapped_column(JSONB, server_default=text("'[]'::jsonb"))
|
||||
|
||||
is_verified: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
221
backend/app/models/asset.py
Normal file
221
backend/app/models/asset.py
Normal file
@@ -0,0 +1,221 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/asset.py
|
||||
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.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB
|
||||
from sqlalchemy.sql import func
|
||||
from app.database import Base
|
||||
|
||||
class AssetCatalog(Base):
|
||||
""" Jármű katalógus mesteradatok (Validált technikai sablonok). """
|
||||
__tablename__ = "vehicle_catalog"
|
||||
__table_args__ = (
|
||||
UniqueConstraint('make', 'model', 'year_from', 'fuel_type', name='uix_vehicle_catalog_full'),
|
||||
{"schema": "data"}
|
||||
)
|
||||
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"))
|
||||
|
||||
make: Mapped[str] = mapped_column(String, index=True, nullable=False)
|
||||
model: Mapped[str] = mapped_column(String, index=True, nullable=False)
|
||||
generation: Mapped[Optional[str]] = mapped_column(String, index=True)
|
||||
year_from: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
year_to: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
fuel_type: Mapped[Optional[str]] = mapped_column(String, index=True)
|
||||
power_kw: Mapped[Optional[int]] = mapped_column(Integer, index=True)
|
||||
engine_capacity: Mapped[Optional[int]] = mapped_column(Integer, index=True)
|
||||
|
||||
factory_data: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
|
||||
master_definition: Mapped[Optional["VehicleModelDefinition"]] = relationship("VehicleModelDefinition", back_populates="variants")
|
||||
assets: Mapped[List["Asset"]] = relationship("Asset", back_populates="catalog")
|
||||
|
||||
class Asset(Base):
|
||||
""" A fizikai eszköz (Digital Twin) - Minden adat itt fut össze. """
|
||||
__tablename__ = "assets"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
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)
|
||||
license_plate: Mapped[Optional[str]] = mapped_column(String(20), index=True)
|
||||
name: Mapped[Optional[str]] = mapped_column(String)
|
||||
|
||||
# Állapot és életút mérőszámok
|
||||
year_of_manufacture: Mapped[Optional[int]] = mapped_column(Integer, index=True)
|
||||
first_registration_date: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
current_mileage: Mapped[int] = mapped_column(Integer, default=0, index=True)
|
||||
condition_score: Mapped[int] = mapped_column(Integer, default=100)
|
||||
|
||||
# Értékesítési modul
|
||||
is_for_sale: Mapped[bool] = mapped_column(Boolean, default=False, index=True)
|
||||
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"))
|
||||
|
||||
# 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"))
|
||||
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"))
|
||||
|
||||
status: Mapped[str] = mapped_column(String(20), default="active")
|
||||
individual_equipment: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
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 ---
|
||||
catalog: Mapped["AssetCatalog"] = relationship("AssetCatalog", back_populates="assets")
|
||||
financials: Mapped[Optional["AssetFinancials"]] = relationship("AssetFinancials", back_populates="asset", uselist=False)
|
||||
costs: Mapped[List["AssetCost"]] = relationship("AssetCost", back_populates="asset")
|
||||
events: Mapped[List["AssetEvent"]] = relationship("AssetEvent", back_populates="asset")
|
||||
logbook: Mapped[List["VehicleLogbook"]] = relationship("VehicleLogbook", back_populates="asset")
|
||||
inspections: Mapped[List["AssetInspection"]] = relationship("AssetInspection", back_populates="asset")
|
||||
reviews: Mapped[List["AssetReview"]] = relationship("AssetReview", back_populates="asset")
|
||||
telemetry: Mapped[Optional["AssetTelemetry"]] = relationship("AssetTelemetry", back_populates="asset", uselist=False)
|
||||
assignments: Mapped[List["AssetAssignment"]] = relationship("AssetAssignment", back_populates="asset")
|
||||
ownership_history: Mapped[List["VehicleOwnership"]] = relationship("VehicleOwnership", back_populates="asset")
|
||||
|
||||
class AssetFinancials(Base):
|
||||
""" I. Beszerzés és IV. Értékcsökkenés (Amortizáció). """
|
||||
__tablename__ = "asset_financials"
|
||||
__table_args__ = {"schema": "data"}
|
||||
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)
|
||||
|
||||
purchase_price_net: Mapped[float] = mapped_column(Numeric(18, 2))
|
||||
purchase_price_gross: Mapped[float] = mapped_column(Numeric(18, 2))
|
||||
vat_rate: Mapped[float] = mapped_column(Numeric(5, 2), default=27.00)
|
||||
activation_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
financing_type: Mapped[str] = mapped_column(String(50))
|
||||
accounting_details: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
|
||||
asset: Mapped["Asset"] = relationship("Asset", back_populates="financials")
|
||||
|
||||
class AssetCost(Base):
|
||||
""" II. Üzemeltetés és TCO kimutatás. """
|
||||
__tablename__ = "asset_costs"
|
||||
__table_args__ = {"schema": "data"}
|
||||
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)
|
||||
|
||||
cost_category: Mapped[str] = mapped_column(String(50), index=True)
|
||||
amount_net: Mapped[float] = mapped_column(Numeric(18, 2), nullable=False)
|
||||
currency: Mapped[str] = mapped_column(String(3), default="HUF")
|
||||
date: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
invoice_number: Mapped[Optional[str]] = mapped_column(String(100), index=True)
|
||||
|
||||
data: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
asset: Mapped["Asset"] = relationship("Asset", back_populates="costs")
|
||||
organization: Mapped["Organization"] = relationship("Organization")
|
||||
|
||||
class VehicleLogbook(Base):
|
||||
""" Útnyilvántartás (NAV, Kiküldetés, Munkábajárás). """
|
||||
__tablename__ = "vehicle_logbook"
|
||||
__table_args__ = {"schema": "data"}
|
||||
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)
|
||||
driver_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False)
|
||||
|
||||
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)
|
||||
|
||||
asset: Mapped["Asset"] = relationship("Asset", back_populates="logbook")
|
||||
driver: Mapped["User"] = relationship("User")
|
||||
|
||||
class AssetInspection(Base):
|
||||
""" Napi ellenőrző lista és Biztonsági check. """
|
||||
__tablename__ = "asset_inspections"
|
||||
__table_args__ = {"schema": "data"}
|
||||
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)
|
||||
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())
|
||||
checklist_results: Mapped[dict] = mapped_column(JSONB, nullable=False)
|
||||
is_safe: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
|
||||
asset: Mapped["Asset"] = relationship("Asset", back_populates="inspections")
|
||||
inspector: Mapped["User"] = relationship("User")
|
||||
|
||||
class AssetReview(Base):
|
||||
""" Jármű értékelések és visszajelzések. """
|
||||
__tablename__ = "asset_reviews"
|
||||
__table_args__ = {"schema": "data"}
|
||||
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)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False)
|
||||
|
||||
overall_rating: Mapped[Optional[int]] = mapped_column(Integer) # 1-5 csillag
|
||||
comment: Mapped[Optional[str]] = mapped_column(Text)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
asset: Mapped["Asset"] = relationship("Asset", back_populates="reviews")
|
||||
user: Mapped["User"] = relationship("User")
|
||||
|
||||
class VehicleOwnership(Base):
|
||||
""" Tulajdonosváltások története. """
|
||||
__tablename__ = "vehicle_ownership_history"
|
||||
__table_args__ = {"schema": "data"}
|
||||
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)
|
||||
|
||||
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())
|
||||
disposed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
asset: Mapped["Asset"] = relationship("Asset", back_populates="ownership_history")
|
||||
# EZ A SOR HIÁNYZIK A KÓDODBÓL ÉS EZ JAVÍTJA A HIBÁT:
|
||||
user: Mapped["User"] = relationship("User", back_populates="ownership_history")
|
||||
|
||||
class AssetTelemetry(Base):
|
||||
__tablename__ = "asset_telemetry"
|
||||
__table_args__ = {"schema": "data"}
|
||||
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)
|
||||
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"}
|
||||
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)
|
||||
status: Mapped[str] = mapped_column(String(30), default="active")
|
||||
|
||||
asset: Mapped["Asset"] = relationship("Asset", back_populates="assignments")
|
||||
organization: Mapped["Organization"] = relationship("Organization", back_populates="assets")
|
||||
|
||||
class AssetEvent(Base):
|
||||
""" Szerviz, baleset és egyéb jelentős események. """
|
||||
__tablename__ = "asset_events"
|
||||
__table_args__ = {"schema": "data"}
|
||||
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)
|
||||
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"}
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
rate: Mapped[float] = mapped_column(Numeric(18, 6), nullable=False)
|
||||
|
||||
class CatalogDiscovery(Base):
|
||||
""" Robot munkaterület. """
|
||||
__tablename__ = "catalog_discovery"
|
||||
__table_args__ = (UniqueConstraint('make', 'model', name='_make_model_uc'), {"schema": "data"})
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
make: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
||||
model: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
||||
status: Mapped[str] = mapped_column(String(20), server_default=text("'pending'"), index=True)
|
||||
63
backend/app/models/audit.py
Executable file
63
backend/app/models/audit.py
Executable file
@@ -0,0 +1,63 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/audit.py
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional
|
||||
from sqlalchemy import String, DateTime, JSON, ForeignKey, text, Numeric, Boolean, BigInteger, Integer
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.sql import func
|
||||
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"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
action: Mapped[Optional[str]] = mapped_column(String(50)) # 'ROLE_CHANGE', 'MANUAL_CREDIT_ADJUST'
|
||||
|
||||
actor_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
target_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
confirmed_by_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=True)
|
||||
|
||||
is_critical: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
payload_before: Mapped[Any] = mapped_column(JSON)
|
||||
payload_after: Mapped[Any] = mapped_column(JSON)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
class OperationalLog(Base):
|
||||
""" Felhasználói szintű napi üzemi események (Audit Trail). """
|
||||
__tablename__ = "operational_logs"
|
||||
|
||||
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"))
|
||||
action: Mapped[str] = mapped_column(String(100), nullable=False) # pl. "ADD_VEHICLE"
|
||||
resource_type: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
resource_id: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
details: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
|
||||
ip_address: Mapped[Optional[str]] = mapped_column(String(45))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
class ProcessLog(Base):
|
||||
""" Robotok és háttérfolyamatok futási naplója (A reggeli jelentésekhez). """
|
||||
__tablename__ = "process_logs"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
process_name: Mapped[str] = mapped_column(String(100), index=True) # 'Master-Enricher'
|
||||
start_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
end_time: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
items_processed: Mapped[int] = mapped_column(Integer, default=0)
|
||||
items_failed: Mapped[int] = mapped_column(Integer, default=0)
|
||||
details: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
class FinancialLedger(Base):
|
||||
""" Minden pénz- és kreditmozgás központi naplója. Billing Engine alapja. """
|
||||
__tablename__ = "financial_ledger"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
person_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id"))
|
||||
amount: Mapped[float] = mapped_column(Numeric(18, 4), nullable=False)
|
||||
currency: Mapped[Optional[str]] = mapped_column(String(10))
|
||||
transaction_type: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
related_agent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
details: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
76
backend/app/models/core_logic.py
Executable file
76
backend/app/models/core_logic.py
Executable file
@@ -0,0 +1,76 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/core_logic.py
|
||||
from typing import Optional, List, Any
|
||||
from datetime import datetime # Python saját típusa a típusjelöléshez
|
||||
from sqlalchemy import String, Integer, ForeignKey, Boolean, DateTime, Numeric, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
# MB 2.0: A központi aszinkron adatbázis motorból húzzuk be a Base-t
|
||||
from app.database import Base
|
||||
|
||||
class SubscriptionTier(Base):
|
||||
"""
|
||||
Előfizetési csomagok definíciója (pl. Free, Premium, VIP).
|
||||
A csomagok határozzák meg a korlátokat (pl. max járműszám).
|
||||
"""
|
||||
__tablename__ = "subscription_tiers"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String, unique=True, index=True) # pl. 'premium'
|
||||
rules: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) # pl. {"max_vehicles": 5}
|
||||
is_custom: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
|
||||
class OrganizationSubscription(Base):
|
||||
"""
|
||||
Szervezetek aktuális előfizetései és azok érvényessége.
|
||||
"""
|
||||
__tablename__ = "org_subscriptions"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
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 csomaggal (data séma)
|
||||
tier_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.subscription_tiers.id"), nullable=False)
|
||||
|
||||
valid_from: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
valid_until: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
|
||||
class CreditTransaction(Base):
|
||||
"""
|
||||
Kreditnapló (Pontok, kreditek vagy virtuális egyenleg követése).
|
||||
"""
|
||||
__tablename__ = "credit_logs"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
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)
|
||||
|
||||
amount: Mapped[float] = mapped_column(Numeric(10, 2), nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(String)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
class ServiceSpecialty(Base):
|
||||
"""
|
||||
Hierarchikus fa struktúra a szerviz szolgáltatásokhoz (pl. Motor -> Futómű).
|
||||
"""
|
||||
__tablename__ = "service_specialties"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
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"))
|
||||
|
||||
name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
slug: Mapped[str] = mapped_column(String, unique=True, index=True)
|
||||
|
||||
# Kapcsolat az ős-szolgáltatással (Self-referential relationship)
|
||||
parent: Mapped[Optional["ServiceSpecialty"]] = relationship("ServiceSpecialty", remote_side=[id], backref="children")
|
||||
30
backend/app/models/document.py
Executable file
30
backend/app/models/document.py
Executable file
@@ -0,0 +1,30 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/document.py
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from sqlalchemy import String, Integer, Boolean, DateTime, ForeignKey, text
|
||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.sql import func
|
||||
from app.db.base_class import Base
|
||||
|
||||
class Document(Base):
|
||||
""" NAS alapú dokumentumtár metaadatai. """
|
||||
__tablename__ = "documents"
|
||||
|
||||
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'
|
||||
parent_id: Mapped[str] = mapped_column(String(50), index=True)
|
||||
doc_type: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
|
||||
original_name: Mapped[str] = mapped_column(String(255))
|
||||
file_hash: Mapped[str] = mapped_column(String(64))
|
||||
file_ext: Mapped[str] = mapped_column(String(10), default="webp")
|
||||
mime_type: Mapped[str] = mapped_column(String(100), default="image/webp")
|
||||
file_size: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
|
||||
has_thumbnail: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
thumbnail_path: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
|
||||
uploaded_by: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
86
backend/app/models/gamification.py
Executable file
86
backend/app/models/gamification.py
Executable file
@@ -0,0 +1,86 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/gamification.py
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, TYPE_CHECKING
|
||||
from sqlalchemy import ForeignKey, String, Integer, DateTime, func, Boolean, Text, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
||||
from app.database import Base # MB 2.0: Központi Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.models.identity import User
|
||||
|
||||
class PointRule(Base):
|
||||
__tablename__ = "point_rules"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
action_key: Mapped[str] = mapped_column(String, unique=True, index=True)
|
||||
points: Mapped[int] = mapped_column(Integer, default=0)
|
||||
description: Mapped[Optional[str]] = mapped_column(String)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
|
||||
class LevelConfig(Base):
|
||||
__tablename__ = "level_configs"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
level_number: Mapped[int] = mapped_column(Integer, unique=True)
|
||||
min_points: Mapped[int] = mapped_column(Integer)
|
||||
rank_name: Mapped[str] = mapped_column(String)
|
||||
|
||||
class PointsLedger(Base):
|
||||
__tablename__ = "points_ledger"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
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"))
|
||||
|
||||
points: Mapped[int] = mapped_column(Integer, default=0)
|
||||
penalty_change: Mapped[int] = mapped_column(Integer, server_default=text("0"), default=0)
|
||||
reason: Mapped[str] = mapped_column(String)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
user: Mapped["User"] = relationship("User")
|
||||
|
||||
class UserStats(Base):
|
||||
__tablename__ = "user_stats"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
# MB 2.0: User az identity sémában lakik!
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), primary_key=True)
|
||||
|
||||
total_xp: Mapped[int] = mapped_column(Integer, default=0)
|
||||
social_points: Mapped[int] = mapped_column(Integer, default=0)
|
||||
current_level: Mapped[int] = mapped_column(Integer, default=1)
|
||||
|
||||
penalty_points: Mapped[int] = mapped_column(Integer, server_default=text("0"), default=0)
|
||||
restriction_level: Mapped[int] = mapped_column(Integer, server_default=text("0"), default=0)
|
||||
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
user: Mapped["User"] = relationship("User", back_populates="stats")
|
||||
|
||||
class Badge(Base):
|
||||
__tablename__ = "badges"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
name: Mapped[str] = mapped_column(String, unique=True)
|
||||
description: Mapped[str] = mapped_column(String)
|
||||
icon_url: Mapped[Optional[str]] = mapped_column(String)
|
||||
|
||||
class UserBadge(Base):
|
||||
__tablename__ = "user_badges"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
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"))
|
||||
|
||||
earned_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
user: Mapped["User"] = relationship("User")
|
||||
47
backend/app/models/history.py
Executable file
47
backend/app/models/history.py
Executable file
@@ -0,0 +1,47 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/history.py
|
||||
import uuid
|
||||
import enum
|
||||
from datetime import datetime, date
|
||||
from typing import Optional, Any
|
||||
from sqlalchemy import String, DateTime, ForeignKey, JSON, Date, Text, Integer
|
||||
from sqlalchemy.dialects.postgresql import ENUM as PG_ENUM, UUID as PG_UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
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
|
||||
|
||||
class LogSeverity(str, enum.Enum):
|
||||
info = "info"
|
||||
warning = "warning"
|
||||
critical = "critical"
|
||||
emergency = "emergency"
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
""" Rendszerszintű műveletnapló. """
|
||||
__tablename__ = "audit_logs"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
|
||||
# MB 2.0 JAVÍTÁS: A felhasználó az identity sémában lakik!
|
||||
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"),
|
||||
default=LogSeverity.info
|
||||
)
|
||||
|
||||
action: Mapped[str] = mapped_column(String(100), index=True)
|
||||
target_type: Mapped[Optional[str]] = mapped_column(String(50), index=True)
|
||||
target_id: Mapped[Optional[str]] = mapped_column(String(50), index=True)
|
||||
|
||||
old_data: Mapped[Optional[Any]] = mapped_column(JSON)
|
||||
new_data: Mapped[Optional[Any]] = mapped_column(JSON)
|
||||
|
||||
ip_address: Mapped[Optional[str]] = mapped_column(String(45), index=True)
|
||||
user_agent: Mapped[Optional[Text]] = mapped_column(Text)
|
||||
timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
||||
|
||||
user: Mapped[Optional["User"]] = relationship("User")
|
||||
174
backend/app/models/identity.py
Executable file
174
backend/app/models/identity.py
Executable file
@@ -0,0 +1,174 @@
|
||||
# /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("data.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"))
|
||||
|
||||
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")
|
||||
|
||||
@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")
|
||||
|
||||
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")
|
||||
31
backend/app/models/legal.py
Executable file
31
backend/app/models/legal.py
Executable file
@@ -0,0 +1,31 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/legal.py
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from sqlalchemy import Integer, String, Text, DateTime, ForeignKey, Boolean
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.sql import func
|
||||
from app.db.base_class import Base
|
||||
|
||||
class LegalDocument(Base):
|
||||
__tablename__ = "legal_documents"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
title: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
content: Mapped[str] = mapped_column(Text)
|
||||
version: Mapped[str] = mapped_column(String(20))
|
||||
|
||||
region_code: Mapped[str] = mapped_column(String(5), default="HU")
|
||||
language: Mapped[str] = mapped_column(String(5), default="hu")
|
||||
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
class LegalAcceptance(Base):
|
||||
__tablename__ = "legal_acceptances"
|
||||
|
||||
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"))
|
||||
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)
|
||||
26
backend/app/models/logistics.py
Executable file
26
backend/app/models/logistics.py
Executable file
@@ -0,0 +1,26 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/logistics.py
|
||||
import enum
|
||||
from typing import Optional
|
||||
from sqlalchemy import Integer, String, Enum
|
||||
from sqlalchemy.dialects.postgresql import ENUM as PG_ENUM
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from app.db.base_class import Base
|
||||
|
||||
class LocationType(str, enum.Enum):
|
||||
stop = "stop"
|
||||
warehouse = "warehouse"
|
||||
client = "client"
|
||||
|
||||
class Location(Base):
|
||||
__tablename__ = "locations"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
name: Mapped[str] = mapped_column(String)
|
||||
type: Mapped[LocationType] = mapped_column(
|
||||
PG_ENUM(LocationType, name="location_type", inherit_schema=True),
|
||||
nullable=False
|
||||
)
|
||||
|
||||
coordinates: Mapped[Optional[str]] = mapped_column(String)
|
||||
address_full: Mapped[Optional[str]] = mapped_column(String)
|
||||
capacity: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
217
backend/app/models/organization.py
Executable file
217
backend/app/models/organization.py
Executable file
@@ -0,0 +1,217 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/organization.py
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, List, Optional
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, JSON, text, Numeric, BigInteger, Float
|
||||
from sqlalchemy.dialects.postgresql import ENUM as PG_ENUM, UUID as PG_UUID, JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship, foreign
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
# MB 2.0: A központi aszinkron adatbázis motorból húzzuk be a Base-t
|
||||
from app.database import Base
|
||||
|
||||
class OrgType(str, enum.Enum):
|
||||
individual = "individual"
|
||||
service = "service"
|
||||
service_provider = "service_provider"
|
||||
fleet_owner = "fleet_owner"
|
||||
club = "club"
|
||||
business = "business"
|
||||
|
||||
class OrgUserRole(str, enum.Enum):
|
||||
OWNER = "OWNER"
|
||||
ADMIN = "ADMIN"
|
||||
FLEET_MANAGER = "FLEET_MANAGER"
|
||||
DRIVER = "DRIVER"
|
||||
MECHANIC = "MECHANIC"
|
||||
RECEPTIONIST = "RECEPTIONIST"
|
||||
|
||||
class Organization(Base):
|
||||
"""
|
||||
Szervezet entitás (MB 2.0).
|
||||
Támogatja a 'Digital Twin' logikát: a cég törölhető, de a statisztika és
|
||||
a jármű-életút adatok megmaradnak az eredeti Person-höz kötve.
|
||||
"""
|
||||
__tablename__ = "organizations"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
|
||||
# --- 🛡️ BIZTONSÁGI ÉS ÉLETÚT KIEGÉSZÍTÉSEK ---
|
||||
|
||||
# A Jogi képviselő/Tulajdonos (A Person örök DNS-e az identity sémában)
|
||||
# Ez segít felismerni, ha ugyanaz az ember új céggel akar 'tiszta lapot' nyitni.
|
||||
legal_owner_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id"), index=True)
|
||||
|
||||
# ÉLETÚT DÁTUMOK (A kért logika alapján)
|
||||
# 1. A legelső regisztráció dátuma (Soha nem változik)
|
||||
first_registered_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# 2. Az AKTUÁLIS életciklus kezdete. Újraregisztrációkor ez frissül.
|
||||
# Az API ezt használja szűrőnek: a cég csak az ezutáni adatokat látja a Dashboardon.
|
||||
current_lifecycle_started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# 3. Az utolsó deaktiválás/törlés időpontja
|
||||
last_deactivated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
# Hányszor regisztrált újra ez a cég/adószám (Reinkarnációs index)
|
||||
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"))
|
||||
|
||||
is_anonymized: Mapped[bool] = mapped_column(Boolean, default=False, server_default=text("false"))
|
||||
anonymized_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
full_name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
display_name: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
folder_slug: Mapped[str] = mapped_column(String(12), unique=True, index=True)
|
||||
|
||||
default_currency: Mapped[str] = mapped_column(String(3), default="HUF")
|
||||
country_code: Mapped[str] = mapped_column(String(2), default="HU")
|
||||
language: Mapped[str] = mapped_column(String(5), default="hu")
|
||||
|
||||
address_zip: Mapped[Optional[str]] = mapped_column(String(10))
|
||||
address_city: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
address_street_name: Mapped[Optional[str]] = mapped_column(String(150))
|
||||
address_street_type: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
address_house_number: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
address_hrsz: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
|
||||
tax_number: Mapped[Optional[str]] = mapped_column(String(20), unique=True, index=True)
|
||||
reg_number: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
|
||||
org_type: Mapped[OrgType] = mapped_column(
|
||||
PG_ENUM(OrgType, name="orgtype", schema="data"),
|
||||
default=OrgType.individual
|
||||
)
|
||||
|
||||
status: Mapped[str] = mapped_column(String(30), default="pending_verification")
|
||||
|
||||
# Soft delete: is_active=False és is_deleted=True esetén a cég 'törölt'
|
||||
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
|
||||
subscription_plan: Mapped[str] = mapped_column(String(30), server_default=text("'FREE'"), index=True)
|
||||
base_asset_limit: Mapped[int] = mapped_column(Integer, server_default=text("1"))
|
||||
purchased_extra_slots: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
|
||||
notification_settings: Mapped[Any] = mapped_column(JSON, server_default=text("'{\"notify_owner\": true, \"alert_days_before\": [30, 15, 7, 1]}'::jsonb"))
|
||||
external_integration_config: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
|
||||
|
||||
# A technikai tulajdonos (User fiók - törölhető)
|
||||
owner_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
|
||||
is_verified: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
|
||||
# Időbélyegek az aktuális állapothoz
|
||||
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())
|
||||
is_ownership_transferable: Mapped[bool] = mapped_column(Boolean, server_default=text("true"))
|
||||
|
||||
# --- 🔗 KAPCSOLATOK (RELATIONSHIPS) ---
|
||||
assets: Mapped[List["AssetAssignment"]] = relationship("AssetAssignment", back_populates="organization", cascade="all, delete-orphan")
|
||||
members: Mapped[List["OrganizationMember"]] = relationship("OrganizationMember", back_populates="organization", cascade="all, delete-orphan")
|
||||
owner: Mapped[Optional["User"]] = relationship("User", back_populates="owned_organizations")
|
||||
financials: Mapped[List["OrganizationFinancials"]] = relationship("OrganizationFinancials", back_populates="organization", cascade="all, delete-orphan")
|
||||
|
||||
# JAVÍTVA: Ha az Organization törlődik, a ServiceProfile megmarad 'Ghost'-ként (ondelete="SET NULL")
|
||||
service_profile: Mapped[Optional["ServiceProfile"]] = relationship("ServiceProfile", back_populates="organization", uselist=False)
|
||||
|
||||
branches: Mapped[List["Branch"]] = relationship("Branch", back_populates="organization", cascade="all, delete-orphan")
|
||||
|
||||
# Kapcsolat az örök személy rekordhoz
|
||||
legal_owner: Mapped[Optional["Person"]] = relationship("Person", back_populates="owned_business_entities")
|
||||
|
||||
class OrganizationFinancials(Base):
|
||||
__tablename__ = "organization_financials"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.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))
|
||||
employee_count: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
source: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
organization: Mapped["Organization"] = relationship("Organization", back_populates="financials")
|
||||
|
||||
class OrganizationMember(Base):
|
||||
__tablename__ = "organization_members"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.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"),
|
||||
default=OrgUserRole.DRIVER
|
||||
)
|
||||
permissions: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
|
||||
is_permanent: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
is_verified: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
|
||||
organization: Mapped["Organization"] = relationship("Organization", back_populates="members")
|
||||
user: Mapped[Optional["User"]] = relationship("User")
|
||||
person: Mapped[Optional["Person"]] = relationship("Person", back_populates="memberships")
|
||||
|
||||
class OrganizationSalesAssignment(Base):
|
||||
__tablename__ = "org_sales_assignments"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.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())
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
|
||||
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"}
|
||||
|
||||
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"))
|
||||
|
||||
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
is_main: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
|
||||
# Denormalizált adatok a gyors lekérdezéshez
|
||||
postal_code: Mapped[Optional[str]] = mapped_column(String(10), index=True)
|
||||
city: Mapped[Optional[str]] = mapped_column(String(100), index=True)
|
||||
street_name: Mapped[Optional[str]] = mapped_column(String(150))
|
||||
street_type: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
house_number: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
stairwell: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
floor: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
door: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
hrsz: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
|
||||
opening_hours: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
branch_rating: Mapped[float] = mapped_column(Float, default=0.0)
|
||||
|
||||
status: Mapped[str] = mapped_column(String(30), default="active")
|
||||
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Kapcsolatok
|
||||
organization: Mapped["Organization"] = relationship("Organization", back_populates="branches")
|
||||
address: Mapped[Optional["Address"]] = relationship("Address")
|
||||
|
||||
# Kapcsolatok (Primaryjoin tartva a rating rendszerhez)
|
||||
reviews: Mapped[List["Rating"]] = relationship(
|
||||
"Rating",
|
||||
primaryjoin="and_(Branch.id==foreign(Rating.target_branch_id))"
|
||||
)
|
||||
51
backend/app/models/security.py
Executable file
51
backend/app/models/security.py
Executable file
@@ -0,0 +1,51 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/security.py
|
||||
import enum
|
||||
from datetime import datetime
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from sqlalchemy import String, Integer, ForeignKey, DateTime, text, Enum
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
# MB 2.0: Központi aszinkron adatbázis motorból származó Base
|
||||
from app.database import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .identity import User
|
||||
|
||||
class ActionStatus(str, enum.Enum):
|
||||
pending = "pending"
|
||||
approved = "approved"
|
||||
rejected = "rejected"
|
||||
expired = "expired"
|
||||
|
||||
class PendingAction(Base):
|
||||
""" Sentinel: Kritikus műveletek jóváhagyási lánca. """
|
||||
__tablename__ = "pending_actions"
|
||||
__table_args__ = {"schema": "system"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
|
||||
# JAVÍTÁS: A User az identity sémában van, nem a data-ban!
|
||||
requester_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False)
|
||||
approver_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=True)
|
||||
|
||||
status: Mapped[ActionStatus] = mapped_column(
|
||||
Enum(ActionStatus, name="actionstatus", schema="system"),
|
||||
default=ActionStatus.pending
|
||||
)
|
||||
|
||||
action_type: Mapped[str] = mapped_column(String(50)) # pl. "WALLET_ADJUST"
|
||||
payload: Mapped[dict] = mapped_column(JSONB, nullable=False)
|
||||
reason: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
expires_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=text("now() + interval '24 hours'")
|
||||
)
|
||||
processed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
# Kapcsolatok meghatározása (String hivatkozással a körkörös import ellen)
|
||||
requester: Mapped["User"] = relationship("User", foreign_keys=[requester_id])
|
||||
approver: Mapped[Optional["User"]] = relationship("User", foreign_keys=[approver_id])
|
||||
159
backend/app/models/service.py
Executable file
159
backend/app/models/service.py
Executable file
@@ -0,0 +1,159 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/service.py
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, List, Optional
|
||||
from sqlalchemy import Integer, String, Boolean, DateTime, ForeignKey, text, Text, Float, Index, Numeric, BigInteger
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB
|
||||
from geoalchemy2 import Geometry
|
||||
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
|
||||
|
||||
class ServiceProfile(Base):
|
||||
""" Szerviz szolgáltató adatai (v1.3.1). """
|
||||
__tablename__ = "service_profiles"
|
||||
__table_args__ = (
|
||||
Index('idx_service_fingerprint', 'fingerprint', unique=True),
|
||||
{"schema": "data"}
|
||||
)
|
||||
|
||||
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"))
|
||||
|
||||
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)
|
||||
|
||||
status: Mapped[str] = mapped_column(String(20), server_default=text("'ghost'"), index=True)
|
||||
last_audit_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
google_place_id: Mapped[Optional[str]] = mapped_column(String(100), unique=True)
|
||||
rating: Mapped[Optional[float]] = mapped_column(Float)
|
||||
user_ratings_total: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
|
||||
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"))
|
||||
|
||||
trust_score: Mapped[int] = mapped_column(Integer, default=30)
|
||||
is_verified: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
verification_log: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
|
||||
opening_hours: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
contact_phone: Mapped[Optional[str]] = mapped_column(String)
|
||||
contact_email: Mapped[Optional[str]] = mapped_column(String)
|
||||
website: Mapped[Optional[str]] = mapped_column(String)
|
||||
bio: Mapped[Optional[str]] = mapped_column(Text)
|
||||
|
||||
# Kapcsolatok
|
||||
organization: Mapped["Organization"] = relationship("Organization", back_populates="service_profile")
|
||||
expertises: Mapped[List["ServiceExpertise"]] = relationship("ServiceExpertise", 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())
|
||||
|
||||
class ExpertiseTag(Base):
|
||||
"""
|
||||
Szakmai címkék mesterlistája (MB 2.0).
|
||||
Ez a tábla vezérli a robotok keresését és a Gamification pontozást is.
|
||||
"""
|
||||
__tablename__ = "expertise_tags"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
|
||||
# Egyedi azonosító kulcs (pl. 'ENGINE_REBUILD')
|
||||
key: Mapped[str] = mapped_column(String(50), unique=True, index=True)
|
||||
|
||||
# Megjelenítendő nevek
|
||||
name_hu: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
name_en: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
|
||||
# Főcsoport (pl. 'MECHANICS', 'ELECTRICAL', 'EMERGENCY')
|
||||
category: Mapped[Optional[str]] = mapped_column(String(30), index=True)
|
||||
|
||||
# --- 🎮 GAMIFICATION ÉS DISCOVERY ---
|
||||
|
||||
# Hivatalos címke (True) vagy júzer/robot által javasolt (False)
|
||||
is_official: Mapped[bool] = mapped_column(Boolean, default=True, server_default=text("true"))
|
||||
|
||||
# Ha júzer javasolta, itt tároljuk, ki volt az (XP jóváíráshoz)
|
||||
suggested_by_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id"))
|
||||
|
||||
# ÁLLÍTHATÓ PONTÉRTÉK: Az adatbázisból jön, így bármikor módosítható.
|
||||
# Ritka szakmáknál magasabb, gyakoriaknál alacsonyabb érték állítható be.
|
||||
discovery_points: Mapped[int] = mapped_column(Integer, default=10, server_default=text("10"))
|
||||
|
||||
# Robot kulcsszavak (JSONB): ["fék", "betét", "tárcsa", "fékfolyadék"]
|
||||
# A Scout robot ez alapján azonosítja be a szervizt a weboldala alapján.
|
||||
search_keywords: Mapped[Any] = mapped_column(JSONB, server_default=text("'[]'::jsonb"))
|
||||
|
||||
# Népszerűségi mutató (hányszor lett felhasználva a rendszerben)
|
||||
usage_count: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"))
|
||||
|
||||
# UI ikon azonosító (pl. 'wrench', 'tire-flat', 'car-electric')
|
||||
icon: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
|
||||
# Leírás a szakmáról (Adminisztratív célokra)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text)
|
||||
|
||||
# Időbélyegek
|
||||
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 ---
|
||||
services: Mapped[List["ServiceExpertise"]] = relationship("ServiceExpertise", back_populates="tag")
|
||||
# Visszamutatás a beküldőre (ha van)
|
||||
suggested_by: Mapped[Optional["Person"]] = relationship("Person")
|
||||
|
||||
class ServiceExpertise(Base):
|
||||
"""
|
||||
KAPCSOLÓTÁBLA: Ez köti össze a szervizt a szakmáival.
|
||||
Itt tároljuk, hogy az adott szerviznél mennyire validált egy szakma.
|
||||
"""
|
||||
__tablename__ = "service_expertises"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
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"))
|
||||
|
||||
# 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"))
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=text("now()"))
|
||||
|
||||
# Kapcsolatok visszafelé
|
||||
service = relationship("ServiceProfile", back_populates="expertises")
|
||||
tag = relationship("ExpertiseTag", back_populates="services")
|
||||
|
||||
class ServiceStaging(Base):
|
||||
""" Hunter (robot) adatok tárolója. """
|
||||
__tablename__ = "service_staging"
|
||||
__table_args__ = (
|
||||
Index('idx_staging_fingerprint', 'fingerprint', unique=True),
|
||||
{"schema": "data"}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
name: Mapped[str] = mapped_column(String, index=True, nullable=False)
|
||||
postal_code: Mapped[Optional[str]] = mapped_column(String(10), index=True)
|
||||
city: Mapped[Optional[str]] = mapped_column(String(100), index=True)
|
||||
full_address: Mapped[Optional[str]] = mapped_column(String)
|
||||
fingerprint: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
raw_data: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
status: Mapped[str] = mapped_column(String(20), server_default=text("'pending'"), index=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
class DiscoveryParameter(Base):
|
||||
""" Robot vezérlési paraméterek adminból. """
|
||||
__tablename__ = "discovery_parameters"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
city: Mapped[str] = mapped_column(String(100))
|
||||
keyword: Mapped[str] = mapped_column(String(100))
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
last_run_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
78
backend/app/models/social.py
Executable file
78
backend/app/models/social.py
Executable file
@@ -0,0 +1,78 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/social.py
|
||||
import enum
|
||||
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.sql import func
|
||||
from app.db.base_class import Base
|
||||
|
||||
class ModerationStatus(str, enum.Enum):
|
||||
pending = "pending"
|
||||
approved = "approved"
|
||||
rejected = "rejected"
|
||||
|
||||
class SourceType(str, enum.Enum):
|
||||
manual = "manual"
|
||||
ocr = "ocr"
|
||||
api_import = "import"
|
||||
|
||||
class ServiceProvider(Base):
|
||||
""" Közösség által beküldött szolgáltatók (v1.3.1). """
|
||||
__tablename__ = "service_providers"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
address: Mapped[str] = mapped_column(String, nullable=False)
|
||||
category: Mapped[Optional[str]] = mapped_column(String)
|
||||
|
||||
status: Mapped[ModerationStatus] = mapped_column(
|
||||
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),
|
||||
default=SourceType.manual
|
||||
)
|
||||
|
||||
validation_score: Mapped[int] = mapped_column(Integer, default=0)
|
||||
evidence_image_path: Mapped[Optional[str]] = mapped_column(String)
|
||||
added_by_user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
class Vote(Base):
|
||||
""" Közösségi validációs szavazatok. """
|
||||
__tablename__ = "votes"
|
||||
__table_args__ = (
|
||||
UniqueConstraint('user_id', 'provider_id', name='uq_user_provider_vote'),
|
||||
)
|
||||
|
||||
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)
|
||||
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"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text)
|
||||
start_date: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
end_date: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
|
||||
class UserScore(Base):
|
||||
""" Versenyenkénti ranglista pontszámok. """
|
||||
__tablename__ = "user_scores"
|
||||
__table_args__ = (
|
||||
UniqueConstraint('user_id', 'competition_id', name='uq_user_competition_score'),
|
||||
)
|
||||
|
||||
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"))
|
||||
points: Mapped[int] = mapped_column(Integer, default=0)
|
||||
last_updated: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
56
backend/app/models/staged_data.py
Executable file
56
backend/app/models/staged_data.py
Executable file
@@ -0,0 +1,56 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/staged_data.py
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any
|
||||
from sqlalchemy import String, Integer, DateTime, text, Boolean, Float
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.sql import func
|
||||
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"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
source_url: Mapped[Optional[str]] = mapped_column(String)
|
||||
raw_data: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
|
||||
status: Mapped[str] = mapped_column(String(20), default="PENDING", index=True)
|
||||
error_log: Mapped[Optional[str]] = mapped_column(String)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
class ServiceStaging(Base):
|
||||
""" Robot 1.3 (Scout) által talált nyers szerviz adatok. """
|
||||
__tablename__ = "service_staging"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(255), index=True)
|
||||
source: Mapped[str] = mapped_column(String(50))
|
||||
external_id: Mapped[Optional[str]] = mapped_column(String(100), index=True)
|
||||
fingerprint: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
||||
|
||||
city: Mapped[str] = mapped_column(String(100), index=True)
|
||||
full_address: Mapped[Optional[str]] = mapped_column(String(500))
|
||||
contact_phone: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
website: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
|
||||
raw_data: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
status: Mapped[str] = mapped_column(String(20), default="pending", index=True)
|
||||
trust_score: Mapped[int] = mapped_column(Integer, default=30)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
class DiscoveryParameter(Base):
|
||||
""" Felderítési paraméterek (Városok, ahol a Scout keres). """
|
||||
__tablename__ = "discovery_parameters"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
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'"))
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
last_run_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
53
backend/app/models/system.py
Executable file
53
backend/app/models/system.py
Executable file
@@ -0,0 +1,53 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/system.py
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from sqlalchemy import String, Integer, Boolean, DateTime, text, UniqueConstraint, ForeignKey, Text
|
||||
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
|
||||
|
||||
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}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
key: Mapped[str] = mapped_column(String, index=True)
|
||||
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))
|
||||
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
description: Mapped[Optional[str]] = mapped_column(String)
|
||||
last_modified_by: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
|
||||
|
||||
class InternalNotification(Base):
|
||||
"""
|
||||
Belső értesítési központ.
|
||||
Ezek az üzenetek várják a felhasználót belépéskor.
|
||||
"""
|
||||
__tablename__ = "internal_notifications"
|
||||
__table_args__ = ({"schema": "data", "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)
|
||||
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
message: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
category: Mapped[str] = mapped_column(String(50), server_default="info") # insurance, mot, service, legal
|
||||
priority: Mapped[str] = mapped_column(String(20), server_default="medium") # low, medium, high, critical
|
||||
|
||||
is_read: Mapped[bool] = mapped_column(Boolean, default=False, index=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
read_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
# Metaadatok a gyors eléréshez (melyik autó, melyik VIN)
|
||||
data: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)
|
||||
27
backend/app/models/translation.py
Executable file
27
backend/app/models/translation.py
Executable file
@@ -0,0 +1,27 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/translation.py
|
||||
from sqlalchemy import String, Integer, Text, Boolean, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
# MB 2.0: A központi aszinkron adatbázis motorból húzzuk be a Base-t
|
||||
from app.database import Base
|
||||
|
||||
class Translation(Base):
|
||||
"""
|
||||
Többnyelvűséget támogató tábla a felületi elemekhez és dinamikus tartalmakhoz.
|
||||
"""
|
||||
__tablename__ = "translations"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
|
||||
# A fordítandó kulcs (pl. 'NAV_DASHBOARD' vagy 'ERR_USER_NOT_FOUND')
|
||||
key: Mapped[str] = mapped_column(String(255), index=True)
|
||||
|
||||
# Nyelvi kód (pl: 'hu', 'en', 'de')
|
||||
lang: Mapped[str] = mapped_column(String(5), index=True)
|
||||
|
||||
# A tényleges fordított szöveg
|
||||
value: Mapped[str] = mapped_column(Text)
|
||||
|
||||
# --- JAVÍTÁS: A diagnosztika által hiányolt publikációs állapot ---
|
||||
is_published: Mapped[bool] = mapped_column(Boolean, default=True, server_default=text("true"))
|
||||
155
backend/app/models/vehicle_definitions.py
Executable file
155
backend/app/models/vehicle_definitions.py
Executable file
@@ -0,0 +1,155 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/vehicle_definitions.py
|
||||
from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, text, JSON, Index, UniqueConstraint, Text, ARRAY, func, Numeric
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
# MB 2.0: Egységesített Base import a központi adatbázis motorból
|
||||
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"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
code: Mapped[str] = mapped_column(String(30), unique=True, index=True)
|
||||
name: Mapped[str] = mapped_column(String(50))
|
||||
icon: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
units: Mapped[dict] = mapped_column(JSONB, server_default=text("'{\"power\": \"kW\", \"weight\": \"kg\"}'::jsonb"))
|
||||
|
||||
# Kapcsolatok
|
||||
features: Mapped[List["FeatureDefinition"]] = relationship("FeatureDefinition", back_populates="vehicle_type")
|
||||
definitions: Mapped[List["VehicleModelDefinition"]] = relationship("VehicleModelDefinition", back_populates="v_type_rel")
|
||||
|
||||
|
||||
class FeatureDefinition(Base):
|
||||
""" Felszereltségi elemek definíciója (pl. ABS, Klíma, LED fényszóró) """
|
||||
__tablename__ = "feature_definitions"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
vehicle_type_id: Mapped[int] = mapped_column(Integer, ForeignKey("data.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)
|
||||
|
||||
vehicle_type: Mapped["VehicleType"] = relationship("VehicleType", back_populates="features")
|
||||
model_maps: Mapped[List["ModelFeatureMap"]] = relationship("ModelFeatureMap", back_populates="feature")
|
||||
|
||||
|
||||
class VehicleModelDefinition(Base):
|
||||
"""
|
||||
Robot v1.1.0 Multi-Tier MDM Master Adattábla.
|
||||
Az ökoszisztéma technikai igazságforrása.
|
||||
"""
|
||||
__tablename__ = "vehicle_model_definitions"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
make: Mapped[str] = mapped_column(String(100), index=True)
|
||||
marketing_name: Mapped[str] = mapped_column(String(255), index=True) # Nyers név az RDW-ből
|
||||
official_marketing_name: Mapped[Optional[str]] = mapped_column(String(255)) # Dúsított, validált név (Robot 2.2)
|
||||
|
||||
# --- ROBOT LOGIKAI MEZŐK (JAVÍTVA 2.0 STÍLUSBAN) ---
|
||||
attempts: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"))
|
||||
last_error: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
|
||||
priority_score: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"))
|
||||
|
||||
# --- PRECISION LOGIC MEZŐK ---
|
||||
normalized_name: Mapped[Optional[str]] = mapped_column(String(255), index=True, nullable=True)
|
||||
marketing_name_aliases: Mapped[list] = mapped_column(JSONB, server_default=text("'[]'::jsonb"))
|
||||
engine_code: Mapped[Optional[str]] = mapped_column(String(50), index=True) # A GLOBÁLIS KAPOCS
|
||||
|
||||
# --- TECHNIKAI AZONOSÍTÓK ---
|
||||
technical_code: Mapped[str] = mapped_column(String(100), index=True) # Holland rendszám (kulcs)
|
||||
variant_code: Mapped[Optional[str]] = mapped_column(String(100), index=True)
|
||||
version_code: Mapped[Optional[str]] = mapped_column(String(100), index=True)
|
||||
|
||||
# --- ÚJ PRÉMIUM MŰSZAKI MEZŐK ---
|
||||
type_approval_number: Mapped[Optional[str]] = mapped_column(String(100), index=True) # e1*2001/...
|
||||
seats: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
width: Mapped[Optional[int]] = mapped_column(Integer) # cm
|
||||
wheelbase: Mapped[Optional[int]] = mapped_column(Integer) # cm
|
||||
list_price: Mapped[Optional[int]] = mapped_column(Integer) # EUR (catalogusprijs)
|
||||
max_speed: Mapped[Optional[int]] = mapped_column(Integer) # km/h
|
||||
|
||||
# Vontatási adatok
|
||||
towing_weight_unbraked: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
towing_weight_braked: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
|
||||
# Környezetvédelmi adatok
|
||||
fuel_consumption_combined: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), nullable=True)
|
||||
co2_emissions_combined: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
|
||||
|
||||
# --- SPECIFIKÁCIÓK ---
|
||||
vehicle_type_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("data.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)
|
||||
|
||||
engine_capacity: Mapped[int] = mapped_column(Integer, default=0, index=True)
|
||||
power_kw: Mapped[int] = mapped_column(Integer, default=0, index=True)
|
||||
torque_nm: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
cylinders: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
cylinder_layout: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
|
||||
curb_weight: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
max_weight: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
euro_classification: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
doors: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
transmission_type: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
drive_type: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
|
||||
# --- ÉLETCIKLUS ÉS STÁTUSZ ---
|
||||
year_from: Mapped[Optional[int]] = mapped_column(Integer, index=True)
|
||||
year_to: Mapped[Optional[int]] = mapped_column(Integer, index=True)
|
||||
production_status: Mapped[Optional[str]] = mapped_column(String(50)) # active / discontinued
|
||||
|
||||
# Státusz szintek: unverified, research_in_progress, awaiting_ai_synthesis, gold_enriched
|
||||
status: Mapped[str] = mapped_column(String(50), server_default=text("'unverified'"), index=True)
|
||||
is_manual: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
source: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
|
||||
# --- ADAT-KONTÉNEREK ---
|
||||
raw_search_context: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
research_metadata: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
specifications: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb")) # Robot 2.2/2.5 Arany adatai
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
last_research_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
# --- BEÁLLÍTÁSOK ---
|
||||
__table_args__ = (
|
||||
UniqueConstraint('make', 'normalized_name', 'variant_code', 'version_code', 'fuel_type', name='uix_vmd_precision'),
|
||||
Index('idx_vmd_lookup_fast', 'make', 'normalized_name'),
|
||||
Index('idx_vmd_engine_bridge', 'make', 'engine_code'),
|
||||
{"schema": "data"}
|
||||
)
|
||||
|
||||
# KAPCSOLATOK
|
||||
v_type_rel: Mapped["VehicleType"] = relationship("VehicleType", back_populates="definitions")
|
||||
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
|
||||
variants: Mapped[List["AssetCatalog"]] = relationship("AssetCatalog", back_populates="master_definition")
|
||||
|
||||
|
||||
class ModelFeatureMap(Base):
|
||||
""" Kapcsolótábla a modellek és az alapfelszereltség között """
|
||||
__tablename__ = "model_feature_maps"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
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"))
|
||||
is_standard: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
|
||||
model_definition: Mapped["VehicleModelDefinition"] = relationship("VehicleModelDefinition", back_populates="feature_maps")
|
||||
feature: Mapped["FeatureDefinition"] = relationship("FeatureDefinition", back_populates="model_maps")
|
||||
Reference in New Issue
Block a user