admin frontend elkezdése, járművek tisztázása, frontend fejleszts

This commit is contained in:
Roo
2026-06-15 18:52:38 +00:00
parent ef8df9608c
commit 213ba3b0f1
80 changed files with 11615 additions and 2304 deletions

View File

@@ -3,23 +3,23 @@
from app.database import Base
# 1. Alapvető identitás és szerepkörök
from .identity.identity import Person, User, Wallet, VerificationToken, SocialAccount, UserRole
from .identity.identity import Person, User, Wallet, VerificationToken, SocialAccount, UserRole, OneTimePassword
# 2. Földrajzi adatok és címek
# 2. Szervezeti felépítés (MUST be before Address/Rating due to FK references)
from .marketplace.organization import Organization, OrganizationMember, OrganizationFinancials, OrganizationSalesAssignment, OrgType, OrgUserRole, Branch
# 3. Földrajzi adatok és címek
from .identity.address import Address, GeoPostalCode, GeoStreet, GeoStreetType, Rating
# 3. Jármű definíciók
# 4. Jármű definíciók
from .vehicle.vehicle_definitions import VehicleModelDefinition, VehicleType, FeatureDefinition, ModelFeatureMap
from .reference_data import ReferenceLookup
from .vehicle.vehicle import CostCategory, VehicleCost, GbCatalogDiscovery
from .vehicle.external_reference import ExternalReferenceLibrary
from .vehicle.external_reference_queue import ExternalReferenceQueue
# 4. Szervezeti felépítés
from .marketplace.organization import Organization, OrganizationMember, OrganizationFinancials, OrganizationSalesAssignment, OrgType, OrgUserRole, Branch
# 5. Eszközök és katalógusok
from .vehicle.asset import Asset, AssetCatalog, AssetCost, AssetEvent, AssetAssignment, AssetFinancials, AssetTelemetry, AssetReview, ExchangeRate, CatalogDiscovery, VehicleOwnership
from .vehicle.asset import Asset, AssetCatalog, AssetCost, AssetEvent, AssetAssignment, AssetFinancials, AssetTelemetry, AssetReview, ExchangeRate, CatalogDiscovery, VehicleOwnership, OdometerReading
# 6. Üzleti logika és előfizetések
from .core_logic import SubscriptionTier, OrganizationSubscription, CreditTransaction, ServiceSpecialty
@@ -61,7 +61,7 @@ __all__ = [
"Base", "User", "Person", "Wallet", "UserRole", "VerificationToken", "SocialAccount",
"Organization", "OrganizationMember", "OrganizationSalesAssignment", "OrgType", "OrgUserRole",
"Asset", "AssetCatalog", "AssetCost", "AssetEvent", "AssetAssignment", "AssetFinancials",
"AssetTelemetry", "AssetReview", "ExchangeRate", "CatalogDiscovery",
"AssetTelemetry", "AssetReview", "ExchangeRate", "CatalogDiscovery", "OdometerReading",
"Address", "GeoPostalCode", "GeoStreet", "GeoStreetType", "Branch",
"PointRule", "LevelConfig", "UserStats", "Badge", "UserBadge", "Rating", "PointsLedger", "UserContribution",
@@ -81,5 +81,5 @@ __all__ = [
"Vehicle", "UserVehicle", "VehicleCatalog", "ServiceRecord", "VehicleModelDefinition", "ReferenceLookup",
"VehicleType", "FeatureDefinition", "ModelFeatureMap", "LegalDocument", "LegalAcceptance",
"Location", "LocationType", "Issuer", "IssuerType", "CostCategory", "VehicleCost", "ExternalReferenceLibrary", "ExternalReferenceQueue",
"GbCatalogDiscovery", "Season", "StagedVehicleData"
"GbCatalogDiscovery", "Season", "StagedVehicleData", "OneTimePassword"
]

View File

@@ -41,6 +41,29 @@ class OrganizationSubscription(Base):
valid_until: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
class UserSubscription(Base):
"""
Felhasználók aktuális előfizetései és azok érvényessége.
Tábla: finance.user_subscriptions
"""
__tablename__ = "user_subscriptions"
__table_args__ = {"schema": "finance"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
# Kapcsolat a felhasználóval (identity séma)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False)
# Kapcsolat a csomaggal (system séma)
tier_id: Mapped[int] = mapped_column(Integer, ForeignKey("system.subscription_tiers.id"), nullable=False)
valid_from: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
valid_until: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
class CreditTransaction(Base):
"""
Kreditnapló (Pontok, kreditek vagy virtuális egyenleg követése).
@@ -73,4 +96,21 @@ class ServiceSpecialty(Base):
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")
parent: Mapped[Optional["ServiceSpecialty"]] = relationship("ServiceSpecialty", remote_side=[id], backref="children")
class ServiceCatalog(Base):
"""
Szolgáltatás Katalógus (Service Catalog).
A rendszer által kínált szolgáltatások definíciói, pl. 'SRV_DATA_EXPORT'.
Tábla: system.service_catalog
"""
__tablename__ = "service_catalog"
__table_args__ = {"schema": "system"}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
service_code: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False) # pl. 'SRV_DATA_EXPORT'
name: Mapped[str] = mapped_column(String, nullable=False)
description: Mapped[Optional[str]] = mapped_column(String)
credit_cost: Mapped[int] = mapped_column(Integer, default=0) # alapértelmezett ár kreditben
is_active: Mapped[bool] = mapped_column(Boolean, default=True)

View File

@@ -10,6 +10,7 @@ from .identity import (
UserRole,
Device,
UserDeviceLink,
OneTimePassword,
)
from .address import (
@@ -42,6 +43,7 @@ __all__ = [
"UserRole",
"Device",
"UserDeviceLink",
"OneTimePassword",
"Address",
"GeoPostalCode",
"GeoStreet",

View File

@@ -78,6 +78,9 @@ class Person(Base):
index=True
)
# === SOFT DELETE (structure only - never delete Person data) ===
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=func.now(), nullable=False)
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), onupdate=func.now())
@@ -147,6 +150,9 @@ class User(Base):
is_active: Mapped[bool] = mapped_column(Boolean, default=False)
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)
# === SOFT DELETE ===
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
folder_slug: Mapped[Optional[str]] = mapped_column(String(12), unique=True, index=True)
preferred_language: Mapped[str] = mapped_column(String(5), server_default="hu")
@@ -214,19 +220,24 @@ class User(Base):
service_requests: Mapped[List["ServiceRequest"]] = relationship("ServiceRequest", back_populates="user", cascade="all, delete-orphan")
class Wallet(Base):
""" Felhasználói pénztárca. """
""" Felhasználói / Szervezeti pénztárca (B2B & B2C). """
__tablename__ = "wallets"
__table_args__ = {"schema": "identity"}
__table_args__ = (
# Biztosítjuk, hogy vagy user_id, vagy organization_id legyen kitöltve, de ne egyszerre
# Ezt ORM szinten is ellenőrizzük, de DB constraint is véd
{"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"), unique=True)
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id", ondelete="CASCADE"), unique=True, nullable=True)
organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), nullable=True, index=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")
user: Mapped[Optional["User"]] = relationship("User", back_populates="wallet")
active_vouchers: Mapped[List["ActiveVoucher"]] = relationship("ActiveVoucher", back_populates="wallet", cascade="all, delete-orphan")
class VerificationToken(Base):
@@ -306,6 +317,25 @@ class UserTrustProfile(Base):
user: Mapped["User"] = relationship("User", back_populates="trust_profile", uselist=False)
class OneTimePassword(Base):
"""
6-jegyű OTP (One Time Password) ideiglenes tárolása.
Típusok: 'restore' (fiók visszaállítás), 'company_claim' (cég átvétel)
Lejárati idő: 15 perc.
"""
__tablename__ = "one_time_passwords"
__table_args__ = {"schema": "identity"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
email: Mapped[str] = mapped_column(String, index=True, nullable=False)
code: Mapped[str] = mapped_column(String(6), nullable=False)
otp_type: Mapped[str] = mapped_column(String(30), nullable=False) # 'restore' or 'company_claim'
extra_data: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"), nullable=True)
is_used: Mapped[bool] = mapped_column(Boolean, default=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)
class Device(Base):
""" Ismert eszközök (Device Fingerprinting) a Device-Hub architektúrához. """
__tablename__ = "devices"

View File

@@ -93,6 +93,8 @@ class Organization(Base):
default=OrgType.individual
)
is_affiliate_partner: Mapped[bool] = mapped_column(Boolean, server_default=text('false'), default=False)
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'

View File

@@ -10,7 +10,6 @@ from .vehicle_definitions import (
from .vehicle import (
CostCategory,
VehicleCost,
VehicleOdometerState,
VehicleUserRating,
GbCatalogDiscovery,
)
@@ -28,6 +27,7 @@ from .asset import (
ExchangeRate,
CatalogDiscovery,
VehicleOwnership,
OdometerReading,
)
from .history import AuditLog, LogSeverity
@@ -42,7 +42,6 @@ __all__ = [
"ModelFeatureMap",
"CostCategory",
"VehicleCost",
"VehicleOdometerState",
"VehicleUserRating",
"GbCatalogDiscovery",
"ExternalReferenceLibrary",
@@ -62,4 +61,5 @@ __all__ = [
# --- EXPORT LISTA KIEGÉSZÍTÉSE ---
"MotorcycleSpecs",
"BodyTypeDictionary",
"OdometerReading",
]

View File

@@ -4,7 +4,7 @@ import uuid
import enum
from datetime import datetime
from typing import List, Optional, TYPE_CHECKING
from sqlalchemy import String, Boolean, DateTime, ForeignKey, Numeric, text, Text, UniqueConstraint, BigInteger, Integer, Float
from sqlalchemy import String, Boolean, DateTime, ForeignKey, Numeric, text, Text, UniqueConstraint, CheckConstraint, BigInteger, Integer, Float
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB
from sqlalchemy.sql import func
@@ -69,7 +69,13 @@ class RoofTypeEnum(str, enum.Enum):
class Asset(Base):
""" A fizikai eszköz (Digital Twin) - Minden adat itt fut össze. """
__tablename__ = "assets"
__table_args__ = {"schema": "vehicle"}
__table_args__ = (
CheckConstraint(
"vin IS NOT NULL OR license_plate IS NOT NULL",
name="ck_asset_vin_or_plate_required"
),
{"schema": "vehicle"}
)
# === IDENTIFICATION ===
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
@@ -147,6 +153,7 @@ class Asset(Base):
assignments: Mapped[List["AssetAssignment"]] = relationship("AssetAssignment", back_populates="asset")
ownership_history: Mapped[List["VehicleOwnership"]] = relationship("VehicleOwnership", back_populates="asset")
service_requests: Mapped[List["ServiceRequest"]] = relationship("ServiceRequest", back_populates="asset", cascade="all, delete-orphan")
odometer_readings: Mapped[List["OdometerReading"]] = relationship("OdometerReading", back_populates="asset", order_by="OdometerReading.recorded_at.desc()")
# --- COMPUTED PROPERTIES (for Pydantic schema compatibility) ---
@property
@@ -249,6 +256,7 @@ class AssetCost(Base):
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)
document_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("system.documents.id"), nullable=True)
data: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
asset: Mapped["Asset"] = relationship("Asset", back_populates="costs")
@@ -342,6 +350,23 @@ class AssetTelemetry(Base):
asset: Mapped["Asset"] = relationship("Asset", back_populates="telemetry")
class OdometerReading(Base):
""" Km óra állás rögzítése (időbeli nyomonkövetés). """
__tablename__ = "odometer_readings"
__table_args__ = {"schema": "vehicle"}
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
asset_id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.assets.id"), nullable=False, index=True)
reading: Mapped[int] = mapped_column(Integer, nullable=False) # km óra állás
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
source: Mapped[str] = mapped_column(String(30), default="manual") # manual, api, telemetry
cost_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("vehicle.asset_costs.id"), nullable=True)
# Relationships
asset: Mapped["Asset"] = relationship("Asset", back_populates="odometer_readings")
cost: Mapped[Optional["AssetCost"]] = relationship("AssetCost")
class AssetAssignment(Base):
""" Eszköz-Szervezet összerendelés. """
__tablename__ = "asset_assignments"

View File

@@ -6,11 +6,11 @@ TCO (Total Cost of Ownership) alapmodelljei a 'vehicle' sémában.
"""
from __future__ import annotations
import uuid
from datetime import datetime
from typing import Optional
import uuid
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Text, Numeric, UniqueConstraint, Float, CheckConstraint
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Text, Numeric, UniqueConstraint, CheckConstraint
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func
from app.database import Base
@@ -22,7 +22,7 @@ class CostCategory(Base):
Rendszerkategóriák (is_system=True) nem törölhetők, csak felhasználói kategóriák.
"""
__tablename__ = "cost_categories"
__table_args__ = {"schema": "vehicle"}
__table_args__ = {"schema": "vehicle", "extend_existing": True}
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
parent_id: Mapped[Optional[int]] = mapped_column(
@@ -36,6 +36,7 @@ class CostCategory(Base):
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
is_system: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
visibility: Mapped[str] = mapped_column(String(20), default="both", server_default="'both'")
min_tier: Mapped[str] = mapped_column(String(20), default="free", server_default="'free'") # free, premium, enterprise
accounting_code: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
@@ -108,36 +109,6 @@ class VehicleCost(Base):
return f"VehicleCost(id={self.id}, vehicle_id={self.vehicle_id}, amount={self.amount} {self.currency})"
class VehicleOdometerState(Base):
"""
Jármű kilométeróra állapotának és becslésének tárolása.
Adminisztrátor által paraméterezhető algoritmusokkal működik.
"""
__tablename__ = "vehicle_odometer_states"
__table_args__ = {"schema": "vehicle"}
vehicle_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("vehicle.vehicle_model_definitions.id", ondelete="CASCADE"),
primary_key=True,
nullable=False
)
last_recorded_odometer: Mapped[int] = mapped_column(Integer, nullable=False)
last_recorded_date: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
daily_avg_distance: Mapped[float] = mapped_column(Numeric(10, 2), nullable=False)
estimated_current_odometer: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False)
confidence_score: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
manual_override_avg: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
# Kapcsolat a jármű definícióval
vehicle: Mapped["VehicleModelDefinition"] = relationship("VehicleModelDefinition", back_populates="odometer_state")
def __repr__(self) -> str:
return f"VehicleOdometerState(vehicle_id={self.vehicle_id}, estimated={self.estimated_current_odometer}, confidence={self.confidence_score})"
class VehicleUserRating(Base):
"""
Jármű értékelési rendszer - User -> Vehicle kapcsolat.
@@ -155,7 +126,7 @@ class VehicleUserRating(Base):
)
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
PG_UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
server_default=func.gen_random_uuid()

View File

@@ -12,7 +12,7 @@ from app.database import Base
# Típus ellenőrzés a körkörös importok elkerülésére
if TYPE_CHECKING:
from .asset import AssetCatalog
from .vehicle import VehicleCost, VehicleOdometerState, VehicleUserRating
from .vehicle import VehicleCost, VehicleUserRating
class VehicleType(Base):
""" Jármű kategóriák (pl. Személyautó, Motorkerékpár, Teherautó, Hajó) """
@@ -106,6 +106,10 @@ class VehicleModelDefinition(Base):
transmission_type: Mapped[Optional[str]] = mapped_column(String(50))
drive_type: Mapped[Optional[str]] = mapped_column(String(50))
# --- SZERVIZ INTERVALLUMOK ---
service_interval_km: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
service_interval_months: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
# --- ÉLETCIKLUS ---
year_from: Mapped[int] = mapped_column(Integer, index=True, server_default=text("0")) # EZT IS BELETETTÜK A KULCSBA
year_to: Mapped[Optional[int]] = mapped_column(Integer, index=True)
@@ -139,7 +143,6 @@ class VehicleModelDefinition(Base):
ratings: Mapped[List["VehicleUserRating"]] = relationship("VehicleUserRating", back_populates="vehicle", cascade="all, delete-orphan")
costs: Mapped[List["VehicleCost"]] = relationship("VehicleCost", back_populates="vehicle", cascade="all, delete-orphan")
odometer_state: Mapped[Optional["VehicleOdometerState"]] = relationship("VehicleOdometerState", back_populates="vehicle", uselist=False, cascade="all, delete-orphan")
class ModelFeatureMap(Base):