"""迁移 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 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 列")