"""Re-enable + hourly cadence for the hire-day catch-up cron. ``hr.hire_day_it_notify`` is the ONLY scheduled job that provisions a deferred new hire's Google account + sends the Day-1 welcome email once they enter the 24h-before-start window (event-time hooks in ``hr_sync_coordinator`` defer any hire first synced earlier than that; this cron re-fires them). Two problems, both fixed here: 1. The row was ``enabled=0`` in production (disabled 2026-04-24) — every deferred US/EU hire (and any hire first synced the day before) silently never got provisioned. 2026-07-06 hire P10002 (Ritika Bharati, New York) was stuck this way: no Google account, no welcome email. 2. It ran once daily at 09:00 Asia/Shanghai (= 01:00 UTC). At that instant it is still the previous evening in the Americas, so US hires fail the per-person local ``is_future_hire`` gate at that run and are only picked up a day late. Hourly cadence catches every timezone within ~1h of the moment their 24h lead window opens (the per-person gate + ``it_info_sent_at`` idempotency keep each hire provisioned exactly once). Provisioning now opens ~24h BEFORE the local hire date (SF-agreed 2026-07-06, ``DEFAULT_PROVISION_LEAD_HOURS``) so the account is live and the welcome/ password mail delivered before the employee's day 1. Companion code changes in the same PR: city-level timezone resolution (``working_hours``), recovery-phone create fallback (``account_provisioner``), and per-hire dedup on the failed-account admin card (so hourly retries of a permanently-failing hire don't storm the HRBP / IT contact). Revision ID: 387 Revises: 386 """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "387" down_revision = "386" branch_labels = None depends_on = None JOB = "hr.hire_day_it_notify" NEW_CRON = "0 * * * *" # hourly, on the hour OLD_CRON = "0 9 * * *" # daily 09:00 (original seed, migration 038) NEW_DESC = ( "每小时扫描进入「入职前 24 小时」窗口(按员工本地时区)且尚未推送的新员工," "提前建 Google 账号 + 推送 Day-1 IT 信息(登录入口 / 已分配软件 / 设备领取 / " "2FA / IT 支持邮箱),Email + Feishu 双通道,通过 identity_persons." "it_info_sent_at 幂等。事件路径推迟的新人(首次同步早于入职前 24h 窗口)由此 " "cron 在窗口开启后 ≤1 小时兜底建号发信,使账号在员工到岗前一天就绪。" ) def upgrade() -> None: bind = op.get_bind() bind.execute( sa.text( """ UPDATE scheduled_jobs SET enabled = 1, trigger_type = 'cron', interval_seconds = NULL, cron_expr = :cron, description = :desc, updated_at = NOW() WHERE name = :n """ ), {"cron": NEW_CRON, "desc": NEW_DESC, "n": JOB}, ) def downgrade() -> None: # Restore the original daily cadence (migration 038 seed). ``enabled`` is # left at 1 to match that seed rather than re-encoding the later manual # disable, whose reason was never recorded. bind = op.get_bind() bind.execute( sa.text( """ UPDATE scheduled_jobs SET cron_expr = :cron, updated_at = NOW() WHERE name = :n """ ), {"cron": OLD_CRON, "n": JOB}, )