"""Seed the offboarding.reconcile_processes scheduled job row. Revision ID: 135 Revises: 134 Create Date: 2026-05-08 D-4 fix from the offboarding hardening pass. Adds a ``scheduled_jobs`` row so the worker picks up the new ``offboarding.reconcile_processes`` handler (see ``app/scheduled_jobs/handlers.py``). Runs every 5 minutes — covers the gap between async provisioning task completion and the offboarding blocker state. Operators no longer need to remember to click "Reconcile" in the offboarding panel; the cron keeps the dashboard moving on its own. Idempotent: re-running this migration on a DB that already has the row is a no-op. Renumbered from 132 → 135 during rebase: PR #209 / PR #211 / PR #210 landed migrations 132, 133, 134 ahead of this branch. """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "135" down_revision = "134" branch_labels = None depends_on = None _JOB_NAME = "offboarding.reconcile_processes" def upgrade() -> None: bind = op.get_bind() exists = bind.execute( sa.text( "SELECT 1 FROM scheduled_jobs WHERE name = :n LIMIT 1", ), {"n": _JOB_NAME}, ).scalar() if exists: return bind.execute(sa.text( """ INSERT INTO scheduled_jobs (id, name, display_name, description, handler_key, trigger_type, interval_seconds, cron_expr, timezone, enabled, config, run_count, failure_count, created_at, updated_at) VALUES (UUID(), :n, :dn, :desc, :hk, 'interval', :ivl, NULL, 'UTC', 1, JSON_OBJECT(), 0, 0, NOW(), NOW()) """, ), { "n": _JOB_NAME, "dn": "离职流程对账", "desc": ( "每 5 分钟扫描 BLOCKED / READY 状态的 OffboardingProcess," "把已 COMPLETED 的 provisioning task 关联的 blocker 翻为 " "revoked_auto,把已 COMPLETED 的 transfer 关联的 blocker 翻为 " "transferred,并按 _rescan 重扫以捕获新增资源占用。" ), "hk": _JOB_NAME, "ivl": 300, }) def downgrade() -> None: op.get_bind().execute( sa.text("DELETE FROM scheduled_jobs WHERE name = :n"), {"n": _JOB_NAME}, )