feat(T06): Verkaufsmodul + Rechtsdokumente + DATEV-Export + Sales UI
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
"""SQLAlchemy models package.
|
||||
|
||||
Import all models here so they register with Base.metadata
|
||||
before create_all/drop_all is called.
|
||||
"""
|
||||
|
||||
from app.models.contact import Contact, ContactPerson
|
||||
from app.models.datev_export import DATEVExport
|
||||
from app.models.file import File
|
||||
from app.models.ocr_result import OCRResult
|
||||
from app.models.sale import Sale, SaleStatus
|
||||
from app.models.user import User, UserRole
|
||||
from app.models.vehicle import MobileDeListing, Vehicle
|
||||
|
||||
__all__ = [
|
||||
"Contact",
|
||||
"ContactPerson",
|
||||
"DATEVExport",
|
||||
"File",
|
||||
"OCRResult",
|
||||
"Sale",
|
||||
"SaleStatus",
|
||||
"User",
|
||||
"UserRole",
|
||||
"Vehicle",
|
||||
"MobileDeListing",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""SQLAlchemy model for DATEV export records."""
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import Date, DateTime, Numeric, String, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class DATEVExport(Base):
|
||||
"""DATEV export record tracking CSV exports for accounting."""
|
||||
|
||||
__tablename__ = "datev_exports"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
default=uuid.uuid4,
|
||||
)
|
||||
start_date: Mapped[date] = mapped_column(Date, nullable=False)
|
||||
end_date: Mapped[date] = mapped_column(Date, nullable=False)
|
||||
file_path: Mapped[str | None] = mapped_column(
|
||||
String(500), nullable=True
|
||||
)
|
||||
total_amount: Mapped[Decimal] = mapped_column(
|
||||
Numeric(14, 2), nullable=False, default=0
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<DATEVExport id={self.id} start={self.start_date} end={self.end_date}>"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Serialize DATEV export for API responses."""
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"start_date": self.start_date.isoformat() if self.start_date else None,
|
||||
"end_date": self.end_date.isoformat() if self.end_date else None,
|
||||
"file_path": self.file_path,
|
||||
"total_amount": (
|
||||
float(self.total_amount) if self.total_amount is not None else None
|
||||
),
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
"""SQLAlchemy model for sales (Verkauf)."""
|
||||
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
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
|
||||
|
||||
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user