"""迁移 027:PPT/海报品牌策略与后台对象软删除字段。""" import logging from sqlalchemy import inspect, text logger = logging.getLogger(__name__) def _column_names(db, table_name: str) -> set[str]: return {item["name"] for item in inspect(db.engine).get_columns(table_name)} def _index_names(db, table_name: str) -> set[str]: return {item["name"] for item in inspect(db.engine).get_indexes(table_name)} def _add_columns(db, table_name: str, columns: dict[str, str]): existing = _column_names(db, table_name) for name, definition in columns.items(): if name not in existing: db.session.execute(text( f"ALTER TABLE {table_name} ADD COLUMN {name} {definition}" )) def _add_index(db, table_name: str, index_name: str, columns: str): if index_name not in _index_names(db, table_name): db.session.execute(text( f"CREATE INDEX {index_name} ON {table_name} ({columns})" )) def migrate(): """幂等增加品牌策略和软删除字段。""" from insurance.db.compat import db db.session.execute(text(""" CREATE TABLE IF NOT EXISTS insurance_ppt_scenarios ( code VARCHAR(50) PRIMARY KEY, name VARCHAR(100) NOT NULL, base_scenario VARCHAR(50), generation_mode VARCHAR(20) NOT NULL DEFAULT 'single', description TEXT, status SMALLINT NOT NULL DEFAULT 1, sort_order INTEGER NOT NULL DEFAULT 0, is_builtin BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP ) """)) # 先提交新表,确保后续 inspector 使用的独立连接能够看到它。 db.session.commit() _add_columns(db, "insurance_ppt_companies", { "masking_enabled": "BOOLEAN NOT NULL DEFAULT FALSE", "logo_enabled": "BOOLEAN NOT NULL DEFAULT TRUE", "deleted_at": "TIMESTAMP", }) _add_columns(db, "insurance_ppt_products", { "masking_enabled": "BOOLEAN NOT NULL DEFAULT FALSE", "deleted_at": "TIMESTAMP", }) _add_columns(db, "insurance_ppt_templates", { "deleted_at": "TIMESTAMP", }) _add_columns(db, "poster_copy_templates", { "deleted_at": "TIMESTAMP", }) # 新增字段提交后再检查并创建索引,避免 DDL 对其他连接不可见。 db.session.commit() _add_index( db, "insurance_ppt_companies", "idx_ppt_companies_status_deleted", "status, deleted_at", ) _add_index( db, "insurance_ppt_scenarios", "idx_ppt_scenarios_status_deleted", "status, deleted_at", ) builtins = [ ("single_savings", "单份储蓄计划", "single_savings", "single", 10), ("multi_savings_comparison", "双/多储蓄险对比", "multi_savings_comparison", "compare", 20), ("savings_iul_comprehensive", "储蓄险 + IUL 综合方案", "savings_iul_comprehensive", "portfolio", 30), ("generic_single", "通用单产品", "generic_single", "single", 40), ("generic_compare", "通用同险种对比", "generic_compare", "compare", 50), ("generic_portfolio", "通用组合方案", "generic_portfolio", "portfolio", 60), ] for code, name, base_scenario, generation_mode, sort_order in builtins: db.session.execute(text(""" INSERT INTO insurance_ppt_scenarios (code, name, base_scenario, generation_mode, status, sort_order, is_builtin) SELECT :code, :name, :base_scenario, :generation_mode, 1, :sort_order, TRUE WHERE NOT EXISTS ( SELECT 1 FROM insurance_ppt_scenarios WHERE code = :code ) """), { "code": code, "name": name, "base_scenario": base_scenario, "generation_mode": generation_mode, "sort_order": sort_order, }) _add_index( db, "insurance_ppt_products", "idx_ppt_products_status_deleted", "status, deleted_at", ) _add_index( db, "insurance_ppt_products", "idx_ppt_products_company_deleted", "company_id, deleted_at", ) _add_index( db, "insurance_ppt_templates", "idx_ppt_templates_status_deleted", "status, deleted_at", ) _add_index( db, "poster_copy_templates", "idx_copy_templates_status_deleted", "status, deleted_at", ) db.session.commit() logger.info("[migrate_027] 品牌策略与软删除字段迁移完成")