"""finance_cost_centers.headcount — active-employee count per cost centre. Revision ID: 303 Revises: 302 Create Date: 2026-06-17 PR1 of the finance closed-loop theme: the authoritative cost-centre master (``finance_cost_centers``) is no longer populated from 汇联易 alone (which returns nothing for this app) — a daily reconciler rebuilds it from the HRIS-synced ``identity_persons.cost_center`` truth. This adds a nullable ``headcount`` column so the reconciler can stamp the active-employee count per cost centre, letting the coverage UI render "N cost centres / M people" without a cross-domain join. Idempotent + re-runnable (guards on column presence so a partial prod apply converges). """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "303" down_revision = "302" branch_labels = None depends_on = None TABLE = "finance_cost_centers" COLUMN = "headcount" def _column_exists(bind, table: str, column: str) -> bool: return bind.execute( sa.text( "SELECT 1 FROM information_schema.COLUMNS " "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t " "AND COLUMN_NAME = :c" ), {"t": table, "c": column}, ).scalar() is not None def upgrade() -> None: bind = op.get_bind() if not _column_exists(bind, TABLE, COLUMN): op.add_column(TABLE, sa.Column(COLUMN, sa.Integer(), nullable=True)) def downgrade() -> None: bind = op.get_bind() if _column_exists(bind, TABLE, COLUMN): op.drop_column(TABLE, COLUMN)