"""Seed default platform_settings. Revision ID: 029 Revises: 028 Create Date: 2026-04-18 Populates the admin UI's ``/admin/settings`` page with a minimal set of sensible defaults so it's not empty on first boot. Values here are the *defaults*; admin can override in the UI. Seeded keys only — callers still need to be updated to *read* from ``platform_settings`` (follow-up PRs). This migration is idempotent: it skips keys that already exist so admin edits made between PR deployments are preserved. """ from __future__ import annotations import json from typing import Any import sqlalchemy as sa from alembic import op revision = "029" down_revision = "028" _DEFAULTS: list[tuple[str, Any, str]] = [ ( "asset.default_currency", "USD", "资产 / 软件 / 付款记录的默认币种。新建资产未显式填写时取此值。" "取值须在 ALLOWED_CURRENCIES 列表内(CNY/USD/EUR/JPY/SGD/MYR/THB)。", ), ( "offboarding.default_grace_days", 7, "离职缓冲期天数。软件目录里未显式设置 offboarding_grace_days 的," "平台按此值托底。负数或 0 表示立即执行。", ), ( "approval.request_number_prefix", "REQ", "申请单号的前缀。完整格式为 {prefix}-{YYYYMMDD}-{XXXX}。" "替换时需保证原有单号仍可解析(建议只在初始化阶段修改)。", ), ( "hr.overdue_leaver_check_interval_hours", 1, "worker 每隔多久检查一次过期的 leaver_pending 员工并转成 left 状态。" "数值越小响应越快,但会增加 DB 轮询开销。", ), ] def upgrade() -> None: bind = op.get_bind() # Idempotent: insert only when the key doesn't already exist. for key, value, description in _DEFAULTS: 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), "d": description}, ) def downgrade() -> None: bind = op.get_bind() for key, _value, _description in _DEFAULTS: bind.execute( sa.text("DELETE FROM platform_settings WHERE `key` = :k"), {"k": key}, )