"""PPT 配置模型(公司、产品、模板)。""" import uuid from sqlalchemy import Column, String, Text, Boolean, Integer, SmallInteger, TIMESTAMP, func from insurance.db.compat import db class PptCompany(db.Model): """保险公司配置表。""" __tablename__ = "insurance_ppt_companies" id = Column(String(50), primary_key=True, comment="公司 ID,如 aia/ctf/fwd") display_name = Column(String(100), nullable=False, comment="显示名称") aliases_json = Column(Text, nullable=True, comment="别名列表 JSON") tenant_id = Column(String(50), default="default", comment="租户 ID") knowledge_directories_json = Column(Text, nullable=True, comment="知识库目录 JSON") evidence_ranking_json = Column(Text, nullable=True, comment="证据排序关键词 JSON") company_intro = Column(Text, nullable=True, comment="公司简介") company_highlights_json = Column(Text, nullable=True, comment="公司亮点 JSON") # 脱敏展示名(PPT/海报导出时替代真实名称) masked_display_name = Column(String(100), nullable=True, comment="脱敏展示名") masking_enabled = Column(Boolean, default=False, nullable=False, comment="是否启用名称脱敏") # 品牌信息 name_zh = Column(String(100), nullable=True, comment="中文名称") name_en = Column(String(100), nullable=True, comment="英文名称") short_en = Column(String(20), nullable=True, comment="英文缩写") rating = Column(String(50), nullable=True, comment="评级") founded_year = Column(Integer, nullable=True, comment="成立年份") # 扩展字段(PPT/海报功能) logo_url = Column(String(500), nullable=True, comment="公司 Logo 图片地址") logo_enabled = Column(Boolean, default=True, nullable=False, comment="生成物是否展示 Logo") status = Column(SmallInteger, default=1, comment="1=启用, 0=停用") sort_order = Column(Integer, default=0, comment="排序权重") created_at = Column(TIMESTAMP, server_default=func.now()) updated_at = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) deleted_at = Column(TIMESTAMP, nullable=True, comment="软删除时间") def to_dict(self): import json return { "id": self.id, "displayName": self.display_name, "maskedDisplayName": self.masked_display_name or "", "maskingEnabled": bool(self.masking_enabled), "aliases": json.loads(self.aliases_json) if self.aliases_json else [], "tenantId": self.tenant_id, "knowledgeDirectories": json.loads(self.knowledge_directories_json) if self.knowledge_directories_json else [], "evidenceRanking": json.loads(self.evidence_ranking_json) if self.evidence_ranking_json else [], "companyIntro": self.company_intro, "companyHighlights": json.loads(self.company_highlights_json) if self.company_highlights_json else [], "rating": self.rating, "foundedYear": self.founded_year, "nameZh": self.name_zh, "nameEn": self.name_en, "shortEn": self.short_en, "logoUrl": self.logo_url, "logoEnabled": bool(self.logo_enabled), "status": self.status, "sortOrder": self.sort_order, "deletedAt": self.deleted_at.isoformat() if self.deleted_at else None, } class PptProduct(db.Model): """保险产品配置表。""" __tablename__ = "insurance_ppt_products" id = Column(String(50), primary_key=True, comment="产品 ID") company_id = Column(String(50), nullable=False, comment="所属公司 ID") plan_type = Column(String(10), nullable=False, comment="产品类型: savings/ci/iul") display_name = Column(String(100), nullable=False, comment="显示名称") aliases_json = Column(Text, nullable=True, comment="别名列表 JSON") required_modules_json = Column(Text, nullable=True, comment="所需模块 JSON") # 脱敏展示名(PPT/海报导出时替代真实名称) masked_display_name = Column(String(100), nullable=True, comment="脱敏展示名") masking_enabled = Column(Boolean, default=False, nullable=False, comment="是否启用名称脱敏") # 扩展字段 product_code = Column(String(50), nullable=True, comment="产品编码") product_type = Column(String(20), nullable=True, comment="产品类型: savings/ci/iul") coverage_period = Column(String(50), nullable=True, comment="保障期限") payment_period = Column(String(50), nullable=True, comment="缴费期限") insured_age_range = Column(String(50), nullable=True, comment="投保年龄范围") waiting_period = Column(String(50), nullable=True, comment="等待期") highlights = Column(Text, nullable=True, comment="产品亮点 JSON") extra_fields = Column(Text, nullable=True, comment="扩展字段 JSON") status = Column(SmallInteger, default=1, comment="1=启用, 0=停用") sort_order = Column(Integer, default=0, comment="排序") # 海报相关 manual_file_url = Column(String(500), nullable=True, comment="产品小册子 PDF 地址") manual_parse_status = Column(String(20), default="none", comment="none/pending/queued/parsing/parsed/reviewed/failed") manual_parse_message = Column(String(500), default="", comment="解析进度消息") manual_parse_error = Column(Text, nullable=True, comment="解析失败原因") manual_parse_task_id = Column(String(200), nullable=True, comment="Celery 任务ID") manual_parse_started_at = Column(TIMESTAMP, nullable=True, comment="解析开始时间") manual_parse_finished_at = Column(TIMESTAMP, nullable=True, comment="解析完成时间") manual_parsed_rules = Column(Text, nullable=True, comment="解析出的产品规则 JSON") manual_reviewed_by = Column(String(50), nullable=True, comment="核对人") manual_reviewed_at = Column(TIMESTAMP, nullable=True, comment="核对时间") created_at = Column(TIMESTAMP, server_default=func.now()) updated_at = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) deleted_at = Column(TIMESTAMP, nullable=True, comment="软删除时间") def to_dict(self): import json return { "id": self.id, "companyId": self.company_id, "planType": self.plan_type, "displayName": self.display_name, "maskedDisplayName": self.masked_display_name or "", "maskingEnabled": bool(self.masking_enabled), "aliases": json.loads(self.aliases_json) if self.aliases_json else [], "requiredModules": json.loads(self.required_modules_json) if self.required_modules_json else [], "productCode": self.product_code, "productType": self.product_type, "coveragePeriod": self.coverage_period, "paymentPeriod": self.payment_period, "insuredAgeRange": self.insured_age_range, "waitingPeriod": self.waiting_period, "highlights": json.loads(self.highlights) if self.highlights else [], "extraFields": json.loads(self.extra_fields) if self.extra_fields else {}, "status": self.status, "sortOrder": self.sort_order, "manualFileUrl": self.manual_file_url, "manualParseStatus": self.manual_parse_status, "manualParseMessage": self.manual_parse_message or "", "manualParseError": self.manual_parse_error or "", "manualParseTaskId": self.manual_parse_task_id, "manualParseStartedAt": self.manual_parse_started_at.isoformat() if self.manual_parse_started_at else None, "manualParseFinishedAt": self.manual_parse_finished_at.isoformat() if self.manual_parse_finished_at else None, "manualParsedRules": json.loads(self.manual_parsed_rules) if self.manual_parsed_rules else None, "manualReviewedBy": self.manual_reviewed_by, "manualReviewedAt": self.manual_reviewed_at.isoformat() if self.manual_reviewed_at else None, "deletedAt": self.deleted_at.isoformat() if self.deleted_at else None, } class PptTemplate(db.Model): """PPT 模板配置表。""" __tablename__ = "insurance_ppt_templates" id = Column(String(50), primary_key=True, comment="模板 ID") plan_type = Column(String(10), nullable=False, comment="产品类型: savings/ci/iul") style_preset = Column(String(20), nullable=False, comment="风格: broker/business/minimal/chinese/ink") source_template_asset_id = Column(String(255), nullable=True, comment="源模板资产 ID") asset_sha256 = Column(String(64), nullable=True, comment="当前模板资产 SHA-256") asset_version = Column(Integer, default=1, comment="当前模板资产版本") clone_ready = Column(Boolean, default=False, comment="是否克隆就绪") clone_renderer = Column(String(100), nullable=True, comment="克隆渲染器 ID") required_page_types_json = Column(Text, nullable=True, comment="必需页面类型 JSON") # 扩展字段 name = Column(String(100), nullable=True, comment="模板名称") scenario_tag = Column(String(50), nullable=True, comment="场景标签") preview_image = Column(String(500), nullable=True, comment="预览图地址") applicable_company_ids = Column(Text, nullable=True, comment="适用保司 ID 列表 JSON") applicable_product_ids = Column(Text, nullable=True, comment="适用产品 ID 列表 JSON") slides_config_json = Column(Text, nullable=True, comment="逐页幻灯片配置 JSON") status = Column(SmallInteger, default=1, comment="1=启用, 0=停用") created_at = Column(TIMESTAMP, server_default=func.now()) deleted_at = Column(TIMESTAMP, nullable=True, comment="软删除时间") def to_dict(self): import json try: slides = json.loads(self.slides_config_json) if self.slides_config_json else [] except (json.JSONDecodeError, TypeError): slides = [] try: required_types = json.loads(self.required_page_types_json) if self.required_page_types_json else [] except (json.JSONDecodeError, TypeError): required_types = [] try: company_ids = json.loads(self.applicable_company_ids) if self.applicable_company_ids else [] except (json.JSONDecodeError, TypeError): company_ids = [] try: product_ids = json.loads(self.applicable_product_ids) if self.applicable_product_ids else [] except (json.JSONDecodeError, TypeError): product_ids = [] return { "id": self.id, "planType": self.plan_type, "stylePreset": self.style_preset, "sourceTemplateAssetId": self.source_template_asset_id, "assetSha256": self.asset_sha256, "assetVersion": self.asset_version or 1, "sourceFileUrl": ( f"/insurance/admin/ppt/templates/{self.id}/file" if self.source_template_asset_id else "" ), "isBuiltIn": str(self.source_template_asset_id or "").startswith("builtin://"), "cloneReady": self.clone_ready, "cloneRenderer": self.clone_renderer, "requiredPageTypes": required_types, "name": self.name, "scenarioTag": self.scenario_tag, "previewImage": self.preview_image, "applicableCompanyIds": company_ids, "applicableProductIds": product_ids, "slidesConfig": slides, "slideCount": len(slides), "status": self.status, "deletedAt": self.deleted_at.isoformat() if self.deleted_at else None, } class PptScenario(db.Model): """PPT 生成场景;场景分类与底层计算模式分离。""" __tablename__ = "insurance_ppt_scenarios" code = Column(String(50), primary_key=True) name = Column(String(100), nullable=False) base_scenario = Column(String(50), nullable=True) generation_mode = Column(String(20), nullable=False, default="single") description = Column(Text, nullable=True) status = Column(SmallInteger, default=1, nullable=False) sort_order = Column(Integer, default=0, nullable=False) is_builtin = Column(Boolean, default=False, nullable=False) created_at = Column(TIMESTAMP, server_default=func.now()) updated_at = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) deleted_at = Column(TIMESTAMP, nullable=True) def to_dict(self): return { "code": self.code, "name": self.name, "baseScenario": self.base_scenario, "generationMode": self.generation_mode, "description": self.description or "", "status": self.status, "sortOrder": self.sort_order, "isBuiltIn": bool(self.is_builtin), "deletedAt": self.deleted_at.isoformat() if self.deleted_at else None, } class PptBundle(db.Model): """PPT Bundle 组合配置表。""" __tablename__ = "insurance_ppt_bundles" id = Column(String(50), primary_key=True, comment="Bundle ID") display_name = Column(String(100), nullable=False, comment="显示名称") products_json = Column(Text, nullable=False, comment="产品类型列表 JSON") template_family = Column(String(50), default="bundle", comment="模板族") status = Column(String(20), default="active", comment="状态: active/inactive") modules_json = Column(Text, nullable=True, comment="模块列表 JSON") created_at = Column(TIMESTAMP, server_default=func.now()) def to_dict(self): import json return { "id": self.id, "displayName": self.display_name, "products": json.loads(self.products_json) if self.products_json else [], "templateFamily": self.template_family, "status": self.status, "modules": json.loads(self.modules_json) if self.modules_json else [], } class CompanyLogo(db.Model): """保司Logo图片表(支持多张)。""" __tablename__ = "insurance_ppt_company_logos" id = Column(String(36), primary_key=True, default=lambda: uuid.uuid4().hex) company_id = Column(String(50), nullable=False, index=True, comment="所属公司 ID") file_path = Column(String(500), nullable=False, comment="服务器文件路径") file_url = Column(String(500), nullable=True, comment="可访问的URL") original_name = Column(String(200), nullable=True, comment="原始文件名") mime_type = Column(String(50), nullable=True, comment="MIME类型") file_size = Column(Integer, nullable=True, comment="文件大小(字节)") is_primary = Column(Boolean, default=False, comment="是否为主Logo") sort_order = Column(Integer, default=0, comment="排序权重") created_at = Column(TIMESTAMP, server_default=func.now()) def to_dict(self): return { "id": self.id, "companyId": self.company_id, "url": self.file_url or f"/insurance/admin/ppt/assets/company-logos/{self.id}", "originalName": self.original_name or "", "mimeType": self.mime_type, "fileSize": self.file_size, "isPrimary": self.is_primary, "sortOrder": self.sort_order, }