Files
wecom_it_smart_desk/backend/alembic/versions/047_business_contacts.py
T
Simon bea288e414 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
2026-07-11 23:13:10 +08:00

195 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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')