148 lines
4.8 KiB
Python
148 lines
4.8 KiB
Python
|
|
"""Pydantic schemas for contact-related request and response bodies."""
|
||
|
|
|
||
|
|
import uuid
|
||
|
|
from datetime import datetime
|
||
|
|
from typing import Literal, Optional
|
||
|
|
|
||
|
|
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
|
||
|
|
|
||
|
|
from app.utils.ust_validation import validate_vat_id
|
||
|
|
|
||
|
|
|
||
|
|
ContactRole = Literal["kaeufer", "verkaeufer", "beide"]
|
||
|
|
VatIdStatus = Literal["ungeprueft", "geprueft", "ungueltig", "manuell_bestaetigt"]
|
||
|
|
|
||
|
|
|
||
|
|
class ContactPersonBase(BaseModel):
|
||
|
|
"""Base contact person fields."""
|
||
|
|
|
||
|
|
name: str = Field(..., min_length=1, max_length=255)
|
||
|
|
function: Optional[str] = Field(None, max_length=100)
|
||
|
|
phone: Optional[str] = Field(None, max_length=50)
|
||
|
|
email: Optional[str] = Field(None, max_length=255)
|
||
|
|
|
||
|
|
|
||
|
|
class ContactPersonCreate(ContactPersonBase):
|
||
|
|
"""POST /api/v1/contacts/:id/persons request body."""
|
||
|
|
pass
|
||
|
|
|
||
|
|
|
||
|
|
class ContactPersonResponse(ContactPersonBase):
|
||
|
|
"""Contact person response schema."""
|
||
|
|
|
||
|
|
model_config = ConfigDict(from_attributes=True)
|
||
|
|
|
||
|
|
id: uuid.UUID
|
||
|
|
contact_id: uuid.UUID
|
||
|
|
created_at: Optional[datetime] = None
|
||
|
|
|
||
|
|
|
||
|
|
class ContactBase(BaseModel):
|
||
|
|
"""Base contact fields shared across schemas."""
|
||
|
|
|
||
|
|
company_name: str = Field(..., min_length=1, max_length=255)
|
||
|
|
legal_form: Optional[str] = Field(None, max_length=50)
|
||
|
|
address_street: Optional[str] = Field(None, max_length=255)
|
||
|
|
address_zip: Optional[str] = Field(None, max_length=10)
|
||
|
|
address_city: Optional[str] = Field(None, max_length=100)
|
||
|
|
address_country: str = Field("DE", min_length=2, max_length=2)
|
||
|
|
vat_id: Optional[str] = Field(None, max_length=20)
|
||
|
|
phone: Optional[str] = Field(None, max_length=50)
|
||
|
|
email: Optional[str] = Field(None, max_length=255)
|
||
|
|
website: Optional[str] = Field(None, max_length=255)
|
||
|
|
role: ContactRole
|
||
|
|
is_private: bool = False
|
||
|
|
|
||
|
|
@field_validator("vat_id")
|
||
|
|
@classmethod
|
||
|
|
def validate_vat_id_format(cls, v: str | None) -> str | None:
|
||
|
|
"""Validate VAT ID format if provided."""
|
||
|
|
if v is None or v == "":
|
||
|
|
return None
|
||
|
|
if not validate_vat_id(v):
|
||
|
|
raise ValueError(f"Invalid VAT ID format: '{v}'")
|
||
|
|
return v.strip().upper().replace(" ", "")
|
||
|
|
|
||
|
|
@field_validator("address_country")
|
||
|
|
@classmethod
|
||
|
|
def validate_country_code(cls, v: str) -> str:
|
||
|
|
"""Normalise country code to uppercase."""
|
||
|
|
return v.upper()
|
||
|
|
|
||
|
|
|
||
|
|
class ContactCreate(ContactBase):
|
||
|
|
"""POST /api/v1/contacts request body."""
|
||
|
|
|
||
|
|
contact_persons: list[ContactPersonCreate] = Field(
|
||
|
|
default_factory=list, description="Contact persons to create with the contact"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class ContactUpdate(BaseModel):
|
||
|
|
"""PUT /api/v1/contacts/:id request body (all fields optional)."""
|
||
|
|
|
||
|
|
company_name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||
|
|
legal_form: Optional[str] = Field(None, max_length=50)
|
||
|
|
address_street: Optional[str] = Field(None, max_length=255)
|
||
|
|
address_zip: Optional[str] = Field(None, max_length=10)
|
||
|
|
address_city: Optional[str] = Field(None, max_length=100)
|
||
|
|
address_country: Optional[str] = Field(None, min_length=2, max_length=2)
|
||
|
|
vat_id: Optional[str] = Field(None, max_length=20)
|
||
|
|
phone: Optional[str] = Field(None, max_length=50)
|
||
|
|
email: Optional[str] = Field(None, max_length=255)
|
||
|
|
website: Optional[str] = Field(None, max_length=255)
|
||
|
|
role: Optional[ContactRole] = None
|
||
|
|
is_private: Optional[bool] = None
|
||
|
|
|
||
|
|
@field_validator("vat_id")
|
||
|
|
@classmethod
|
||
|
|
def validate_vat_id_format(cls, v: str | None) -> str | None:
|
||
|
|
"""Validate VAT ID format if provided."""
|
||
|
|
if v is None or v == "":
|
||
|
|
return None
|
||
|
|
if not validate_vat_id(v):
|
||
|
|
raise ValueError(f"Invalid VAT ID format: '{v}'")
|
||
|
|
return v.strip().upper().replace(" ", "")
|
||
|
|
|
||
|
|
@field_validator("address_country")
|
||
|
|
@classmethod
|
||
|
|
def validate_country_code(cls, v: str | None) -> str | None:
|
||
|
|
"""Normalise country code to uppercase."""
|
||
|
|
if v is not None:
|
||
|
|
return v.upper()
|
||
|
|
return v
|
||
|
|
|
||
|
|
|
||
|
|
class ContactResponse(BaseModel):
|
||
|
|
"""Contact response schema."""
|
||
|
|
|
||
|
|
model_config = ConfigDict(from_attributes=True)
|
||
|
|
|
||
|
|
id: uuid.UUID
|
||
|
|
company_name: str
|
||
|
|
legal_form: Optional[str] = None
|
||
|
|
address_street: Optional[str] = None
|
||
|
|
address_zip: Optional[str] = None
|
||
|
|
address_city: Optional[str] = None
|
||
|
|
address_country: str
|
||
|
|
vat_id: Optional[str] = None
|
||
|
|
phone: Optional[str] = None
|
||
|
|
email: Optional[str] = None
|
||
|
|
website: Optional[str] = None
|
||
|
|
role: str
|
||
|
|
vat_id_status: str = "ungeprueft"
|
||
|
|
is_private: bool = False
|
||
|
|
created_at: Optional[datetime] = None
|
||
|
|
updated_at: Optional[datetime] = None
|
||
|
|
deleted_at: Optional[datetime] = None
|
||
|
|
contact_persons: list[ContactPersonResponse] = Field(default_factory=list)
|
||
|
|
|
||
|
|
|
||
|
|
class ContactListResponse(BaseModel):
|
||
|
|
"""Paginated contact list response."""
|
||
|
|
|
||
|
|
items: list[ContactResponse]
|
||
|
|
total: int
|
||
|
|
page: int
|
||
|
|
page_size: int
|