"""ORM model for invoice / proforma attachments (table `quote_attachments`). File bytes live in MinIO object storage; this table is the metadata record (``object_key`` points at the object inside the PROCUREMENT_MINIO_BUCKET). Multiple attachments per quote per kind are allowed (multi-file contract, no unique constraint on (quote_id, kind)). """ import uuid from datetime import datetime from sqlalchemy import BigInteger, CheckConstraint, DateTime, ForeignKey, Text, func from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.base import Base from app.models.catalog import _timestamptz, _uuid_pk from app.utils.serialization import to_iso class QuoteAttachment(Base): __tablename__ = "quote_attachments" __mapper_args__ = {"eager_defaults": True} # fetch onupdate=func.now() via RETURNING __table_args__ = ( CheckConstraint("kind IN ('invoice', 'proforma')", name="ck_quote_attachments_kind"), ) id: Mapped[uuid.UUID] = _uuid_pk() quote_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("quotes.id", ondelete="CASCADE"), nullable=False) kind: Mapped[str] = mapped_column(Text, nullable=False) # "invoice" | "proforma" file_name: Mapped[str] = mapped_column(Text, nullable=False) mime_type: Mapped[str] = mapped_column(Text, nullable=False) size_bytes: Mapped[int] = mapped_column(BigInteger, nullable=False) object_key: Mapped[str] = mapped_column(Text, nullable=False) # MinIO key inside the bucket uploaded_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) created_at: Mapped[datetime] = _timestamptz() updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() ) quote: Mapped["Quote"] = relationship(lazy="raise") def to_dict(self) -> dict: return { "id": str(self.id), "quote_id": str(self.quote_id), "kind": self.kind, "file_name": self.file_name, "mime_type": self.mime_type, "size_bytes": self.size_bytes, "uploaded_at": to_iso(self.uploaded_at), }