feat: 2026-07-11 全量更新 - 代办集成+会议室预定+知识迭代修复+UI统一+Bug修复

== 已部署上线 (9项) ==
- 代办事项真实数据源集成 (企微审批API 8bug修复链)
- H5/坐席端 Logo样式统一+绿色背景
- 视频引导页修复 (localStorage key v2)
- 坐席端 v9 Vue版本修复 (ElMessage._context)
- 截图按钮 v10 修复 (getDisplayMedia user gesture)
- 扫码样式恢复+H5扫码登录跳转修复
- H5截图快捷键提示

== 代码完成待部署 (3项) ==
- 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查)
- 会议室预定-小鱼易联终端 (40文件, 40/40测试通过)
- IT资产升级审批推送 (asset_service.py)

== 需求文档 (2项) ==
- 坐席端AI辅助消息框-PRD (4项新功能确认)
- 坐席端布局优化建议 v2.0 (7天计划)

== 新增文档 ==
- 日报-2026-07-11.md
- 知识迭代Bug修复报告-20260711.md
- 会议室预定-部署指南.md
- CHANGELOG.md 更新

== 测试 ==
- test_todo_integration.py: 40/40
- test_meetingroom.py: 40/40
- test_bugfix_ki_suggestions.py: 21/21
This commit is contained in:
Simon
2026-07-11 23:13:10 +08:00
parent 3d152fc8eb
commit bea288e414
928 changed files with 85169 additions and 54205 deletions
+11
View File
@@ -27,6 +27,12 @@ from app.models.audit_log import AuditLog
from app.models.conversation_annotation import ConversationAnnotation # P2-10 会话标注
from app.models.knowledge_suggestion import KnowledgeSuggestion # P2-13 知识库自动迭代
from app.models.knowledge_base import KnowledgeBase
from app.models.login_log import LoginLog # 登录日志(三端认证重构)
from app.models.business_contact import BusinessContact # 业务联系人(路由推荐)
from app.models.routing_event import RoutingEvent # 路由命中统计(P1
# 会议室预定模块模型
from app.models.terminal_room_binding import TerminalRoomBinding
from app.models.meetingroom_booking_snapshot import MeetingroomBookingSnapshot
# 阶段5 自动化闭环模型
from app.models.automation import (
AutoSession,
@@ -60,6 +66,11 @@ __all__ = [
"ConversationAnnotation",
"KnowledgeSuggestion",
"KnowledgeBase",
"LoginLog",
"BusinessContact",
"RoutingEvent",
"TerminalRoomBinding",
"MeetingroomBookingSnapshot",
"AutoSession",
"AutoAction",
"ApprovalTicket",
+121 -1
View File
@@ -20,7 +20,7 @@ import uuid
from datetime import datetime
from typing import Optional
from sqlalchemy import JSON, Boolean, DateTime, Float, Integer, String, Text
from sqlalchemy import JSON, Boolean, DateTime, Float, Integer, Numeric, SmallInteger, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
@@ -75,6 +75,8 @@ class AutoSession(Base):
resolved_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
closed_by: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
meta: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
# 复杂场景重构:暂停时间戳,用于超时计算;恢复后置 NULL
paused_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=datetime.now, onupdate=datetime.now
@@ -311,3 +313,121 @@ class MappingCache(Base):
def __repr__(self) -> str:
return f"<MappingCache(employee={self.employee_id}, source={self.source})>"
# =============================================================================
# 8. 信息项(复杂场景重构第一阶段)
# =============================================================================
class InformationItem(Base):
"""信息项 — 对应 auto_information_items 表。
管理对话中收集的信息项及其变更历史,支持更正(CORRECT)与补充(SUPPLEMENT)。
修饰符(modifiers)取值:
固定 — 动作执行后锁定,不可更正
增量 — 补充时追加而非覆盖
明确 — 用户明确提供的值
隐含 — 从上下文推断的值
复述 — 更正后需向员工发送确认消息
必需 — 流程阻塞项,未填写时不可继续执行
Attributes:
id: UUID 主键
session_id: 关联会话ID(弱关联,不建外键)
name: 信息项名称(如"用户名""终端ID""部门"
value: 当前值
modifiers: 修饰符列表,如 ["固定","必需"]
is_filled: 是否已填写
is_locked: 是否已锁定(固定修饰符 + 关联动作已执行 → True,不可更正)
version: 版本号,每次更正/补充 +1
update_history: 变更历史数组
created_at: 创建时间
updated_at: 最后更新时间
"""
__tablename__ = "auto_information_items"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
session_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(128), nullable=False)
value: Mapped[str] = mapped_column(Text, nullable=False, default="")
modifiers: Mapped[list] = mapped_column(JSON, nullable=False, default=list)
is_filled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
is_locked: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
update_history: Mapped[list] = mapped_column(JSON, nullable=False, default=list)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=datetime.now, onupdate=datetime.now
)
# Phase 2: 复杂场景重构第二阶段新增
derived_from: Mapped[Optional[list]] = mapped_column(JSON, nullable=True) # 推导来源 item_key 数组
correction_reason: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) # 更正备注
def __repr__(self) -> str:
return f"<InformationItem(session={self.session_id}, name={self.name}, v={self.version})>"
# =============================================================================
# 9. 上下文压缩记录(复杂场景重构第二阶段 P2)
# =============================================================================
class ContextCompression(Base):
"""上下文压缩记录 — 对应 auto_context_compressions 表。
每次上下文压缩操作的日志记录,包含压缩前后 token 数、压缩比、耗时等。
Attributes:
session_id: 关联会话ID
tokens_before: 压缩前 token 数
tokens_after: 压缩后 token 数
compression_ratio: 压缩比(after/before
task_node: 压缩时任务节点
duration_ms: 压缩耗时(毫秒)
compression_level: 压缩级别(1/2/3
summary: 压缩摘要内容
"""
__tablename__ = "auto_context_compressions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
session_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
tokens_before: Mapped[int] = mapped_column(Integer, nullable=False)
tokens_after: Mapped[int] = mapped_column(Integer, nullable=False)
compression_ratio: Mapped[float] = mapped_column(Numeric(5, 2), nullable=False)
task_node: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
duration_ms: Mapped[int] = mapped_column(Integer, nullable=False)
compression_level: Mapped[int] = mapped_column(SmallInteger, nullable=False, default=1)
summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
def __repr__(self) -> str:
return f"<ContextCompression(session={self.session_id}, {self.tokens_before}{self.tokens_after})>"
# =============================================================================
# 10. 信息项快照(复杂场景重构第二阶段 P3)
# =============================================================================
class InformationSnapshot(Base):
"""信息项快照 — 对应 auto_information_snapshots 表。
每次更正发生前创建快照,记录该 session 全部信息项的完整状态,
用于支持更正撤销(undo)功能。
Attributes:
session_id: 关联会话ID
trigger_item_key: 触发更正的信息项 key
snapshot_data: 全部信息项快照 {item_key: {value, version}}
correction_ids: 本次更正涉及的信息项版本 ID 列表
is_undone: 是否已被撤销
"""
__tablename__ = "auto_information_snapshots"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
session_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
trigger_item_key: Mapped[str] = mapped_column(String(64), nullable=False)
snapshot_data: Mapped[dict] = mapped_column(JSON, nullable=False)
correction_ids: Mapped[list] = mapped_column(JSON, nullable=False, default=list)
is_undone: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
def __repr__(self) -> str:
return f"<InformationSnapshot(session={self.session_id}, trigger={self.trigger_item_key}, undone={self.is_undone})>"
+186
View File
@@ -0,0 +1,186 @@
# =============================================================================
# 企微IT智能服务台 — 业务联系人模型
# =============================================================================
# 说明:对应数据库 business_contacts 表,存储非IT业务部门联系人信息
# 当员工提出的问题不属于IT服务台服务范围时,系统通过 Dify 意图识别
# 判定业务类别,从本表查询对应联系人,推送名片卡片到聊天中。
# 员工点击「联系TA」可跳转企微单聊(openEnterpriseChat)。
#
# 业务类别(business_category)取值:
# 行政 / 人力资源 / 财务 / 法务 / 行政-物业
# =============================================================================
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Index, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class BusinessContact(Base):
"""业务联系人模型 — 对应 business_contacts 表。
存储非IT业务部门联系人信息,用于路由推荐功能。
当 Dify 判定消息为非IT业务路由时,按 business_category 查询本表,
取第一条 is_active=True 的联系人,推送名片卡片。
Attributes:
id: 主键,自增
name: 联系人姓名
gender: 性别(male/female
department: 部门名称(如「行政部」)
position: 岗位(如「设备管理岗」)
responsibility: 负责业务描述(如「打印机/复印机/扫描仪」)
extension: 分机号
service_area: 服务区域/办公地点
wecom_userid: 企微用户ID(用于 openEnterpriseChat 调用)
avatar_url: 头像URL(为空时用姓名首字渲染)
business_category: 业务类别(行政/人力资源/财务/法务/行政-物业)
is_active: 是否启用
created_at: 创建时间
updated_at: 更新时间
"""
__tablename__ = "business_contacts"
# 主键:自增整数
id: Mapped[int] = mapped_column(
Integer,
primary_key=True,
autoincrement=True,
comment="主键",
)
# 联系人姓名
name: Mapped[str] = mapped_column(
String(50),
nullable=False,
comment="联系人姓名",
)
# 性别(male/female
gender: Mapped[str] = mapped_column(
String(10),
nullable=False,
default="male",
comment="性别(male/female",
)
# 部门名称
department: Mapped[str] = mapped_column(
String(100),
nullable=False,
comment="部门名称",
)
# 岗位
position: Mapped[str] = mapped_column(
String(100),
nullable=False,
comment="岗位",
)
# 负责业务描述
responsibility: Mapped[str] = mapped_column(
String(500),
nullable=False,
comment="负责业务描述",
)
# 分机号
extension: Mapped[str | None] = mapped_column(
String(20),
nullable=True,
default=None,
comment="分机号",
)
# 服务区域/办公地点
service_area: Mapped[str | None] = mapped_column(
String(200),
nullable=True,
default=None,
comment="服务区域/办公地点",
)
# 企微用户ID(用于 wx.invoke('openEnterpriseChat', {userids: '...'}) 调用)
wecom_userid: Mapped[str] = mapped_column(
String(100),
nullable=False,
comment="企微用户ID",
)
# 头像URL(为空时前端用姓名首字渐变色块渲染)
avatar_url: Mapped[str | None] = mapped_column(
String(500),
nullable=True,
default=None,
comment="头像URL(为空用姓名首字渲染)",
)
# 业务类别(与 Dify 输出的 business_category 对应)
# 取值:行政 / 人力资源 / 财务 / 法务 / 行政-物业
business_category: Mapped[str] = mapped_column(
String(50),
nullable=False,
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="更新时间",
)
# 索引定义
__table_args__ = (
# 按业务类别 + 是否启用查询(路由推荐时使用)
Index("idx_business_contacts_category", "business_category", "is_active"),
)
def to_dict(self) -> dict:
"""将联系人信息转为字典(用于 extra_data 透传给前端)。
Returns:
dict: 联系人完整信息
"""
return {
"id": self.id,
"name": self.name,
"gender": self.gender,
"department": self.department,
"position": self.position,
"responsibility": self.responsibility,
"extension": self.extension or "",
"service_area": self.service_area or "",
"wecom_userid": self.wecom_userid,
"avatar_url": self.avatar_url or "",
"business_category": self.business_category,
}
def __repr__(self) -> str:
"""联系人对象的字符串表示,方便调试。"""
return (
f"<BusinessContact(id={self.id}, name={self.name}, "
f"category={self.business_category})>"
)
+7
View File
@@ -162,6 +162,13 @@ class Employee(Base):
comment="最后登录时间",
)
# 最后登录IP
last_login_ip: Mapped[str] = mapped_column(
String(45),
nullable=True,
comment="最后登录IP地址",
)
# 创建时间
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
+142
View File
@@ -0,0 +1,142 @@
# =============================================================================
# 企微IT智能服务台 — 登录日志模型
# =============================================================================
# 说明:对应数据库 login_logs 表,记录所有登录尝试(成功/失败),用于安全审计和故障排查。
# 三端认证重构:统一认证后,所有登录方式(oauth/qrcode/bind)都记录到此表。
# =============================================================================
import uuid
from datetime import datetime
from sqlalchemy import DateTime, Index, String
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class LoginLog(Base):
"""登录日志模型 — 对应 login_logs 表。
记录所有登录尝试(成功/失败),用于安全审计和故障排查。
Attributes:
id: 主键(UUID)
employee_id: 员工UserID(成功登录时有值)
corp_id: 企业ID
login_method: 登录方式(oauth/qrcode/bind)
login_source: 登录来源(h5/agent/admin)
ip_address: 客户端IP
user_agent: 客户端User-Agent
status: 登录状态(success/failed/cancelled)
fail_reason: 失败原因
created_at: 登录时间
"""
# 表名
__tablename__ = "login_logs"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUID
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
comment="登录日志唯一标识",
)
# 员工UserID(成功登录时有值)
employee_id: Mapped[str] = mapped_column(
String(64),
nullable=True,
comment="企微员工UserID(成功登录时有值)",
)
# 企业微信企业ID
corp_id: Mapped[str] = mapped_column(
String(64),
nullable=False,
comment="企业微信企业ID",
)
# 登录方式
# oauth: 企微内OAuth静默授权
# qrcode: 企微外扫码登录
# bind: 互联企业账号绑定
login_method: Mapped[str] = mapped_column(
String(20),
nullable=False,
comment="登录方式: oauth/qrcode/bind",
)
# 登录来源
# h5: 员工H5端
# agent: 坐席端
# admin: 管理后台
login_source: Mapped[str] = mapped_column(
String(20),
nullable=False,
comment="登录来源: h5/agent/admin",
)
# 客户端IPIPv6支持)
ip_address: Mapped[str] = mapped_column(
String(45),
nullable=True,
comment="客户端IP地址",
)
# 客户端User-Agent
user_agent: Mapped[str] = mapped_column(
String(512),
nullable=True,
comment="客户端User-Agent",
)
# 登录状态
# success: 登录成功
# failed: 登录失败
# cancelled: 用户取消/授权超时
status: Mapped[str] = mapped_column(
String(20),
nullable=False,
comment="登录状态: success/failed/cancelled",
)
# 失败原因
fail_reason: Mapped[str] = mapped_column(
String(256),
nullable=True,
comment="失败原因",
)
# 登录时间
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
default=datetime.now,
comment="登录时间",
)
# --------------------------------------------------------------------------
# 索引和约束定义
# --------------------------------------------------------------------------
__table_args__ = (
# 按员工ID查询登录历史
Index("idx_login_logs_employee_id", "employee_id"),
# 按企业ID查询
Index("idx_login_logs_corp_id", "corp_id"),
# 按登录时间倒序查询(常用)
Index("idx_login_logs_created_at", "created_at"),
# 按状态查询
Index("idx_login_logs_status", "status"),
)
def __repr__(self) -> str:
"""登录日志对象的字符串表示。"""
return (
f"<LoginLog(id={self.id}, employee_id={self.employee_id}, "
f"login_method={self.login_method}, status={self.status})>"
)
@@ -0,0 +1,63 @@
# =============================================================================
# 企微IT智能服务台 — 会议室预定快照模型(P2统计用)
# =============================================================================
# 说明:每日定时拉取预定记录存入本地数据库,作为统计快照(非实时数据源)
# 实时预定状态仍以企微API为唯一数据源,此表仅用于P2统计分析
# =============================================================================
from datetime import date, datetime
from sqlalchemy import Date, DateTime, Integer, String, func
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class MeetingroomBookingSnapshot(Base):
"""会议室预定记录快照模型(P2统计用)。
每日定时拉取企微预定记录,存入本地作为统计快照。
非实时数据源 — 实时预定状态仍以企微API为准。
Attributes:
id: 自增主键
meetingroom_id: 企微会议室ID
booking_id: 企微预定ID
subject: 会议主题
booker: 预定人userid
start_time: 会议开始时间
end_time: 会议结束时间
status: 预定状态(0=已预定, 1=已取消)
snapshot_date: 快照日期
created_at: 记录创建时间
"""
__tablename__ = "meetingroom_booking_snapshot"
# 自增主键
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
# 企微会议室ID
meetingroom_id: Mapped[int] = mapped_column(Integer, index=True, nullable=False)
# 企微预定ID
booking_id: Mapped[str] = mapped_column(String(64), index=True, nullable=False)
# 会议主题
subject: Mapped[str] = mapped_column(String(200), nullable=False, default="")
# 预定人userid
booker: Mapped[str] = mapped_column(String(64), nullable=False, default="")
# 会议开始时间
start_time: Mapped[datetime] = mapped_column(DateTime, nullable=False)
# 会议结束时间
end_time: Mapped[datetime] = mapped_column(DateTime, nullable=False)
# 预定状态(0=已预定, 1=已取消)
status: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
# 快照日期(用于按日期统计)
snapshot_date: Mapped[date] = mapped_column(Date, index=True, nullable=False)
# 记录创建时间
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
def __repr__(self) -> str:
"""返回模型的可读字符串表示。"""
return (
f"<MeetingroomBookingSnapshot(id={self.id}, meetingroom_id={self.meetingroom_id}, "
f"booking_id='{self.booking_id}', subject='{self.subject}', snapshot_date={self.snapshot_date})>"
)
+128
View File
@@ -0,0 +1,128 @@
# =============================================================================
# 企微IT智能服务台 — 路由命中事件模型(P1)
# =============================================================================
# 说明:对应数据库 routing_events 表,记录每次路由推荐事件
# 用于后续优化 Prompt 准确率、分析高频非IT业务
#
# 记录字段:会话ID、员工消息内容、识别的业务类别、推荐联系人、是否点击联系
# =============================================================================
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Index, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class RoutingEvent(Base):
"""路由命中事件模型 — 对应 routing_events 表(P1)。
记录每次路由推荐事件,为后续优化 Prompt 准确率、分析高频非IT业务
提供数据支撑。
Attributes:
id: 主键,自增
conversation_id: 会话ID(外键)
employee_id: 员工ID
message_content: 触发路由的员工消息(截断至500字)
business_category: 识别的业务类别
routing_confidence: 路由置信度
contact_id: 推荐的联系人ID(外键)
contact_name: 联系人姓名(冗余存储)
is_clicked: 员工是否点击了「联系TA」
created_at: 创建时间
"""
__tablename__ = "routing_events"
# 主键:自增整数
id: Mapped[int] = mapped_column(
Integer,
primary_key=True,
autoincrement=True,
comment="主键",
)
# 会话ID(外键,关联 conversations 表)
conversation_id: Mapped[str] = mapped_column(
String(36),
ForeignKey("conversations.id", ondelete="CASCADE"),
nullable=False,
comment="会话ID",
)
# 员工ID
employee_id: Mapped[str] = mapped_column(
String(64),
nullable=False,
comment="员工ID",
)
# 触发路由的员工消息内容(截断至500字防止过长)
message_content: Mapped[str] = mapped_column(
String(500),
nullable=False,
comment="触发路由的员工消息(截断)",
)
# 识别的业务类别(行政/人力资源/财务/法务/行政-物业)
business_category: Mapped[str] = mapped_column(
String(50),
nullable=False,
comment="识别的业务类别",
)
# 路由置信度(0.0~1.0,≥0.7 触发名片推荐)
routing_confidence: Mapped[float] = mapped_column(
Float,
nullable=False,
comment="路由置信度",
)
# 推荐的联系人ID(外键,关联 business_contacts 表)
contact_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey("business_contacts.id", ondelete="SET NULL"),
nullable=True,
default=None,
comment="推荐的联系人ID",
)
# 联系人姓名(冗余存储,避免联系人删除后丢失记录)
contact_name: Mapped[str] = mapped_column(
String(50),
nullable=False,
comment="联系人姓名(冗余)",
)
# 员工是否点击了「联系TA」按钮
is_clicked: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
comment="是否点击了联系TA",
)
# 创建时间
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
default=datetime.now,
comment="创建时间",
)
# 索引定义
__table_args__ = (
# 按业务类别查询(分析高频非IT业务)
Index("idx_routing_events_category", "business_category"),
# 按会话ID查询(查看某会话的路由历史)
Index("idx_routing_events_conv", "conversation_id"),
)
def __repr__(self) -> str:
"""路由事件对象的字符串表示,方便调试。"""
return (
f"<RoutingEvent(id={self.id}, conv={self.conversation_id}, "
f"category={self.business_category}, confidence={self.routing_confidence})>"
)
@@ -0,0 +1,61 @@
# =============================================================================
# 企微IT智能服务台 — 终端-会议室绑定模型
# =============================================================================
# 说明:存储小鱼易联终端SN与企微会议室ID的映射关系
# 这是企微API不提供的关系数据,需要在本地维护
# =============================================================================
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Integer, String, func
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class TerminalRoomBinding(Base):
"""终端-会议室绑定模型。
存储小鱼易联终端SN与企微会议室ID的映射关系。
一个终端只能绑定一个会议室(terminal_sn 唯一索引),
一个会议室可被多个终端绑定(meetingroom_id 普通索引)。
Attributes:
id: 自增主键
terminal_sn: 小鱼易联终端序列号(唯一)
terminal_name: 终端名称(如"18F东区大屏"
meetingroom_id: 企微会议室ID
meetingroom_name: 会议室名称(冗余字段,便于终端页面展示)
location: 位置描述(如"18F东区"
is_active: 是否启用
created_at: 创建时间
updated_at: 更新时间
"""
__tablename__ = "terminal_room_binding"
# 自增主键
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
# 小鱼易联终端序列号(唯一索引,一个终端只能绑定一个会议室)
terminal_sn: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
# 终端名称(便于管理后台展示)
terminal_name: Mapped[str] = mapped_column(String(100), nullable=False, default="")
# 企微会议室ID(普通索引,一个会议室可被多个终端绑定)
meetingroom_id: Mapped[int] = mapped_column(Integer, index=True, nullable=False)
# 会议室名称(冗余字段,便于终端页面直接展示,无需额外查询企微API)
meetingroom_name: Mapped[str] = mapped_column(String(100), nullable=False, default="")
# 位置描述
location: Mapped[str] = mapped_column(String(200), nullable=False, default="")
# 是否启用(默认True,管理员可禁用绑定)
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
# 创建时间(服务器默认当前时间)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
# 更新时间(每次更新时自动刷新)
updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now(), nullable=False)
def __repr__(self) -> str:
"""返回模型的可读字符串表示。"""
return (
f"<TerminalRoomBinding(id={self.id}, terminal_sn='{self.terminal_sn}', "
f"meetingroom_id={self.meetingroom_id}, meetingroom_name='{self.meetingroom_name}')>"
)
+4 -4
View File
@@ -2,7 +2,7 @@
# 企微IT智能服务台 — 待办事项模型
# =============================================================================
# 说明:对应数据库 todo_items 表,存储坐席的待办事项
# 待办类型:ticket(工单)/approval(审批)/device(设备) 等
# 待办类型:ticket(工单)/approval(审批)
# =============================================================================
import uuid
@@ -18,11 +18,11 @@ from app.database import Base
class TodoItem(Base):
"""待办事项模型 — 对应 todo_items 表。
存储坐席需要跟进的各类待办事项,包括工单、审批、设备处理等。
存储坐席需要跟进的各类待办事项,包括工单、审批等。
Attributes:
id: 待办唯一标识(UUID,数据库自动生成)
type: 待办类型(ticket/approval/device
type: 待办类型(ticket/approval
title: 待办标题
priority: 优先级(urgent/high/normal
description: 详细描述(JSON,存储结构化数据)
@@ -53,7 +53,7 @@ class TodoItem(Base):
String(20),
nullable=False,
default="ticket",
comment="待办类型: ticket/approval/device",
comment="待办类型: ticket/approval",
)
# 待办标题