"""phase54 — per-account notification overrides Creates `user_notification_matrix`, the per-account layer over the global NotificationMatrix. One row = one account's explicit answer for one event (enabled True/False); NO row means "inherit the global column". See app/models/user_notification_matrix.py for the semantics. **No backfill, deliberately.** An empty table means every account inherits, which is exactly today's behaviour — so this migration cannot change who gets notified. Overrides are created only when an admin sets one on the account's page in Customer Management. Backfilling from the current global matrix would freeze every account at today's routing and quietly break future changes to the global columns. The rest of phase51 (Customer Director / Customer Inspector) is a LABEL-only rename over the existing 'customer' and 'external_inspector' ENUM values, so there is no ENUM change and no user row is touched here. Table-existence check — safe to re-run. """ revision = 'phase54_user_notif_matrix' down_revision = 'phase53_knowledge_sort_order' branch_labels = None depends_on = None from alembic import op import sqlalchemy as sa def _has_table(conn, name): return conn.execute(sa.text( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t" ), {'t': name}).scalar() > 0 def upgrade(): conn = op.get_bind() if _has_table(conn, 'user_notification_matrix'): return op.create_table( 'user_notification_matrix', sa.Column('id', sa.Integer, primary_key=True), sa.Column('user_id', sa.Integer, sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False, index=True), sa.Column('event_type', sa.String(50), nullable=False), sa.Column('enabled', sa.Boolean, nullable=False, server_default=sa.text('1')), sa.UniqueConstraint('user_id', 'event_type', name='uq_user_notif_matrix_user_event'), ) def downgrade(): conn = op.get_bind() if _has_table(conn, 'user_notification_matrix'): # Every row here is an explicit admin decision; dropping the table # discards them and returns all accounts to global-matrix routing. op.drop_table('user_notification_matrix')