baodan/api/insurance/admin/ppt_admin_service.py

496 lines
22 KiB
Python
Raw Normal View History

2026-07-23 15:04:16 +08:00
"""PPT/海报管理后台服务。"""
import json
2026-07-23 17:40:12 +08:00
import logging
2026-07-23 15:04:16 +08:00
import os
import uuid
from flask import current_app, request
from insurance.db.compat import db
from insurance.models.ppt_config import PptCompany, PptProduct, PptTemplate
from insurance.models.ppt_history import PptHistory
from insurance.models.poster_template_model import PosterTemplate
from insurance.models.poster_copy_template import PosterCopyTemplate
from insurance.models.poster_record import PosterRecord
from insurance.models.system_setting import SystemSetting
from sqlalchemy import or_
2026-07-23 17:40:12 +08:00
logger = logging.getLogger(__name__)
2026-07-23 15:04:16 +08:00
def _safe_page_params(params: dict) -> tuple[int, int]:
"""安全获取分页参数,防止负数和超大值。"""
page = max(1, params.get("page", 1))
page_size = min(100, max(1, params.get("page_size", 20)))
return page, page_size
class PptAdminService:
"""PPT/海报管理后台业务逻辑。"""
# ---- 保司管理 ----
def list_companies(self, params: dict) -> dict:
query = db.session.query(PptCompany)
if params.get("status") is not None:
query = query.filter(PptCompany.status == params["status"])
if params.get("keyword"):
kw = f"%{params['keyword']}%"
query = query.filter(or_(
PptCompany.display_name.ilike(kw),
PptCompany.name_zh.ilike(kw),
PptCompany.name_en.ilike(kw),
))
query = query.order_by(PptCompany.sort_order.asc(), PptCompany.id.asc())
page, page_size = _safe_page_params(params)
total = query.count()
items = query.offset((page - 1) * page_size).limit(page_size).all()
return {
"code": 0,
"data": {
"total": total,
"items": [c.to_dict() for c in items],
},
}
def create_company(self, data: dict) -> dict:
if not data.get("id") or not data.get("displayName"):
return {"code": 1001, "message": "id 和 displayName 必填", "data": None}
if PptCompany.query.get(data["id"]):
return {"code": 1001, "message": "公司 ID 已存在", "data": None}
company = PptCompany(
id=data["id"],
display_name=data["displayName"],
aliases_json=json.dumps(data.get("aliases", []), ensure_ascii=False),
name_zh=data.get("nameZh"),
name_en=data.get("nameEn"),
short_en=data.get("shortEn"),
logo_url=data.get("logoUrl"),
company_intro=data.get("companyIntro"),
status=data.get("status", 1),
sort_order=data.get("sortOrder", 0),
)
db.session.add(company)
db.session.commit()
return {"code": 0, "data": company.to_dict()}
def update_company(self, company_id: str, data: dict) -> dict:
company = PptCompany.query.get(company_id)
if not company:
return {"code": 1002, "message": "公司不存在", "data": None}
if "displayName" in data:
company.display_name = data["displayName"]
if "aliases" in data:
company.aliases_json = json.dumps(data["aliases"], ensure_ascii=False)
if "nameZh" in data:
company.name_zh = data["nameZh"]
if "nameEn" in data:
company.name_en = data["nameEn"]
if "shortEn" in data:
company.short_en = data["shortEn"]
if "logoUrl" in data:
company.logo_url = data["logoUrl"]
if "companyIntro" in data:
company.company_intro = data["companyIntro"]
if "status" in data:
company.status = data["status"]
if "sortOrder" in data:
company.sort_order = data["sortOrder"]
db.session.commit()
return {"code": 0, "data": company.to_dict()}
def update_company_status(self, company_id: str, data: dict) -> dict:
company = PptCompany.query.get(company_id)
if not company:
return {"code": 1002, "message": "公司不存在", "data": None}
company.status = data.get("status", 1)
db.session.commit()
return {"code": 0, "data": company.to_dict()}
# ---- 产品管理 ----
def list_products(self, params: dict) -> dict:
query = db.session.query(PptProduct)
if params.get("company_id"):
query = query.filter(PptProduct.company_id == params["company_id"])
if params.get("plan_type"):
query = query.filter(PptProduct.plan_type == params["plan_type"])
if params.get("status") is not None:
query = query.filter(PptProduct.status == params["status"])
query = query.order_by(PptProduct.sort_order.asc(), PptProduct.id.asc())
page, page_size = _safe_page_params(params)
total = query.count()
items = query.offset((page - 1) * page_size).limit(page_size).all()
return {
"code": 0,
"data": {
"total": total,
"items": [p.to_dict() for p in items],
},
}
def create_product(self, data: dict) -> dict:
if not data.get("id") or not data.get("displayName"):
return {"code": 1001, "message": "id 和 displayName 必填", "data": None}
if PptProduct.query.get(data["id"]):
return {"code": 1001, "message": "产品 ID 已存在", "data": None}
product = PptProduct(
id=data["id"],
company_id=data.get("companyId", ""),
plan_type=data.get("planType", "savings"),
display_name=data["displayName"],
aliases_json=json.dumps(data.get("aliases", []), ensure_ascii=False),
required_modules_json=json.dumps(data.get("requiredModules", []), ensure_ascii=False),
product_code=data.get("productCode"),
product_type=data.get("productType"),
coverage_period=data.get("coveragePeriod"),
payment_period=data.get("paymentPeriod"),
insured_age_range=data.get("insuredAgeRange"),
waiting_period=data.get("waitingPeriod"),
highlights=json.dumps(data.get("highlights", []), ensure_ascii=False) if data.get("highlights") else None,
extra_fields=json.dumps(data.get("extraFields", {}), ensure_ascii=False) if data.get("extraFields") else None,
status=data.get("status", 1),
sort_order=data.get("sortOrder", 0),
)
db.session.add(product)
db.session.commit()
return {"code": 0, "data": product.to_dict()}
def update_product(self, product_id: str, data: dict) -> dict:
product = PptProduct.query.get(product_id)
if not product:
return {"code": 1002, "message": "产品不存在", "data": None}
simple_fields = {
"displayName": "display_name", "companyId": "company_id",
"planType": "plan_type", "productCode": "product_code",
"productType": "product_type", "coveragePeriod": "coverage_period",
"paymentPeriod": "payment_period", "insuredAgeRange": "insured_age_range",
"waitingPeriod": "waiting_period", "status": "status", "sortOrder": "sort_order",
}
for key, attr in simple_fields.items():
if key in data:
setattr(product, attr, data[key])
json_fields = {
"aliases": "aliases_json", "requiredModules": "required_modules_json",
"highlights": "highlights", "extraFields": "extra_fields",
}
for key, attr in json_fields.items():
if key in data:
setattr(product, attr, json.dumps(data[key], ensure_ascii=False) if data[key] else None)
db.session.commit()
return {"code": 0, "data": product.to_dict()}
def update_product_status(self, product_id: str, data: dict) -> dict:
product = PptProduct.query.get(product_id)
if not product:
return {"code": 1002, "message": "产品不存在", "data": None}
product.status = data.get("status", 1)
db.session.commit()
return {"code": 0, "data": product.to_dict()}
# ---- 产品小册子 ----
def upload_manual(self, product_id: str, file) -> dict:
product = PptProduct.query.get(product_id)
if not product:
return {"code": 1002, "message": "产品不存在", "data": None}
if not file.filename or not file.filename.lower().endswith(".pdf"):
return {"code": 1003, "message": "仅支持 PDF 文件", "data": None}
upload_dir = os.path.join(current_app.config.get("UPLOAD_FOLDER", "uploads"), "manuals")
os.makedirs(upload_dir, exist_ok=True)
filename = f"{product_id}_{uuid.uuid4().hex[:8]}.pdf"
filepath = os.path.join(upload_dir, filename)
file.save(filepath)
product.manual_file_url = filepath
product.manual_parse_status = "pending"
db.session.commit()
return {"code": 0, "data": {"fileUrl": filepath}}
def parse_manual(self, product_id: str) -> dict:
product = PptProduct.query.get(product_id)
if not product:
return {"code": 1002, "message": "产品不存在", "data": None}
if not product.manual_file_url:
return {"code": 1003, "message": "请先上传小册子", "data": None}
if not os.path.exists(product.manual_file_url):
return {"code": 1004, "message": "小册子文件不存在", "data": None}
product.manual_parse_status = "parsing"
db.session.commit()
# 调用 LLM 解析 PDF
try:
import asyncio
from insurance.poster.manual_parser import parse_manual_pdf
from insurance.poster.service import _run_async
result = _run_async(parse_manual_pdf(product.manual_file_url))
product.manual_parsed_rules = json.dumps(result, ensure_ascii=False)
product.manual_parse_status = "parsed"
db.session.commit()
return {"code": 0, "data": {"status": "parsed", "rules": result}}
except Exception as e:
logger.warning(f"小册子解析失败: {e}")
product.manual_parse_status = "none"
db.session.commit()
return {"code": 1005, "message": f"解析失败: {e}", "data": None}
def review_manual(self, product_id: str, data: dict) -> dict:
product = PptProduct.query.get(product_id)
if not product:
return {"code": 1002, "message": "产品不存在", "data": None}
if product.manual_parse_status not in ("parsed", "reviewed"):
return {"code": 1003, "message": "产品尚未解析,无法核对", "data": None}
if data.get("rules"):
product.manual_parsed_rules = json.dumps(data["rules"], ensure_ascii=False)
product.manual_parse_status = "reviewed"
product.manual_reviewed_by = data.get("reviewedBy", "")
from datetime import datetime
product.manual_reviewed_at = datetime.now()
db.session.commit()
return {"code": 0, "data": product.to_dict()}
# ---- PPT 模板管理 ----
def list_templates(self, params: dict) -> dict:
query = db.session.query(PptTemplate)
if params.get("plan_type"):
query = query.filter(PptTemplate.plan_type == params["plan_type"])
if params.get("status") is not None:
query = query.filter(PptTemplate.status == params["status"])
page, page_size = _safe_page_params(params)
total = query.count()
items = query.offset((page - 1) * page_size).limit(page_size).all()
return {
"code": 0,
"data": {
"total": total,
"items": [t.to_dict() for t in items],
},
}
def create_template(self, data: dict) -> dict:
if not data.get("id"):
return {"code": 1001, "message": "id 必填", "data": None}
if PptTemplate.query.get(data["id"]):
return {"code": 1001, "message": "模板 ID 已存在", "data": None}
template = PptTemplate(
id=data["id"],
plan_type=data.get("planType", "savings"),
style_preset=data.get("stylePreset", "broker"),
name=data.get("name"),
scenario_tag=data.get("scenarioTag"),
preview_image=data.get("previewImage"),
applicable_company_ids=json.dumps(data.get("applicableCompanyIds", []), ensure_ascii=False) if data.get("applicableCompanyIds") else None,
applicable_product_ids=json.dumps(data.get("applicableProductIds", []), ensure_ascii=False) if data.get("applicableProductIds") else None,
status=data.get("status", 1),
)
db.session.add(template)
db.session.commit()
return {"code": 0, "data": template.to_dict()}
def update_template(self, template_id: str, data: dict) -> dict:
template = PptTemplate.query.get(template_id)
if not template:
return {"code": 1002, "message": "模板不存在", "data": None}
for key, attr in [("name", "name"), ("scenarioTag", "scenario_tag"), ("previewImage", "preview_image")]:
if key in data:
setattr(template, attr, data[key])
if "applicableCompanyIds" in data:
template.applicable_company_ids = json.dumps(data["applicableCompanyIds"], ensure_ascii=False) if data["applicableCompanyIds"] else None
if "applicableProductIds" in data:
template.applicable_product_ids = json.dumps(data["applicableProductIds"], ensure_ascii=False) if data["applicableProductIds"] else None
if "status" in data:
template.status = data["status"]
db.session.commit()
return {"code": 0, "data": template.to_dict()}
def update_template_status(self, template_id: str, data: dict) -> dict:
template = PptTemplate.query.get(template_id)
if not template:
return {"code": 1002, "message": "模板不存在", "data": None}
template.status = data.get("status", 1)
db.session.commit()
return {"code": 0, "data": template.to_dict()}
# ---- 海报模板管理 ----
def list_poster_templates(self, params: dict) -> dict:
query = db.session.query(PosterTemplate)
if params.get("status") is not None:
query = query.filter(PosterTemplate.status == params["status"])
page, page_size = _safe_page_params(params)
total = query.count()
items = query.offset((page - 1) * page_size).limit(page_size).all()
return {
"code": 0,
"data": {
"total": total,
"items": [t.to_dict() for t in items],
},
}
def create_poster_template(self, data: dict) -> dict:
if not data.get("name") or not data.get("styleDescription"):
return {"code": 1001, "message": "name 和 styleDescription 必填", "data": None}
template = PosterTemplate(
name=data["name"],
scenario_tag=data.get("scenarioTag"),
style_description=data["styleDescription"],
color_scheme=json.dumps(data["colorScheme"], ensure_ascii=False) if data.get("colorScheme") else None,
reference_image=data.get("referenceImage"),
preview_image=data.get("previewImage"),
status=data.get("status", 1),
)
db.session.add(template)
db.session.commit()
return {"code": 0, "data": template.to_dict()}
def update_poster_template(self, template_id: int, data: dict) -> dict:
template = PosterTemplate.query.get(template_id)
if not template:
return {"code": 1002, "message": "模板不存在", "data": None}
for key, attr in [("name", "name"), ("scenarioTag", "scenario_tag"),
("styleDescription", "style_description"),
("referenceImage", "reference_image"), ("previewImage", "preview_image")]:
if key in data:
setattr(template, attr, data[key])
if "colorScheme" in data:
template.color_scheme = json.dumps(data["colorScheme"], ensure_ascii=False) if data["colorScheme"] else None
if "status" in data:
template.status = data["status"]
db.session.commit()
return {"code": 0, "data": template.to_dict()}
def update_poster_template_status(self, template_id: int, data: dict) -> dict:
template = PosterTemplate.query.get(template_id)
if not template:
return {"code": 1002, "message": "模板不存在", "data": None}
template.status = data.get("status", 1)
db.session.commit()
return {"code": 0, "data": template.to_dict()}
# ---- 文案模板管理 ----
def list_copy_templates(self, params: dict) -> dict:
query = db.session.query(PosterCopyTemplate)
if params.get("status") is not None:
query = query.filter(PosterCopyTemplate.status == params["status"])
page, page_size = _safe_page_params(params)
total = query.count()
items = query.offset((page - 1) * page_size).limit(page_size).all()
return {
"code": 0,
"data": {
"total": total,
"items": [t.to_dict() for t in items],
},
}
def create_copy_template(self, data: dict) -> dict:
if not data.get("name") or not data.get("content"):
return {"code": 1001, "message": "name 和 content 必填", "data": None}
template = PosterCopyTemplate(
name=data["name"],
scenario_tag=data.get("scenarioTag"),
content=data["content"],
variables=json.dumps(data.get("variables", []), ensure_ascii=False) if data.get("variables") else None,
status=data.get("status", 1),
)
db.session.add(template)
db.session.commit()
return {"code": 0, "data": template.to_dict()}
def update_copy_template(self, template_id: int, data: dict) -> dict:
template = PosterCopyTemplate.query.get(template_id)
if not template:
return {"code": 1002, "message": "模板不存在", "data": None}
for key, attr in [("name", "name"), ("scenarioTag", "scenario_tag"), ("content", "content")]:
if key in data:
setattr(template, attr, data[key])
if "variables" in data:
template.variables = json.dumps(data["variables"], ensure_ascii=False) if data["variables"] else None
if "status" in data:
template.status = data["status"]
db.session.commit()
return {"code": 0, "data": template.to_dict()}
def delete_copy_template(self, template_id: int) -> dict:
template = PosterCopyTemplate.query.get(template_id)
if not template:
return {"code": 1002, "message": "模板不存在", "data": None}
db.session.delete(template)
db.session.commit()
return {"code": 0, "data": None}
# ---- 历史记录管理 ----
def list_history(self, params: dict) -> dict:
query = db.session.query(PptHistory)
if params.get("user_id"):
query = query.filter(PptHistory.user_id == params["user_id"])
if params.get("company_id"):
query = query.filter(PptHistory.company_id == params["company_id"])
if params.get("action_type"):
query = query.filter(PptHistory.action_type == params["action_type"])
query = query.order_by(PptHistory.created_at.desc())
page, page_size = _safe_page_params(params)
total = query.count()
items = query.offset((page - 1) * page_size).limit(page_size).all()
return {
"code": 0,
"data": {
"total": total,
"items": [h.to_dict() for h in items],
},
}
def delete_history(self, history_id: int) -> dict:
record = PptHistory.query.get(history_id)
if not record:
return {"code": 1002, "message": "记录不存在", "data": None}
db.session.delete(record)
db.session.commit()
return {"code": 0, "data": None}
def export_history_csv(self, params: dict):
"""导出历史记录为 CSV。"""
import csv
import io
query = db.session.query(PptHistory)
if params.get("user_id"):
query = query.filter(PptHistory.user_id == params["user_id"])
if params.get("company_id"):
query = query.filter(PptHistory.company_id == params["company_id"])
if params.get("action_type"):
query = query.filter(PptHistory.action_type == params["action_type"])
query = query.order_by(PptHistory.created_at.desc())
items = query.limit(10000).all() # 限制最大导出行数
output = io.StringIO()
output.write("") # UTF-8 BOM确保 Excel 正确显示中文
writer = csv.writer(output)
writer.writerow(["ID", "用户ID", "会话ID", "操作类型", "保司ID", "产品ID", "文件地址", "时间"])
for h in items:
writer.writerow([
h.id, h.user_id, h.session_id or "", h.action_type,
h.company_id or "", h.product_id or "",
h.file_url or "", str(h.created_at) if h.created_at else "",
])
return output.getvalue()
# ---- 系统配置 ----
def get_settings(self) -> dict:
settings = SystemSetting.query.all()
return {
"code": 0,
"data": {s.key: s.value for s in settings},
}
def update_settings(self, data: dict, updated_by: str = "") -> dict:
for key, value in data.items():
setting = SystemSetting.query.filter_by(key=key).first()
if setting:
setting.value = str(value)
setting.updated_by = updated_by
else:
db.session.add(SystemSetting(key=key, value=str(value), updated_by=updated_by))
db.session.commit()
return {"code": 0, "data": None}