baodan/api/insurance/db/migrate_015.py

345 lines
13 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.

"""迁移 015: PPT/海报功能扩展 — 扩展现有表 + 创建新表。
覆盖:
- 扩展 insurance_ppt_companies+3 字段)
- 扩展 insurance_ppt_products+16 字段)
- 扩展 insurance_ppt_templates+6 字段)
- 创建 insurance_ppt_history
- 创建 poster_case_uploads
- 创建 poster_templates
- 创建 poster_copy_templates
- 创建 poster_records
- 创建 system_settings + 种子数据
"""
import logging
logger = logging.getLogger(__name__)
def _add_column(table, column_def, db):
"""安全添加列(已存在则跳过)。"""
from sqlalchemy import text
try:
db.session.execute(text(f"ALTER TABLE {table} ADD COLUMN {column_def}"))
except Exception as e:
# 仅忽略"列已存在"错误,其他错误记录日志
err_msg = str(e).lower()
if "already exists" not in err_msg and "duplicate column" not in err_msg:
logger.warning(f"添加列 {table}.{column_def} 失败: {e}")
def _table_exists(table_name, db):
"""检查表是否存在。"""
from sqlalchemy import text
try:
dialect = db.engine.dialect.name
if dialect == 'postgresql':
result = db.session.execute(text(
"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = :name)"
), {"name": table_name})
return result.fetchone()[0]
else:
# SQLite
result = db.session.execute(text(
"SELECT name FROM sqlite_master WHERE type='table' AND name=:name"
), {"name": table_name})
return result.fetchone() is not None
except Exception:
return False
def _get_create_table_sql_postgres():
"""返回 PostgreSQL 的 CREATE TABLE 语句。"""
return {
"insurance_ppt_history": """
CREATE TABLE IF NOT EXISTS insurance_ppt_history (
id BIGSERIAL PRIMARY KEY,
user_id VARCHAR(50) NOT NULL,
session_id VARCHAR(50),
action_type VARCHAR(20) NOT NULL,
company_id VARCHAR(50),
product_id VARCHAR(50),
template_id VARCHAR(50),
content_snapshot TEXT,
file_url VARCHAR(500),
ip VARCHAR(50),
user_agent VARCHAR(500),
created_at TIMESTAMP DEFAULT NOW()
)
""",
"poster_case_uploads": """
CREATE TABLE IF NOT EXISTS poster_case_uploads (
id BIGSERIAL PRIMARY KEY,
user_id VARCHAR(50) NOT NULL,
product_id VARCHAR(50) NOT NULL,
source_file_url VARCHAR(500) NOT NULL,
parse_status VARCHAR(20) DEFAULT 'pending',
parsed_data TEXT,
confirmed_data TEXT,
confirmed_by VARCHAR(50),
confirmed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
)
""",
"poster_templates": """
CREATE TABLE IF NOT EXISTS poster_templates (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
scenario_tag VARCHAR(50),
style_description TEXT NOT NULL,
color_scheme TEXT,
reference_image VARCHAR(500),
preview_image VARCHAR(500),
status SMALLINT DEFAULT 1,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
)
""",
"poster_copy_templates": """
CREATE TABLE IF NOT EXISTS poster_copy_templates (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
scenario_tag VARCHAR(50),
content TEXT NOT NULL,
variables TEXT,
status SMALLINT DEFAULT 1,
created_at TIMESTAMP DEFAULT NOW()
)
""",
"poster_records": """
CREATE TABLE IF NOT EXISTS poster_records (
id BIGSERIAL PRIMARY KEY,
user_id VARCHAR(50) NOT NULL,
product_id VARCHAR(50),
case_upload_id BIGINT,
template_id BIGINT,
copy_mode VARCHAR(20),
copy_content TEXT,
ai_raw_content TEXT,
export_url VARCHAR(500),
export_format VARCHAR(10),
export_size VARCHAR(20),
reference_image_used VARCHAR(500),
prompt_used TEXT,
created_at TIMESTAMP DEFAULT NOW()
)
""",
"system_settings": """
CREATE TABLE IF NOT EXISTS system_settings (
id BIGSERIAL PRIMARY KEY,
key VARCHAR(100) UNIQUE NOT NULL,
value TEXT NOT NULL,
description VARCHAR(500),
updated_by VARCHAR(50),
updated_at TIMESTAMP DEFAULT NOW()
)
""",
}
def _get_create_table_sql_sqlite():
"""返回 SQLite 的 CREATE TABLE 语句。"""
return {
"insurance_ppt_history": """
CREATE TABLE IF NOT EXISTS insurance_ppt_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id VARCHAR(50) NOT NULL,
session_id VARCHAR(50),
action_type VARCHAR(20) NOT NULL,
company_id VARCHAR(50),
product_id VARCHAR(50),
template_id VARCHAR(50),
content_snapshot TEXT,
file_url VARCHAR(500),
ip VARCHAR(50),
user_agent VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"poster_case_uploads": """
CREATE TABLE IF NOT EXISTS poster_case_uploads (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id VARCHAR(50) NOT NULL,
product_id VARCHAR(50) NOT NULL,
source_file_url VARCHAR(500) NOT NULL,
parse_status VARCHAR(20) DEFAULT 'pending',
parsed_data TEXT,
confirmed_data TEXT,
confirmed_by VARCHAR(50),
confirmed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"poster_templates": """
CREATE TABLE IF NOT EXISTS poster_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(100) NOT NULL,
scenario_tag VARCHAR(50),
style_description TEXT NOT NULL,
color_scheme TEXT,
reference_image VARCHAR(500),
preview_image VARCHAR(500),
status SMALLINT DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"poster_copy_templates": """
CREATE TABLE IF NOT EXISTS poster_copy_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(100) NOT NULL,
scenario_tag VARCHAR(50),
content TEXT NOT NULL,
variables TEXT,
status SMALLINT DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"poster_records": """
CREATE TABLE IF NOT EXISTS poster_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id VARCHAR(50) NOT NULL,
product_id VARCHAR(50),
case_upload_id BIGINT,
template_id BIGINT,
copy_mode VARCHAR(20),
copy_content TEXT,
ai_raw_content TEXT,
export_url VARCHAR(500),
export_format VARCHAR(10),
export_size VARCHAR(20),
reference_image_used VARCHAR(500),
prompt_used TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"system_settings": """
CREATE TABLE IF NOT EXISTS system_settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key VARCHAR(100) UNIQUE NOT NULL,
value TEXT NOT NULL,
description VARCHAR(500),
updated_by VARCHAR(50),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
}
def migrate():
"""迁移入口。"""
from insurance.db.compat import db
from sqlalchemy import text
dialect = db.engine.dialect.name
is_postgres = dialect == 'postgresql'
# ========== 1. 扩展 insurance_ppt_companies ==========
logger.info("[migrate_015] 扩展 insurance_ppt_companies ...")
for col in [
"logo_url VARCHAR(500)",
"status SMALLINT DEFAULT 1",
"sort_order INTEGER DEFAULT 0",
]:
_add_column("insurance_ppt_companies", col, db)
db.session.execute(text(
"UPDATE insurance_ppt_companies SET status = 1 WHERE status IS NULL"
))
db.session.commit()
logger.info("[migrate_015] insurance_ppt_companies 扩展完成")
# ========== 2. 扩展 insurance_ppt_products ==========
logger.info("[migrate_015] 扩展 insurance_ppt_products ...")
for col in [
"product_code VARCHAR(50)",
"product_type VARCHAR(20)",
"coverage_period VARCHAR(50)",
"payment_period VARCHAR(50)",
"insured_age_range VARCHAR(50)",
"waiting_period VARCHAR(50)",
"highlights TEXT",
"extra_fields TEXT",
"status SMALLINT DEFAULT 1",
"sort_order INTEGER DEFAULT 0",
"manual_file_url VARCHAR(500)",
"manual_parse_status VARCHAR(20) DEFAULT 'none'",
"manual_parsed_rules TEXT",
"manual_reviewed_by VARCHAR(50)",
"manual_reviewed_at TIMESTAMP",
]:
_add_column("insurance_ppt_products", col, db)
# updated_at 列可能已存在
if is_postgres:
_add_column("insurance_ppt_products", "updated_at TIMESTAMP DEFAULT NOW()", db)
else:
_add_column("insurance_ppt_products", "updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP", db)
db.session.execute(text(
"UPDATE insurance_ppt_products SET status = 1 WHERE status IS NULL"
))
db.session.commit()
logger.info("[migrate_015] insurance_ppt_products 扩展完成")
# ========== 3. 扩展 insurance_ppt_templates ==========
logger.info("[migrate_015] 扩展 insurance_ppt_templates ...")
for col in [
"name VARCHAR(100)",
"scenario_tag VARCHAR(50)",
"preview_image VARCHAR(500)",
"applicable_company_ids TEXT",
"applicable_product_ids TEXT",
"status SMALLINT DEFAULT 1",
]:
_add_column("insurance_ppt_templates", col, db)
db.session.commit()
logger.info("[migrate_015] insurance_ppt_templates 扩展完成")
# ========== 4-9. 创建新表(根据数据库方言选择 SQL ==========
sql_map = _get_create_table_sql_postgres() if is_postgres else _get_create_table_sql_sqlite()
for table_name in ["insurance_ppt_history", "poster_case_uploads", "poster_templates",
"poster_copy_templates", "poster_records", "system_settings"]:
if _table_exists(table_name, db):
logger.info(f"[migrate_015] 表 {table_name} 已存在,跳过创建")
continue
logger.info(f"[migrate_015] 创建 {table_name} ...")
db.session.execute(text(sql_map[table_name]))
db.session.commit()
logger.info(f"[migrate_015] {table_name} 创建完成")
# ========== 索引 ==========
indexes = [
"CREATE INDEX IF NOT EXISTS idx_ppt_history_user ON insurance_ppt_history(user_id, created_at DESC)",
"CREATE INDEX IF NOT EXISTS idx_poster_records_user ON poster_records(user_id, created_at DESC)",
]
for idx_sql in indexes:
try:
db.session.execute(text(idx_sql))
except Exception as e:
logger.warning(f"创建索引失败: {e}")
db.session.commit()
# ========== 种子数据 ==========
logger.info("[migrate_015] 写入种子数据 ...")
for key, value, desc in [
("ppt_history_retention_days", "365", "PPT 历史记录保留天数0=永久保留)"),
("poster_history_retention_days", "365", "海报历史记录保留天数0=永久保留)"),
]:
if is_postgres:
db.session.execute(text(
"INSERT INTO system_settings (key, value, description) "
"SELECT :key, :value, :desc "
"WHERE NOT EXISTS (SELECT 1 FROM system_settings WHERE key = :key)"
), {"key": key, "value": value, "desc": desc})
else:
# SQLite 不支持 SELECT ... INSERT 语法,用 try/except
try:
db.session.execute(text(
"INSERT INTO system_settings (key, value, description) VALUES (:key, :value, :desc)"
), {"key": key, "value": value, "desc": desc})
except Exception:
pass # 已存在则忽略
db.session.commit()
logger.info("[migrate_015] 种子数据写入完成")
logger.info("[migrate_015] 全部迁移完成")