"""phase37 — per-contract additional notification recipients Creates the project_notification_recipients table. Each row subscribes one recipient — either a staff User (in-app + email) or an external email address (email only) — to a chosen set of notification-matrix event types, scoped to events occurring in facilities that belong to that contract (Project). Dispatched by notify_by_matrix() → _notify_project_recipients() in app/utils/notifications.py, AFTER the global matrix roles and custom emails. Idempotent: guarded by an INFORMATION_SCHEMA table-existence check so it is safe to re-run across every tenant DB (CLAUDE.md rule 14). NOTE: the revision id is deliberately short — alembic_version.version_num is VARCHAR(32); ids longer than 32 chars fail the version-pointer UPDATE with MySQL error 1406 ("Data too long for column 'version_num'"). """ import sqlalchemy as sa from alembic import op revision = 'phase37_contract_recipients' down_revision = 'phase36_issue_work_orders' branch_labels = None depends_on = None def _table_exists(bind, table: str) -> bool: result = bind.execute(sa.text( "SELECT COUNT(*) FROM information_schema.tables " "WHERE table_schema = DATABASE() AND table_name = :t" ), {'t': table}) return result.scalar() > 0 def upgrade(): bind = op.get_bind() if not _table_exists(bind, 'project_notification_recipients'): op.execute(sa.text(""" CREATE TABLE project_notification_recipients ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, project_id INT NOT NULL, user_id INT NULL, email VARCHAR(255) NULL, events TEXT NOT NULL, created_at DATETIME NOT NULL, CONSTRAINT fk_pnr_project FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, CONSTRAINT fk_pnr_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX ix_pnr_project (project_id), INDEX ix_pnr_user (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 """)) def downgrade(): bind = op.get_bind() if _table_exists(bind, 'project_notification_recipients'): op.execute(sa.text('DROP TABLE project_notification_recipients'))