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:
@@ -13,7 +13,7 @@ import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '041_message_server_timestamp'
|
||||
down_revision: Union[str, None] = '027_audit_logs'
|
||||
down_revision: Union[str, None] = '028_merge_heads'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
"""add login_logs table for unified auth
|
||||
|
||||
新建 login_logs 表,记录所有登录尝试(成功/失败),用于安全审计和故障排查。
|
||||
三端认证重构:统一认证后,所有登录方式(oauth/qrcode/bind)都记录到此表。
|
||||
|
||||
关联设计文档:技术方案-认证模块重构
|
||||
|
||||
Revision ID: 046_add_login_logs
|
||||
Revises: 045_add_graph_confidence_audience
|
||||
Create Date: 2026-07-10
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '046_add_login_logs'
|
||||
down_revision: Union[str, None] = '045_graph_confidence_audience'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# --------------------------------------------------------------------------
|
||||
# 新建 login_logs 表
|
||||
# --------------------------------------------------------------------------
|
||||
op.create_table(
|
||||
'login_logs',
|
||||
sa.Column('id', sa.String(36), primary_key=True, comment='登录日志唯一标识'),
|
||||
sa.Column('employee_id', sa.String(64), nullable=True, comment='企微员工UserID'),
|
||||
sa.Column('corp_id', sa.String(64), nullable=False, comment='企业微信企业ID'),
|
||||
sa.Column('login_method', sa.String(20), nullable=False,
|
||||
comment='登录方式: oauth/qrcode/bind'),
|
||||
sa.Column('login_source', sa.String(20), nullable=False,
|
||||
comment='登录来源: h5/agent/admin'),
|
||||
sa.Column('ip_address', sa.String(45), nullable=True, comment='客户端IP地址'),
|
||||
sa.Column('user_agent', sa.String(512), nullable=True, comment='客户端User-Agent'),
|
||||
sa.Column('status', sa.String(20), nullable=False,
|
||||
comment='登录状态: success/failed/cancelled'),
|
||||
sa.Column('fail_reason', sa.String(256), nullable=True, comment='失败原因'),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False,
|
||||
server_default=sa.func.now(), comment='登录时间'),
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 创建索引
|
||||
# --------------------------------------------------------------------------
|
||||
op.create_index('idx_login_logs_employee_id', 'login_logs', ['employee_id'])
|
||||
op.create_index('idx_login_logs_corp_id', 'login_logs', ['corp_id'])
|
||||
op.create_index('idx_login_logs_created_at', 'login_logs', ['created_at'])
|
||||
op.create_index('idx_login_logs_status', 'login_logs', ['status'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# --------------------------------------------------------------------------
|
||||
# 删除索引
|
||||
# --------------------------------------------------------------------------
|
||||
op.drop_index('idx_login_logs_status', table_name='login_logs')
|
||||
op.drop_index('idx_login_logs_created_at', table_name='login_logs')
|
||||
op.drop_index('idx_login_logs_corp_id', table_name='login_logs')
|
||||
op.drop_index('idx_login_logs_employee_id', table_name='login_logs')
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 删除表
|
||||
# --------------------------------------------------------------------------
|
||||
op.drop_table('login_logs')
|
||||
@@ -0,0 +1,194 @@
|
||||
"""add business_contacts and routing_events tables
|
||||
|
||||
新建 business_contacts 表(业务联系人)和 routing_events 表(路由命中统计),
|
||||
并预置初始联系人数据(行政/HR/财务/法务/物业 各1-2人)。
|
||||
|
||||
关联设计文档:docs/03-技术架构/业务路由推荐-架构设计.md
|
||||
|
||||
Revision ID: 047_business_contacts
|
||||
Revises: 046_add_login_logs
|
||||
Create Date: 2026-07-15
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '047_business_contacts'
|
||||
down_revision: Union[str, None] = '046_add_login_logs'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# --------------------------------------------------------------------------
|
||||
# 新建 business_contacts 表
|
||||
# --------------------------------------------------------------------------
|
||||
op.create_table(
|
||||
'business_contacts',
|
||||
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True,
|
||||
comment='主键'),
|
||||
sa.Column('name', sa.String(50), nullable=False, comment='联系人姓名'),
|
||||
sa.Column('gender', sa.String(10), nullable=False, server_default='male',
|
||||
comment='性别(male/female)'),
|
||||
sa.Column('department', sa.String(100), nullable=False, comment='部门名称'),
|
||||
sa.Column('position', sa.String(100), nullable=False, comment='岗位'),
|
||||
sa.Column('responsibility', sa.String(500), nullable=False, comment='负责业务描述'),
|
||||
sa.Column('extension', sa.String(20), nullable=True, comment='分机号'),
|
||||
sa.Column('service_area', sa.String(200), nullable=True, comment='服务区域/办公地点'),
|
||||
sa.Column('wecom_userid', sa.String(100), nullable=False, comment='企微用户ID'),
|
||||
sa.Column('avatar_url', sa.String(500), nullable=True, comment='头像URL'),
|
||||
sa.Column('business_category', sa.String(50), nullable=False,
|
||||
comment='业务类别(行政/人力资源/财务/法务/行政-物业)'),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=False, server_default=sa.text('true'),
|
||||
comment='是否启用'),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False,
|
||||
server_default=sa.func.now(), comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False,
|
||||
server_default=sa.func.now(), comment='更新时间'),
|
||||
)
|
||||
|
||||
# 索引:按业务类别 + 是否启用查询
|
||||
op.create_index(
|
||||
'idx_business_contacts_category',
|
||||
'business_contacts',
|
||||
['business_category', 'is_active'],
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 新建 routing_events 表(P1)
|
||||
# --------------------------------------------------------------------------
|
||||
op.create_table(
|
||||
'routing_events',
|
||||
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True,
|
||||
comment='主键'),
|
||||
sa.Column('conversation_id', sa.String(36),
|
||||
sa.ForeignKey('conversations.id', ondelete='CASCADE'),
|
||||
nullable=False, comment='会话ID'),
|
||||
sa.Column('employee_id', sa.String(64), nullable=False, comment='员工ID'),
|
||||
sa.Column('message_content', sa.String(500), nullable=False,
|
||||
comment='触发路由的员工消息(截断)'),
|
||||
sa.Column('business_category', sa.String(50), nullable=False,
|
||||
comment='识别的业务类别'),
|
||||
sa.Column('routing_confidence', sa.Float(), nullable=False, comment='路由置信度'),
|
||||
sa.Column('contact_id', sa.Integer(),
|
||||
sa.ForeignKey('business_contacts.id', ondelete='SET NULL'),
|
||||
nullable=True, comment='推荐的联系人ID'),
|
||||
sa.Column('contact_name', sa.String(50), nullable=False,
|
||||
comment='联系人姓名(冗余)'),
|
||||
sa.Column('is_clicked', sa.Boolean(), nullable=False, server_default=sa.text('false'),
|
||||
comment='是否点击了联系TA'),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False,
|
||||
server_default=sa.func.now(), comment='创建时间'),
|
||||
)
|
||||
|
||||
# 索引
|
||||
op.create_index('idx_routing_events_category', 'routing_events', ['business_category'])
|
||||
op.create_index('idx_routing_events_conv', 'routing_events', ['conversation_id'])
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 预置初始联系人数据
|
||||
# --------------------------------------------------------------------------
|
||||
# 说明:wecom_userid 使用占位符,上线前需替换为真实的企微通讯录 userid
|
||||
# --------------------------------------------------------------------------
|
||||
contacts_data = [
|
||||
# 行政(2人)
|
||||
{
|
||||
'name': '王芳', 'gender': 'female', 'department': '行政部',
|
||||
'position': '设备管理岗', 'responsibility': '打印机/复印机/扫描仪',
|
||||
'extension': '8002', 'service_area': '滨江园区 3-5楼',
|
||||
'wecom_userid': 'WangFang', 'avatar_url': '',
|
||||
'business_category': '行政', 'is_active': True,
|
||||
},
|
||||
{
|
||||
'name': '陈伟', 'gender': 'male', 'department': '行政部',
|
||||
'position': '行政事务岗', 'responsibility': '办公用品/名片印刷/保洁服务',
|
||||
'extension': '8003', 'service_area': '滨江园区 1-2楼',
|
||||
'wecom_userid': 'ChenWei', 'avatar_url': '',
|
||||
'business_category': '行政', 'is_active': True,
|
||||
},
|
||||
# 人力资源(2人)
|
||||
{
|
||||
'name': '李娜', 'gender': 'female', 'department': '人力资源部',
|
||||
'position': '员工服务岗', 'responsibility': '工牌补办/考勤异常/入职手续',
|
||||
'extension': '8005', 'service_area': '滨江园区 A栋3楼',
|
||||
'wecom_userid': 'LiNa', 'avatar_url': '',
|
||||
'business_category': '人力资源', 'is_active': True,
|
||||
},
|
||||
{
|
||||
'name': '张磊', 'gender': 'male', 'department': '人力资源部',
|
||||
'position': '薪酬福利岗', 'responsibility': '社保/公积金/离职手续',
|
||||
'extension': '8006', 'service_area': '滨江园区 A栋3楼',
|
||||
'wecom_userid': 'ZhangLei', 'avatar_url': '',
|
||||
'business_category': '人力资源', 'is_active': True,
|
||||
},
|
||||
# 财务(1人)
|
||||
{
|
||||
'name': '刘洋', 'gender': 'male', 'department': '财务部',
|
||||
'position': '费用报销岗', 'responsibility': '报销/发票/借款/工资条',
|
||||
'extension': '8010', 'service_area': '滨江园区 B栋4楼',
|
||||
'wecom_userid': 'LiuYang', 'avatar_url': '',
|
||||
'business_category': '财务', 'is_active': True,
|
||||
},
|
||||
# 法务(1人)
|
||||
{
|
||||
'name': '赵敏', 'gender': 'female', 'department': '法务部',
|
||||
'position': '法务顾问岗', 'responsibility': '合同/法律咨询/知识产权',
|
||||
'extension': '8015', 'service_area': '滨江园区 C栋5楼',
|
||||
'wecom_userid': 'ZhaoMin', 'avatar_url': '',
|
||||
'business_category': '法务', 'is_active': True,
|
||||
},
|
||||
# 行政-物业(1人)
|
||||
{
|
||||
'name': '孙强', 'gender': 'male', 'department': '行政部',
|
||||
'position': '物业管理岗', 'responsibility': '空调/电梯/门禁/停车',
|
||||
'extension': '8008', 'service_area': '滨江园区 全园区',
|
||||
'wecom_userid': 'SunQiang', 'avatar_url': '',
|
||||
'business_category': '行政-物业', 'is_active': True,
|
||||
},
|
||||
]
|
||||
|
||||
# 批量插入初始数据
|
||||
op.bulk_insert(
|
||||
sa.table(
|
||||
'business_contacts',
|
||||
sa.column('name', sa.String),
|
||||
sa.column('gender', sa.String),
|
||||
sa.column('department', sa.String),
|
||||
sa.column('position', sa.String),
|
||||
sa.column('responsibility', sa.String),
|
||||
sa.column('extension', sa.String),
|
||||
sa.column('service_area', sa.String),
|
||||
sa.column('wecom_userid', sa.String),
|
||||
sa.column('avatar_url', sa.String),
|
||||
sa.column('business_category', sa.String),
|
||||
sa.column('is_active', sa.Boolean),
|
||||
sa.column('created_at', sa.DateTime),
|
||||
sa.column('updated_at', sa.DateTime),
|
||||
),
|
||||
[
|
||||
{
|
||||
**c,
|
||||
'avatar_url': c['avatar_url'] or None,
|
||||
'extension': c.get('extension') or None,
|
||||
'service_area': c.get('service_area') or None,
|
||||
'created_at': sa.func.now(),
|
||||
'updated_at': sa.func.now(),
|
||||
}
|
||||
for c in contacts_data
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# 删除索引
|
||||
op.drop_index('idx_routing_events_conv', table_name='routing_events')
|
||||
op.drop_index('idx_routing_events_category', table_name='routing_events')
|
||||
op.drop_index('idx_business_contacts_category', table_name='business_contacts')
|
||||
|
||||
# 删除表
|
||||
op.drop_table('routing_events')
|
||||
op.drop_table('business_contacts')
|
||||
@@ -0,0 +1,70 @@
|
||||
"""add information_items table and paused_at column (复杂场景重构第一阶段)
|
||||
|
||||
新建 auto_information_items 表(信息项管理),并在 auto_sessions 表新增
|
||||
paused_at 字段(暂停时间戳,用于超时计算)。
|
||||
|
||||
关联设计文档:docs/03-技术架构/复杂场景重构第一阶段-架构设计.md
|
||||
|
||||
Revision ID: 048_add_information_items
|
||||
Revises: 047_business_contacts
|
||||
Create Date: 2026-07-20
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '048_add_information_items'
|
||||
down_revision: Union[str, None] = '047_business_contacts'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# --------------------------------------------------------------------------
|
||||
# 1. 新建 auto_information_items 表
|
||||
# --------------------------------------------------------------------------
|
||||
op.create_table(
|
||||
'auto_information_items',
|
||||
sa.Column('id', sa.String(36), primary_key=True),
|
||||
sa.Column('session_id', sa.String(36), nullable=False, index=True,
|
||||
comment='关联会话ID(弱关联,不建外键)'),
|
||||
sa.Column('name', sa.String(128), nullable=False, comment='信息项名称'),
|
||||
sa.Column('value', sa.Text(), nullable=False, server_default='', comment='当前值'),
|
||||
sa.Column('modifiers', sa.JSON(), nullable=False, server_default='[]',
|
||||
comment='修饰符列表,如 ["固定","必需"]'),
|
||||
sa.Column('is_filled', sa.Boolean(), nullable=False, server_default=sa.false(),
|
||||
comment='是否已填写'),
|
||||
sa.Column('is_locked', sa.Boolean(), nullable=False, server_default=sa.false(),
|
||||
comment='是否已锁定(固定修饰符 + 关联动作已执行 → True)'),
|
||||
sa.Column('version', sa.Integer(), nullable=False, server_default='1', comment='版本号'),
|
||||
sa.Column('update_history', sa.JSON(), nullable=False, server_default='[]',
|
||||
comment='变更历史数组'),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False,
|
||||
server_default=sa.text('NOW()'), comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False,
|
||||
server_default=sa.text('NOW()'), comment='最后更新时间'),
|
||||
)
|
||||
op.create_index('idx_info_items_session_id', 'auto_information_items', ['session_id'])
|
||||
op.create_index('idx_info_items_session_name', 'auto_information_items', ['session_id', 'name'])
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 2. auto_sessions 表新增 paused_at 字段
|
||||
# --------------------------------------------------------------------------
|
||||
op.add_column(
|
||||
'auto_sessions',
|
||||
sa.Column('paused_at', sa.DateTime(timezone=True), nullable=True,
|
||||
comment='暂停时间戳,用于超时计算;恢复后置NULL'),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# 回滚 auto_sessions 新增字段
|
||||
op.drop_column('auto_sessions', 'paused_at')
|
||||
# 回滚 auto_information_items 表
|
||||
op.drop_index('idx_info_items_session_name', table_name='auto_information_items')
|
||||
op.drop_index('idx_info_items_session_id', table_name='auto_information_items')
|
||||
op.drop_table('auto_information_items')
|
||||
@@ -0,0 +1,63 @@
|
||||
"""add p2 context compression and p3 snapshot tables
|
||||
|
||||
Revision ID: 049_add_p2_p3_tables
|
||||
Revises: 048_add_information_items
|
||||
Create Date: 2025-07-11
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "049_add_p2_p3_tables"
|
||||
down_revision: Union[str, None] = "048_add_information_items"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 1. 新增 auto_context_compressions 表
|
||||
op.create_table(
|
||||
"auto_context_compressions",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("session_id", sa.String(36), nullable=False),
|
||||
sa.Column("tokens_before", sa.Integer(), nullable=False),
|
||||
sa.Column("tokens_after", sa.Integer(), nullable=False),
|
||||
sa.Column("compression_ratio", sa.Numeric(5, 2), nullable=False),
|
||||
sa.Column("task_node", sa.String(128), nullable=True),
|
||||
sa.Column("duration_ms", sa.Integer(), nullable=False),
|
||||
sa.Column("compression_level", sa.SmallInteger(), nullable=False, server_default="1"),
|
||||
sa.Column("summary", sa.Text(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_auto_context_compressions_session_id", "auto_context_compressions", ["session_id"])
|
||||
|
||||
# 2. 新增 auto_information_snapshots 表
|
||||
op.create_table(
|
||||
"auto_information_snapshots",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("session_id", sa.String(36), nullable=False),
|
||||
sa.Column("trigger_item_key", sa.String(64), nullable=False),
|
||||
sa.Column("snapshot_data", postgresql.JSONB(), nullable=False),
|
||||
sa.Column("correction_ids", postgresql.JSONB(), nullable=False, server_default="[]"),
|
||||
sa.Column("is_undone", sa.Boolean(), nullable=False, server_default="false"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_auto_information_snapshots_session_id", "auto_information_snapshots", ["session_id"])
|
||||
|
||||
# 3. auto_information_items 表新增列
|
||||
op.add_column("auto_information_items", sa.Column("derived_from", postgresql.JSONB(), nullable=True))
|
||||
op.add_column("auto_information_items", sa.Column("correction_reason", sa.String(200), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("auto_information_items", "correction_reason")
|
||||
op.drop_column("auto_information_items", "derived_from")
|
||||
op.drop_index("ix_auto_information_snapshots_session_id", table_name="auto_information_snapshots")
|
||||
op.drop_table("auto_information_snapshots")
|
||||
op.drop_index("ix_auto_context_compressions_session_id", table_name="auto_context_compressions")
|
||||
op.drop_table("auto_context_compressions")
|
||||
@@ -0,0 +1,74 @@
|
||||
"""add meetingroom terminal binding and booking snapshot tables
|
||||
|
||||
Revision ID: 050_add_meetingroom_tables
|
||||
Revises: 049_add_p2_p3_tables
|
||||
Create Date: 2026-07-15
|
||||
|
||||
新增表:
|
||||
- terminal_room_binding: 终端-会议室绑定关系(小鱼易联终端SN ↔ 企微会议室ID)
|
||||
- meetingroom_booking_snapshot: 会议室预定记录快照(P2统计用)
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# Alembic 修订标识符
|
||||
revision: str = "050_add_meetingroom_tables"
|
||||
down_revision: Union[str, None] = "049_add_p2_p3_tables"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""升级:创建 terminal_room_binding 和 meetingroom_booking_snapshot 表。"""
|
||||
|
||||
# 1. 终端-会议室绑定表
|
||||
op.create_table(
|
||||
"terminal_room_binding",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("terminal_sn", sa.String(64), nullable=False),
|
||||
sa.Column("terminal_name", sa.String(100), nullable=False, server_default=""),
|
||||
sa.Column("meetingroom_id", sa.Integer(), nullable=False),
|
||||
sa.Column("meetingroom_name", sa.String(100), nullable=False, server_default=""),
|
||||
sa.Column("location", sa.String(200), nullable=False, server_default=""),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
# terminal_sn 唯一索引(一个终端只能绑定一个会议室)
|
||||
op.create_index("ix_terminal_room_binding_terminal_sn", "terminal_room_binding", ["terminal_sn"], unique=True)
|
||||
# meetingroom_id 普通索引(一个会议室可被多个终端绑定)
|
||||
op.create_index("ix_terminal_room_binding_meetingroom_id", "terminal_room_binding", ["meetingroom_id"])
|
||||
|
||||
# 2. 会议室预定记录快照表(P2统计用)
|
||||
op.create_table(
|
||||
"meetingroom_booking_snapshot",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("meetingroom_id", sa.Integer(), nullable=False),
|
||||
sa.Column("booking_id", sa.String(64), nullable=False),
|
||||
sa.Column("subject", sa.String(200), nullable=False, server_default=""),
|
||||
sa.Column("booker", sa.String(64), nullable=False, server_default=""),
|
||||
sa.Column("start_time", sa.DateTime(), nullable=False),
|
||||
sa.Column("end_time", sa.DateTime(), nullable=False),
|
||||
sa.Column("status", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("snapshot_date", sa.Date(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_meetingroom_booking_snapshot_meetingroom_id", "meetingroom_booking_snapshot", ["meetingroom_id"])
|
||||
op.create_index("ix_meetingroom_booking_snapshot_booking_id", "meetingroom_booking_snapshot", ["booking_id"])
|
||||
op.create_index("ix_meetingroom_booking_snapshot_snapshot_date", "meetingroom_booking_snapshot", ["snapshot_date"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""回滚:删除 meetingroom_booking_snapshot 和 terminal_room_binding 表。"""
|
||||
op.drop_index("ix_meetingroom_booking_snapshot_snapshot_date", table_name="meetingroom_booking_snapshot")
|
||||
op.drop_index("ix_meetingroom_booking_snapshot_booking_id", table_name="meetingroom_booking_snapshot")
|
||||
op.drop_index("ix_meetingroom_booking_snapshot_meetingroom_id", table_name="meetingroom_booking_snapshot")
|
||||
op.drop_table("meetingroom_booking_snapshot")
|
||||
|
||||
op.drop_index("ix_terminal_room_binding_meetingroom_id", table_name="terminal_room_binding")
|
||||
op.drop_index("ix_terminal_room_binding_terminal_sn", table_name="terminal_room_binding")
|
||||
op.drop_table("terminal_room_binding")
|
||||
Reference in New Issue
Block a user