"""SQLAlchemy models for vehicles and mobile.de listings.""" import enum import uuid from datetime import date, datetime from decimal import Decimal from typing import TYPE_CHECKING from sqlalchemy import ( CheckConstraint, Date, DateTime, ForeignKey, Integer, Numeric, String, Text, func, ) from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base if TYPE_CHECKING: from app.models.file import File class VehicleCondition(str, enum.Enum): new = "new" used = "used" class VehicleAvailability(str, enum.Enum): available = "available" reserved = "reserved" sold = "sold" class VehicleType(str, enum.Enum): lkw = "lkw" pkw = "pkw" baumaschine = "baumaschine" stapler = "stapler" transporter = "transporter" class SyncStatus(str, enum.Enum): pending = "pending" synced = "synced" fehler = "fehler" class Vehicle(Base): """Vehicle entity for the ERP inventory.""" __tablename__ = "vehicles" __table_args__ = ( CheckConstraint("char_length(fin) = 17", name="ck_vehicles_fin_length"), CheckConstraint("condition IN ('new', 'used')", name="ck_vehicles_condition"), CheckConstraint( "availability IN ('available', 'reserved', 'sold')", name="ck_vehicles_availability", ), CheckConstraint( "vehicle_type IN ('lkw', 'pkw', 'baumaschine', 'stapler', 'transporter')", name="ck_vehicles_vehicle_type", ), CheckConstraint( "operating_hours_unit IS NULL OR operating_hours_unit IN ('h', 'min')", name="ck_vehicles_operating_hours_unit", ), ) id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, ) make: Mapped[str] = mapped_column(String(100), nullable=False) model: Mapped[str] = mapped_column(String(100), nullable=False) fin: Mapped[str] = mapped_column( String(17), unique=True, nullable=False, index=True ) year: Mapped[int | None] = mapped_column(Integer, nullable=True) first_registration: Mapped[date | None] = mapped_column(Date, nullable=True) power_kw: Mapped[int | None] = mapped_column(Integer, nullable=True) power_hp: Mapped[int | None] = mapped_column(Integer, nullable=True) fuel_type: Mapped[str | None] = mapped_column(String(50), nullable=True) transmission: Mapped[str | None] = mapped_column(String(20), nullable=True) color: Mapped[str | None] = mapped_column(String(50), nullable=True) condition: Mapped[str] = mapped_column(String(20), nullable=False, default="used") location: Mapped[str | None] = mapped_column(String(255), nullable=True) availability: Mapped[str] = mapped_column( String(20), nullable=False, default="available" ) price: Mapped[Decimal] = mapped_column(Numeric(12, 2), nullable=False) vehicle_type: Mapped[str] = mapped_column(String(20), nullable=False) lkw_type: Mapped[str | None] = mapped_column(String(50), nullable=True) machine_type: Mapped[str | None] = mapped_column(String(50), nullable=True) body_type: Mapped[str | None] = mapped_column(String(100), nullable=True) operating_hours: Mapped[Decimal | None] = mapped_column( Numeric(12, 1), nullable=True ) operating_hours_unit: Mapped[str | None] = mapped_column(String(5), nullable=True) mileage_km: Mapped[int | None] = mapped_column(Integer, nullable=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now(), ) deleted_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) mobile_de_listings: Mapped[list["MobileDeListing"]] = relationship( back_populates="vehicle", cascade="all, delete-orphan" ) files: Mapped[list["File"]] = relationship( "File", back_populates="vehicle", cascade="all, delete-orphan" ) def __repr__(self) -> str: return f"" def to_dict(self) -> dict: """Serialize vehicle for API responses.""" return { "id": str(self.id), "make": self.make, "model": self.model, "fin": self.fin, "year": self.year, "first_registration": ( self.first_registration.isoformat() if self.first_registration else None ), "power_kw": self.power_kw, "power_hp": self.power_hp, "fuel_type": self.fuel_type, "transmission": self.transmission, "color": self.color, "condition": self.condition, "location": self.location, "availability": self.availability, "price": float(self.price) if self.price is not None else None, "vehicle_type": self.vehicle_type, "lkw_type": self.lkw_type, "machine_type": self.machine_type, "body_type": self.body_type, "operating_hours": ( float(self.operating_hours) if self.operating_hours is not None else None ), "operating_hours_unit": self.operating_hours_unit, "mileage_km": self.mileage_km, "description": self.description, "created_at": (self.created_at.isoformat() if self.created_at else None), "updated_at": (self.updated_at.isoformat() if self.updated_at else None), "deleted_at": (self.deleted_at.isoformat() if self.deleted_at else None), } class MobileDeListing(Base): """mobile.de listing sync tracking for a vehicle.""" __tablename__ = "mobile_de_listings" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, ) vehicle_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("vehicles.id", ondelete="CASCADE"), nullable=False, index=True, ) ad_id: Mapped[str | None] = mapped_column(String(100), nullable=True) sync_status: Mapped[str] = mapped_column( String(20), nullable=False, default="pending" ) synced_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) error_log: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now(), ) vehicle: Mapped["Vehicle"] = relationship(back_populates="mobile_de_listings") def __repr__(self) -> str: return f"" def to_dict(self) -> dict: """Serialize listing for API responses.""" return { "id": str(self.id), "vehicle_id": str(self.vehicle_id), "ad_id": self.ad_id, "sync_status": self.sync_status, "synced_at": (self.synced_at.isoformat() if self.synced_at else None), "error_log": self.error_log, "created_at": (self.created_at.isoformat() if self.created_at else None), "updated_at": (self.updated_at.isoformat() if self.updated_at else None), }