"""Seed the 年假到期提醒 stale-batch reaper cron. **This job never sends a notification.** The annual-leave expiry reminder has exactly one send path — HR clicking send — and that is deliberate (see ``leave_reminder_service`` module docstring). This cron only reconciles the batch ledger: the fan-out runs as an ``asyncio`` background task inside the API process, so a restart (deploy / OOM) leaves the batch row stuck at ``sending`` forever. The worst shape is a process killed between the batch commit and the task spawn — nobody was notified, yet HR sees "sending" indefinitely with no signal at all. Revision ID: 507 Revises: 506 """ import uuid import sqlalchemy as sa from alembic import op revision = "507" down_revision = "506" branch_labels = None depends_on = None _JOB = "hr.leave_reminder_reap" def upgrade() -> None: bind = op.get_bind() exists = bind.execute( sa.text("SELECT 1 FROM scheduled_jobs WHERE name = :name LIMIT 1"), {"name": _JOB}, ).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 (:id, :name, :display_name, :description, :name, 'cron', NULL, '*/20 * * * *', 'Asia/Shanghai', 1, :config, 0, 0, UTC_TIMESTAMP(), UTC_TIMESTAMP()) """ ), { "id": str(uuid.uuid4()), "name": _JOB, "display_name": "年假到期提醒 · 僵死批次收尾", "description": ( "每 20 分钟把僵死在 sending 的年假到期提醒批次按 outbox 真值收尾。" "不发送任何通知,只修批次台账(进程重启会让批次永久停在「正在发送」;" "最坏情况是一个人都没收到而 HR 毫不知情)。幂等。" ), "config": "{}", }, ) def downgrade() -> None: op.get_bind().execute( sa.text("DELETE FROM scheduled_jobs WHERE name = :name"), {"name": _JOB}, )