baodan/api/insurance/db/migrate_017.py
wsb1224 8e897276e7 fix: rename upgrade() to migrate() in migrate_017 so it runs automatically
The migration runner (db/__init__.py) calls module.migrate() with no
arguments, but migrate_017 defined upgrade(engine). This meant the
slides_config_json column was never added to insurance_ppt_templates
in production, causing a 500 on /insurance/ppt/render-options.
2026-07-25 08:47:35 +08:00

145 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""迁移 017: 给 PPT 模板表新增 slides_config_json 列。
该列存储每种模板的逐页幻灯片配置(标题模板、叙事提示、图表类型等),
使管理员可以在后台管理页面中配置 PPT 的每一页内容。
"""
import json
import logging
from sqlalchemy import text
logger = logging.getLogger(__name__)
def migrate():
"""执行迁移。"""
from insurance.db.compat import db
# 检查列是否已存在
result = db.session.execute(text(
"SELECT COUNT(*) FROM information_schema.columns "
"WHERE table_name = 'insurance_ppt_templates' AND column_name = 'slides_config_json'"
))
if result.scalar() > 0:
logger.info("slides_config_json 列已存在,跳过迁移")
return
db.session.execute(text(
"ALTER TABLE insurance_ppt_templates ADD COLUMN slides_config_json TEXT"
))
db.session.commit()
logger.info("已添加 slides_config_json 列")
# 为现有模板填充默认 slides_config
_seed_default_slides_config(db)
def _seed_default_slides_config(db):
"""为现有模板填充默认的逐页配置。"""
templates = db.session.execute(text(
"SELECT id, plan_type, required_page_types_json FROM insurance_ppt_templates WHERE status = 1"
)).fetchall()
for tpl in templates:
tpl_id, plan_type, rpt_json = tpl
if not rpt_json:
continue
try:
page_types = json.loads(rpt_json)
except (json.JSONDecodeError, TypeError):
continue
slides_config = _build_default_slides_config(page_types, plan_type)
db.session.execute(text(
"UPDATE insurance_ppt_templates SET slides_config_json = :config WHERE id = :id"
), {"config": json.dumps(slides_config, ensure_ascii=False), "id": tpl_id})
db.session.commit()
logger.info(f"已为 {len(templates)} 个模板填充默认 slides_config")
def _build_default_slides_config(page_types, plan_type):
"""根据 pageType 列表构建默认的逐页配置。"""
defaults = {
"cover": {"pageType": "cover", "title": "{{customerName}} 专属方案",
"subtitle": "{{productName}}", "narrativeHint": ""},
"company": {"pageType": "company", "title": "{{companyName}} 公司介绍",
"narrativeHint": "公司事实来自内部知识库权威口径"},
"narrative": {"pageType": "narrative", "title": "方案核心逻辑",
"narrativeHint": "先看结构,再看收益与保障"},
"chart": {"pageType": "chart", "title": "价值增长曲线",
"chartType": "growth", "narrativeHint": "先看回本区间再看20/30年关键点"},
"timeline": {"pageType": "timeline", "title": "家庭时间轴",
"narrativeHint": "把谁在什么阶段发挥作用讲清楚"},
"table": {"pageType": "table", "title": "数据表每10年",
"tableType": "no_withdraw", "narrativeHint": "含单利复利,便于长期价值对比"},
"compare": {"pageType": "compare", "title": "产品对比",
"narrativeHint": "保证部分是底线,非保证部分是弹性"},
"synergy": {"pageType": "synergy", "title": "协同关系",
"narrativeHint": "功能分层,互不冲突"},
"conclusion": {"pageType": "conclusion", "title": "结论",
"narrativeHint": "一张保家庭不失速,一张保未来不落空"},
"closing": {"pageType": "closing", "title": "感谢信任",
"narrativeHint": "方案可继续迭代优化,如有疑问请随时联系"},
}
# 根据产品类型调整默认标题
if plan_type == "ci":
defaults["narrative"]["title"] = "家庭风险防线"
defaults["narrative"]["narrativeHint"] = "先防风险,再做未来"
defaults["chart"]["title"] = "重疾保障数据曲线"
defaults["chart"]["chartType"] = "growth"
elif plan_type == "iul":
defaults["narrative"]["title"] = "传承杠杆设计"
defaults["narrative"]["narrativeHint"] = "先看杠杆,再看现金价值弹性"
defaults["chart"]["title"] = "IUL 长期利益曲线"
defaults["chart"]["chartType"] = "growth"
slides = []
table_count = 0
chart_count = 0
for pt in page_types:
if pt == "chart":
meta = dict(defaults.get(pt, {"pageType": pt}))
if chart_count == 0:
meta["chartType"] = "growth"
meta["title"] = "价值增长曲线"
else:
meta["chartType"] = "stacked"
meta["title"] = "保证/非保证构成"
chart_count += 1
elif pt == "table":
meta = dict(defaults.get(pt, {"pageType": pt}))
if table_count == 0:
meta["tableType"] = "no_withdraw"
meta["title"] = "不提领方案数据表每10年"
else:
meta["tableType"] = "withdraw"
meta["title"] = "提领方案数据表每10年"
table_count += 1
elif pt == "timeline":
meta = dict(defaults.get(pt, {"pageType": pt}))
# 第二个 timeline 用不同标题
if sum(1 for s in slides if s["pageType"] == "timeline") > 0:
meta["title"] = "中后期里程碑"
meta["narrativeHint"] = "养老金与长期传承阶段"
elif pt == "conclusion":
meta = dict(defaults.get(pt, {"pageType": pt}))
if sum(1 for s in slides if s["pageType"] == "conclusion") > 0:
meta["title"] = "下一步行动建议"
meta["narrativeHint"] = "建议尽快与保险经纪人预约确认方案"
else:
meta = dict(defaults.get(pt, {"pageType": pt}))
slides.append(meta)
return {"slides": slides}
def downgrade(engine):
"""回滚迁移。"""
with engine.begin() as conn:
conn.execute(text(
"ALTER TABLE insurance_ppt_templates DROP COLUMN IF EXISTS slides_config_json"
))
logger.info("已删除 slides_config_json 列")