"""CustomFieldDefinition model — user-defined custom fields stored in DB.""" from __future__ import annotations import uuid from typing import Any from sqlalchemy import Boolean, Integer, JSON, String, UniqueConstraint 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 CustomFieldDefinition(Base, TenantMixin, OwnedMixin): """User-defined custom field definition stored in the database. These definitions are merged with plugin-provided custom fields at query time. Each definition is scoped to a tenant and entity type. """ __tablename__ = "custom_field_definitions" __table_args__ = ( UniqueConstraint( "tenant_id", "entity", "name", name="uq_custom_field_def_tenant_entity_name", ), ) id: Mapped[uuid.UUID] = mapped_column( PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) # ── Identity ── entity: Mapped[str] = mapped_column(String(50), nullable=False, index=True) name: Mapped[str] = mapped_column(String(100), nullable=False) label: Mapped[str] = mapped_column(String(200), nullable=False) # ── Type & Options ── field_type: Mapped[str] = mapped_column( String(20), nullable=False, default="text" ) # text, number, date, select, multiselect, boolean options: Mapped[list[str] | None] = mapped_column(JSON, nullable=True, default=list) default_value: Mapped[Any | None] = mapped_column(JSON, nullable=True, default=None) # ── Sensitivity (field-level permissions) ── sensitivity: Mapped[str] = mapped_column(String(20), nullable=False, default='normal') # ── Behaviour ── required: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0) # ── Audit ── created_by: Mapped[uuid.UUID | None] = mapped_column( PGUUID(as_uuid=True), nullable=True ) updated_by: Mapped[uuid.UUID | None] = mapped_column( PGUUID(as_uuid=True), nullable=True )