"""Seed the terminal Zoom-account delete scheduled job. Seeds ``hr.offboarding_zoom_account_delete`` (daily 03:10 UTC): the terminal disposition of a leaver's Zoom account, parity with the Google delete job (395). After ``_push_force_logout`` auto-DEACTIVATES the account at leaver_date+1, this job hard-DELETES it once ``leaver_date + delete_lead_days`` (config, default 2) local days have passed — for accounts we confirmed deactivated and that are NOT retention-exempt. ⚠️ Zoom deletes are PERMANENT (no ``users.undelete`` window), so the deactivate→delete gap is the only mistake window. Staggered to 03:10 (Google runs 03:00) so the two terminal-delete sweeps don't hit their respective vendor APIs in the same minute. Config: ``{"delete_lead_days": 2}``. No schema change (the delete ledger rides the existing ``offboarding_processes.session_enforcement`` JSON column). Revision ID: 451 Revises: 450 """ import uuid import sqlalchemy as sa from alembic import op revision = "451" down_revision = "450" branch_labels = None depends_on = None JOB = "hr.offboarding_zoom_account_delete" def upgrade() -> None: bind = op.get_bind() job_exists = bind.execute( sa.text("SELECT 1 FROM scheduled_jobs WHERE name = :n LIMIT 1"), {"n": JOB}, ).scalar() if not job_exists: 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 (:id, :name, :display_name, :description, :name, 'cron', NULL, '10 3 * * *', 'UTC', 1, :config, 0, 0, NOW(), NOW()) """ ), { "id": str(uuid.uuid4()), "name": JOB, "display_name": "离职 Zoom 账号最终删除", "description": ( "每日 03:10 UTC 对已完成离职流程,在 leaver_date + " "delete_lead_days(默认 2,员工本地时区)到达后硬删除" "已停用的 Zoom 账号;保留号/返聘/未确认停用跳过;" "⚠️ Zoom 删除永久、无恢复窗口;幂等键 " "session_enforcement.zoom_account_deletion。" ), "config": '{"delete_lead_days": 2}', }, ) def downgrade() -> None: bind = op.get_bind() bind.execute(sa.text("DELETE FROM scheduled_jobs WHERE name = :n"), {"n": JOB})