201 lines
5.9 KiB
Python
201 lines
5.9 KiB
Python
|
|
"""SQLAlchemy models for contacts and contact persons."""
|
||
|
|
|
||
|
|
import enum
|
||
|
|
import uuid
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
from sqlalchemy import (
|
||
|
|
Boolean,
|
||
|
|
CheckConstraint,
|
||
|
|
DateTime,
|
||
|
|
ForeignKey,
|
||
|
|
String,
|
||
|
|
func,
|
||
|
|
)
|
||
|
|
from sqlalchemy.dialects.postgresql import UUID
|
||
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||
|
|
|
||
|
|
from app.database import Base
|
||
|
|
|
||
|
|
|
||
|
|
class ContactRole(str, enum.Enum):
|
||
|
|
kaeufer = "kaeufer"
|
||
|
|
verkaeufer = "verkaeufer"
|
||
|
|
beide = "beide"
|
||
|
|
|
||
|
|
|
||
|
|
class VatIdStatus(str, enum.Enum):
|
||
|
|
ungeprueft = "ungeprueft"
|
||
|
|
geprueft = "geprueft"
|
||
|
|
ungueltig = "ungueltig"
|
||
|
|
manuell_bestaetigt = "manuell_bestaetigt"
|
||
|
|
|
||
|
|
|
||
|
|
class Contact(Base):
|
||
|
|
"""Contact entity representing a company or private contact."""
|
||
|
|
|
||
|
|
__tablename__ = "contacts"
|
||
|
|
__table_args__ = (
|
||
|
|
CheckConstraint(
|
||
|
|
"role IN ('kaeufer', 'verkaeufer', 'beide')",
|
||
|
|
name="ck_contacts_role",
|
||
|
|
),
|
||
|
|
CheckConstraint(
|
||
|
|
"vat_id_status IN ('ungeprueft', 'geprueft', 'ungueltig', 'manuell_bestaetigt')",
|
||
|
|
name="ck_contacts_vat_id_status",
|
||
|
|
),
|
||
|
|
CheckConstraint(
|
||
|
|
"char_length(address_country) = 2",
|
||
|
|
name="ck_contacts_country_alpha2",
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
UUID(as_uuid=True),
|
||
|
|
primary_key=True,
|
||
|
|
default=uuid.uuid4,
|
||
|
|
)
|
||
|
|
company_name: Mapped[str] = mapped_column(
|
||
|
|
String(255), nullable=False, index=True,
|
||
|
|
)
|
||
|
|
legal_form: Mapped[str | None] = mapped_column(
|
||
|
|
String(50), nullable=True,
|
||
|
|
)
|
||
|
|
address_street: Mapped[str | None] = mapped_column(
|
||
|
|
String(255), nullable=True,
|
||
|
|
)
|
||
|
|
address_zip: Mapped[str | None] = mapped_column(
|
||
|
|
String(10), nullable=True,
|
||
|
|
)
|
||
|
|
address_city: Mapped[str | None] = mapped_column(
|
||
|
|
String(100), nullable=True,
|
||
|
|
)
|
||
|
|
address_country: Mapped[str] = mapped_column(
|
||
|
|
String(2), nullable=False, default="DE", index=True,
|
||
|
|
)
|
||
|
|
vat_id: Mapped[str | None] = mapped_column(
|
||
|
|
String(20), nullable=True,
|
||
|
|
)
|
||
|
|
phone: Mapped[str | None] = mapped_column(
|
||
|
|
String(50), nullable=True,
|
||
|
|
)
|
||
|
|
email: Mapped[str | None] = mapped_column(
|
||
|
|
String(255), nullable=True,
|
||
|
|
)
|
||
|
|
website: Mapped[str | None] = mapped_column(
|
||
|
|
String(255), nullable=True,
|
||
|
|
)
|
||
|
|
role: Mapped[str] = mapped_column(
|
||
|
|
String(20), nullable=False, index=True,
|
||
|
|
)
|
||
|
|
vat_id_status: Mapped[str] = mapped_column(
|
||
|
|
String(20), nullable=False, default="ungeprueft",
|
||
|
|
)
|
||
|
|
is_private: Mapped[bool] = mapped_column(
|
||
|
|
Boolean, nullable=False, default=False,
|
||
|
|
)
|
||
|
|
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(),
|
||
|
|
)
|
||
|
|
deleted_at: Mapped[datetime | None] = mapped_column(
|
||
|
|
DateTime(timezone=True), nullable=True, index=True,
|
||
|
|
)
|
||
|
|
|
||
|
|
contact_persons: Mapped[list["ContactPerson"]] = relationship(
|
||
|
|
back_populates="contact",
|
||
|
|
cascade="all, delete-orphan",
|
||
|
|
lazy="selectin",
|
||
|
|
)
|
||
|
|
|
||
|
|
def __repr__(self) -> str:
|
||
|
|
return f"<Contact id={self.id} company_name={self.company_name}>"
|
||
|
|
|
||
|
|
def to_dict(self) -> dict:
|
||
|
|
"""Serialize contact for API responses."""
|
||
|
|
return {
|
||
|
|
"id": str(self.id),
|
||
|
|
"company_name": self.company_name,
|
||
|
|
"legal_form": self.legal_form,
|
||
|
|
"address_street": self.address_street,
|
||
|
|
"address_zip": self.address_zip,
|
||
|
|
"address_city": self.address_city,
|
||
|
|
"address_country": self.address_country,
|
||
|
|
"vat_id": self.vat_id,
|
||
|
|
"phone": self.phone,
|
||
|
|
"email": self.email,
|
||
|
|
"website": self.website,
|
||
|
|
"role": self.role,
|
||
|
|
"vat_id_status": self.vat_id_status,
|
||
|
|
"is_private": self.is_private,
|
||
|
|
"created_at": (
|
||
|
|
self.created_at.isoformat() if self.created_at else None
|
||
|
|
),
|
||
|
|
"updated_at": (
|
||
|
|
self.updated_at.isoformat() if self.updated_at else None
|
||
|
|
),
|
||
|
|
"deleted_at": (
|
||
|
|
self.deleted_at.isoformat() if self.deleted_at else None
|
||
|
|
),
|
||
|
|
"contact_persons": [
|
||
|
|
p.to_dict() for p in (self.contact_persons or [])
|
||
|
|
],
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class ContactPerson(Base):
|
||
|
|
"""Contact person associated with a contact (company)."""
|
||
|
|
|
||
|
|
__tablename__ = "contact_persons"
|
||
|
|
|
||
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
UUID(as_uuid=True),
|
||
|
|
primary_key=True,
|
||
|
|
default=uuid.uuid4,
|
||
|
|
)
|
||
|
|
contact_id: Mapped[uuid.UUID] = mapped_column(
|
||
|
|
UUID(as_uuid=True),
|
||
|
|
ForeignKey("contacts.id", ondelete="CASCADE"),
|
||
|
|
nullable=False,
|
||
|
|
index=True,
|
||
|
|
)
|
||
|
|
name: Mapped[str] = mapped_column(
|
||
|
|
String(255), nullable=False,
|
||
|
|
)
|
||
|
|
function: Mapped[str | None] = mapped_column(
|
||
|
|
String(100), nullable=True,
|
||
|
|
)
|
||
|
|
phone: Mapped[str | None] = mapped_column(
|
||
|
|
String(50), nullable=True,
|
||
|
|
)
|
||
|
|
email: Mapped[str | None] = mapped_column(
|
||
|
|
String(255), nullable=True,
|
||
|
|
)
|
||
|
|
created_at: Mapped[datetime] = mapped_column(
|
||
|
|
DateTime(timezone=True), nullable=False, server_default=func.now(),
|
||
|
|
)
|
||
|
|
|
||
|
|
contact: Mapped["Contact"] = relationship(back_populates="contact_persons")
|
||
|
|
|
||
|
|
def __repr__(self) -> str:
|
||
|
|
return f"<ContactPerson id={self.id} name={self.name}>"
|
||
|
|
|
||
|
|
def to_dict(self) -> dict:
|
||
|
|
"""Serialize contact person for API responses."""
|
||
|
|
return {
|
||
|
|
"id": str(self.id),
|
||
|
|
"contact_id": str(self.contact_id),
|
||
|
|
"name": self.name,
|
||
|
|
"function": self.function,
|
||
|
|
"phone": self.phone,
|
||
|
|
"email": self.email,
|
||
|
|
"created_at": (
|
||
|
|
self.created_at.isoformat() if self.created_at else None
|
||
|
|
),
|
||
|
|
}
|