"""Create custom_field_definitions table for user-defined custom fields. Revision ID: 0041_custom_field_definitions Revises: 0040_outbox Create Date: 2026-07-26 Stores user-defined custom field definitions that are merged with plugin-provided custom fields at query time. """ from __future__ import annotations from typing import Union import sqlalchemy as sa from alembic import op # revision identifiers revision: str = "0041_custom_field_definitions" down_revision: Union[str, None] = "0040_outbox" branch_labels: Union[str, None] = None depends_on: Union[str, None] = None def upgrade() -> None: conn = op.get_bind() conn.execute( sa.text( """ CREATE TABLE IF NOT EXISTS custom_field_definitions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, entity VARCHAR(50) NOT NULL, name VARCHAR(100) NOT NULL, label VARCHAR(200) NOT NULL, field_type VARCHAR(20) NOT NULL DEFAULT 'text', options JSONB, default_value JSONB, required BOOLEAN NOT NULL DEFAULT FALSE, is_active BOOLEAN NOT NULL DEFAULT TRUE, sort_order INTEGER NOT NULL DEFAULT 0, created_by UUID, updated_by UUID, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ) """ ) ) # Indexes conn.execute( sa.text( "CREATE INDEX IF NOT EXISTS ix_custom_field_def_tenant " "ON custom_field_definitions (tenant_id)" ) ) conn.execute( sa.text( "CREATE INDEX IF NOT EXISTS ix_custom_field_def_entity " "ON custom_field_definitions (entity)" ) ) conn.execute( sa.text( "CREATE INDEX IF NOT EXISTS ix_custom_field_def_tenant_active " "ON custom_field_definitions (tenant_id, is_active)" ) ) conn.execute( sa.text( "CREATE UNIQUE INDEX IF NOT EXISTS uq_custom_field_def_tenant_entity_name " "ON custom_field_definitions (tenant_id, entity, name)" ) ) def downgrade() -> None: conn = op.get_bind() conn.execute(sa.text("DROP INDEX IF EXISTS uq_custom_field_def_tenant_entity_name")) conn.execute(sa.text("DROP INDEX IF EXISTS ix_custom_field_def_tenant_active")) conn.execute(sa.text("DROP INDEX IF EXISTS ix_custom_field_def_entity")) conn.execute(sa.text("DROP INDEX IF EXISTS ix_custom_field_def_tenant")) conn.execute(sa.text("DROP TABLE IF EXISTS custom_field_definitions"))