"""助手提交结果对账 cron —— 让「最终生效」不必等员工回来点一下。 这套东西其余部分都是**被问的时候才答**。留下一个缺口:一个上周二就批下来的 申请,如果员工没再回来点什么,对应的服务请求会永远停在 `active`。于是漏斗读起来 是「没有人的申请完成过」——这是假的;而闲置清扫最终会把它记成 `abandoned` —— 这更糟,那是平台把自己的成功记成了失联。 这个 cron 重读所有未终态的提交链接,把已落地的那些背后的服务请求关掉 (`system_verified`),出现 `rejected` / `failed` 则标记待处理并**保持 active**。 **它不发任何通知。** 终态通知各领域自己已经在发(审批过了会告诉申请人),再加一句 「顺便说一下你要的那件事成了」就是平台在谈论自己。结果通过三个既有出口到达员工: 他来问、他回复卡片、或者领域自己的那封通知。 Revision ID: 573 Revises: 572 """ from __future__ import annotations import uuid import sqlalchemy as sa from alembic import op revision = "573" down_revision = "572" branch_labels = None depends_on = None JOB_NAME = "assistant.submission_reconcile" def upgrade() -> None: bind = op.get_bind() if not sa.inspect(bind).has_table("scheduled_jobs"): return 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 (:id, :name, :display_name, :description, :handler_key, 'cron', NULL, :cron, 'Asia/Shanghai', 1, '{}', 0, 0, NOW(), NOW()) """ ), { "id": str(uuid.uuid4()), "name": JOB_NAME, "display_name": "助手 · 提交结果对账", # 每 30 分钟。频率的依据是「员工多久之后会觉得没人管」,不是数据量: # 未终态的链接总数以十计,一次 tick 的成本是几十次带索引的查询。 "cron": "*/30 * * * *", "description": ( "重读助手提交的业务对象实时状态;全部生效则关闭对应服务请求" "(system_verified),出现 rejected/failed 则标记待处理并保持" "active。不发送任何通知。" ), "handler_key": JOB_NAME, }, ) def downgrade() -> None: bind = op.get_bind() if not sa.inspect(bind).has_table("scheduled_jobs"): return bind.execute( sa.text("DELETE FROM scheduled_jobs WHERE name = :n"), {"n": JOB_NAME}, )