"""Report Template and Report Instance models for the Report Generator plugin.""" from __future__ import annotations import uuid from sqlalchemy import Boolean, ForeignKey, Index, Integer, String, Text from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.dialects.postgresql import UUID as PGUUID from sqlalchemy.orm import Mapped, mapped_column from app.core.db import Base, TenantMixin from app.models.owned_mixin import OwnedMixin class ReportTemplate(Base, TenantMixin, OwnedMixin): """Report template entity — Jinja2 or SQL template, tenant-scoped, soft-deletable.""" __tablename__ = "report_templates" __table_args__ = ( Index("ix_report_templates_tenant", "tenant_id"), Index("ix_report_templates_name", "name"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False, default="") template_type: Mapped[str] = mapped_column( String(50), nullable=False, default="jinja2" ) content: Mapped[str] = mapped_column(Text, nullable=False) output_format: Mapped[str] = mapped_column( String(50), nullable=False, default="csv" ) created_by: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False) folder_id: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True) class ReportInstance(Base, TenantMixin, OwnedMixin): """Report instance entity — a generated report, tenant-scoped.""" __tablename__ = "report_instances" __table_args__ = ( Index("ix_report_instances_tenant", "tenant_id"), Index("ix_report_instances_template", "template_id"), Index("ix_report_instances_status", "status"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) template_id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), ForeignKey("report_templates.id", ondelete="CASCADE"), nullable=False, ) status: Mapped[str] = mapped_column( String(50), nullable=False, default="pending" ) output_path: Mapped[str | None] = mapped_column( String(1024), nullable=True ) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) created_by: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False) class Letterhead(Base, TenantMixin, OwnedMixin): """Letterhead (Briefpapier) — page setup + header/footer block composition. Phase L1: per-tenant letterhead. ``config`` stores the page geometry (size/orientation/margins) plus header/footer/watermark block lists. Blocks use the same ``{id, type, config}`` shape as print templates so the drag/drop editor can edit both with one component set. """ __tablename__ = "letterheads" __table_args__ = ( Index("ix_letterheads_tenant", "tenant_id"), Index("ix_letterheads_name", "name"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False, default="") config: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict) is_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) created_by: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False) class PrintTemplate(Base, TenantMixin, OwnedMixin): """Print template — drag/drop block composition bound to a letterhead. Phase L1: ``blocks`` is an ordered JSONB array of ``{id, type, config}`` entries validated against the document block registry. ``entity_type`` selects the module placeholder contribution (e.g. "company" → contacts placeholders). """ __tablename__ = "print_templates" __table_args__ = ( Index("ix_print_templates_tenant", "tenant_id"), Index("ix_print_templates_name", "name"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False, default="") letterhead_id: Mapped[uuid.UUID | None] = mapped_column( PGUUID(as_uuid=True), ForeignKey("letterheads.id", ondelete="SET NULL"), nullable=True, ) entity_type: Mapped[str] = mapped_column(String(100), nullable=False, default="contact") blocks: Mapped[list] = mapped_column(JSONB, nullable=False, default=list) output_format: Mapped[str] = mapped_column(String(20), nullable=False, default="pdf") created_by: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False) class DocumentAsset(Base, TenantMixin, OwnedMixin): """Image asset for letterheads/print templates (logos, pictures). Stored via the central storage backend; ``data_url`` is rendered into PDFs inline (WeasyPrint URL fetcher allows data: URIs only). """ __tablename__ = "document_assets" __table_args__ = ( Index("ix_document_assets_tenant", "tenant_id"), Index("ix_document_assets_letterhead", "letterhead_id"), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) letterhead_id: Mapped[uuid.UUID | None] = mapped_column( PGUUID(as_uuid=True), ForeignKey("letterheads.id", ondelete="CASCADE"), nullable=True, ) filename: Mapped[str] = mapped_column(String(255), nullable=False) mime_type: Mapped[str] = mapped_column(String(100), nullable=False) size_bytes: Mapped[int] = mapped_column(Integer, nullable=False, default=0) storage_path: Mapped[str] = mapped_column(String(1024), nullable=False) created_by: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)