2026-07-17 01:58:34 +02:00
|
|
|
"""SQLAlchemy model for sales (Verkauf)."""
|
|
|
|
|
|
|
|
|
|
import enum
|
|
|
|
|
import uuid
|
|
|
|
|
from datetime import date, datetime
|
|
|
|
|
from decimal import Decimal
|
2026-07-17 21:16:38 +02:00
|
|
|
from typing import TYPE_CHECKING
|
2026-07-17 01:58:34 +02:00
|
|
|
|
|
|
|
|
from sqlalchemy import (
|
|
|
|
|
Boolean,
|
|
|
|
|
CheckConstraint,
|
|
|
|
|
Date,
|
|
|
|
|
DateTime,
|
|
|
|
|
ForeignKey,
|
|
|
|
|
Numeric,
|
|
|
|
|
String,
|
|
|
|
|
func,
|
|
|
|
|
)
|
|
|
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
|
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
|
|
|
|
|
|
from app.database import Base
|
|
|
|
|
|
2026-07-17 21:16:38 +02:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from app.models.contact import Contact
|
|
|
|
|
from app.models.vehicle import Vehicle
|
|
|
|
|
|
2026-07-17 01:58:34 +02:00
|
|
|
|
|
|
|
|
class SaleStatus(str, enum.Enum):
|
|
|
|
|
draft = "draft"
|
|
|
|
|
completed = "completed"
|
|
|
|
|
cancelled = "cancelled"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Sale(Base):
|
|
|
|
|
"""Sale entity linking a vehicle to a buyer (and optionally a seller)."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "sales"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
CheckConstraint(
|
|
|
|
|
"status IN ('draft', 'completed', 'cancelled')",
|
|
|
|
|
name="ck_sales_status",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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="RESTRICT"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
index=True,
|
|
|
|
|
)
|
|
|
|
|
buyer_contact_id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
UUID(as_uuid=True),
|
|
|
|
|
ForeignKey("contacts.id", ondelete="RESTRICT"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
index=True,
|
|
|
|
|
)
|
|
|
|
|
seller_contact_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
|
|
|
UUID(as_uuid=True),
|
|
|
|
|
ForeignKey("contacts.id", ondelete="SET NULL"),
|
|
|
|
|
nullable=True,
|
|
|
|
|
index=True,
|
|
|
|
|
)
|
|
|
|
|
sale_price: Mapped[Decimal] = mapped_column(
|
|
|
|
|
Numeric(12, 2), nullable=False
|
|
|
|
|
)
|
|
|
|
|
sale_date: Mapped[date] = mapped_column(
|
|
|
|
|
Date, nullable=False, default=func.current_date()
|
|
|
|
|
)
|
|
|
|
|
status: Mapped[str] = mapped_column(
|
|
|
|
|
String(20), nullable=False, default="draft"
|
|
|
|
|
)
|
|
|
|
|
is_gwg: Mapped[bool] = mapped_column(
|
|
|
|
|
Boolean, nullable=False, default=False
|
|
|
|
|
)
|
|
|
|
|
contract_pdf_path: Mapped[str | None] = mapped_column(
|
|
|
|
|
String(500), 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(lazy="selectin")
|
|
|
|
|
buyer: Mapped["Contact"] = relationship(
|
|
|
|
|
"Contact",
|
|
|
|
|
foreign_keys=[buyer_contact_id],
|
|
|
|
|
lazy="selectin",
|
|
|
|
|
)
|
|
|
|
|
seller: Mapped["Contact | None"] = relationship(
|
|
|
|
|
"Contact",
|
|
|
|
|
foreign_keys=[seller_contact_id],
|
|
|
|
|
lazy="selectin",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
|
return f"<Sale id={self.id} vehicle_id={self.vehicle_id} status={self.status}>"
|
|
|
|
|
|
|
|
|
|
def to_dict(self) -> dict:
|
|
|
|
|
"""Serialize sale for API responses."""
|
|
|
|
|
return {
|
|
|
|
|
"id": str(self.id),
|
|
|
|
|
"vehicle_id": str(self.vehicle_id),
|
|
|
|
|
"buyer_contact_id": str(self.buyer_contact_id),
|
|
|
|
|
"seller_contact_id": (
|
|
|
|
|
str(self.seller_contact_id) if self.seller_contact_id else None
|
|
|
|
|
),
|
|
|
|
|
"sale_price": float(self.sale_price) if self.sale_price is not None else None,
|
|
|
|
|
"sale_date": self.sale_date.isoformat() if self.sale_date else None,
|
|
|
|
|
"status": self.status,
|
|
|
|
|
"is_gwg": self.is_gwg,
|
|
|
|
|
"contract_pdf_path": self.contract_pdf_path,
|
|
|
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
|
|
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
|
|
|
}
|