"""Add Business Unit and Client Branch scope to client related persons. This migration is intentionally defensive. It adds only columns, foreign keys, and indexes that are missing, so it is safe for databases where part of the change may already exist. """ from alembic import op import sqlalchemy as sa revision = "20260806_related_person_scope" down_revision = "20260805_scope_aware_services" branch_labels = None depends_on = None TABLE_NAME = "client_related_persons" def _column_names(inspector) -> set[str]: return {column["name"] for column in inspector.get_columns(TABLE_NAME)} def _index_names(inspector) -> set[str]: return {index["name"] for index in inspector.get_indexes(TABLE_NAME) if index.get("name")} def _foreign_key_names(inspector) -> set[str]: return { foreign_key["name"] for foreign_key in inspector.get_foreign_keys(TABLE_NAME) if foreign_key.get("name") } def upgrade(): bind = op.get_bind() inspector = sa.inspect(bind) columns = _column_names(inspector) with op.batch_alter_table(TABLE_NAME) as batch: if "business_unit_id" not in columns: batch.add_column(sa.Column("business_unit_id", sa.Integer(), nullable=True)) if "client_branch_id" not in columns: batch.add_column(sa.Column("client_branch_id", sa.Integer(), nullable=True)) inspector = sa.inspect(bind) foreign_keys = _foreign_key_names(inspector) indexes = _index_names(inspector) with op.batch_alter_table(TABLE_NAME) as batch: if "fk_client_related_persons_business_unit" not in foreign_keys: batch.create_foreign_key( "fk_client_related_persons_business_unit", "client_business_units", ["business_unit_id"], ["id"], ondelete="SET NULL", ) if "fk_client_related_persons_client_branch" not in foreign_keys: batch.create_foreign_key( "fk_client_related_persons_client_branch", "client_branches", ["client_branch_id"], ["id"], ondelete="SET NULL", ) if "ix_client_related_persons_business_unit_id" not in indexes: batch.create_index( "ix_client_related_persons_business_unit_id", ["business_unit_id"], ) if "ix_client_related_persons_client_branch_id" not in indexes: batch.create_index( "ix_client_related_persons_client_branch_id", ["client_branch_id"], ) def downgrade(): bind = op.get_bind() inspector = sa.inspect(bind) columns = _column_names(inspector) indexes = _index_names(inspector) foreign_keys = _foreign_key_names(inspector) with op.batch_alter_table(TABLE_NAME) as batch: if "ix_client_related_persons_client_branch_id" in indexes: batch.drop_index("ix_client_related_persons_client_branch_id") if "ix_client_related_persons_business_unit_id" in indexes: batch.drop_index("ix_client_related_persons_business_unit_id") if "fk_client_related_persons_client_branch" in foreign_keys: batch.drop_constraint( "fk_client_related_persons_client_branch", type_="foreignkey", ) if "fk_client_related_persons_business_unit" in foreign_keys: batch.drop_constraint( "fk_client_related_persons_business_unit", type_="foreignkey", ) if "client_branch_id" in columns: batch.drop_column("client_branch_id") if "business_unit_id" in columns: batch.drop_column("business_unit_id")