"""rename audit classification to assurance and add client-wise review partner Revision ID: 20260503_assurance_review_partner Revises: 20260502_tenant_firm_type_review_partner Create Date: 2026-05-03 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa revision: str = "20260503_assurance_review_partner" down_revision: Union[str, None] = "20260502_tenant_firm_type_review_partner" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def _has_column(table_name: str, column_name: str) -> bool: bind = op.get_bind() insp = sa.inspect(bind) return any(c["name"] == column_name for c in insp.get_columns(table_name)) def _has_index(table_name: str, index_name: str) -> bool: bind = op.get_bind() insp = sa.inspect(bind) return any(ix.get("name") == index_name for ix in insp.get_indexes(table_name)) def _create_index_if_missing(table_name: str, index_name: str, columns: list[str]) -> None: if not _has_index(table_name, index_name): op.create_index(index_name, table_name, columns) def upgrade() -> None: bind = op.get_bind() # Client master: optional default review partner used for partnership + assurance engagements. if not _has_column("clients", "default_review_partner_user_id"): op.add_column("clients", sa.Column("default_review_partner_user_id", sa.Integer(), nullable=True)) _create_index_if_missing( "clients", "ix_clients_default_review_partner_user_id", ["default_review_partner_user_id"], ) # Safety only: normally this column is created by 20260502_tenant_firm_type_review_partner. if not _has_column("client_service_subscriptions", "review_partner_user_id"): op.add_column( "client_service_subscriptions", sa.Column("review_partner_user_id", sa.Integer(), nullable=True), ) _create_index_if_missing( "client_service_subscriptions", "ix_client_service_subscriptions_review_partner_user_id", ["review_partner_user_id"], ) # Convert old audit / non-audit values to final assurance / non-assurance values. bind.execute(sa.text(""" UPDATE service_catalogues SET engagement_type = CASE WHEN LOWER(COALESCE(engagement_type, '')) IN ('audit', 'aud', 'assurance') THEN 'assurance' ELSE 'non_assurance' END """)) bind.execute(sa.text(""" UPDATE client_service_subscriptions SET engagement_type = CASE WHEN LOWER(COALESCE(engagement_type, '')) IN ('audit', 'aud', 'assurance') THEN 'assurance' ELSE 'non_assurance' END """)) # Backfill review partner snapshots only for partnership-firm assurance engagements. # Uses tenants.firm_type created by 20260502, not a separate firm_type column. bind.execute(sa.text(""" UPDATE client_service_subscriptions SET review_partner_user_id = ( SELECT clients.default_review_partner_user_id FROM clients WHERE clients.id = client_service_subscriptions.client_id ) WHERE engagement_type = 'assurance' AND review_partner_user_id IS NULL AND EXISTS ( SELECT 1 FROM tenants WHERE tenants.id = client_service_subscriptions.tenant_id AND COALESCE(tenants.firm_type, 'proprietorship') = 'partnership' ) """)) def downgrade() -> None: bind = op.get_bind() bind.execute(sa.text(""" UPDATE service_catalogues SET engagement_type = CASE WHEN engagement_type = 'assurance' THEN 'audit' ELSE 'non_audit' END """)) bind.execute(sa.text(""" UPDATE client_service_subscriptions SET engagement_type = CASE WHEN engagement_type = 'assurance' THEN 'audit' ELSE 'non_audit' END """)) # Do not drop client_service_subscriptions.review_partner_user_id here. # That column belongs to the previous 20260502 migration. if _has_index("clients", "ix_clients_default_review_partner_user_id"): op.drop_index("ix_clients_default_review_partner_user_id", table_name="clients") if _has_column("clients", "default_review_partner_user_id"): op.drop_column("clients", "default_review_partner_user_id")