chore: initial baseline with P0-safety .gitignore
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 模型包初始化
|
||||
# =============================================================================
|
||||
# 说明:导出所有模型类,方便 Alembic 和其他模块统一导入
|
||||
# 注意:即使某些模型在当前文件未直接使用,也必须导入
|
||||
# 否则 Alembic 无法检测到这些模型,不会生成对应的迁移脚本
|
||||
# =============================================================================
|
||||
|
||||
from app.models.conversation import Conversation
|
||||
from app.models.message import Message
|
||||
from app.models.agent import Agent
|
||||
from app.models.quick_reply_template import QuickReplyTemplate
|
||||
from app.models.system_config import SystemConfig
|
||||
from app.models.funny_phrase import FunnyPhrase
|
||||
from app.models.approval_link import ApprovalLink
|
||||
from app.models.software_download import SoftwareDownload
|
||||
from app.models.agent_note import AgentNote
|
||||
from app.models.employee import Employee
|
||||
from app.models.todo_item import TodoItem
|
||||
from app.models.troubleshooting_template import TroubleshootingTemplate
|
||||
from app.models.config_change_log import ConfigChangeLog
|
||||
from app.models.role import Role
|
||||
from app.models.user_role import UserRole
|
||||
from app.models.role_mapping_rule import RoleMappingRule
|
||||
|
||||
# 所有模型类的列表,方便遍历
|
||||
__all__ = [
|
||||
"Conversation",
|
||||
"Message",
|
||||
"Agent",
|
||||
"QuickReplyTemplate",
|
||||
"SystemConfig",
|
||||
"FunnyPhrase",
|
||||
"ApprovalLink",
|
||||
"SoftwareDownload",
|
||||
"AgentNote",
|
||||
"Employee",
|
||||
"TodoItem",
|
||||
"TroubleshootingTemplate",
|
||||
"ConfigChangeLog",
|
||||
"Role",
|
||||
"UserRole",
|
||||
"RoleMappingRule",
|
||||
]
|
||||
@@ -0,0 +1,146 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 坐席模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 agents 表,存储坐席(IT服务人员)信息
|
||||
# 坐席状态:online(在线)/offline(离线)/busy(忙碌)
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, Integer, JSON, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Agent(Base):
|
||||
"""坐席模型 — 对应 agents 表。
|
||||
|
||||
记录坐席的基本信息和状态,用于消息分配和负载管理。
|
||||
|
||||
Attributes:
|
||||
id: 坐席唯一标识(UUID,数据库自动生成)
|
||||
user_id: 企微用户ID(唯一,关联企微通讯录)
|
||||
name: 坐席姓名
|
||||
status: 坐席状态(online/offline/busy)
|
||||
current_load: 当前服务会话数
|
||||
max_load: 最大同时服务数(默认5)
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
# 表名(必须和架构文档 DDL 一致)
|
||||
__tablename__ = "agents"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID,Python端生成(兼容PostgreSQL和SQLite)
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 企微用户ID(唯一,用于关联企微通讯录和登录认证)
|
||||
user_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
unique=True,
|
||||
nullable=False,
|
||||
comment="企微用户ID(唯一)",
|
||||
)
|
||||
|
||||
# 坐席姓名
|
||||
name: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
comment="坐席姓名",
|
||||
)
|
||||
|
||||
# 坐席状态(CHECK 约束:只能取三种值)
|
||||
# online: 在线,可以接收新的会话分配
|
||||
# offline: 离线,不接收任何会话
|
||||
# busy: 忙碌,不接收新会话但继续处理已有的
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="offline",
|
||||
comment="坐席状态: online/offline/busy",
|
||||
)
|
||||
|
||||
# 当前服务会话数(分配新会话时 +1,结单时 -1)
|
||||
current_load: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="当前服务会话数",
|
||||
)
|
||||
|
||||
# 最大同时服务数(坐席同时处理的会话数上限)
|
||||
# 默认5个,可根据坐席能力调整
|
||||
max_load: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=5,
|
||||
comment="最大同时服务数",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
# 角色(admin=组长, agent=坐席)
|
||||
# 管理后台需要 admin 角色才能访问,坐席端无限制
|
||||
role: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="agent",
|
||||
comment="角色:admin=组长, agent=坐席",
|
||||
)
|
||||
|
||||
# 技能标签列表(JSON 数组,存储坐席的技能分类)
|
||||
# 可选值:电脑/软件/外设/网络/安全/资产/其他
|
||||
skill_tags: Mapped[list] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=list,
|
||||
comment="技能标签列表(电脑/软件/外设/网络/安全/资产/其他)",
|
||||
)
|
||||
|
||||
# OTP密钥(用于TOTP动态码验证,为空表示未绑定)
|
||||
otp_secret: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="OTP密钥(Base32编码)",
|
||||
)
|
||||
|
||||
# OTP是否启用(admin角色强制启用)
|
||||
otp_enabled: Mapped[bool] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="OTP是否启用(0=否, 1=是)",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""坐席对象的字符串表示,方便调试。"""
|
||||
return (
|
||||
f"<Agent(id={self.id}, name={self.name}, "
|
||||
f"status={self.status}, load={self.current_load}/{self.max_load})>"
|
||||
)
|
||||
@@ -0,0 +1,100 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 坐席备注模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 agent_notes 表,存储坐席对会话的备注
|
||||
# 用途:坐席可以记录处理过程中的关键信息,方便后续跟进
|
||||
# 一个会话可以有多条备注(不同坐席或同一坐席多次记录)
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Index, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class AgentNote(Base):
|
||||
"""坐席备注模型 — 对应 agent_notes 表。
|
||||
|
||||
记录坐席在处理会话时添加的备注信息。
|
||||
一个会话可以有多条备注。
|
||||
|
||||
Attributes:
|
||||
id: 备注唯一标识(UUID,数据库自动生成)
|
||||
conversation_id: 所属会话ID(外键,关联 conversations 表)
|
||||
agent_id: 坐席ID
|
||||
content: 备注内容
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
# 表名(必须和架构文档 DDL 一致)
|
||||
__tablename__ = "agent_notes"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID,Python端生成(兼容PostgreSQL和SQLite)
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 所属会话ID(外键,关联 conversations 表)
|
||||
# ON DELETE CASCADE:删除会话时自动删除该会话的所有备注
|
||||
conversation_id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
ForeignKey("conversations.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
comment="所属会话ID",
|
||||
)
|
||||
|
||||
# 坐席ID(记录是哪个坐席写的备注)
|
||||
agent_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
comment="坐席ID",
|
||||
)
|
||||
|
||||
# 备注内容(坐席自由输入的文本)
|
||||
content: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
comment="备注内容",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 索引定义(和架构文档 DDL 严格一致)
|
||||
# --------------------------------------------------------------------------
|
||||
__table_args__ = (
|
||||
# 按会话ID查询(如获取某会话的所有备注)
|
||||
Index("idx_an_conversation", "conversation_id"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""备注对象的字符串表示,方便调试。"""
|
||||
return (
|
||||
f"<AgentNote(id={self.id}, conv={self.conversation_id}, "
|
||||
f"agent={self.agent_id})>"
|
||||
)
|
||||
@@ -0,0 +1,104 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 审批流程链接模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 approval_links 表,存储审批流程的外部链接
|
||||
# 分类:IT/HR/行政/财务
|
||||
# 在H5用户端右侧AI助手面板中展示,方便员工快速访问审批页面
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, Index, Integer, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class ApprovalLink(Base):
|
||||
"""审批流程链接模型 — 对应 approval_links 表。
|
||||
|
||||
存储公司各类审批流程的外部链接,
|
||||
在H5用户端AI助手面板中按分类展示。
|
||||
|
||||
Attributes:
|
||||
id: 链接唯一标识(UUID,数据库自动生成)
|
||||
category: 分类(IT/HR/行政/财务)
|
||||
title: 审批名称
|
||||
url: 审批链接
|
||||
sort_order: 排序权重
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
# 表名(必须和架构文档 DDL 一致)
|
||||
__tablename__ = "approval_links"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID,Python端生成(兼容PostgreSQL和SQLite)
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 分类(按部门分类,方便在H5面板中折叠展示)
|
||||
category: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
comment="分类:IT/HR/行政/财务",
|
||||
)
|
||||
|
||||
# 审批名称(展示给用户看的标题)
|
||||
title: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
comment="审批名称",
|
||||
)
|
||||
|
||||
# 审批链接(点击后跳转到对应的审批页面)
|
||||
url: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
comment="审批链接",
|
||||
)
|
||||
|
||||
# 排序权重(同一分类内排序,数值越小越靠前)
|
||||
sort_order: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="排序权重",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 索引定义(和架构文档 DDL 严格一致)
|
||||
# --------------------------------------------------------------------------
|
||||
__table_args__ = (
|
||||
# 按分类查询(如获取所有"IT"分类的审批链接)
|
||||
Index("idx_al_category", "category"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""链接对象的字符串表示,方便调试。"""
|
||||
return f"<ApprovalLink(id={self.id}, category={self.category}, title={self.title})>"
|
||||
@@ -0,0 +1,95 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 配置变更日志模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 config_change_logs 表,记录每次配置项的变更历史
|
||||
# 包含变更前后的值、操作人和时间,用于配置审计和回滚
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, Index, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class ConfigChangeLog(Base):
|
||||
"""配置变更日志模型 — 对应 config_change_logs 表。
|
||||
|
||||
记录每次配置项的变更历史,包含变更前后的值、操作人和时间。
|
||||
|
||||
Attributes:
|
||||
id: 日志唯一标识(UUID)
|
||||
config_key: 变更的配置键
|
||||
old_value: 变更前的值
|
||||
new_value: 变更后的值
|
||||
changed_by: 变更操作人(agent_id)
|
||||
changed_at: 变更时间
|
||||
"""
|
||||
|
||||
# 表名(必须和架构文档 DDL 一致)
|
||||
__tablename__ = "config_change_logs"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID,Python端生成(兼容PostgreSQL和SQLite)
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 配置键(关联 system_configs 表的 config_key)
|
||||
config_key: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
comment="配置键",
|
||||
)
|
||||
|
||||
# 变更前的值(空字符串表示新增配置项)
|
||||
old_value: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="变更前的值",
|
||||
)
|
||||
|
||||
# 变更后的值
|
||||
new_value: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="变更后的值",
|
||||
)
|
||||
|
||||
# 变更操作人(关联 agents 表的 id)
|
||||
changed_by: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
nullable=False,
|
||||
comment="变更操作人 agent_id",
|
||||
)
|
||||
|
||||
# 变更时间
|
||||
changed_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="变更时间",
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 索引定义(和架构文档 DDL 严格一致)
|
||||
# --------------------------------------------------------------------------
|
||||
__table_args__ = (
|
||||
# 按配置键查询(如查询某配置项的所有变更历史)
|
||||
Index("idx_ccl_config_key", "config_key"),
|
||||
# 按变更时间查询(如查询最近的变更记录)
|
||||
Index("idx_ccl_changed_at", "changed_at"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""变更日志对象的字符串表示,方便调试。"""
|
||||
return f"<ConfigChangeLog(key={self.config_key}, by={self.changed_by})>"
|
||||
@@ -0,0 +1,292 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 会话模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 conversations 表,存储所有会话信息
|
||||
# 核心概念:每个员工的每次咨询对应一个会话(Conversation)
|
||||
# 会话状态流转:ai_handling → queued → serving → resolved
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Index, Integer, JSON, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Conversation(Base):
|
||||
"""会话模型 — 对应 conversations 表。
|
||||
|
||||
每个员工的一次完整咨询过程对应一个会话记录。
|
||||
包含员工信息、会话状态、紧急度评分、标签等核心数据。
|
||||
|
||||
Attributes:
|
||||
id: 会话唯一标识(UUID,数据库自动生成)
|
||||
employee_id: 企微员工UserID(关联企微通讯录)
|
||||
employee_name: 员工姓名(冗余存储,减少关联查询)
|
||||
department: 员工部门
|
||||
position: 员工岗位
|
||||
level: 员工等级(用于 VIP 判断)
|
||||
status: 会话状态(ai_handling/queued/serving/resolved)
|
||||
is_vip: VIP标记(基于企微通讯录规则自动匹配)
|
||||
is_pinned: 置顶标记(坐席手动操作)
|
||||
is_todo: 代办标记(坐席手动操作)
|
||||
urgency_score: 紧急度评分(1-5,数值越大越紧急)
|
||||
tags: 标签集合(JSONB,存储举手/需介入/情绪等标记)
|
||||
assigned_agent_id: 分配的坐席ID
|
||||
last_message_at: 最后消息时间(用于会话排序)
|
||||
last_message_summary: 最后消息摘要(会话列表预览用)
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
# 表名(必须和架构文档 DDL 一致)
|
||||
__tablename__ = "conversations"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID,Python端生成(兼容PostgreSQL和SQLite)
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 企业微信企业ID(US-7: 区分主企业和下游企业员工)
|
||||
# 默认值为主企业 corp_id,下游企业员工使用下游企业 corp_id
|
||||
corp_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="企业微信企业ID(主企业或下游企业)",
|
||||
)
|
||||
|
||||
# 企微员工UserID(NOT NULL,配合 corp_id 唯一标识员工)
|
||||
employee_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
comment="企微员工UserID",
|
||||
)
|
||||
|
||||
# 员工姓名(冗余存储,避免每次查询都要关联企微API)
|
||||
employee_name: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="员工姓名",
|
||||
)
|
||||
|
||||
# 部门
|
||||
department: Mapped[str] = mapped_column(
|
||||
String(256),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="部门",
|
||||
)
|
||||
|
||||
# 岗位
|
||||
position: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="岗位",
|
||||
)
|
||||
|
||||
# 等级(用于 VIP 判断:总监及以上为 VIP)
|
||||
level: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="等级",
|
||||
)
|
||||
|
||||
# 会话状态(CHECK 约束:只能取四种值)
|
||||
# ai_handling: AI处理中(第二步启用)
|
||||
# queued: 排队中,等待坐席接入
|
||||
# serving: 服务中,坐席正在处理
|
||||
# resolved: 已结单
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="queued",
|
||||
comment="会话状态: ai_handling/queued/serving/resolved",
|
||||
)
|
||||
|
||||
# VIP标记(基于企微通讯录API规则自动匹配)
|
||||
is_vip: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=False,
|
||||
comment="VIP标记",
|
||||
)
|
||||
|
||||
# 置顶标记(坐席手动操作,置顶的会话在列表中优先显示)
|
||||
is_pinned: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=False,
|
||||
comment="置顶标记",
|
||||
)
|
||||
|
||||
# 代办标记(坐席手动操作,标记需要后续跟进的会话)
|
||||
is_todo: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=False,
|
||||
comment="代办标记",
|
||||
)
|
||||
|
||||
# 紧急度评分(1-5,数值越大越紧急)
|
||||
# 计算公式:基础分 + 情绪加成 + VIP加成 + 重复追问加成
|
||||
urgency_score: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=1,
|
||||
comment="紧急度1-5",
|
||||
)
|
||||
|
||||
# 标签集合(JSON 格式,存储结构化标记数据,兼容所有数据库)
|
||||
# 示例:{"hand_raise": true, "emotion": "angry", "need_intervene": true}
|
||||
tags: Mapped[Dict[str, Any]] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=dict,
|
||||
comment="标签集合",
|
||||
)
|
||||
|
||||
# 分配的坐席ID(可为空,表示尚未分配坐席)
|
||||
assigned_agent_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(64),
|
||||
nullable=True,
|
||||
comment="分配的坐席ID",
|
||||
)
|
||||
|
||||
# 协作坐席ID列表(JSON 数组,存储所有被邀请来协作的坐席ID)
|
||||
# 和 assigned_agent_id 的区别:
|
||||
# - assigned_agent_id:会话的「主责」坐席(接单人),只有他才能结单/转接
|
||||
# - collaborating_agent_ids:被邀请来协助的坐席,可以查看和回复,但不能结单
|
||||
# 设计决策:用 JSON 而非关联表,因为协作人数少(1-3人),JSON 查询足够
|
||||
collaborating_agent_ids: Mapped[list] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=list,
|
||||
comment="协作坐席ID列表",
|
||||
)
|
||||
|
||||
# 被邀请参与会话的非坐席人员列表(JSON 数组,存储员工ID或部门ID)
|
||||
# 和 collaborating_agent_ids 的区别:
|
||||
# - collaborating_agent_ids:坐席 → 坐席协作(摇人)
|
||||
# - participants:坐席 → 任意员工/部门(邀请功能 P0-09~P0-11)
|
||||
# 格式:[ {"id": "employee_id", "name": "姓名", "department": "部门", "type": "employee"},
|
||||
# {"id": "dept_id", "name": "部门名", "type": "department"} ]
|
||||
# 设计决策:存储完整信息,减少企微API调用
|
||||
participants: Mapped[list] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=list,
|
||||
comment="被邀请参与会话的人员列表(邀请功能)",
|
||||
)
|
||||
|
||||
# AI 实质性回复计数(排除打招呼/呼叫人工的引导回复)
|
||||
# 当计数 >= 3 时,前端显示「呼叫坐席」按钮
|
||||
ai_substantive_reply_count: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="AI实质性回复计数(满3次可呼叫坐席)",
|
||||
)
|
||||
|
||||
# 影响范围(受影响人数,0=未评估,数值越大影响范围越广)
|
||||
impact_scope: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="影响范围",
|
||||
)
|
||||
|
||||
# 阻断性标记(问题是否阻断员工正常工作流程)
|
||||
is_blocking: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=False,
|
||||
comment="阻断性标记",
|
||||
)
|
||||
|
||||
# 情绪状态(normal: 正常, worried: 担忧, angry: 愤怒, urgent: 紧急)
|
||||
emotion_state: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="normal",
|
||||
comment="情绪状态",
|
||||
)
|
||||
|
||||
# Dify 会话ID(用于多轮对话上下文保持)
|
||||
# Dify 侧通过此 ID 关联同一员工的多轮对话
|
||||
dify_conversation_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(128),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="Dify会话ID(多轮对话上下文)",
|
||||
)
|
||||
|
||||
# 最后消息时间(用于会话列表按最新消息排序)
|
||||
last_message_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
comment="最后消息时间",
|
||||
)
|
||||
|
||||
# 最后消息摘要(会话列表预览用,截取消息前256字符)
|
||||
last_message_summary: Mapped[str] = mapped_column(
|
||||
String(256),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="最后消息摘要",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 索引定义(和架构文档 DDL 严格一致)
|
||||
# --------------------------------------------------------------------------
|
||||
__table_args__ = (
|
||||
# 按状态查询(如查询所有排队中的会话)
|
||||
Index("idx_conversations_status", "status"),
|
||||
# 按员工ID查询(如查询某个员工的所有会话)
|
||||
Index("idx_conversations_employee_id", "employee_id"),
|
||||
# US-7: 按企业ID查询(如查询某企业所有会话)
|
||||
Index("idx_conversations_corp_id", "corp_id"),
|
||||
# 按坐席ID查询(如查询某个坐席正在服务的所有会话)
|
||||
Index("idx_conversations_assigned_agent", "assigned_agent_id"),
|
||||
# 按紧急度倒序查询(紧急度高的排前面)
|
||||
Index("idx_conversations_urgency_score", "urgency_score"),
|
||||
# 按最后消息时间倒序查询(最新消息的排前面)
|
||||
Index("idx_conversations_last_message_at", "last_message_at"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""会话对象的字符串表示,方便调试。"""
|
||||
return (
|
||||
f"<Conversation(id={self.id}, employee={self.employee_name}, "
|
||||
f"status={self.status}, urgency={self.urgency_score})>"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 员工模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 employees 表,存储通过 OAuth2 认证的员工信息
|
||||
# US-7 扩展:增加 corp_id 字段支持上下游互联企业场景
|
||||
# 主企业(亿企赢总部): corp_id = 主企业 corp_id
|
||||
# 下游企业(亿企赢): corp_id = 下游企业 corp_id
|
||||
# 复合唯一键 (corp_id, employee_id) 确保跨企业员工标识唯一
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, Index, JSON, String, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Employee(Base):
|
||||
"""员工模型 — 对应 employees 表。
|
||||
|
||||
通过 OAuth2 认证后记录员工信息,支持跨企业场景(US-7)。
|
||||
与 Conversation.employee_id 通过 (corp_id, employee_id) 关联。
|
||||
|
||||
Attributes:
|
||||
id: 主键(UUID,数据库自动生成)
|
||||
corp_id: 企业微信企业ID(主企业或下游企业)
|
||||
employee_id: 员工UserID(企业内唯一)
|
||||
name: 员工姓名
|
||||
department: 部门(JSON数组字符串)
|
||||
position: 岗位
|
||||
mobile: 手机号
|
||||
email: 邮箱
|
||||
avatar: 头像URL
|
||||
status: 激活状态(1=已激活, 2=已禁用, 4=未激活)
|
||||
last_login_at: 最后登录时间
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
# 表名
|
||||
__tablename__ = "employees"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
comment="员工记录唯一标识",
|
||||
)
|
||||
|
||||
# 企业微信企业ID(主企业或下游企业)
|
||||
# US-7: 用于区分不同企业的员工,格式如 "wwa8c87970b2011f41"
|
||||
corp_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
comment="企业微信企业ID",
|
||||
)
|
||||
|
||||
# 员工UserID(企业内唯一)
|
||||
# 注意:不同企业的 userid 可能重复,需配合 corp_id 使用
|
||||
employee_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
comment="企微员工UserID(企业内唯一)",
|
||||
)
|
||||
|
||||
# 员工姓名
|
||||
name: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="员工姓名",
|
||||
)
|
||||
|
||||
# 部门(JSON数组字符串,如 "[1, 2, 3]")
|
||||
department: Mapped[str] = mapped_column(
|
||||
String(512),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="部门ID列表(JSON数组)",
|
||||
)
|
||||
|
||||
# 岗位
|
||||
position: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="岗位",
|
||||
)
|
||||
|
||||
# 手机号
|
||||
mobile: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="手机号",
|
||||
)
|
||||
|
||||
# 邮箱
|
||||
email: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="邮箱",
|
||||
)
|
||||
|
||||
# 头像URL
|
||||
avatar: Mapped[str] = mapped_column(
|
||||
String(512),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="头像URL",
|
||||
)
|
||||
|
||||
# 激活状态(企微通讯录返回: 1=已激活, 2=已禁用, 4=未激活)
|
||||
status: Mapped[int] = mapped_column(
|
||||
default=1,
|
||||
comment="激活状态: 1=已激活, 2=已禁用, 4=未激活",
|
||||
)
|
||||
|
||||
# IT技能等级(7级: bronze/silver/gold/platinum/diamond/star/king)
|
||||
it_level: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="silver",
|
||||
comment="IT技能等级",
|
||||
)
|
||||
|
||||
# 等级来源(system: 系统自动评定, manual: 坐席手动调整, assessment: 评估结果)
|
||||
it_level_source: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="system",
|
||||
comment="等级来源",
|
||||
)
|
||||
|
||||
# 坐席备注(JSON 格式,存储坐席对员工的备注信息)
|
||||
notes: Mapped[dict] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=dict,
|
||||
comment="坐席备注",
|
||||
)
|
||||
|
||||
# 最后登录时间
|
||||
last_login_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
comment="最后登录时间",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 索引和约束定义
|
||||
# --------------------------------------------------------------------------
|
||||
__table_args__ = (
|
||||
# 复合唯一约束:同一企业内 employee_id 唯一
|
||||
UniqueConstraint("corp_id", "employee_id", name="uq_employee_corp"),
|
||||
# 按 corp_id 查询(查询某企业所有员工)
|
||||
Index("idx_employees_corp_id", "corp_id"),
|
||||
# 按 employee_id 查询
|
||||
Index("idx_employees_employee_id", "employee_id"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""员工对象的字符串表示。"""
|
||||
return (
|
||||
f"<Employee(corp_id={self.corp_id}, employee_id={self.employee_id}, "
|
||||
f"name={self.name})>"
|
||||
)
|
||||
@@ -0,0 +1,120 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 趣味话术模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 funny_phrases 表,存储各场景的趣味话术
|
||||
# 场景:shake(摇人)/keyword(关键词)/waiting(等待)/connected(接入)/timeout(超时)/vip
|
||||
# 话术在用户端H5中显示,给等待过程增添趣味性
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Index, Integer, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class FunnyPhrase(Base):
|
||||
"""趣味话术模型 — 对应 funny_phrases 表。
|
||||
|
||||
按触发场景存储趣味话术,在用户等待过程中显示。
|
||||
支持后台动态修改,无需发版。
|
||||
|
||||
Attributes:
|
||||
id: 话术唯一标识(UUID,数据库自动生成)
|
||||
scene: 触发场景(shake/keyword/waiting/connected/timeout/vip)
|
||||
content: 话术内容
|
||||
tone: 语气标签(亲切/稍正式/安抚/明确交接/降级安抚/正式)
|
||||
sort_order: 排序权重
|
||||
is_active: 是否启用
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
# 表名(必须和架构文档 DDL 一致)
|
||||
__tablename__ = "funny_phrases"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID,Python端生成(兼容PostgreSQL和SQLite)
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 触发场景
|
||||
# shake: 点击摇人按钮时
|
||||
# keyword: 关键词触发转人工时
|
||||
# waiting: 排队等待时(30秒无人接单)
|
||||
# connected: 坐席接入时
|
||||
# timeout: 等待超时时(2分钟)
|
||||
# vip: VIP员工时
|
||||
scene: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
comment="触发场景: shake/keyword/waiting/connected/timeout/vip",
|
||||
)
|
||||
|
||||
# 话术内容
|
||||
content: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
comment="话术内容",
|
||||
)
|
||||
|
||||
# 语气标签(方便管理员理解话术风格)
|
||||
tone: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=False,
|
||||
default="亲切",
|
||||
comment="语气标签",
|
||||
)
|
||||
|
||||
# 排序权重(同一场景下有多条话术时,按此排序)
|
||||
sort_order: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="排序权重",
|
||||
)
|
||||
|
||||
# 是否启用(False 的话术不会被使用)
|
||||
is_active: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=True,
|
||||
comment="是否启用",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 索引定义(和架构文档 DDL 严格一致)
|
||||
# --------------------------------------------------------------------------
|
||||
__table_args__ = (
|
||||
# 按场景查询(如获取所有"摇人"场景的话术)
|
||||
Index("idx_fp_scene", "scene"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""话术对象的字符串表示,方便调试。"""
|
||||
return f"<FunnyPhrase(id={self.id}, scene={self.scene}, content={self.content[:20]})>"
|
||||
@@ -0,0 +1,252 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 消息模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 messages 表,存储会话中的所有消息
|
||||
# 消息来源:员工(employee)、坐席(agent)、AI(ai)、系统(system)
|
||||
# 消息类型:文本(text)、图片(image)、文件(file)、语音(voice)、系统提示(system)
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Message(Base):
|
||||
"""消息模型 — 对应 messages 表。
|
||||
|
||||
每条消息都属于一个会话(Conversation),记录对话中的每一条信息。
|
||||
包含发送者信息、消息内容、消息类型等。
|
||||
|
||||
Attributes:
|
||||
id: 消息唯一标识(UUID,数据库自动生成)
|
||||
conversation_id: 所属会话ID(外键,关联 conversations 表)
|
||||
sender_type: 发送者类型(employee/agent/ai/system)
|
||||
sender_id: 发送者ID
|
||||
sender_name: 发送者姓名(冗余存储,减少关联查询)
|
||||
content: 消息内容(文本消息为文字,媒体消息为描述文字或URL)
|
||||
msg_type: 消息类型(text/image/file/voice/system)
|
||||
media_id: 企微媒体文件ID(图片/语音/视频消息,3天有效)
|
||||
media_url: 本地存储的媒体文件URL(下载后保存到服务器)
|
||||
file_name: 文件名(文件消息用)
|
||||
file_size: 文件大小(字节)
|
||||
extra_data: 扩展元数据(JSON,如图片尺寸、语音格式等)
|
||||
ai_suggestion: 是否为AI建议(坐席端展示用)
|
||||
status: 消息状态(sending/sent/delivered/read)
|
||||
recallable_until: 可撤回截止时间(创建时间+2分钟)
|
||||
is_read: 是否已读
|
||||
created_at: 创建时间
|
||||
"""
|
||||
|
||||
# 表名(必须和架构文档 DDL 一致)
|
||||
__tablename__ = "messages"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID,Python端生成(兼容PostgreSQL和SQLite)
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 所属会话ID(外键,关联 conversations 表)
|
||||
# ON DELETE CASCADE:删除会话时自动删除该会话的所有消息
|
||||
conversation_id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
ForeignKey("conversations.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
comment="所属会话ID",
|
||||
)
|
||||
|
||||
# 发送者类型(CHECK 约束:只能取四种值)
|
||||
# employee: 员工发送的消息
|
||||
# agent: 坐席发送的消息
|
||||
# ai: AI生成的消息(第二步启用)
|
||||
# system: 系统消息(如"坐席已接入"等通知)
|
||||
sender_type: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
comment="发送者类型: employee/agent/ai/system",
|
||||
)
|
||||
|
||||
# 发送者ID
|
||||
# 员工消息时为企微UserID,坐席消息时为坐席user_id
|
||||
sender_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
comment="发送者ID",
|
||||
)
|
||||
|
||||
# 发送者姓名(冗余存储,避免每次查消息都要关联用户表)
|
||||
sender_name: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="发送者姓名",
|
||||
)
|
||||
|
||||
# 消息内容
|
||||
# 文本消息时为文本内容,图片/文件消息时为媒体URL或描述文字
|
||||
content: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="消息内容",
|
||||
)
|
||||
|
||||
# 消息类型(CHECK 约束)
|
||||
# text: 文本消息
|
||||
# image: 图片消息
|
||||
# file: 文件消息
|
||||
# voice: 语音消息
|
||||
# system: 系统消息
|
||||
msg_type: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="text",
|
||||
comment="消息类型: text/image/file/voice/system",
|
||||
)
|
||||
|
||||
# 引用回复:指向被回复的消息ID(M1 新增)
|
||||
# 为 None 时表示普通消息,非 None 时表示对某条消息的回复
|
||||
# 前端根据此字段显示引用内容(被回复消息的摘要)
|
||||
reply_to_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(36),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="引用回复:被回复的消息ID",
|
||||
)
|
||||
|
||||
# 企微媒体文件ID(图片/语音/视频消息携带)
|
||||
# 注意:MediaId 仅3天有效,收到消息后应尽快下载保存到本地
|
||||
# 下载接口:GET https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=TOKEN&media_id=MEDIA_ID
|
||||
media_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(256),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="企微媒体文件ID(3天有效)",
|
||||
)
|
||||
|
||||
# 本地存储的媒体文件URL(下载后保存到服务器/NAS的访问路径)
|
||||
# 格式示例:/media/2026/06/03/abc123.jpg
|
||||
media_url: Mapped[Optional[str]] = mapped_column(
|
||||
String(512),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="本地存储的媒体文件URL",
|
||||
)
|
||||
|
||||
# 文件名(文件消息携带,或下载后自定义的文件名)
|
||||
file_name: Mapped[Optional[str]] = mapped_column(
|
||||
String(256),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="文件名",
|
||||
)
|
||||
|
||||
# 文件大小(字节,文件消息携带)
|
||||
file_size: Mapped[Optional[int]] = mapped_column(
|
||||
Integer,
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="文件大小(字节)",
|
||||
)
|
||||
|
||||
# 扩展元数据(JSON格式,存储各消息类型的额外信息)
|
||||
# 示例:
|
||||
# 图片消息: {"pic_url": "https://...", "width": 1920, "height": 1080}
|
||||
# 语音消息: {"format": "amr", "duration_seconds": 15}
|
||||
# 视频消息: {"thumb_media_id": "...", "duration_seconds": 30}
|
||||
# 位置消息: {"location_x": 23.134, "location_y": 113.358, "label": "杭州市"}
|
||||
extra_data: Mapped[Optional[Dict[str, Any]]] = mapped_column(
|
||||
JSON,
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="扩展元数据(JSON)",
|
||||
)
|
||||
|
||||
# 是否为AI建议(坐席端展示用)
|
||||
# True: 此消息为AI建议的回复,坐席可选择采纳/编辑/忽略
|
||||
# False: 正常消息
|
||||
ai_suggestion: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=False,
|
||||
comment="是否为AI建议",
|
||||
)
|
||||
|
||||
# 消息状态(新增:sending/sent/delivered/read)
|
||||
# sending: 发送中
|
||||
# sent: 已发送
|
||||
# delivered: 已送达
|
||||
# read: 已读
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="sent",
|
||||
comment="消息状态: sending/sent/delivered/read",
|
||||
)
|
||||
|
||||
# 可撤回截止时间(创���时间+2分钟)
|
||||
# 用于判断消息是否在可撤回时间窗口内
|
||||
recallable_until: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="可撤回截止时间",
|
||||
)
|
||||
|
||||
# 是否已读
|
||||
# 用于统计未读消息数(坐席端显示红点)
|
||||
is_read: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=False,
|
||||
comment="是否已读",
|
||||
)
|
||||
|
||||
# 坐席对 AI 建议的操作行为
|
||||
# 仅当 ai_suggestion=True 时有意义
|
||||
# accepted: 坐席直接采纳了AI建议
|
||||
# edited: 坐席编辑后采纳了AI建议
|
||||
# ignored: 坐席忽略了AI建议
|
||||
suggestion_action: Mapped[Optional[str]] = mapped_column(
|
||||
String(20),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="AI建议操作行为: accepted/edited/ignored",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 索引定义(和架构文档 DDL 严格一致)
|
||||
# --------------------------------------------------------------------------
|
||||
__table_args__ = (
|
||||
# 按会话ID查询(如查询某会话的所有消息)
|
||||
Index("idx_messages_conversation_id", "conversation_id"),
|
||||
# 按创建时间查询(如按时间排序消息)
|
||||
Index("idx_messages_created_at", "created_at"),
|
||||
# 复合索引:按会话+时间查询(最常见的查询:获取某会话的消息列表)
|
||||
Index("idx_messages_conversation_created", "conversation_id", "created_at"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""消息对象的字符串表示,方便调试。"""
|
||||
return (
|
||||
f"<Message(id={self.id}, conv={self.conversation_id}, "
|
||||
f"from={self.sender_type}, type={self.msg_type})>"
|
||||
)
|
||||
@@ -0,0 +1,145 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 快速回复模板模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 quick_reply_templates 表,存储坐席常用回复模板
|
||||
# 分类:账号/网络/软件/硬件/通用
|
||||
# 支持变量替换:如 {employee_name} 会被替换为实际员工姓名
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from sqlalchemy import DateTime, Index, Integer, JSON, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class QuickReplyTemplate(Base):
|
||||
"""快速回复模板模型 — 对应 quick_reply_templates 表。
|
||||
|
||||
坐席常用回复模板,按分类组织,支持变量替换。
|
||||
变量如 {employee_name} 在使用时会被替换为实际值。
|
||||
|
||||
Attributes:
|
||||
id: 模板唯一标识(UUID,数据库自动生成)
|
||||
category: 分类(账号/网络/软件/硬件/通用)
|
||||
title: 模板标题(简短描述,方便坐席快速识别)
|
||||
content: 模板内容(支持 {employee_name} 等变量)
|
||||
variables: 可用变量列表(JSONB,如 ["employee_name","department"])
|
||||
sort_order: 排序权重(数值越小越靠前)
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
# 表名(必须和架构文档 DDL 一致)
|
||||
__tablename__ = "quick_reply_templates"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID,Python端生成(兼容PostgreSQL和SQLite)
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 分类(用于按类别组织模板,在坐席端折叠展示)
|
||||
category: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
default="通用",
|
||||
comment="分类:账号/网络/软件/硬件/通用",
|
||||
)
|
||||
|
||||
# 模板标题(简短描述,方便坐席快速识别)
|
||||
title: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
comment="模板标题",
|
||||
)
|
||||
|
||||
# 模板内容(支持变量替换)
|
||||
# 示例:"您好{employee_name},您的密码重置链接已发送"
|
||||
content: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
comment="模板内容,支持变量如 {employee_name}",
|
||||
)
|
||||
|
||||
# 可用变量列表(JSON 格式,存储模板中可替换的变量名,兼容所有数据库)
|
||||
# 示例:["employee_name", "department"]
|
||||
variables: Mapped[List[str]] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=list,
|
||||
comment="可用变量列表",
|
||||
)
|
||||
|
||||
# 排序权重(数值越小越靠前,同一分类内排序)
|
||||
sort_order: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="排序权重",
|
||||
)
|
||||
|
||||
# 状态(draft=草稿/pending_review=待审核/approved=已通过/rejected=已驳回)
|
||||
# 审核流转:draft → pending_review → approved / rejected
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="approved",
|
||||
comment="状态:draft/pending_review/approved/rejected",
|
||||
)
|
||||
|
||||
# 版本号(每次审核通过后 +1)
|
||||
version: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=1,
|
||||
comment="版本号,每次审核通过后 +1",
|
||||
)
|
||||
|
||||
# 提交人 agent_id(提交审核的坐席ID,可为空表示系统创建)
|
||||
submitted_by: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="提交人 agent_id",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 索引定义(和架构文档 DDL 严格一致)
|
||||
# --------------------------------------------------------------------------
|
||||
__table_args__ = (
|
||||
# 按分类查询(如获取所有"账号"分类的模板)
|
||||
Index("idx_qr_category", "category"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""模板对象的字符串表示,方便调试。"""
|
||||
return (
|
||||
f"<QuickReplyTemplate(id={self.id}, category={self.category}, "
|
||||
f"title={self.title})>"
|
||||
)
|
||||
@@ -0,0 +1,91 @@
|
||||
# =============================================================================
|
||||
# 角色模型 — roles 表
|
||||
# =============================================================================
|
||||
# 说明:定义系统角色(user/agent/admin),支持 RBAC 权限控制
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import String, Boolean, DateTime, Text, JSON
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class Role(Base):
|
||||
"""角色模型 — 对应 roles 表。
|
||||
|
||||
预置三个角色:
|
||||
- user: 所有在职员工默认角色(is_default=True)
|
||||
- agent: IT坐席,通过企微标签或eHR字段映射
|
||||
- admin: 管理员,通过管理后台手动绑定
|
||||
"""
|
||||
|
||||
__tablename__ = "roles"
|
||||
|
||||
# 主键:UUID
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 角色标识:user/agent/admin(唯一)
|
||||
name: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
unique=True,
|
||||
nullable=False,
|
||||
comment="角色标识:user/agent/admin",
|
||||
)
|
||||
|
||||
# 显示名称:用户/坐席/管理员
|
||||
display_name: Mapped[str] = mapped_column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
comment="显示名称:用户/坐席/管理员",
|
||||
)
|
||||
|
||||
# 角色描述
|
||||
description: Mapped[Optional[str]] = mapped_column(
|
||||
Text,
|
||||
nullable=True,
|
||||
comment="角色描述",
|
||||
)
|
||||
|
||||
# 权限列表(JSON数组)
|
||||
permissions: Mapped[list] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=list,
|
||||
comment="权限列表(JSON数组)",
|
||||
)
|
||||
|
||||
# 是否默认角色(user=True)
|
||||
is_default: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=False,
|
||||
comment="是否默认角色(所有员工自动获得)",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间(自动刷新)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Role(id={self.id}, name={self.name}, display_name={self.display_name})>"
|
||||
@@ -0,0 +1,89 @@
|
||||
# =============================================================================
|
||||
# 角色映射规则模型 — role_mapping_rules 表
|
||||
# =============================================================================
|
||||
# 说明:定义角色自动映射规则,支持企微标签和eHR字段两种来源
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import String, Boolean, DateTime, Integer, ForeignKey, Index
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class RoleMappingRule(Base):
|
||||
"""角色映射规则模型 — 对应 role_mapping_rules 表。
|
||||
|
||||
定义自动映射规则,当用户满足条件时自动获得对应角色:
|
||||
- wecom_tag: 企微标签匹配(如标签包含"IT坐席")
|
||||
- ehr_position: eHR岗位关键词匹配(如岗位包含"IT支持")
|
||||
"""
|
||||
|
||||
__tablename__ = "role_mapping_rules"
|
||||
__table_args__ = (
|
||||
# 按 role_id 查询优化
|
||||
Index("idx_role_mapping_rules_role_id", "role_id"),
|
||||
# 按 source_type 查询优化
|
||||
Index("idx_role_mapping_rules_source_type", "source_type"),
|
||||
)
|
||||
|
||||
# 主键:UUID
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 角色 ID(外键)
|
||||
role_id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
ForeignKey("roles.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
comment="目标角色 ID",
|
||||
)
|
||||
|
||||
# 来源类型:wecom_tag/ehr_position
|
||||
source_type: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
nullable=False,
|
||||
comment="来源类型:wecom_tag/ehr_position",
|
||||
)
|
||||
|
||||
# 来源值:标签名/岗位关键词
|
||||
source_value: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
comment="来源值:标签名/岗位关键词",
|
||||
)
|
||||
|
||||
# 优先级(数值越大优先级越高)
|
||||
priority: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="优先级(数值越大优先级越高)",
|
||||
)
|
||||
|
||||
# 是否启用
|
||||
is_active: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=True,
|
||||
comment="是否启用",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<RoleMappingRule(id={self.id}, role_id={self.role_id}, "
|
||||
f"source_type={self.source_type}, source_value={self.source_value})>"
|
||||
)
|
||||
@@ -0,0 +1,125 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 软件下载入口模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 software_downloads 表,存储软件下载链接
|
||||
# 分类:办公/开发/安全/工具
|
||||
# 在H5用户端右侧AI助手面板中展示,方便员工下载常用软件
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, Index, Integer, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class SoftwareDownload(Base):
|
||||
"""软件下载入口模型 — 对应 software_downloads 表。
|
||||
|
||||
存储公司常用软件的下载链接,
|
||||
在H5用户端AI助手面板中按分类展示。
|
||||
|
||||
Attributes:
|
||||
id: 下载入口唯一标识(UUID,数据库自动生成)
|
||||
category: 分类(办公/开发/安全/工具)
|
||||
name: 软件名称
|
||||
version: 版本号
|
||||
platform: 平台(Windows/Mac/Linux/全平台)
|
||||
download_url: 下载链接
|
||||
sort_order: 排序权重
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
# 表名(必须和架构文档 DDL 一致)
|
||||
__tablename__ = "software_downloads"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID,Python端生成(兼容PostgreSQL和SQLite)
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 分类(按用途分类,方便在H5面板中折叠展示)
|
||||
category: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
comment="分类:办公/开发/安全/工具",
|
||||
)
|
||||
|
||||
# 软件名称
|
||||
name: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
comment="软件名称",
|
||||
)
|
||||
|
||||
# 版本号(如 "12.1"、"最新版")
|
||||
version: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="版本号",
|
||||
)
|
||||
|
||||
# 支持平台(如 "Windows/Mac"、"全平台")
|
||||
platform: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="平台: Windows/Mac/Linux/全平台",
|
||||
)
|
||||
|
||||
# 下载链接
|
||||
download_url: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
comment="下载链接",
|
||||
)
|
||||
|
||||
# 排序权重(同一分类内排序,数值越小越靠前)
|
||||
sort_order: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="排序权重",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 索引定义(和架构文档 DDL 严格一致)
|
||||
# --------------------------------------------------------------------------
|
||||
__table_args__ = (
|
||||
# 按分类查询(如获取所有"办公"分类的软件)
|
||||
Index("idx_sd_category", "category"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""下载入口对象的字符串表示,方便调试。"""
|
||||
return (
|
||||
f"<SoftwareDownload(id={self.id}, category={self.category}, "
|
||||
f"name={self.name}, version={self.version})>"
|
||||
)
|
||||
@@ -0,0 +1,83 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 系统配置模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 system_configs 表,存储系统级配置项
|
||||
# 包括:关键词列表、评分阈值、话术模板等
|
||||
# 优势:配置存在数据库中,支持后台动态修改,无需重启服务
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class SystemConfig(Base):
|
||||
"""系统配置模型 — 对应 system_configs 表。
|
||||
|
||||
将可动态修改的配置项存储在数据库中,
|
||||
支持后台修改后立即生效,无需重启服务。
|
||||
|
||||
Attributes:
|
||||
id: 配置唯一标识(UUID,数据库自动生成)
|
||||
config_key: 配置键(唯一,如 "hand_raise_keywords")
|
||||
config_value: 配置值(JSON 字符串或纯文本)
|
||||
description: 配置说明
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
# 表名(必须和架构文档 DDL 一致)
|
||||
__tablename__ = "system_configs"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID,Python端生成(兼容PostgreSQL和SQLite)
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 配置键(唯一,用于查找配置项)
|
||||
# 示例:hand_raise_keywords, emotion_keywords_angry, polling_interval_seconds
|
||||
config_key: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
unique=True,
|
||||
nullable=False,
|
||||
comment="配置键",
|
||||
)
|
||||
|
||||
# 配置值(存储 JSON 字符串或纯文本)
|
||||
# JSON 示例:'["转人工","人工","人工服务"]'
|
||||
# 纯文本示例:'3'(表示阈值)
|
||||
config_value: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
comment="配置值(JSON字符串或纯文本)",
|
||||
)
|
||||
|
||||
# 配置说明(方便管理员理解配置用途)
|
||||
description: Mapped[str] = mapped_column(
|
||||
String(256),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="配置说明",
|
||||
)
|
||||
|
||||
# 更新时间(配置修改时自动更新)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""配置对象的字符串表示,方便调试。"""
|
||||
return f"<SystemConfig(key={self.config_key}, value={self.config_value})>"
|
||||
@@ -0,0 +1,128 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 待办事项模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 todo_items 表,存储坐席的待办事项
|
||||
# 待办类型:ticket(工单)/approval(审批)/device(设备) 等
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Integer, JSON, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class TodoItem(Base):
|
||||
"""待办事项模型 — 对应 todo_items 表。
|
||||
|
||||
存储坐席需要跟进的各类待办事项,包括工单、审批、设备处理等。
|
||||
|
||||
Attributes:
|
||||
id: 待办唯一标识(UUID,数据库自动生成)
|
||||
type: 待办类型(ticket/approval/device)
|
||||
title: 待办标题
|
||||
priority: 优先级(urgent/high/normal)
|
||||
description: 详细描述(JSON,存储结构化数据)
|
||||
status: 状态(pending/processing/resolved)
|
||||
assigned_agent_id: 分配的坐席ID(可为空,表示未分配)
|
||||
corp_id: 企业微信企业ID
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
# 表名
|
||||
__tablename__ = "todo_items"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
comment="待办唯一标识",
|
||||
)
|
||||
|
||||
# 待办类型
|
||||
type: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="ticket",
|
||||
comment="待办类型: ticket/approval/device",
|
||||
)
|
||||
|
||||
# 待办标题
|
||||
title: Mapped[str] = mapped_column(
|
||||
String(256),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="待办标题",
|
||||
)
|
||||
|
||||
# 优先级
|
||||
priority: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="normal",
|
||||
comment="优先级: urgent/high/normal",
|
||||
)
|
||||
|
||||
# 详细描述(JSON 格式,存储结构化数据)
|
||||
description: Mapped[Dict[str, Any]] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=dict,
|
||||
comment="详细描述",
|
||||
)
|
||||
|
||||
# 状态
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="pending",
|
||||
comment="状态: pending/processing/resolved",
|
||||
)
|
||||
|
||||
# 分配的坐席ID(可为空,表示未分配)
|
||||
assigned_agent_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(64),
|
||||
nullable=True,
|
||||
comment="分配的坐席ID",
|
||||
)
|
||||
|
||||
# 企业微信企业ID
|
||||
corp_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="企业微信企业ID",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""待办事项对象的字符串表示。"""
|
||||
return (
|
||||
f"<TodoItem(id={self.id}, type={self.type}, "
|
||||
f"title={self.title}, status={self.status})>"
|
||||
)
|
||||
@@ -0,0 +1,114 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 排障模板模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 troubleshooting_templates 表,存储常见问题的排障模板
|
||||
# 包含排障步骤路径和流程图定义
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, JSON, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class TroubleshootingTemplate(Base):
|
||||
"""排障模板模型 — 对应 troubleshooting_templates 表。
|
||||
|
||||
存储常见 IT 问题的标准化排障模板,包括步骤路径和流程图。
|
||||
分类:vpn/email/system/account 等。
|
||||
|
||||
Attributes:
|
||||
id: 模板唯一标识(UUID,数据库自动生成)
|
||||
name: 模板名称
|
||||
category: 分类(vpn/email/system/account)
|
||||
path_steps: 排障步骤路径(JSON,存储步骤序列)
|
||||
flowchart: 流程图定义(JSON,存储节点和连线)
|
||||
is_active: 是否启用
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
# 表名
|
||||
__tablename__ = "troubleshooting_templates"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
comment="模板唯一标识",
|
||||
)
|
||||
|
||||
# 模板名称
|
||||
name: Mapped[str] = mapped_column(
|
||||
String(256),
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="模板名称",
|
||||
)
|
||||
|
||||
# 分类
|
||||
category: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="system",
|
||||
comment="分类: vpn/email/system/account",
|
||||
)
|
||||
|
||||
# 排障步骤路径(JSON 格式)
|
||||
# 示例:[{"step": 1, "title": "检查VPN连接状态", "action": "..."}, ...]
|
||||
path_steps: Mapped[list] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=list,
|
||||
comment="排障步骤路径",
|
||||
)
|
||||
|
||||
# 流程图定义(JSON 格式)
|
||||
# 示例:{"nodes": [...], "edges": [...]}
|
||||
flowchart: Mapped[Dict[str, Any]] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=dict,
|
||||
comment="流程图定义",
|
||||
)
|
||||
|
||||
# 是否启用
|
||||
is_active: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=True,
|
||||
comment="是否启用",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""排障模板对象的字符串表示。"""
|
||||
return (
|
||||
f"<TroubleshootingTemplate(id={self.id}, name={self.name}, "
|
||||
f"category={self.category}, is_active={self.is_active})>"
|
||||
)
|
||||
@@ -0,0 +1,89 @@
|
||||
# =============================================================================
|
||||
# 用户角色关联模型 — user_roles 表
|
||||
# =============================================================================
|
||||
# 说明:用户与角色的多对多关联表,记录角色来源和分配信息
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import String, DateTime, ForeignKey, UniqueConstraint, Index
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class UserRole(Base):
|
||||
"""用户角色关联模型 — 对应 user_roles 表。
|
||||
|
||||
记录用户拥有的角色,支持以下来源:
|
||||
- auto: 系统自动分配(所有员工默认 user 角色)
|
||||
- tag: 企微标签映射
|
||||
- ehr: eHR 字段映射
|
||||
- manual: 管理后台手动分配
|
||||
"""
|
||||
|
||||
__tablename__ = "user_roles"
|
||||
__table_args__ = (
|
||||
# 同一用户同一角色只能有一条记录
|
||||
UniqueConstraint("employee_id", "role_id", name="uq_user_role"),
|
||||
# 按 employee_id 查询优化
|
||||
Index("idx_user_roles_employee_id", "employee_id"),
|
||||
# 按 role_id 查询优化
|
||||
Index("idx_user_roles_role_id", "role_id"),
|
||||
)
|
||||
|
||||
# 主键:UUID
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 企微 UserID
|
||||
employee_id: Mapped[str] = mapped_column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
comment="企微 UserID",
|
||||
)
|
||||
|
||||
# 角色 ID(外键)
|
||||
role_id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
ForeignKey("roles.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
comment="角色 ID",
|
||||
)
|
||||
|
||||
# 角色来源:auto/tag/ehr/manual
|
||||
source: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
nullable=False,
|
||||
comment="角色来源:auto/tag/ehr/manual",
|
||||
)
|
||||
|
||||
# 分配者(手动分配时记录操作人)
|
||||
assigned_by: Mapped[Optional[str]] = mapped_column(
|
||||
String(100),
|
||||
nullable=True,
|
||||
comment="分配者(手动分配时记录操作人)",
|
||||
)
|
||||
|
||||
# 分配时间
|
||||
assigned_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="分配时间",
|
||||
)
|
||||
|
||||
# 过期时间(可选,用于临时角色)
|
||||
expires_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
comment="过期时间(可选,用于临时角色)",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<UserRole(employee_id={self.employee_id}, role_id={self.role_id}, source={self.source})>"
|
||||
Reference in New Issue
Block a user