"""Seed default email templates into platform_settings. Revision ID: 035 Revises: 034 Create Date: 2026-04-20 Before this migration, the 4 email templates (welcome / transfer_received / leaver_reminder / leaver_escalation) existed only as Python-level defaults in :data:`app.notifications.service._DEFAULT_TEMPLATES`. The admin edit page showed an empty form because there was no corresponding ``platform_settings`` row, even though the actual emails rendered fine at send time. This migration writes the Python defaults into ``platform_settings`` as ``email_template.`` rows so the edit UI shows the template content on first visit and admin has something concrete to customise. Idempotent: only inserts if the key doesn't exist yet, so running twice or on an install that has already customised a template won't clobber anything. """ from __future__ import annotations import json import sqlalchemy as sa from alembic import op revision = "035" down_revision = "034" branch_labels = None depends_on = None _WELCOME = { "subject": "欢迎加入 {company_name}!你的 IT 账号已就绪", "body_text": ( "Hi {person_name},\n\n" "欢迎加入 {company_name}!以下是你的 IT 账号信息:\n\n" " · 工作邮箱:{person_email}\n" " · 部门:{department}\n" " · 职位:{job_title}\n" " · 工作地:{location}\n\n" "登录方式\n" "-----------\n" "首次登录请使用 Google SSO(点击「Sign in with Google」)即可。\n" "公司内部平台:https://helix.example.com/\n\n" "电脑与软件坐席\n" "-----------\n" "IT 团队会在你入职当天为你分配电脑,并按你所在部门的规则自动开通常用软件坐席\n" "(Google Workspace、飞书、如适用还会有 Salesforce / Slack / Zoom 等)。\n" "如果你发现应该有的坐席没开通,请在「我的申请」里提交申请,注明原因。\n\n" "常用流程\n" "-----------\n" " · 领软件坐席 / 权限 → 左侧「软件目录」找对应软件,点「申请」\n" " · 领 API Key → 左侧「我的申请」→「新建 API Key」\n" " · 电脑问题 / 换机 → 左侧「我的申请」选对应流程\n" " · 资产转让 → 「我」→「我的资产」找到需要转的项目,点转让\n\n" "如需帮助\n" "-----------\n" " · IT 使用说明: 平台右上角问号图标\n" " · 联系 IT: {it_contact}\n\n" "祝工作顺利!\n" "— Helix IT 平台" ), } _TRANSFER_RECEIVED = { "subject": "[待处理] {initiator_name} 发起了一项资产转让给你", "body_text": ( "Hi,\n\n" "{initiator_name} 把一项资产转让给了你,请尽快登录 Helix 确认接收:\n\n" " · 资产: {asset_label}\n" " · 类型: {asset_type}\n" " · 转让理由: {reason}\n\n" "接收操作\n" "-----------\n" "打开 {portal_url}\n" "进入「我」→「待确认转让」→ 点「接收」或「拒绝」。\n\n" "说明\n" "-----------\n" "点击「接收」后,该资产的所有权会正式转到你名下,审计日志会完整记录。\n" "如果转让是错误的或者你不想接收,点「拒绝」并填写理由。\n\n" "— Helix IT 平台" ), } _LEAVER_REMINDER = { "subject": "[提醒] {person_name} 距离离职还有 {days_left} 天", "body_text": ( "以下是关于 {person_name} ({person_email}) 离职的提醒。\n\n" " · 预计离职日: {leaver_date}\n" " · 剩余天数: T-{days_left}\n\n" "仍需处理的资产\n" "-------------\n" "{pending_blockers}\n\n" "这些资产必须在离职日前完成转让或归还。到期仍未处理的,系统会在离职日当天\n" "自动转给直属 Manager 兜底,并给 Manager + IT 发通知。\n\n" "处理链接\n" "-------------\n" "{portal_url}\n\n" "— Helix IT 平台" ), } _LEAVER_ESCALATION = { "subject": "[兜底转让] {person_name} 的 {count} 项资产已自动转给你", "body_text": ( "Hi,\n\n" "你的直属下属 {person_name} ({person_email}) 今日离职,仍有 {count} 项资产\n" "未能在离职前完成转让。系统按兜底策略自动把这些资产转给了你(他/她的\n" "直属 Manager),请登录 Helix 接收:\n\n" "{asset_list}\n\n" "接收操作\n" "-------------\n" "打开 {portal_url}\n" "进入「我」→「待确认转让」→ 点「接收」。\n\n" "接收后这些资产就是你的,你可以后续保留、重新分配、或在「我的资产」里继续处理。\n\n" "— Helix IT 平台" ), } _SEEDS: list[tuple[str, dict]] = [ ("email_template.welcome", _WELCOME), ("email_template.transfer_received", _TRANSFER_RECEIVED), ("email_template.leaver_reminder", _LEAVER_REMINDER), ("email_template.leaver_escalation", _LEAVER_ESCALATION), ] def upgrade() -> None: bind = op.get_bind() for key, value in _SEEDS: exists = bind.execute( sa.text("SELECT 1 FROM platform_settings WHERE `key`=:k"), {"k": key}, ).scalar() if exists: continue bind.execute( sa.text( "INSERT INTO platform_settings (`key`, value, description) " "VALUES (:k, :v, :d)" ), { "k": key, "v": json.dumps(value, ensure_ascii=False), "d": "Email 模板(可编辑)。使用 {变量名} 做占位。", }, ) def downgrade() -> None: bind = op.get_bind() for key, _ in _SEEDS: bind.execute( sa.text("DELETE FROM platform_settings WHERE `key`=:k"), {"k": key}, )