Files
erp-nutzfahrzeuge/backend/app/models/sale.py
T
Leopoldadmin 341d0c6f38 fix: resolve all ruff and ESLint lint errors
Backend (ruff):
- F821: Add TYPE_CHECKING imports for Vehicle, Contact, File in models
  (file.py, retouch.py, sale.py, vehicle.py)
- E741: Rename ambiguous variable  to /
  (copilot_service.py, price_compare_service.py, openrouter.py)
- F841: Remove unused variable  in sale_service.py

Frontend (ESLint):
- react/no-unescaped-entities: Escape quotes in ContractPreview.tsx
- @next/next/no-img-element: Replace <img> with <Image> from next/image
  (FileGallery.tsx, FileList.tsx, FilePreview.tsx, BeforeAfterSlider.tsx)

Tests: 392 backend passed, 112 frontend passed, next build successful
2026-07-17 21:16:38 +02:00

126 lines
3.7 KiB
Python

"""SQLAlchemy model for sales (Verkauf)."""
import enum
import uuid
from datetime import date, datetime
from decimal import Decimal
from typing import TYPE_CHECKING
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
if TYPE_CHECKING:
from app.models.contact import Contact
from app.models.vehicle import Vehicle
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,
}