feat(T06): Verkaufsmodul + Rechtsdokumente + DATEV-Export + Sales UI

This commit is contained in:
2026-07-17 01:58:34 +02:00
parent a38d340ddc
commit 6603e411e9
33 changed files with 3481 additions and 107 deletions
+44
View File
@@ -0,0 +1,44 @@
"""Pydantic schemas for DATEV export request and response bodies."""
import uuid
from datetime import date, datetime
from decimal import Decimal
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field, model_validator
class DATEVExportCreate(BaseModel):
"""POST /api/v1/datev/export request body."""
start_date: date = Field(..., description="Start date of the export range")
end_date: date = Field(..., description="End date of the export range (inclusive)")
@model_validator(mode="after")
def validate_date_range(self) -> "DATEVExportCreate":
"""Ensure start_date is not after end_date."""
if self.start_date > self.end_date:
raise ValueError("start_date must not be after end_date")
return self
class DATEVExportResponse(BaseModel):
"""DATEV export response schema."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
start_date: date
end_date: date
file_path: Optional[str] = None
total_amount: Decimal
created_at: Optional[datetime] = None
class DATEVExportListResponse(BaseModel):
"""Paginated DATEV export list response."""
items: list[DATEVExportResponse]
total: int
page: int
page_size: int
+107
View File
@@ -0,0 +1,107 @@
"""Pydantic schemas for sale-related request and response bodies."""
import uuid
from datetime import date, datetime
from decimal import Decimal
from typing import Literal, Optional
from pydantic import BaseModel, ConfigDict, Field
SALE_STATUSES = Literal["draft", "completed", "cancelled"]
class SaleCreate(BaseModel):
"""POST /api/v1/sales request body."""
vehicle_id: uuid.UUID
buyer_contact_id: uuid.UUID
seller_contact_id: Optional[uuid.UUID] = None
sale_price: Decimal = Field(..., ge=0)
sale_date: Optional[date] = None
status: SALE_STATUSES = "draft"
is_gwg: bool = False
class SaleUpdate(BaseModel):
"""PUT /api/v1/sales/:id request body (all fields optional)."""
sale_price: Optional[Decimal] = Field(None, ge=0)
sale_date: Optional[date] = None
status: Optional[SALE_STATUSES] = None
is_gwg: Optional[bool] = None
seller_contact_id: Optional[uuid.UUID] = None
class VehicleNested(BaseModel):
"""Nested vehicle info in sale response."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
make: str
model: str
fin: str
vehicle_type: str
availability: str
price: Decimal
class ContactNested(BaseModel):
"""Nested contact info in sale response."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
company_name: str
address_city: Optional[str] = None
address_country: str
vat_id: Optional[str] = None
email: Optional[str] = None
phone: Optional[str] = None
class SaleResponse(BaseModel):
"""Sale response schema with nested vehicle and contacts."""
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
vehicle_id: uuid.UUID
buyer_contact_id: uuid.UUID
seller_contact_id: Optional[uuid.UUID] = None
sale_price: Decimal
sale_date: date
status: str
is_gwg: bool
contract_pdf_path: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
vehicle: Optional[VehicleNested] = None
buyer: Optional[ContactNested] = None
seller: Optional[ContactNested] = None
class SaleListResponse(BaseModel):
"""Paginated sale list response."""
items: list[SaleResponse]
total: int
page: int
page_size: int
class ContractResponse(BaseModel):
"""Response for contract PDF generation/download."""
sale_id: uuid.UUID
contract_pdf_path: str
message: str = "Contract generated"
class UstIdVerifyResponse(BaseModel):
"""Response for USt-IdNr. verification."""
verified: bool
message: str
vat_id: Optional[str] = None