Changes committed

This commit is contained in:
2026-06-01 21:49:53 +05:30
parent 8537c653c1
commit 3163bb213e
387 changed files with 21940 additions and 107 deletions

View File

@@ -0,0 +1,378 @@
from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, String, Text, func
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
from app.models.base import TimestampMixin, UUIDPrimaryKeyMixin
class DocumentFormat(Base, UUIDPrimaryKeyMixin, TimestampMixin):
"""Reusable document template format."""
__tablename__ = "document_formats"
name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
page_width: Mapped[float] = mapped_column(Float, nullable=False)
page_height: Mapped[float] = mapped_column(Float, nullable=False)
page_count: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
margin_top: Mapped[float] = mapped_column(Float, default=72.0, nullable=False)
margin_right: Mapped[float] = mapped_column(Float, default=72.0, nullable=False)
margin_bottom: Mapped[float] = mapped_column(Float, default=72.0, nullable=False)
margin_left: Mapped[float] = mapped_column(Float, default=72.0, nullable=False)
fingerprint: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
source_document_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey("documents.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
version: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False, index=True)
created_by: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True,
)
cells: Mapped[list[DocumentCell]] = relationship(
"DocumentCell",
back_populates="format",
cascade="all, delete-orphan",
order_by="DocumentCell.sequence",
lazy="selectin",
)
regions: Mapped[list[DocumentRegion]] = relationship(
"DocumentRegion",
back_populates="format",
cascade="all, delete-orphan",
order_by="DocumentRegion.sequence",
lazy="selectin",
)
table_formats: Mapped[list[TableFormat]] = relationship(
"TableFormat",
back_populates="format",
cascade="all, delete-orphan",
lazy="selectin",
)
watermarks: Mapped[list[Watermark]] = relationship(
"Watermark",
back_populates="format",
cascade="all, delete-orphan",
lazy="selectin",
)
image_regions: Mapped[list[ImageRegion]] = relationship(
"ImageRegion",
back_populates="format",
cascade="all, delete-orphan",
lazy="selectin",
)
fingerprint_record: Mapped[TemplateFingerprint | None] = relationship(
"TemplateFingerprint",
back_populates="format",
uselist=False,
cascade="all, delete-orphan",
lazy="selectin",
)
def __repr__(self) -> str:
return f"<DocumentFormat(id={self.id}, name={self.name}, v{self.version})>"
class DocumentCell(Base, UUIDPrimaryKeyMixin):
"""Cell definition within a document template."""
__tablename__ = "document_cells"
format_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("document_formats.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
page_number: Mapped[int] = mapped_column(Integer, nullable=False)
x: Mapped[float] = mapped_column(Float, nullable=False)
y: Mapped[float] = mapped_column(Float, nullable=False)
width: Mapped[float] = mapped_column(Float, nullable=False)
height: Mapped[float] = mapped_column(Float, nullable=False)
row_no: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
column_no: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
data_type: Mapped[str] = mapped_column(String(50), default="text", nullable=False)
font_family: Mapped[str | None] = mapped_column(String(255), nullable=True)
font_size: Mapped[float | None] = mapped_column(Float, nullable=True)
font_style: Mapped[str | None] = mapped_column(String(50), nullable=True)
font_color: Mapped[str | None] = mapped_column(String(50), nullable=True)
background_color: Mapped[str | None] = mapped_column(String(50), nullable=True)
border_top: Mapped[str | None] = mapped_column(String(100), nullable=True)
border_right: Mapped[str | None] = mapped_column(String(100), nullable=True)
border_bottom: Mapped[str | None] = mapped_column(String(100), nullable=True)
border_left: Mapped[str | None] = mapped_column(String(100), nullable=True)
padding_top: Mapped[float] = mapped_column(Float, default=0.0, nullable=False)
padding_right: Mapped[float] = mapped_column(Float, default=0.0, nullable=False)
padding_bottom: Mapped[float] = mapped_column(Float, default=0.0, nullable=False)
padding_left: Mapped[float] = mapped_column(Float, default=0.0, nullable=False)
alignment: Mapped[str] = mapped_column(String(20), default="left", nullable=False)
vertical_alignment: Mapped[str] = mapped_column(String(20), default="top", nullable=False)
rowspan: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
colspan: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
static_text: Mapped[str | None] = mapped_column(Text, nullable=True)
field_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
sequence: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
is_dynamic: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
format: Mapped[DocumentFormat] = relationship("DocumentFormat", back_populates="cells")
def __repr__(self) -> str:
return f"<DocumentCell(id={self.id}, page={self.page_number}, row={self.row_no}, col={self.column_no})>"
class DocumentRegion(Base, UUIDPrimaryKeyMixin):
"""Region definition within a document template."""
__tablename__ = "document_regions"
format_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("document_formats.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
page_number: Mapped[int] = mapped_column(Integer, nullable=False)
region_type: Mapped[str] = mapped_column(String(50), nullable=False)
x: Mapped[float] = mapped_column(Float, nullable=False)
y: Mapped[float] = mapped_column(Float, nullable=False)
width: Mapped[float] = mapped_column(Float, nullable=False)
height: Mapped[float] = mapped_column(Float, nullable=False)
content: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
sequence: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
format: Mapped[DocumentFormat] = relationship("DocumentFormat", back_populates="regions")
def __repr__(self) -> str:
return f"<DocumentRegion(id={self.id}, type={self.region_type}, page={self.page_number})>"
class TableFormat(Base, UUIDPrimaryKeyMixin):
"""Table definition within a document template."""
__tablename__ = "table_formats"
format_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("document_formats.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
page_number: Mapped[int] = mapped_column(Integer, nullable=False)
x: Mapped[float] = mapped_column(Float, nullable=False)
y: Mapped[float] = mapped_column(Float, nullable=False)
width: Mapped[float] = mapped_column(Float, nullable=False)
height: Mapped[float] = mapped_column(Float, nullable=False)
rows: Mapped[int] = mapped_column(Integer, nullable=False)
columns: Mapped[int] = mapped_column(Integer, nullable=False)
border_style: Mapped[str] = mapped_column(String(50), default="solid", nullable=False)
border_width: Mapped[float] = mapped_column(Float, default=1.0, nullable=False)
border_color: Mapped[str] = mapped_column(String(50), default="#000000", nullable=False)
header_rows: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
format: Mapped[DocumentFormat] = relationship("DocumentFormat", back_populates="table_formats")
table_columns: Mapped[list[TableColumn]] = relationship(
"TableColumn",
back_populates="table_format",
cascade="all, delete-orphan",
order_by="TableColumn.column_index",
lazy="selectin",
)
table_rows: Mapped[list[TableRow]] = relationship(
"TableRow",
back_populates="table_format",
cascade="all, delete-orphan",
order_by="TableRow.row_index",
lazy="selectin",
)
def __repr__(self) -> str:
return f"<TableFormat(id={self.id}, rows={self.rows}, cols={self.columns})>"
class TableColumn(Base, UUIDPrimaryKeyMixin):
"""Column definition within a table format."""
__tablename__ = "table_columns"
table_format_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("table_formats.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
column_index: Mapped[int] = mapped_column(Integer, nullable=False)
width: Mapped[float] = mapped_column(Float, nullable=False)
header_text: Mapped[str | None] = mapped_column(String(500), nullable=True)
data_type: Mapped[str] = mapped_column(String(50), default="text", nullable=False)
alignment: Mapped[str] = mapped_column(String(20), default="left", nullable=False)
font_family: Mapped[str | None] = mapped_column(String(255), nullable=True)
font_size: Mapped[float | None] = mapped_column(Float, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
table_format: Mapped[TableFormat] = relationship("TableFormat", back_populates="table_columns")
def __repr__(self) -> str:
return f"<TableColumn(id={self.id}, index={self.column_index}, header={self.header_text})>"
class TableRow(Base, UUIDPrimaryKeyMixin):
"""Row definition within a table format."""
__tablename__ = "table_rows"
table_format_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("table_formats.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
row_index: Mapped[int] = mapped_column(Integer, nullable=False)
height: Mapped[float] = mapped_column(Float, default=20.0, nullable=False)
is_header: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
background_color: Mapped[str | None] = mapped_column(String(50), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
table_format: Mapped[TableFormat] = relationship("TableFormat", back_populates="table_rows")
def __repr__(self) -> str:
return f"<TableRow(id={self.id}, index={self.row_index}, is_header={self.is_header})>"
class Watermark(Base, UUIDPrimaryKeyMixin):
"""Watermark definition within a document template."""
__tablename__ = "watermarks"
format_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("document_formats.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
page_number: Mapped[int | None] = mapped_column(Integer, nullable=True)
text: Mapped[str | None] = mapped_column(String(500), nullable=True)
image_path: Mapped[str | None] = mapped_column(String(1024), nullable=True)
x: Mapped[float] = mapped_column(Float, nullable=False)
y: Mapped[float] = mapped_column(Float, nullable=False)
width: Mapped[float] = mapped_column(Float, nullable=False)
height: Mapped[float] = mapped_column(Float, nullable=False)
opacity: Mapped[float] = mapped_column(Float, default=0.3, nullable=False)
rotation: Mapped[float] = mapped_column(Float, default=0.0, nullable=False)
font_family: Mapped[str | None] = mapped_column(String(255), nullable=True)
font_size: Mapped[float | None] = mapped_column(Float, nullable=True)
font_color: Mapped[str | None] = mapped_column(String(50), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
format: Mapped[DocumentFormat] = relationship("DocumentFormat", back_populates="watermarks")
def __repr__(self) -> str:
return f"<Watermark(id={self.id}, text={self.text})>"
class ImageRegion(Base, UUIDPrimaryKeyMixin):
"""Image region within a document template."""
__tablename__ = "image_regions"
format_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("document_formats.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
page_number: Mapped[int] = mapped_column(Integer, nullable=False)
x: Mapped[float] = mapped_column(Float, nullable=False)
y: Mapped[float] = mapped_column(Float, nullable=False)
width: Mapped[float] = mapped_column(Float, nullable=False)
height: Mapped[float] = mapped_column(Float, nullable=False)
image_path: Mapped[str | None] = mapped_column(String(1024), nullable=True)
image_type: Mapped[str] = mapped_column(String(50), default="figure", nullable=False)
is_static: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
field_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
format: Mapped[DocumentFormat] = relationship("DocumentFormat", back_populates="image_regions")
def __repr__(self) -> str:
return f"<ImageRegion(id={self.id}, type={self.image_type}, page={self.page_number})>"
class TemplateFingerprint(Base, UUIDPrimaryKeyMixin):
"""Layout fingerprint for template matching."""
__tablename__ = "template_fingerprints"
format_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("document_formats.id", ondelete="CASCADE"),
nullable=False,
unique=True,
index=True,
)
page_dimensions: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
logo_coordinates: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
header_coordinates: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
footer_coordinates: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
table_coordinates: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
cell_coordinates: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
fingerprint_hash: Mapped[str] = mapped_column(String(256), nullable=False, index=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=func.now(),
server_default=func.now(),
nullable=False,
)
format: Mapped[DocumentFormat] = relationship("DocumentFormat", back_populates="fingerprint_record")
def __repr__(self) -> str:
return f"<TemplateFingerprint(id={self.id}, format_id={self.format_id}, hash={self.fingerprint_hash[:16]})>"