átlagos kiegészítséek jó sok
This commit is contained in:
@@ -3,46 +3,53 @@
|
||||
from app.database import Base
|
||||
|
||||
# 1. Alapvető identitás és szerepkörök
|
||||
from .identity import Person, User, Wallet, VerificationToken, SocialAccount, UserRole
|
||||
from .identity.identity import Person, User, Wallet, VerificationToken, SocialAccount, UserRole
|
||||
|
||||
# 2. Földrajzi adatok és címek
|
||||
from .address import Address, GeoPostalCode, GeoStreet, GeoStreetType, Rating
|
||||
from .identity.address import Address, GeoPostalCode, GeoStreet, GeoStreetType, Rating
|
||||
|
||||
# 3. Jármű definíciók
|
||||
from .vehicle_definitions import VehicleModelDefinition, VehicleType, FeatureDefinition, ModelFeatureMap
|
||||
from .vehicle.vehicle_definitions import VehicleModelDefinition, VehicleType, FeatureDefinition, ModelFeatureMap
|
||||
from .reference_data import ReferenceLookup
|
||||
from .vehicle import CostCategory, VehicleCost
|
||||
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 .organization import Organization, OrganizationMember, OrganizationFinancials, OrganizationSalesAssignment, OrgType, OrgUserRole, Branch
|
||||
from .marketplace.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
|
||||
from .vehicle.asset import Asset, AssetCatalog, AssetCost, AssetEvent, AssetAssignment, AssetFinancials, AssetTelemetry, AssetReview, ExchangeRate, CatalogDiscovery, VehicleOwnership
|
||||
|
||||
# 6. Üzleti logika és előfizetések
|
||||
from .core_logic import SubscriptionTier, OrganizationSubscription, CreditTransaction, ServiceSpecialty
|
||||
from .payment import PaymentIntent, PaymentIntentStatus
|
||||
from .finance import Issuer, IssuerType
|
||||
from .marketplace.payment import PaymentIntent, PaymentIntentStatus
|
||||
from .marketplace.finance import Issuer, IssuerType
|
||||
|
||||
# 7. Szolgáltatások és staging
|
||||
from .service import ServiceProfile, ExpertiseTag, ServiceExpertise, ServiceStaging, DiscoveryParameter
|
||||
# JAVÍTVA: ServiceStaging és társai a staged_data-ból jönnek!
|
||||
from .marketplace.service import ServiceProfile, ExpertiseTag, ServiceExpertise
|
||||
from .marketplace.staged_data import ServiceStaging, DiscoveryParameter, StagedVehicleData
|
||||
from .marketplace.service_request import ServiceRequest
|
||||
|
||||
# 8. Közösségi és értékelési modellek (Social 3)
|
||||
from .social import ServiceProvider, Vote, Competition, UserScore, ServiceReview, ModerationStatus, SourceType
|
||||
from .identity.social import ServiceProvider, Vote, Competition, UserScore, ServiceReview, ModerationStatus, SourceType
|
||||
|
||||
# 9. Rendszer, Gamification és egyebek
|
||||
from .gamification import PointRule, LevelConfig, UserStats, Badge, UserBadge, PointsLedger
|
||||
from .gamification.gamification import PointRule, LevelConfig, UserStats, Badge, UserBadge, PointsLedger, UserContribution, Season
|
||||
|
||||
# --- 2.2 ÚJDONSÁG: InternalNotification hozzáadása ---
|
||||
from .system import SystemParameter, InternalNotification
|
||||
from .system.system import SystemParameter, ParameterScope, InternalNotification, SystemServiceStaging
|
||||
|
||||
from .system.document import Document
|
||||
from .system.translation import Translation
|
||||
# Direct import from audit module
|
||||
from .system.audit import SecurityAuditLog, OperationalLog, ProcessLog, FinancialLedger, WalletType, LedgerStatus, LedgerEntryType
|
||||
from .vehicle.history import AuditLog, LogSeverity
|
||||
from .identity.security import PendingAction, ActionStatus
|
||||
from .system.legal import LegalDocument, LegalAcceptance
|
||||
from .marketplace.logistics import Location, LocationType
|
||||
|
||||
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
|
||||
@@ -53,25 +60,26 @@ ServiceRecord = AssetEvent
|
||||
__all__ = [
|
||||
"Base", "User", "Person", "Wallet", "UserRole", "VerificationToken", "SocialAccount",
|
||||
"Organization", "OrganizationMember", "OrganizationSalesAssignment", "OrgType", "OrgUserRole",
|
||||
"Asset", "AssetCatalog", "AssetCost", "AssetEvent", "AssetFinancials",
|
||||
"Asset", "AssetCatalog", "AssetCost", "AssetEvent", "AssetAssignment", "AssetFinancials",
|
||||
"AssetTelemetry", "AssetReview", "ExchangeRate", "CatalogDiscovery",
|
||||
"Address", "GeoPostalCode", "GeoStreet", "GeoStreetType", "Branch",
|
||||
"PointRule", "LevelConfig", "UserStats", "Badge", "UserBadge", "Rating", "PointsLedger",
|
||||
"PointRule", "LevelConfig", "UserStats", "Badge", "UserBadge", "Rating", "PointsLedger", "UserContribution",
|
||||
|
||||
# --- 2.2 ÚJDONSÁG KIEGÉSZÍTÉS ---
|
||||
"SystemParameter", "InternalNotification",
|
||||
"SystemParameter", "ParameterScope", "InternalNotification",
|
||||
|
||||
# Social models (Social 3)
|
||||
"ServiceProvider", "Vote", "Competition", "UserScore", "ServiceReview", "ModerationStatus", "SourceType",
|
||||
|
||||
"Document", "Translation", "PendingAction",
|
||||
"Document", "Translation", "PendingAction", "ActionStatus",
|
||||
"SubscriptionTier", "OrganizationSubscription", "CreditTransaction", "ServiceSpecialty",
|
||||
"PaymentIntent", "PaymentIntentStatus",
|
||||
"AuditLog", "VehicleOwnership", "LogSeverity",
|
||||
"SecurityAuditLog", "ProcessLog", "FinancialLedger",
|
||||
"ServiceProfile", "ExpertiseTag", "ServiceExpertise", "ServiceStaging", "DiscoveryParameter",
|
||||
"SecurityAuditLog", "OperationalLog", "ProcessLog",
|
||||
"FinancialLedger", "WalletType", "LedgerStatus", "LedgerEntryType",
|
||||
"ServiceProfile", "ExpertiseTag", "ServiceExpertise", "ServiceStaging", "DiscoveryParameter", "ServiceRequest",
|
||||
"Vehicle", "UserVehicle", "VehicleCatalog", "ServiceRecord", "VehicleModelDefinition", "ReferenceLookup",
|
||||
"VehicleType", "FeatureDefinition", "ModelFeatureMap", "LegalDocument", "LegalAcceptance",
|
||||
"Location", "LocationType", "Issuer", "IssuerType", "CostCategory", "VehicleCost"
|
||||
]
|
||||
from app.models.payment import PaymentIntent, WithdrawalRequest
|
||||
"Location", "LocationType", "Issuer", "IssuerType", "CostCategory", "VehicleCost", "ExternalReferenceLibrary", "ExternalReferenceQueue",
|
||||
"GbCatalogDiscovery", "Season", "StagedVehicleData"
|
||||
]
|
||||
135
backend/app/models/audit.py
Executable file → Normal file
135
backend/app/models/audit.py
Executable file → Normal file
@@ -1,115 +1,24 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/audit.py
|
||||
import enum
|
||||
import uuid
|
||||
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 sqlalchemy.dialects.postgresql import UUID as PG_UUID, ENUM as PG_ENUM
|
||||
from app.database import Base
|
||||
# Backward compatibility stub for audit module
|
||||
# After restructuring, audit models moved to system.audit
|
||||
# This file re-exports everything to maintain compatibility
|
||||
|
||||
class SecurityAuditLog(Base):
|
||||
""" Kiemelt biztonsági események és a 4-szem elv naplózása. """
|
||||
__tablename__ = "security_audit_logs"
|
||||
__table_args__ = {"schema": "audit"}
|
||||
from .system.audit import (
|
||||
SecurityAuditLog,
|
||||
OperationalLog,
|
||||
ProcessLog,
|
||||
LedgerEntryType,
|
||||
WalletType,
|
||||
LedgerStatus,
|
||||
FinancialLedger,
|
||||
)
|
||||
|
||||
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"
|
||||
__table_args__ = {"schema": "audit"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id", ondelete="SET NULL"))
|
||||
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"
|
||||
__table_args__ = {"schema": "audit"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
process_name: Mapped[str] = mapped_column(String(100), index=True) # 'Master-Enricher'
|
||||
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 LedgerEntryType(str, enum.Enum):
|
||||
DEBIT = "DEBIT"
|
||||
CREDIT = "CREDIT"
|
||||
|
||||
|
||||
class WalletType(str, enum.Enum):
|
||||
EARNED = "EARNED"
|
||||
PURCHASED = "PURCHASED"
|
||||
SERVICE_COINS = "SERVICE_COINS"
|
||||
VOUCHER = "VOUCHER"
|
||||
|
||||
|
||||
class LedgerStatus(str, enum.Enum):
|
||||
PENDING = "PENDING"
|
||||
SUCCESS = "SUCCESS"
|
||||
FAILED = "FAILED"
|
||||
REFUNDED = "REFUNDED"
|
||||
REFUND = "REFUND"
|
||||
|
||||
|
||||
class FinancialLedger(Base):
|
||||
""" Minden pénz- és kreditmozgás központi naplója. Billing Engine alapja. """
|
||||
__tablename__ = "financial_ledger"
|
||||
__table_args__ = {"schema": "audit"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
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())
|
||||
|
||||
# Új mezők double‑entry és okos levonáshoz
|
||||
entry_type: Mapped[LedgerEntryType] = mapped_column(
|
||||
PG_ENUM(LedgerEntryType, name="ledger_entry_type", schema="audit"),
|
||||
nullable=False
|
||||
)
|
||||
balance_after: Mapped[Optional[float]] = mapped_column(Numeric(18, 4))
|
||||
wallet_type: Mapped[Optional[WalletType]] = mapped_column(
|
||||
PG_ENUM(WalletType, name="wallet_type", schema="audit")
|
||||
)
|
||||
# Economy 1: számlázási mezők
|
||||
issuer_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("finance.issuers.id"), nullable=True)
|
||||
invoice_status: Mapped[Optional[str]] = mapped_column(String(50), default="PENDING")
|
||||
tax_amount: Mapped[Optional[float]] = mapped_column(Numeric(18, 4))
|
||||
gross_amount: Mapped[Optional[float]] = mapped_column(Numeric(18, 4))
|
||||
net_amount: Mapped[Optional[float]] = mapped_column(Numeric(18, 4))
|
||||
transaction_id: Mapped[uuid.UUID] = mapped_column(
|
||||
PG_UUID(as_uuid=True), default=uuid.uuid4, nullable=False, index=True
|
||||
)
|
||||
status: Mapped[LedgerStatus] = mapped_column(
|
||||
PG_ENUM(LedgerStatus, name="ledger_status", schema="audit"),
|
||||
default=LedgerStatus.SUCCESS,
|
||||
nullable=False
|
||||
)
|
||||
# Re-export everything
|
||||
__all__ = [
|
||||
"SecurityAuditLog",
|
||||
"OperationalLog",
|
||||
"ProcessLog",
|
||||
"LedgerEntryType",
|
||||
"WalletType",
|
||||
"LedgerStatus",
|
||||
"FinancialLedger",
|
||||
]
|
||||
@@ -1,86 +0,0 @@
|
||||
# /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": "system"}
|
||||
|
||||
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": "system"}
|
||||
|
||||
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": "system"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
|
||||
# MB 2.0: User az identity sémában lakik!
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
|
||||
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": "system"}
|
||||
|
||||
# MB 2.0: User az identity sémában lakik!
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), primary_key=True)
|
||||
|
||||
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": "system"}
|
||||
|
||||
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": "system"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
|
||||
# MB 2.0: User az identity sémában lakik!
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
badge_id: Mapped[int] = mapped_column(Integer, ForeignKey("system.badges.id"))
|
||||
|
||||
earned_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
user: Mapped["User"] = relationship("User")
|
||||
22
backend/app/models/gamification/__init__.py
Normal file
22
backend/app/models/gamification/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# gamification package exports
|
||||
from .gamification import (
|
||||
PointRule,
|
||||
LevelConfig,
|
||||
PointsLedger,
|
||||
UserStats,
|
||||
Badge,
|
||||
UserBadge,
|
||||
UserContribution,
|
||||
Season,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"PointRule",
|
||||
"LevelConfig",
|
||||
"PointsLedger",
|
||||
"UserStats",
|
||||
"Badge",
|
||||
"UserBadge",
|
||||
"UserContribution",
|
||||
"Season",
|
||||
]
|
||||
144
backend/app/models/gamification/gamification.py
Executable file
144
backend/app/models/gamification/gamification.py
Executable file
@@ -0,0 +1,144 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/gamification/gamification.py
|
||||
import uuid
|
||||
from datetime import datetime, date
|
||||
from typing import Optional, List, TYPE_CHECKING
|
||||
from sqlalchemy import ForeignKey, String, Integer, DateTime, func, Boolean, Text, text, Date
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB
|
||||
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": "gamification", "extend_existing": True}
|
||||
|
||||
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": "gamification", "extend_existing": True}
|
||||
|
||||
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": "gamification", "extend_existing": True}
|
||||
|
||||
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": "gamification", "extend_existing": True}
|
||||
|
||||
# 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)
|
||||
penalty_quota_remaining: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
places_discovered: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"))
|
||||
places_validated: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"))
|
||||
banned_until: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
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": "gamification", "extend_existing": True}
|
||||
|
||||
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": "gamification", "extend_existing": True}
|
||||
|
||||
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("gamification.badges.id"))
|
||||
|
||||
earned_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
user: Mapped["User"] = relationship("User")
|
||||
|
||||
|
||||
class UserContribution(Base):
|
||||
"""
|
||||
Felhasználói hozzájárulások nyilvántartása (szerviz beküldés, validálás, jelentés).
|
||||
Ez a tábla tárolja, hogy melyik felhasználó milyen tevékenységet végzett és milyen jutalmat kapott.
|
||||
"""
|
||||
__tablename__ = "user_contributions"
|
||||
__table_args__ = {"schema": "gamification"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=False, index=True)
|
||||
season_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("gamification.seasons.id"), nullable=True, index=True)
|
||||
|
||||
# --- HIÁNYZÓ MEZŐK PÓTOLVA A SPAM VÉDELEMHEZ ---
|
||||
service_fingerprint: Mapped[Optional[str]] = mapped_column(String(255), index=True)
|
||||
cooldown_end: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
action_type: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
earned_xp: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
contribution_type: Mapped[str] = mapped_column(String(50), nullable=False, index=True) # 'service_submission', 'service_validation', 'report_abuse'
|
||||
entity_type: Mapped[Optional[str]] = mapped_column(String(50), index=True) # 'service', 'review', 'comment'
|
||||
entity_id: Mapped[Optional[int]] = mapped_column(Integer, index=True) # ID of the contributed entity
|
||||
|
||||
points_awarded: Mapped[int] = mapped_column(Integer, default=0)
|
||||
xp_awarded: Mapped[int] = mapped_column(Integer, default=0)
|
||||
|
||||
status: Mapped[str] = mapped_column(String(20), default="pending", index=True) # 'pending', 'approved', 'rejected'
|
||||
reviewed_by: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"), nullable=True)
|
||||
reviewed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
# --- JAVÍTOTT FOGLALT SZÓ ---
|
||||
provided_fields: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Relationships
|
||||
user: Mapped["User"] = relationship("User", foreign_keys=[user_id])
|
||||
reviewer: Mapped[Optional["User"]] = relationship("User", foreign_keys=[reviewed_by])
|
||||
season: Mapped[Optional["Season"]] = relationship("Season")
|
||||
|
||||
|
||||
class Season(Base):
|
||||
""" Szezonális versenyek tárolása. """
|
||||
__tablename__ = "seasons"
|
||||
__table_args__ = {"schema": "gamification"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
start_date: Mapped[date] = mapped_column(Date, nullable=False)
|
||||
end_date: Mapped[date] = mapped_column(Date, nullable=False)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
55
backend/app/models/identity/__init__.py
Normal file
55
backend/app/models/identity/__init__.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# identity package exports
|
||||
from .identity import (
|
||||
Person,
|
||||
User,
|
||||
Wallet,
|
||||
VerificationToken,
|
||||
SocialAccount,
|
||||
ActiveVoucher,
|
||||
UserTrustProfile,
|
||||
UserRole,
|
||||
)
|
||||
|
||||
from .address import (
|
||||
Address,
|
||||
GeoPostalCode,
|
||||
GeoStreet,
|
||||
GeoStreetType,
|
||||
Rating,
|
||||
)
|
||||
|
||||
from .security import PendingAction, ActionStatus
|
||||
from .social import (
|
||||
ServiceProvider,
|
||||
Vote,
|
||||
Competition,
|
||||
UserScore,
|
||||
ServiceReview,
|
||||
ModerationStatus,
|
||||
SourceType,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Person",
|
||||
"User",
|
||||
"Wallet",
|
||||
"VerificationToken",
|
||||
"SocialAccount",
|
||||
"ActiveVoucher",
|
||||
"UserTrustProfile",
|
||||
"UserRole",
|
||||
"Address",
|
||||
"GeoPostalCode",
|
||||
"GeoStreet",
|
||||
"GeoStreetType",
|
||||
"Rating",
|
||||
"PendingAction",
|
||||
"ActionStatus",
|
||||
"ServiceProvider",
|
||||
"Vote",
|
||||
"Competition",
|
||||
"UserScore",
|
||||
"ServiceReview",
|
||||
"ModerationStatus",
|
||||
"SourceType",
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/address.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/identity/address.py
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, List, Optional
|
||||
@@ -1,3 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/identity/identity.py
|
||||
from __future__ import annotations
|
||||
import uuid
|
||||
import enum
|
||||
@@ -56,25 +57,51 @@ class Person(Base):
|
||||
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"))
|
||||
identity_docs: Mapped[Any] = mapped_column(JSON, nullable=False, default=lambda: {}, server_default=text("'{}'::jsonb"))
|
||||
ice_contact: Mapped[Any] = mapped_column(JSON, nullable=False, default=lambda: {}, 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"))
|
||||
lifetime_xp: Mapped[int] = mapped_column(BigInteger, default=-1, nullable=False)
|
||||
penalty_points: Mapped[int] = mapped_column(Integer, default=-1, nullable=False)
|
||||
social_reputation: Mapped[float] = mapped_column(Numeric(3, 2), default=0.0, nullable=False)
|
||||
|
||||
is_sales_agent: Mapped[bool] = mapped_column(Boolean, server_default=text("false"))
|
||||
is_sales_agent: Mapped[bool] = mapped_column(Boolean, default=True, nullable=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())
|
||||
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())
|
||||
|
||||
# --- KAPCSOLATOK ---
|
||||
users: Mapped[List["User"]] = relationship("User", back_populates="person")
|
||||
memberships: Mapped[List["OrganizationMember"]] = relationship("OrganizationMember", back_populates="person")
|
||||
owned_business_entities: Mapped[List["Organization"]] = relationship("Organization", back_populates="legal_owner")
|
||||
|
||||
# JAVÍTÁS 1: Explicit 'foreign_keys' megadás az AmbiguousForeignKeysError ellen
|
||||
users: Mapped[List["User"]] = relationship(
|
||||
"User",
|
||||
foreign_keys="[User.person_id]",
|
||||
back_populates="person",
|
||||
cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
# JAVÍTÁS 2: 'post_update' és 'use_alter' a körbe-függőség (circular cycle) feloldásához
|
||||
active_user_account: Mapped[Optional["User"]] = relationship(
|
||||
"User",
|
||||
foreign_keys="[Person.user_id]",
|
||||
post_update=True
|
||||
)
|
||||
user_id: Mapped[Optional[int]] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("identity.users.id", use_alter=True, name="fk_person_active_user"),
|
||||
nullable=True
|
||||
)
|
||||
|
||||
memberships: Mapped[List["OrganizationMember"]] = relationship("OrganizationMember", back_populates="person")
|
||||
|
||||
# Kapcsolat a tulajdonolt szervezetekhez (Organization táblában legal_owner_id)
|
||||
owned_business_entities: Mapped[List["Organization"]] = relationship(
|
||||
"Organization",
|
||||
foreign_keys="[Organization.legal_owner_id]",
|
||||
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"
|
||||
@@ -97,6 +124,7 @@ class User(Base):
|
||||
|
||||
referral_code: Mapped[Optional[str]] = mapped_column(String(20), unique=True)
|
||||
|
||||
# JAVÍTÁS 3: Az ajánló és értékesítő mezőknek is kell a tiszta kapcsolat nevesítés
|
||||
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"))
|
||||
|
||||
@@ -115,10 +143,32 @@ class User(Base):
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# --- KAPCSOLATOK ---
|
||||
person: Mapped[Optional["Person"]] = relationship("Person", back_populates="users")
|
||||
|
||||
# JAVÍTÁS 4: Itt is explicit megadjuk, hogy melyik kulcs köti az emberhez
|
||||
person: Mapped[Optional["Person"]] = relationship(
|
||||
"Person",
|
||||
foreign_keys=[person_id],
|
||||
back_populates="users"
|
||||
)
|
||||
|
||||
# JAVÍTÁS 5: Ajánlói (Referrer) önhivatkozó kapcsolat feloldása
|
||||
referrer: Mapped[Optional["User"]] = relationship(
|
||||
"User",
|
||||
remote_side=[id],
|
||||
foreign_keys=[referred_by_id]
|
||||
)
|
||||
|
||||
# JAVÍTÁS 6: Értékesítő (Sales Agent) önhivatkozó kapcsolat feloldása
|
||||
sales_agent: Mapped[Optional["User"]] = relationship(
|
||||
"User",
|
||||
remote_side=[id],
|
||||
foreign_keys=[current_sales_agent_id]
|
||||
)
|
||||
|
||||
wallet: Mapped[Optional["Wallet"]] = relationship("Wallet", back_populates="user", uselist=False)
|
||||
payment_intents_as_payer = relationship("PaymentIntent", foreign_keys="[PaymentIntent.payer_id]", back_populates="payer")
|
||||
payment_intents_as_beneficiary = relationship("PaymentIntent", foreign_keys="[PaymentIntent.beneficiary_id]", back_populates="beneficiary")
|
||||
|
||||
# JAVÍTÁS: Ez a sor KELL az OCR robot és a Trust Engine működéséhez
|
||||
trust_profile: Mapped[Optional["UserTrustProfile"]] = relationship("UserTrustProfile", back_populates="user", uselist=False, cascade="all, delete-orphan")
|
||||
|
||||
social_accounts: Mapped[List["SocialAccount"]] = relationship("SocialAccount", back_populates="user", cascade="all, delete-orphan")
|
||||
@@ -126,6 +176,9 @@ class User(Base):
|
||||
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")
|
||||
|
||||
# MB 2.1: Vehicle ratings kapcsolat (hiányzott a listából, visszatéve)
|
||||
vehicle_ratings: Mapped[List["VehicleUserRating"]] = relationship("VehicleUserRating", back_populates="user", cascade="all, delete-orphan")
|
||||
|
||||
# Pénzügyi és egyéb kapcsolatok
|
||||
withdrawal_requests: Mapped[List["WithdrawalRequest"]] = relationship("WithdrawalRequest", foreign_keys="[WithdrawalRequest.user_id]", back_populates="user", cascade="all, delete-orphan")
|
||||
service_reviews: Mapped[List["ServiceReview"]] = relationship("ServiceReview", back_populates="user", cascade="all, delete-orphan")
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/security.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/identity/security.py
|
||||
import enum
|
||||
from datetime import datetime
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/social.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/identity/social.py
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
@@ -59,7 +59,7 @@ class Vote(Base):
|
||||
class Competition(Base):
|
||||
""" Gamifikált versenyek (pl. Januári Feltöltő Verseny). """
|
||||
__tablename__ = "competitions"
|
||||
__table_args__ = {"schema": "system"}
|
||||
__table_args__ = {"schema": "gamification"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
@@ -73,12 +73,12 @@ class UserScore(Base):
|
||||
__tablename__ = "user_scores"
|
||||
__table_args__ = (
|
||||
UniqueConstraint('user_id', 'competition_id', name='uq_user_competition_score'),
|
||||
{"schema": "system"}
|
||||
{"schema": "gamification"}
|
||||
)
|
||||
|
||||
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("system.competitions.id"))
|
||||
competition_id: Mapped[int] = mapped_column(Integer, ForeignKey("gamification.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())
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/identity.py
|
||||
from __future__ import annotations
|
||||
import uuid
|
||||
import enum
|
||||
from datetime import datetime
|
||||
from typing import Any, List, Optional, TYPE_CHECKING
|
||||
from sqlalchemy import String, Boolean, DateTime, ForeignKey, JSON, Numeric, text, Integer, BigInteger, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, ENUM as PG_ENUM
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
# MB 2.0: Központi aszinkron adatbázis motorból húzzuk be a Base-t
|
||||
from app.database import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .organization import Organization, OrganizationMember
|
||||
from .asset import VehicleOwnership
|
||||
from .gamification import UserStats
|
||||
|
||||
class UserRole(str, enum.Enum):
|
||||
superadmin = "superadmin"
|
||||
admin = "admin"
|
||||
region_admin = "region_admin"
|
||||
country_admin = "country_admin"
|
||||
moderator = "moderator"
|
||||
sales_agent = "sales_agent"
|
||||
user = "user"
|
||||
service_owner = "service_owner"
|
||||
fleet_manager = "fleet_manager"
|
||||
driver = "driver"
|
||||
|
||||
class Person(Base):
|
||||
"""
|
||||
Természetes személy identitása. A DNS szint.
|
||||
Minden identitás adat az 'identity' sémába kerül.
|
||||
"""
|
||||
__tablename__ = "persons"
|
||||
__table_args__ = {"schema": "identity"}
|
||||
|
||||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, index=True)
|
||||
id_uuid: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), default=uuid.uuid4, unique=True, nullable=False)
|
||||
|
||||
# A lakcím a 'data' sémában marad
|
||||
address_id: Mapped[Optional[uuid.UUID]] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("system.addresses.id"))
|
||||
|
||||
# Kritikus azonosító: Név + Anyja neve + Szül.idő hash-elve.
|
||||
# Ezzel ismerjük fel a személyt akkor is, ha új User accountot hoz létre.
|
||||
identity_hash: Mapped[Optional[str]] = mapped_column(String(64), unique=True, index=True)
|
||||
|
||||
last_name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
first_name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
phone: Mapped[Optional[str]] = mapped_column(String)
|
||||
|
||||
mothers_last_name: Mapped[Optional[str]] = mapped_column(String)
|
||||
mothers_first_name: Mapped[Optional[str]] = mapped_column(String)
|
||||
birth_place: Mapped[Optional[str]] = mapped_column(String)
|
||||
birth_date: Mapped[Optional[datetime]] = mapped_column(DateTime)
|
||||
|
||||
identity_docs: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
|
||||
ice_contact: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
|
||||
|
||||
lifetime_xp: Mapped[int] = mapped_column(BigInteger, server_default=text("0"))
|
||||
penalty_points: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
social_reputation: Mapped[float] = mapped_column(Numeric(3, 2), server_default=text("1.00"))
|
||||
|
||||
is_sales_agent: Mapped[bool] = mapped_column(Boolean, server_default=text("false"))
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
is_ghost: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# --- KAPCSOLATOK ---
|
||||
users: Mapped[List["User"]] = relationship("User", back_populates="person")
|
||||
memberships: Mapped[List["OrganizationMember"]] = relationship("OrganizationMember", back_populates="person")
|
||||
|
||||
# MB 2.0 KIEGÉSZÍTÉS: A személy által birtokolt üzleti entitások (Cégek/Szolgáltatók)
|
||||
# Ez a lista megmarad akkor is, ha az Organization deaktiválódik.
|
||||
owned_business_entities: Mapped[List["Organization"]] = relationship("Organization", back_populates="legal_owner")
|
||||
|
||||
class User(Base):
|
||||
""" Login entitás. Bármikor törölhető (GDPR), de Person-höz kötött. """
|
||||
__tablename__ = "users"
|
||||
__table_args__ = {"schema": "identity"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
email: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False)
|
||||
hashed_password: Mapped[Optional[str]] = mapped_column(String)
|
||||
|
||||
role: Mapped[UserRole] = mapped_column(
|
||||
PG_ENUM(UserRole, name="userrole", schema="identity"),
|
||||
default=UserRole.user
|
||||
)
|
||||
|
||||
person_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey("identity.persons.id"))
|
||||
trust_profile: Mapped[Optional["UserTrustProfile"]] = relationship("UserTrustProfile", back_populates="user", uselist=False, cascade="all, delete-orphan")
|
||||
subscription_plan: Mapped[str] = mapped_column(String(30), server_default=text("'FREE'"))
|
||||
subscription_expires_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
is_vip: Mapped[bool] = mapped_column(Boolean, server_default=text("false"))
|
||||
|
||||
referral_code: Mapped[Optional[str]] = mapped_column(String(20), unique=True)
|
||||
|
||||
referred_by_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
current_sales_agent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
folder_slug: Mapped[Optional[str]] = mapped_column(String(12), unique=True, index=True)
|
||||
|
||||
preferred_language: Mapped[str] = mapped_column(String(5), server_default="hu")
|
||||
region_code: Mapped[str] = mapped_column(String(5), server_default="HU")
|
||||
preferred_currency: Mapped[str] = mapped_column(String(3), server_default="HUF")
|
||||
|
||||
scope_level: Mapped[str] = mapped_column(String(30), server_default="individual")
|
||||
scope_id: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
custom_permissions: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Kapcsolatok
|
||||
person: Mapped[Optional["Person"]] = relationship("Person", back_populates="users")
|
||||
wallet: Mapped[Optional["Wallet"]] = relationship("Wallet", back_populates="user", uselist=False)
|
||||
social_accounts: Mapped[List["SocialAccount"]] = relationship("SocialAccount", back_populates="user", cascade="all, delete-orphan")
|
||||
owned_organizations: Mapped[List["Organization"]] = relationship("Organization", back_populates="owner")
|
||||
stats: Mapped[Optional["UserStats"]] = relationship("UserStats", back_populates="user", uselist=False, cascade="all, delete-orphan")
|
||||
ownership_history: Mapped[List["VehicleOwnership"]] = relationship("VehicleOwnership", back_populates="user")
|
||||
|
||||
# PaymentIntent kapcsolatok
|
||||
payment_intents_as_payer: Mapped[List["PaymentIntent"]] = relationship(
|
||||
"PaymentIntent",
|
||||
foreign_keys="[PaymentIntent.payer_id]",
|
||||
back_populates="payer"
|
||||
)
|
||||
withdrawal_requests: Mapped[List["WithdrawalRequest"]] = relationship("WithdrawalRequest", foreign_keys="[WithdrawalRequest.user_id]", back_populates="user", cascade="all, delete-orphan")
|
||||
payment_intents_as_beneficiary: Mapped[List["PaymentIntent"]] = relationship(
|
||||
"PaymentIntent",
|
||||
foreign_keys="[PaymentIntent.beneficiary_id]",
|
||||
back_populates="beneficiary"
|
||||
)
|
||||
# Service reviews
|
||||
service_reviews: Mapped[List["ServiceReview"]] = relationship("ServiceReview", back_populates="user", cascade="all, delete-orphan")
|
||||
|
||||
@property
|
||||
def tier_name(self) -> str:
|
||||
"""Kompatibilitási mező a keresőhöz: a 'FREE' -> 'free' konverzióhoz"""
|
||||
return (self.subscription_plan or "free").lower()
|
||||
|
||||
class Wallet(Base):
|
||||
__tablename__ = "wallets"
|
||||
__table_args__ = {"schema": "identity"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"), unique=True)
|
||||
|
||||
earned_credits: Mapped[float] = mapped_column(Numeric(18, 4), server_default=text("0"))
|
||||
purchased_credits: Mapped[float] = mapped_column(Numeric(18, 4), server_default=text("0"))
|
||||
service_coins: Mapped[float] = mapped_column(Numeric(18, 4), server_default=text("0"))
|
||||
|
||||
currency: Mapped[str] = mapped_column(String(3), default="HUF")
|
||||
user: Mapped["User"] = relationship("User", back_populates="wallet")
|
||||
active_vouchers: Mapped[List["ActiveVoucher"]] = relationship("ActiveVoucher", back_populates="wallet", cascade="all, delete-orphan")
|
||||
|
||||
class VerificationToken(Base):
|
||||
__tablename__ = "verification_tokens"
|
||||
__table_args__ = {"schema": "identity"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
token: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), default=uuid.uuid4, unique=True, nullable=False)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id", ondelete="CASCADE"), nullable=False)
|
||||
token_type: Mapped[str] = mapped_column(String(20), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
is_used: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
|
||||
class SocialAccount(Base):
|
||||
__tablename__ = "social_accounts"
|
||||
__table_args__ = (
|
||||
UniqueConstraint('provider', 'social_id', name='uix_social_provider_id'),
|
||||
{"schema": "identity"}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id", ondelete="CASCADE"), nullable=False)
|
||||
provider: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
social_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
||||
email: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
extra_data: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
user: Mapped["User"] = relationship("User", back_populates="social_accounts")
|
||||
|
||||
|
||||
class ActiveVoucher(Base):
|
||||
"""Aktív, le nem járt voucher-ek tárolása FIFO elv szerint."""
|
||||
__tablename__ = "active_vouchers"
|
||||
__table_args__ = {"schema": "identity"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
wallet_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.wallets.id", ondelete="CASCADE"), nullable=False)
|
||||
amount: Mapped[float] = mapped_column(Numeric(18, 4), nullable=False)
|
||||
original_amount: Mapped[float] = mapped_column(Numeric(18, 4), nullable=False)
|
||||
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Kapcsolatok
|
||||
wallet: Mapped["Wallet"] = relationship("Wallet", back_populates="active_vouchers")
|
||||
|
||||
|
||||
class UserTrustProfile(Base):
|
||||
"""
|
||||
Gondos Gazda Index (Trust Score) tárolása felhasználónként.
|
||||
A pontszámot a trust_engine számolja dinamikusan a SystemParameter-ek alapján.
|
||||
"""
|
||||
__tablename__ = "user_trust_profiles"
|
||||
__table_args__ = {"schema": "identity"}
|
||||
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("identity.users.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
index=True
|
||||
)
|
||||
trust_score: Mapped[int] = mapped_column(Integer, default=0, nullable=False) # 0-100 pont
|
||||
maintenance_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0.0, nullable=False) # 0.0-1.0
|
||||
quality_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0.0, nullable=False) # 0.0-1.0
|
||||
preventive_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0.0, nullable=False) # 0.0-1.0
|
||||
last_calculated: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
nullable=False
|
||||
)
|
||||
|
||||
# Kapcsolatok
|
||||
user: Mapped["User"] = relationship("User", back_populates="trust_profile", uselist=False)
|
||||
53
backend/app/models/marketplace/__init__.py
Normal file
53
backend/app/models/marketplace/__init__.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# marketplace package exports
|
||||
from .organization import (
|
||||
Organization,
|
||||
OrganizationMember,
|
||||
OrganizationFinancials,
|
||||
OrganizationSalesAssignment,
|
||||
OrgType,
|
||||
OrgUserRole,
|
||||
Branch,
|
||||
)
|
||||
|
||||
from .payment import PaymentIntent, PaymentIntentStatus
|
||||
from .finance import Issuer, IssuerType
|
||||
from .service import (
|
||||
ServiceProfile,
|
||||
ExpertiseTag,
|
||||
ServiceExpertise,
|
||||
)
|
||||
|
||||
from .logistics import Location, LocationType
|
||||
|
||||
# THOUGHT PROCESS: A StagedVehicleData nevet StagedVehicleData-ra javítottuk,
|
||||
# és ide csoportosítottuk a staged_data.py-ban lévő többi osztályt is.
|
||||
from .staged_data import (
|
||||
StagedVehicleData,
|
||||
ServiceStaging,
|
||||
DiscoveryParameter
|
||||
)
|
||||
|
||||
from .service_request import ServiceRequest
|
||||
|
||||
__all__ = [
|
||||
"Organization",
|
||||
"OrganizationMember",
|
||||
"OrganizationFinancials",
|
||||
"OrganizationSalesAssignment",
|
||||
"OrgType",
|
||||
"OrgUserRole",
|
||||
"Branch",
|
||||
"PaymentIntent",
|
||||
"PaymentIntentStatus",
|
||||
"Issuer",
|
||||
"IssuerType",
|
||||
"ServiceProfile",
|
||||
"ExpertiseTag",
|
||||
"ServiceExpertise",
|
||||
"ServiceStaging",
|
||||
"DiscoveryParameter",
|
||||
"Location",
|
||||
"LocationType",
|
||||
"StagedVehicleData",
|
||||
"ServiceRequest",
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/finance.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/marketplace/finance.py
|
||||
"""
|
||||
Finance modellek: Issuer (Kibocsátó) és FinancialLedger (Pénzügyi főkönyv) bővítése.
|
||||
"""
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/logistics.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/marketplace/logistics.py
|
||||
import enum
|
||||
from typing import Optional
|
||||
from sqlalchemy import Integer, String, Enum
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/organization.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/marketplace/organization.py
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
@@ -8,6 +8,7 @@ from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, J
|
||||
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
|
||||
from geoalchemy2 import Geometry
|
||||
|
||||
# MB 2.0: A központi aszinkron adatbázis motorból húzzuk be a Base-t
|
||||
from app.database import Base
|
||||
@@ -202,6 +203,12 @@ class Branch(Base):
|
||||
door: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
hrsz: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
|
||||
# PostGIS location field for geographic queries
|
||||
location: Mapped[Optional[Any]] = mapped_column(
|
||||
Geometry(geometry_type='POINT', srid=4326),
|
||||
nullable=True
|
||||
)
|
||||
|
||||
opening_hours: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
branch_rating: Mapped[float] = mapped_column(Float, default=0.0)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/payment.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/marketplace/payment.py
|
||||
"""
|
||||
Payment Intent modell a Stripe integrációhoz és belső fizetésekhez.
|
||||
Kettős Lakat (Double Lock) biztonságot valósít meg.
|
||||
@@ -14,7 +14,7 @@ from sqlalchemy.dialects.postgresql import UUID as PG_UUID, ENUM as PG_ENUM
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
from app.models.audit import WalletType
|
||||
from app.models.system.audit import WalletType
|
||||
|
||||
|
||||
class PaymentIntentStatus(str, enum.Enum):
|
||||
56
backend/app/models/service.py → backend/app/models/marketplace/service.py
Executable file → Normal file
56
backend/app/models/service.py → backend/app/models/marketplace/service.py
Executable file → Normal file
@@ -1,16 +1,23 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/service.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/marketplace/service.py
|
||||
import enum
|
||||
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 sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB, ENUM as SQLEnum
|
||||
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 ServiceStatus(str, enum.Enum):
|
||||
ghost = "ghost" # Nyers, robot által talált, nem validált
|
||||
active = "active" # Publikus, aktív szerviz
|
||||
flagged = "flagged" # Gyanús, kézi ellenőrzést igényel
|
||||
suspended = "suspended" # Felfüggesztett, tiltott szerviz
|
||||
|
||||
class ServiceProfile(Base):
|
||||
""" Szerviz szolgáltató adatai (v1.3.1). """
|
||||
__tablename__ = "service_profiles"
|
||||
@@ -26,7 +33,12 @@ class ServiceProfile(Base):
|
||||
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)
|
||||
status: Mapped[ServiceStatus] = mapped_column(
|
||||
SQLEnum(ServiceStatus, name="service_status", schema="marketplace"),
|
||||
server_default=ServiceStatus.ghost.value,
|
||||
nullable=False,
|
||||
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)
|
||||
@@ -73,55 +85,29 @@ class ExpertiseTag(Base):
|
||||
__table_args__ = {"schema": "marketplace"}
|
||||
|
||||
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": "marketplace"}
|
||||
@@ -129,13 +115,9 @@ class ServiceExpertise(Base):
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
service_id: Mapped[int] = mapped_column(Integer, ForeignKey("marketplace.service_profiles.id", ondelete="CASCADE"))
|
||||
expertise_id: Mapped[int] = mapped_column(Integer, ForeignKey("marketplace.expertise_tags.id", ondelete="CASCADE"))
|
||||
|
||||
# Mennyire biztos ez a tudás? (0: robot találta, 1: júzer mondta, 2: igazolt szakma)
|
||||
confidence_level: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"))
|
||||
|
||||
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")
|
||||
|
||||
@@ -154,6 +136,14 @@ class ServiceStaging(Base):
|
||||
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"))
|
||||
|
||||
# Audit fix: contact_email hossza rögzítve a DB szinkronhoz
|
||||
contact_email: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
|
||||
contact_phone: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
||||
website: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
external_id: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, index=True)
|
||||
|
||||
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())
|
||||
|
||||
175
backend/app/models/marketplace/service.py.old
Executable file
175
backend/app/models/marketplace/service.py.old
Executable file
@@ -0,0 +1,175 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/marketplace/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": "marketplace"}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
organization_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("fleet.organizations.id"), unique=True)
|
||||
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("marketplace.service_profiles.id"))
|
||||
|
||||
fingerprint: Mapped[str] = mapped_column(String(255), index=True, nullable=False)
|
||||
location: Mapped[Any] = mapped_column(Geometry(geometry_type='POINT', srid=4326, spatial_index=False), index=True)
|
||||
|
||||
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)
|
||||
|
||||
# Aggregated verified review ratings (Social 3)
|
||||
rating_verified_count: Mapped[Optional[int]] = mapped_column(Integer, server_default=text("0"))
|
||||
rating_price_avg: Mapped[Optional[float]] = mapped_column(Float)
|
||||
rating_quality_avg: Mapped[Optional[float]] = mapped_column(Float)
|
||||
rating_time_avg: Mapped[Optional[float]] = mapped_column(Float)
|
||||
rating_communication_avg: Mapped[Optional[float]] = mapped_column(Float)
|
||||
rating_overall: Mapped[Optional[float]] = mapped_column(Float)
|
||||
last_review_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
vibe_analysis: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
social_links: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
specialization_tags: Mapped[Any] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
|
||||
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")
|
||||
reviews: Mapped[List["ServiceReview"]] = relationship("ServiceReview", back_populates="service")
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
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": "marketplace"}
|
||||
|
||||
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": "marketplace"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
service_id: Mapped[int] = mapped_column(Integer, ForeignKey("marketplace.service_profiles.id", ondelete="CASCADE"))
|
||||
expertise_id: Mapped[int] = mapped_column(Integer, ForeignKey("marketplace.expertise_tags.id", ondelete="CASCADE"))
|
||||
|
||||
# Mennyire biztos ez a tudás? (0: robot találta, 1: júzer mondta, 2: igazolt szakma)
|
||||
confidence_level: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"))
|
||||
|
||||
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": "marketplace"}
|
||||
)
|
||||
|
||||
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"))
|
||||
|
||||
# Additional contact and identification fields
|
||||
contact_phone: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
||||
website: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
external_id: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, index=True)
|
||||
|
||||
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": "marketplace"}
|
||||
|
||||
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))
|
||||
95
backend/app/models/marketplace/service_request.py
Normal file
95
backend/app/models/marketplace/service_request.py
Normal file
@@ -0,0 +1,95 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/marketplace/service_request.py
|
||||
"""
|
||||
ServiceRequest - Piactér központi tranzakciós modellje.
|
||||
Epic 7: Marketplace ServiceRequest dedikált modell.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from sqlalchemy import String, ForeignKey, Text, DateTime, Numeric, Integer, Index
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class ServiceRequest(Base):
|
||||
"""
|
||||
Szervizigény (ServiceRequest) tábla.
|
||||
Egy felhasználó által létrehozott szervizigényt reprezentál, amely lehetővé teszi
|
||||
a szervizszolgáltatók számára árajánlatok készítését és a tranzakciók lebonyolítását.
|
||||
"""
|
||||
__tablename__ = "service_requests"
|
||||
__table_args__ = (
|
||||
Index('idx_service_request_status', 'status'),
|
||||
Index('idx_service_request_user_id', 'user_id'),
|
||||
Index('idx_service_request_asset_id', 'asset_id'),
|
||||
Index('idx_service_request_branch_id', 'branch_id'),
|
||||
{"schema": "marketplace"}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
|
||||
# Idegen kulcsok (Kapcsolódási pontok)
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("identity.users.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="A szervizigényt létrehozó felhasználó"
|
||||
)
|
||||
asset_id: Mapped[Optional[int]] = mapped_column(
|
||||
ForeignKey("vehicle.assets.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
comment="Érintett jármű (opcionális)"
|
||||
)
|
||||
branch_id: Mapped[Optional[int]] = mapped_column(
|
||||
ForeignKey("fleet.branches.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
comment="Célzott szerviz (ha van)"
|
||||
)
|
||||
|
||||
# Üzleti logika mezők
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
server_default="pending",
|
||||
index=True,
|
||||
comment="pending, quoted, accepted, scheduled, completed, cancelled"
|
||||
)
|
||||
description: Mapped[Optional[str]] = mapped_column(
|
||||
Text,
|
||||
nullable=True,
|
||||
comment="A szervizigény részletes leírása"
|
||||
)
|
||||
price_estimate: Mapped[Optional[float]] = mapped_column(
|
||||
Numeric(10, 2),
|
||||
nullable=True,
|
||||
comment="Becsült ár (opcionális)"
|
||||
)
|
||||
requested_date: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
comment="Kért szerviz dátum"
|
||||
)
|
||||
|
||||
# Audit
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
nullable=False,
|
||||
comment="Létrehozás időbélyege"
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
nullable=False,
|
||||
comment="Utolsó módosítás időbélyege"
|
||||
)
|
||||
|
||||
# Relationships (opcionális, de ajánlott a lazy loading miatt)
|
||||
user = relationship("User", back_populates="service_requests", lazy="selectin")
|
||||
asset = relationship("Asset", back_populates="service_requests", lazy="selectin")
|
||||
branch = relationship("Branch", back_populates="service_requests", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<ServiceRequest(id={self.id}, status='{self.status}', user_id={self.user_id})>"
|
||||
94
backend/app/models/marketplace/staged_data.py
Normal file
94
backend/app/models/marketplace/staged_data.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any
|
||||
from sqlalchemy import String, Integer, DateTime, text, Boolean, Float, Text, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.sql import func
|
||||
from app.database import Base # MB 2.0 Standard: Központi bázis használata
|
||||
|
||||
class StagedVehicleData(Base):
|
||||
""" Robot 2.1 (Researcher) nyers adatgyűjtője. """
|
||||
__tablename__ = "staged_vehicle_data"
|
||||
__table_args__ = {"schema": "system", "extend_existing": True}
|
||||
|
||||
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 és a Robot 5 (Auditor) naplója.
|
||||
A séma és a mezők szinkronban az adatbázis audittal.
|
||||
"""
|
||||
__tablename__ = "service_staging"
|
||||
__table_args__ = {"schema": "marketplace", "extend_existing": True}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(255), index=True)
|
||||
|
||||
# 1. ⚠️ EXTRA OSZLOP: source
|
||||
source: Mapped[Optional[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)
|
||||
|
||||
# Elérhetőségek
|
||||
city: Mapped[str] = mapped_column(String(100), index=True)
|
||||
postal_code: Mapped[Optional[str]] = mapped_column(String(10))
|
||||
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))
|
||||
contact_email: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
|
||||
# 2. ⚠️ EXTRA OSZLOP: description
|
||||
description: Mapped[Optional[str]] = mapped_column(Text)
|
||||
|
||||
# 3. ⚠️ EXTRA OSZLOP: submitted_by
|
||||
submitted_by: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
|
||||
# 4. ⚠️ EXTRA OSZLOP: trust_score
|
||||
trust_score: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"))
|
||||
|
||||
raw_data: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
status: Mapped[str] = mapped_column(String(20), default="pending", index=True)
|
||||
validation_level: Mapped[int] = mapped_column(Integer, default=40, server_default=text("40"))
|
||||
|
||||
# --- Robot 5 (Auditor) technikai mezők ---
|
||||
|
||||
# 5. ⚠️ EXTRA OSZLOP: rejection_reason
|
||||
rejection_reason: Mapped[Optional[str]] = mapped_column(String(500))
|
||||
|
||||
# 6. ⚠️ EXTRA OSZLOP: published_at
|
||||
published_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
# 7. ⚠️ EXTRA OSZLOP: service_profile_id
|
||||
service_profile_id: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
|
||||
# 8. ⚠️ EXTRA OSZLOP: organization_id
|
||||
organization_id: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
|
||||
# 9. ⚠️ EXTRA OSZLOP: audit_trail
|
||||
audit_trail: Mapped[Optional[dict]] = mapped_column(JSONB)
|
||||
|
||||
# Időbélyegek
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# 10. ⚠️ EXTRA OSZLOP: updated_at
|
||||
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": "marketplace", "extend_existing": True}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
city: Mapped[str] = mapped_column(String(100), unique=True, index=True)
|
||||
country_code: Mapped[Optional[str]] = mapped_column(String(2), nullable=True, default="HU")
|
||||
keyword: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
last_run_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/staged_data.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/marketplace/staged_data.py
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any
|
||||
from sqlalchemy import String, Integer, DateTime, text, Boolean, Float
|
||||
@@ -22,25 +22,42 @@ class StagedVehicleData(Base):
|
||||
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. """
|
||||
""" Robot 1.3 (Scout) által talált nyers szerviz adatok és a Robot 5 (Auditor) naplója. """
|
||||
__tablename__ = "service_staging"
|
||||
__table_args__ = {"schema": "system"}
|
||||
__table_args__ = {"schema": "marketplace"}
|
||||
|
||||
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))
|
||||
source: Mapped[Optional[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)
|
||||
|
||||
# Elérhetőségek
|
||||
city: Mapped[str] = mapped_column(String(100), index=True)
|
||||
postal_code: Mapped[Optional[str]] = mapped_column(String(10))
|
||||
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))
|
||||
contact_email: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
|
||||
# Beküldés és Bizalom
|
||||
description: Mapped[Optional[str]] = mapped_column(Text)
|
||||
submitted_by: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
trust_score: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"))
|
||||
|
||||
# Nyers adatok és Státusz
|
||||
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)
|
||||
|
||||
# --- Robot 5 (Auditor) technikai mezők ---
|
||||
# Ezek kellenek a munka naplózásához
|
||||
rejection_reason: Mapped[Optional[str]] = mapped_column(String(500))
|
||||
published_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
service_profile_id: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
organization_id: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
audit_trail: Mapped[Optional[dict]] = mapped_column(JSONB)
|
||||
|
||||
# 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())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# /app/app/models/reference_data.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/reference_data.py
|
||||
from sqlalchemy import Column, Integer, String, DateTime, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from app.database import Base
|
||||
|
||||
12
backend/app/models/system/__init__.py
Normal file
12
backend/app/models/system/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# system package barrel
|
||||
from .system import SystemParameter, ParameterScope, InternalNotification, SystemServiceStaging
|
||||
from .audit import SecurityAuditLog, OperationalLog, ProcessLog, FinancialLedger, WalletType, LedgerStatus, LedgerEntryType
|
||||
from .document import Document
|
||||
from .translation import Translation
|
||||
from .legal import LegalDocument, LegalAcceptance
|
||||
|
||||
__all__ = [
|
||||
"SystemParameter", "InternalNotification", "SystemServiceStaging",
|
||||
"SecurityAuditLog", "ProcessLog", "FinancialLedger", "WalletType", "LedgerStatus", "LedgerEntryType",
|
||||
"Document", "Translation", "LegalDocument", "LegalAcceptance"
|
||||
]
|
||||
115
backend/app/models/system/audit.py
Executable file
115
backend/app/models/system/audit.py
Executable file
@@ -0,0 +1,115 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/system/audit.py
|
||||
import enum
|
||||
import uuid
|
||||
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 sqlalchemy.dialects.postgresql import UUID as PG_UUID, ENUM as PG_ENUM
|
||||
from app.database import Base
|
||||
|
||||
class SecurityAuditLog(Base):
|
||||
""" Kiemelt biztonsági események és a 4-szem elv naplózása. """
|
||||
__tablename__ = "security_audit_logs"
|
||||
__table_args__ = {"schema": "audit"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
action: Mapped[Optional[str]] = mapped_column(String(50)) # 'ROLE_CHANGE', 'MANUAL_CREDIT_ADJUST'
|
||||
|
||||
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"
|
||||
__table_args__ = {"schema": "audit"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id", ondelete="SET NULL"))
|
||||
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"
|
||||
__table_args__ = {"schema": "audit"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
process_name: Mapped[str] = mapped_column(String(100), index=True) # 'Master-Enricher'
|
||||
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 LedgerEntryType(str, enum.Enum):
|
||||
DEBIT = "DEBIT"
|
||||
CREDIT = "CREDIT"
|
||||
|
||||
|
||||
class WalletType(str, enum.Enum):
|
||||
EARNED = "EARNED"
|
||||
PURCHASED = "PURCHASED"
|
||||
SERVICE_COINS = "SERVICE_COINS"
|
||||
VOUCHER = "VOUCHER"
|
||||
|
||||
|
||||
class LedgerStatus(str, enum.Enum):
|
||||
PENDING = "PENDING"
|
||||
SUCCESS = "SUCCESS"
|
||||
FAILED = "FAILED"
|
||||
REFUNDED = "REFUNDED"
|
||||
REFUND = "REFUND"
|
||||
|
||||
|
||||
class FinancialLedger(Base):
|
||||
""" Minden pénz- és kreditmozgás központi naplója. Billing Engine alapja. """
|
||||
__tablename__ = "financial_ledger"
|
||||
__table_args__ = {"schema": "audit"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
|
||||
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())
|
||||
|
||||
# Új mezők double‑entry és okos levonáshoz
|
||||
entry_type: Mapped[LedgerEntryType] = mapped_column(
|
||||
PG_ENUM(LedgerEntryType, name="ledger_entry_type", schema="audit"),
|
||||
nullable=False
|
||||
)
|
||||
balance_after: Mapped[Optional[float]] = mapped_column(Numeric(18, 4))
|
||||
wallet_type: Mapped[Optional[WalletType]] = mapped_column(
|
||||
PG_ENUM(WalletType, name="wallet_type", schema="audit")
|
||||
)
|
||||
# Economy 1: számlázási mezők
|
||||
issuer_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("finance.issuers.id"), nullable=True)
|
||||
invoice_status: Mapped[Optional[str]] = mapped_column(String(50), default="PENDING")
|
||||
tax_amount: Mapped[Optional[float]] = mapped_column(Numeric(18, 4))
|
||||
gross_amount: Mapped[Optional[float]] = mapped_column(Numeric(18, 4))
|
||||
net_amount: Mapped[Optional[float]] = mapped_column(Numeric(18, 4))
|
||||
transaction_id: Mapped[uuid.UUID] = mapped_column(
|
||||
PG_UUID(as_uuid=True), default=uuid.uuid4, nullable=False, index=True
|
||||
)
|
||||
status: Mapped[LedgerStatus] = mapped_column(
|
||||
PG_ENUM(LedgerStatus, name="ledger_status", schema="audit"),
|
||||
default=LedgerStatus.SUCCESS,
|
||||
nullable=False
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/document.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/system/document.py
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
@@ -6,7 +6,7 @@ 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
|
||||
from app.database import Base # MB 2.0: Egységesített Base a szinkronitáshoz
|
||||
|
||||
class Document(Base):
|
||||
""" NAS alapú dokumentumtár metaadatai. """
|
||||
@@ -35,18 +35,6 @@ class Document(Base):
|
||||
# =========================================================================
|
||||
# Probléma: Az `ocr_robot.py` (Robot 3) módosítani próbálta a dokumentumok
|
||||
# állapotát és menteni akarta az AI eredményeket, de a mezők hiányoztak.
|
||||
#
|
||||
# Megoldás: Hozzáadtuk a szükséges mezőket a munkafolyamat (Workflow)
|
||||
# támogatásához.
|
||||
#
|
||||
# 1. `status`: A robot a 'pending_ocr' státuszra szűr. Indexeljük,
|
||||
# mert a WHERE feltételben szerepel, így az adatbázis sokkal gyorsabb lesz.
|
||||
#
|
||||
# 2. `ocr_data`: A kinyert adatokat tárolja. Text típust használunk String
|
||||
# helyett, mert az AI válasza (pl. JSON formátumú adat) hosszú lehet.
|
||||
#
|
||||
# 3. `error_log`: Ha az AI hibázik, vagy üres választ ad, itt rögzítjük
|
||||
# a hiba okát a könnyebb debuggolás érdekében.
|
||||
# =========================================================================
|
||||
|
||||
status: Mapped[str] = mapped_column(String(50), default="uploaded", index=True)
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/legal.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/system/legal.py
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from sqlalchemy import Integer, String, Text, DateTime, ForeignKey, Boolean
|
||||
@@ -1,9 +1,9 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/system.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/system/system.py
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from datetime import datetime, date
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from sqlalchemy import String, Integer, Boolean, DateTime, text, UniqueConstraint, ForeignKey, Text, Enum as SQLEnum
|
||||
from sqlalchemy import String, Integer, Boolean, DateTime, text, UniqueConstraint, ForeignKey, Text, Enum as SQLEnum, Date
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.sql import func
|
||||
@@ -28,7 +28,7 @@ class SystemParameter(Base):
|
||||
category: Mapped[str] = mapped_column(String, server_default="general", index=True)
|
||||
value: Mapped[dict] = mapped_column(JSONB, nullable=False)
|
||||
|
||||
scope_level: Mapped[ParameterScope] = mapped_column(SQLEnum(ParameterScope, name="parameter_scope"), server_default=ParameterScope.GLOBAL.value, index=True)
|
||||
scope_level: Mapped[ParameterScope] = mapped_column(SQLEnum(ParameterScope, name="parameter_scope", schema="system"), server_default=ParameterScope.GLOBAL.value, index=True)
|
||||
scope_id: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
@@ -49,12 +49,43 @@ class InternalNotification(Base):
|
||||
|
||||
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
|
||||
category: Mapped[str] = mapped_column(String(50), server_default="info")
|
||||
priority: Mapped[str] = mapped_column(String(20), server_default="medium")
|
||||
|
||||
read_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
data: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class SystemServiceStaging(Base):
|
||||
""" Robot 1.3 (Scout) által talált nyers szerviz adatok. """
|
||||
__tablename__ = "service_staging"
|
||||
__table_args__ = {"schema": "system"}
|
||||
|
||||
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)
|
||||
|
||||
# Metaadatok a gyors eléréshez (melyik autó, melyik VIN)
|
||||
data: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)
|
||||
postal_code: Mapped[Optional[str]] = mapped_column(String(20), 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))
|
||||
contact_email: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
|
||||
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())
|
||||
|
||||
# JAVÍTÁS: Ezeket az oszlopokat vissza kell tenni, mert az audit szerint
|
||||
# az adatbázisban léteznek a system.service_staging táblában.
|
||||
read_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
data: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/translation.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/system/translation.py
|
||||
from sqlalchemy import String, Integer, Text, Boolean, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
63
backend/app/models/vehicle/__init__.py
Normal file
63
backend/app/models/vehicle/__init__.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# vehicle package exports
|
||||
from .vehicle_definitions import (
|
||||
VehicleModelDefinition,
|
||||
VehicleType,
|
||||
FeatureDefinition,
|
||||
ModelFeatureMap,
|
||||
)
|
||||
|
||||
from .vehicle import (
|
||||
CostCategory,
|
||||
VehicleCost,
|
||||
VehicleOdometerState,
|
||||
VehicleUserRating,
|
||||
GbCatalogDiscovery,
|
||||
)
|
||||
|
||||
from .external_reference import ExternalReferenceLibrary
|
||||
from .external_reference_queue import ExternalReferenceQueue
|
||||
from .asset import (
|
||||
Asset,
|
||||
AssetCatalog,
|
||||
AssetCost,
|
||||
AssetEvent,
|
||||
AssetFinancials,
|
||||
AssetTelemetry,
|
||||
AssetReview,
|
||||
ExchangeRate,
|
||||
CatalogDiscovery,
|
||||
VehicleOwnership,
|
||||
)
|
||||
|
||||
from .history import AuditLog, LogSeverity
|
||||
|
||||
# --- ÚJ MOTOROS SPECIFIKÁCIÓ MODELL BEEMELÉSE ---
|
||||
from .motorcycle_specs import MotorcycleSpecs
|
||||
|
||||
__all__ = [
|
||||
"VehicleModelDefinition",
|
||||
"VehicleType",
|
||||
"FeatureDefinition",
|
||||
"ModelFeatureMap",
|
||||
"CostCategory",
|
||||
"VehicleCost",
|
||||
"VehicleOdometerState",
|
||||
"VehicleUserRating",
|
||||
"GbCatalogDiscovery",
|
||||
"ExternalReferenceLibrary",
|
||||
"ExternalReferenceQueue",
|
||||
"Asset",
|
||||
"AssetCatalog",
|
||||
"AssetCost",
|
||||
"AssetEvent",
|
||||
"AssetFinancials",
|
||||
"AssetTelemetry",
|
||||
"AssetReview",
|
||||
"ExchangeRate",
|
||||
"CatalogDiscovery",
|
||||
"VehicleOwnership",
|
||||
"AuditLog",
|
||||
"LogSeverity",
|
||||
# --- EXPORT LISTA KIEGÉSZÍTÉSE ---
|
||||
"MotorcycleSpecs",
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/asset.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/vehicle/asset.py
|
||||
from __future__ import annotations
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
@@ -80,6 +80,12 @@ class Asset(Base):
|
||||
assignments: Mapped[List["AssetAssignment"]] = relationship("AssetAssignment", back_populates="asset")
|
||||
ownership_history: Mapped[List["VehicleOwnership"]] = relationship("VehicleOwnership", back_populates="asset")
|
||||
|
||||
# --- COMPUTED PROPERTIES (for Pydantic schema compatibility) ---
|
||||
@property
|
||||
def is_verified(self) -> bool:
|
||||
"""Always False for now, as verification is not yet implemented."""
|
||||
return False
|
||||
|
||||
class AssetFinancials(Base):
|
||||
""" I. Beszerzés és IV. Értékcsökkenés (Amortizáció). """
|
||||
__tablename__ = "asset_financials"
|
||||
36
backend/app/models/vehicle/external_reference.py
Normal file
36
backend/app/models/vehicle/external_reference.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/vehicle/external_reference.py
|
||||
from sqlalchemy import Column, Integer, String, JSON, DateTime, UniqueConstraint, ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from app.database import Base
|
||||
|
||||
class ExternalReferenceLibrary(Base):
|
||||
__tablename__ = "external_reference_library"
|
||||
__table_args__ = (
|
||||
UniqueConstraint('source_url', name='_source_url_uc'),
|
||||
{"schema": "vehicle"}
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
source_name = Column(String(50), default="auto-data.net") # Később jöhet más forrás is (motorokhoz/kamionokhoz)
|
||||
make = Column(String(100), index=True)
|
||||
model = Column(String(100), index=True)
|
||||
generation = Column(String(255))
|
||||
modification = Column(String(255))
|
||||
year_from = Column(Integer)
|
||||
year_to = Column(Integer, nullable=True)
|
||||
power_kw = Column(Integer, index=True)
|
||||
engine_cc = Column(Integer, index=True)
|
||||
category = Column(String(20), default='car', index=True) # ÚJ
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Minden egyéb technikai adat (olaj, gumi, fogyasztás stb.) ide megy
|
||||
specifications = Column(JSON, default={})
|
||||
|
||||
source_url = Column(String(500), unique=True)
|
||||
last_scraped_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
pipeline_status = Column(String(30), default='pending_enrich', index=True)
|
||||
matched_vmd_id = Column(Integer, ForeignKey('vehicle.vehicle_model_definitions.id'), nullable=True, index=True)
|
||||
|
||||
# Biztosítjuk, hogy ne legyen duplikáció azonos linkről
|
||||
|
||||
33
backend/app/models/vehicle/external_reference_queue.py
Normal file
33
backend/app/models/vehicle/external_reference_queue.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/vehicle/external_reference_queue.py
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, text
|
||||
from sqlalchemy.sql import func
|
||||
from app.database import Base
|
||||
|
||||
class ExternalReferenceQueue(Base):
|
||||
__tablename__ = "auto_data_crawler_queue"
|
||||
__table_args__ = {"schema": "vehicle"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
url = Column(String(500), unique=True, nullable=False)
|
||||
|
||||
# Szintek: 'brand', 'model', 'generation', 'engine'
|
||||
level = Column(String(20), nullable=False, index=True)
|
||||
|
||||
# Kategóriák
|
||||
category = Column(String(20), default='car', index=True)
|
||||
|
||||
# Szülő azonosító (pl. a modell tudja, melyik márkához tartozik)
|
||||
parent_id = Column(Integer, nullable=True)
|
||||
|
||||
# Megjelenítési név (pl. "Audi", "A3 Sportback")
|
||||
name = Column(String(255))
|
||||
|
||||
# Állapot: 'pending', 'processing', 'completed', 'error'
|
||||
status = Column(String(20), default='pending', index=True)
|
||||
|
||||
# Hibakezeléshez
|
||||
error_msg = Column(String(1000), nullable=True)
|
||||
retry_count = Column(Integer, default=0)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/history.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/vehicle/history.py
|
||||
import uuid
|
||||
import enum
|
||||
from datetime import datetime, date
|
||||
35
backend/app/models/vehicle/motorcycle_specs.py
Normal file
35
backend/app/models/vehicle/motorcycle_specs.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from sqlalchemy import Column, Integer, Text, ForeignKey, DateTime
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.sql import func
|
||||
from app.database import Base
|
||||
|
||||
class MotorcycleSpecs(Base):
|
||||
"""
|
||||
Gondolatmenet: Ez a modell reprezentálja a motorok végleges technikai adatait.
|
||||
A JSONB mező lehetővé teszi, hogy az AutoEvolution-ról lekerülő összes változatos
|
||||
adatot (hengerűrtartalom, nyomaték, hűtés, stb.) sémakötöttség nélkül tároljuk.
|
||||
"""
|
||||
__tablename__ = "motorcycle_specs"
|
||||
__table_args__ = {"schema": "vehicle"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# Kapcsolat a crawler várólistájával
|
||||
crawler_id = Column(
|
||||
Integer,
|
||||
ForeignKey("vehicle.auto_data_crawler_queue.id", ondelete="CASCADE"),
|
||||
unique=True,
|
||||
nullable=False
|
||||
)
|
||||
|
||||
full_name = Column(Text, nullable=False)
|
||||
url = Column(Text)
|
||||
|
||||
# A lényeg: ide kerül minden technikai adat kulcs-érték párban
|
||||
raw_data = Column(JSONB, nullable=False)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
def __repr__(self):
|
||||
return f"<MotorcycleSpecs(name='{self.full_name}', crawler_id={self.crawler_id})>"
|
||||
@@ -1,4 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/vehicle.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/vehicle/vehicle.py
|
||||
"""
|
||||
TCO (Total Cost of Ownership) alapmodelljei a 'vehicle' sémában.
|
||||
- CostCategory: Standardizált költségkategóriák hierarchiája
|
||||
@@ -189,4 +189,15 @@ class VehicleUserRating(Base):
|
||||
def average_score(self) -> float:
|
||||
"""Számított átlagpontszám a 4 dimenzióból."""
|
||||
scores = [self.driving_experience, self.reliability, self.comfort, self.consumption_satisfaction]
|
||||
return sum(scores) / 4.0
|
||||
return sum(scores) / 4.0
|
||||
|
||||
|
||||
class GbCatalogDiscovery(Base):
|
||||
__tablename__ = "gb_catalog_discovery"
|
||||
__table_args__ = {"schema": "vehicle"}
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
vrm: Mapped[str] = mapped_column(String(20), unique=True)
|
||||
make: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
model: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(20), default='pending')
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
@@ -1,15 +1,19 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/models/vehicle_definitions.py
|
||||
# /opt/docker/dev/service_finder/backend/app/models/vehicle/vehicle_definitions.py
|
||||
from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from typing import Optional, List, TYPE_CHECKING
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
class VehicleType(Base):
|
||||
""" Jármű kategóriák (pl. Személyautó, Motorkerékpár, Teherautó, Hajó) """
|
||||
__tablename__ = "vehicle_types"
|
||||
@@ -42,109 +46,100 @@ class FeatureDefinition(Base):
|
||||
|
||||
|
||||
class VehicleModelDefinition(Base):
|
||||
market: Mapped[str] = mapped_column(String(20), server_default=text("'GLOBAL'"), index=True)
|
||||
"""
|
||||
Robot v1.1.0 Multi-Tier MDM Master Adattábla.
|
||||
Az ökoszisztéma technikai igazságforrása.
|
||||
"""
|
||||
__tablename__ = "vehicle_model_definitions"
|
||||
__table_args__ = {"schema": "vehicle"}
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
||||
market: Mapped[str] = mapped_column(String(20), server_default=text("'EU'"), index=True) # GLOBÁLIS helyett EU az alap
|
||||
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)
|
||||
marketing_name: Mapped[str] = mapped_column(String(255), index=True)
|
||||
official_marketing_name: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
|
||||
# --- ROBOT LOGIKAI MEZŐK (JAVÍTVA 2.0 STÍLUSBAN) ---
|
||||
# --- ROBOT LOGIKAI MEZŐK ---
|
||||
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
|
||||
normalized_name: Mapped[str] = mapped_column(String(255), index=True) # EZT KÖTELEZŐVÉ TETTÜK
|
||||
marketing_name_aliases: Mapped[dict] = mapped_column(JSONB, server_default=text("'[]'::jsonb"))
|
||||
engine_code: Mapped[Optional[str]] = mapped_column(String(50), index=True)
|
||||
|
||||
# --- 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)
|
||||
technical_code: Mapped[str] = mapped_column(String(100), index=True, server_default=text("'UNKNOWN'"))
|
||||
variant_code: Mapped[str] = mapped_column(String(100), index=True, server_default=text("'UNKNOWN'"))
|
||||
version_code: Mapped[str] = mapped_column(String(100), index=True, server_default=text("'UNKNOWN'"))
|
||||
|
||||
# --- Ú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)
|
||||
|
||||
# --- MŰSZAKI MEZŐK ---
|
||||
type_approval_number: Mapped[Optional[str]] = mapped_column(String(100), index=True)
|
||||
seats: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
width: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
wheelbase: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
list_price: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
max_speed: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
towing_weight_unbraked: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
towing_weight_braked: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
fuel_consumption_combined: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), server_default=text("0.0"))
|
||||
co2_emissions_combined: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
|
||||
# --- SPECIFIKÁCIÓK ---
|
||||
vehicle_type_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("vehicle.vehicle_types.id"))
|
||||
vehicle_class: Mapped[Optional[str]] = mapped_column(String(50), index=True)
|
||||
body_type: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
fuel_type: Mapped[Optional[str]] = mapped_column(String(50), index=True)
|
||||
|
||||
engine_capacity: Mapped[int] = mapped_column(Integer, default=0, index=True)
|
||||
power_kw: Mapped[int] = mapped_column(Integer, default=0, index=True)
|
||||
fuel_type: Mapped[str] = mapped_column(String(50), index=True, server_default=text("'Unknown'"))
|
||||
trim_level: Mapped[str] = mapped_column(String(100), server_default=text("''"))
|
||||
|
||||
engine_capacity: Mapped[int] = mapped_column(Integer, server_default=text("0"), index=True)
|
||||
power_kw: Mapped[int] = mapped_column(Integer, server_default=text("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)
|
||||
curb_weight: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
max_weight: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
euro_classification: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
doors: Mapped[Optional[int]] = mapped_column(Integer)
|
||||
doors: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
||||
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)
|
||||
# --- É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)
|
||||
production_status: Mapped[Optional[str]] = mapped_column(String(50)) # active / discontinued
|
||||
|
||||
# Státusz szintek: unverified, research_in_progress, awaiting_ai_synthesis, gold_enriched
|
||||
production_status: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
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))
|
||||
is_manual: Mapped[bool] = mapped_column(Boolean, server_default=text("false"))
|
||||
source: Mapped[str] = mapped_column(String(100), server_default=text("'ROBOT'"))
|
||||
|
||||
# --- ADAT-KONTÉNEREK ---
|
||||
raw_search_context: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
# --- ADATOK ---
|
||||
raw_search_context: Mapped[str] = mapped_column(Text, server_default=text("''")) # JSONB helyett TEXT a keresési adatoknak!
|
||||
raw_api_data: 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
|
||||
|
||||
specifications: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
|
||||
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__ = (
|
||||
# A LEGONTOSABB SOR: Ez határozza meg, mi számít duplikációnak!
|
||||
UniqueConstraint('make', 'normalized_name', 'variant_code', 'version_code', 'fuel_type', 'market', 'year_from', name='uix_vmd_precision_v2'),
|
||||
Index('idx_vmd_lookup_fast', 'make', 'normalized_name'),
|
||||
Index('idx_vmd_engine_bridge', 'make', 'engine_code'),
|
||||
{"schema": "vehicle"}
|
||||
)
|
||||
|
||||
# KAPCSOLATOK
|
||||
# --- KAPCSOLATOK (Relationships) ---
|
||||
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 neve
|
||||
variants: Mapped[List["AssetCatalog"]] = relationship("AssetCatalog", back_populates="master_definition")
|
||||
|
||||
# TCO költségnapló kapcsolata
|
||||
costs: Mapped[List["VehicleCost"]] = relationship("VehicleCost", back_populates="vehicle")
|
||||
# Kilométeróra állapot kapcsolata
|
||||
odometer_state: Mapped["VehicleOdometerState"] = relationship("VehicleOdometerState", back_populates="vehicle")
|
||||
# JAVÍTÁS: Ez a sor hiányzott az API indításához!
|
||||
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):
|
||||
Reference in New Issue
Block a user