baodan/api/insurance/models/user_product_material.py

89 lines
4.1 KiB
Python

"""用户私有产品小册子模型。"""
import json
from sqlalchemy import BigInteger, Column, Integer, SmallInteger, String, Text, TIMESTAMP, func
from insurance.db.compat import db
class UserProductMaterial(db.Model):
"""用户上传、解析并确认的产品小册子。"""
__tablename__ = "insurance_user_product_materials"
id = Column(BigInteger, primary_key=True, autoincrement=True)
owner_user_id = Column(String(64), nullable=False, index=True, comment="上传用户 ID")
tenant_id = Column(String(64), nullable=False, default="default", comment="租户 ID")
company_id = Column(String(50), nullable=True, comment="关联保司 ID")
company_name = Column(String(100), nullable=True, comment="保司名称")
product_name = Column(String(150), nullable=True, comment="产品名称")
plan_type = Column(String(20), nullable=True, comment="savings/ci/iul/other")
original_name = Column(String(200), nullable=False, comment="原始文件名")
file_key = Column(String(500), nullable=False, comment="相对 storage 根目录的文件键")
file_size = Column(BigInteger, nullable=False, default=0, comment="文件字节数")
page_count = Column(Integer, nullable=True, comment="PDF 页数")
sha256 = Column(String(64), nullable=False, comment="文件 SHA-256")
parse_status = Column(String(20), nullable=False, default="pending")
parse_message = Column(String(500), nullable=False, default="")
parse_error = Column(Text, nullable=True)
parse_task_id = Column(String(200), nullable=True)
parse_started_at = Column(TIMESTAMP, nullable=True)
parse_finished_at = Column(TIMESTAMP, nullable=True)
parsed_rules = Column(Text, nullable=True, comment="系统解析结果 JSON")
confirmed_rules = Column(Text, nullable=True, comment="用户确认结果 JSON")
confirmed_by = Column(String(64), nullable=True)
confirmed_at = Column(TIMESTAMP, nullable=True)
review_status = Column(String(20), nullable=False, default="private")
status = Column(SmallInteger, nullable=False, default=1)
deleted_at = Column(TIMESTAMP, nullable=True)
created_at = Column(TIMESTAMP, server_default=func.now())
updated_at = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now())
@staticmethod
def _safe_json(value):
if not value:
return None
try:
return json.loads(value)
except (json.JSONDecodeError, TypeError):
return None
@staticmethod
def _safe_parse_error(value):
if not value:
return ""
message = str(value).strip()
unsafe_markers = (
"traceback", "file \"", "\\", "/app/", "/api/",
"http://", "https://", "api_key", "token=",
)
if "\n" in message or any(marker in message.lower() for marker in unsafe_markers):
return "解析失败,请重试;如多次失败请联系管理员"
return message[:300]
def to_dict(self):
return {
"id": self.id,
"ownerUserId": self.owner_user_id,
"tenantId": self.tenant_id,
"companyId": self.company_id,
"companyName": self.company_name or "",
"productName": self.product_name or "",
"displayName": self.product_name or self.original_name,
"planType": self.plan_type or "other",
"originalName": self.original_name,
"fileSize": self.file_size or 0,
"pageCount": self.page_count,
"parseStatus": self.parse_status,
"parseMessage": self.parse_message or "",
"parseError": self._safe_parse_error(self.parse_error),
"parsedRules": self._safe_json(self.parsed_rules),
"confirmedRules": self._safe_json(self.confirmed_rules),
"confirmedBy": self.confirmed_by,
"confirmedAt": self.confirmed_at.isoformat() if self.confirmed_at else None,
"reviewStatus": self.review_status,
"status": self.status,
"createdAt": self.created_at.isoformat() if self.created_at else None,
"updatedAt": self.updated_at.isoformat() if self.updated_at else None,
}