"""phase_2_client_acceptance_controls Revision ID: 20260622_phase_2_client_acceptance_controls Revises: 20260621_phase_1_udin_final_documents Create Date: 2026-06-22 """ from __future__ import annotations from alembic import op import sqlalchemy as sa from sqlalchemy import inspect, text revision = "20260622_phase_2_client_acceptance_controls" down_revision = "20260621_phase_1_udin_final_documents" branch_labels = None depends_on = None CLIENT_TABLE = "clients" def _columns(table_name: str) -> set[str]: bind = op.get_bind() inspector = inspect(bind) if table_name not in inspector.get_table_names(): return set() return {col["name"] for col in inspector.get_columns(table_name)} def _add_column_if_missing(existing: set[str], column: sa.Column) -> None: if column.name in existing: return op.add_column(CLIENT_TABLE, column) existing.add(column.name) def _create_index_if_missing(index_name: str, columns: list[str]) -> None: bind = op.get_bind() inspector = inspect(bind) existing_indexes = {idx["name"] for idx in inspector.get_indexes(CLIENT_TABLE)} if index_name not in existing_indexes: op.create_index(index_name, CLIENT_TABLE, columns) def upgrade() -> None: existing = _columns(CLIENT_TABLE) _add_column_if_missing(existing, sa.Column("acceptance_status", sa.String(length=30), nullable=False, server_default="pending_review")) _add_column_if_missing(existing, sa.Column("acceptance_required", sa.Boolean(), nullable=False, server_default=sa.true())) _add_column_if_missing(existing, sa.Column("independence_check_completed", sa.Boolean(), nullable=False, server_default=sa.false())) _add_column_if_missing(existing, sa.Column("conflict_check_completed", sa.Boolean(), nullable=False, server_default=sa.false())) _add_column_if_missing(existing, sa.Column("kyc_completed", sa.Boolean(), nullable=False, server_default=sa.false())) _add_column_if_missing(existing, sa.Column("engagement_letter_required", sa.Boolean(), nullable=False, server_default=sa.true())) _add_column_if_missing(existing, sa.Column("engagement_letter_received", sa.Boolean(), nullable=False, server_default=sa.false())) _add_column_if_missing(existing, sa.Column("acceptance_approved_by_user_id", sa.Integer(), nullable=True)) _add_column_if_missing(existing, sa.Column("acceptance_approved_at_utc", sa.DateTime(timezone=True), nullable=True)) _add_column_if_missing(existing, sa.Column("acceptance_review_notes", sa.Text(), nullable=True)) _add_column_if_missing(existing, sa.Column("acceptance_rejection_reason", sa.Text(), nullable=True)) bind = op.get_bind() dialect = bind.dialect.name if dialect == "postgresql": op.execute(text("UPDATE clients SET acceptance_status = 'pending_review' WHERE acceptance_status IS NULL")) op.execute(text("UPDATE clients SET acceptance_required = TRUE WHERE acceptance_required IS NULL")) op.execute(text("UPDATE clients SET independence_check_completed = FALSE WHERE independence_check_completed IS NULL")) op.execute(text("UPDATE clients SET conflict_check_completed = FALSE WHERE conflict_check_completed IS NULL")) op.execute(text("UPDATE clients SET kyc_completed = FALSE WHERE kyc_completed IS NULL")) op.execute(text("UPDATE clients SET engagement_letter_required = TRUE WHERE engagement_letter_required IS NULL")) op.execute(text("UPDATE clients SET engagement_letter_received = FALSE WHERE engagement_letter_received IS NULL")) else: op.execute(text("UPDATE clients SET acceptance_status = 'pending_review' WHERE acceptance_status IS NULL")) op.execute(text("UPDATE clients SET acceptance_required = 1 WHERE acceptance_required IS NULL")) op.execute(text("UPDATE clients SET independence_check_completed = 0 WHERE independence_check_completed IS NULL")) op.execute(text("UPDATE clients SET conflict_check_completed = 0 WHERE conflict_check_completed IS NULL")) op.execute(text("UPDATE clients SET kyc_completed = 0 WHERE kyc_completed IS NULL")) op.execute(text("UPDATE clients SET engagement_letter_required = 1 WHERE engagement_letter_required IS NULL")) op.execute(text("UPDATE clients SET engagement_letter_received = 0 WHERE engagement_letter_received IS NULL")) _create_index_if_missing("ix_clients_acceptance_status", ["acceptance_status"]) _create_index_if_missing("ix_clients_acceptance_approved_by_user_id", ["acceptance_approved_by_user_id"]) def downgrade() -> None: bind = op.get_bind() inspector = inspect(bind) if CLIENT_TABLE not in inspector.get_table_names(): return existing_indexes = {idx["name"] for idx in inspector.get_indexes(CLIENT_TABLE)} if "ix_clients_acceptance_approved_by_user_id" in existing_indexes: op.drop_index("ix_clients_acceptance_approved_by_user_id", table_name=CLIENT_TABLE) if "ix_clients_acceptance_status" in existing_indexes: op.drop_index("ix_clients_acceptance_status", table_name=CLIENT_TABLE) existing = _columns(CLIENT_TABLE) for col in [ "acceptance_rejection_reason", "acceptance_review_notes", "acceptance_approved_at_utc", "acceptance_approved_by_user_id", "engagement_letter_received", "engagement_letter_required", "kyc_completed", "conflict_check_completed", "independence_check_completed", "acceptance_required", "acceptance_status", ]: if col in existing: op.drop_column(CLIENT_TABLE, col)