"""role system — 统一入口角色系统迁移 新增 roles 表(角色定义)。 新增 user_roles 表(用户角色关联)。 新增 role_mapping_rules 表(角色映射规则)。 预置三个基础角色:user、agent、admin。 Revision ID: 007_role_sys Revises: 006_admin_ext Create Date: 2026-06-12 23:00:00.000000 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '007_role_sys' down_revision = '006_admin_ext' branch_labels = None depends_on = None def upgrade() -> None: """执行角色系统迁移。""" # 1. 创建 roles 表 op.create_table( 'roles', sa.Column('id', sa.String(36), primary_key=True), sa.Column('name', sa.String(50), unique=True, nullable=False, comment='角色标识:user/agent/admin'), sa.Column('display_name', sa.String(100), nullable=False, comment='显示名称:用户/坐席/管理员'), sa.Column('description', sa.Text, nullable=True, comment='角色描述'), sa.Column('permissions', sa.JSON, nullable=False, server_default='[]', comment='权限列表'), sa.Column('is_default', sa.Boolean, nullable=False, server_default='0', 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='更新时间'), ) # 2. 创建 user_roles 表 op.create_table( 'user_roles', sa.Column('id', sa.String(36), primary_key=True), sa.Column('employee_id', sa.String(100), nullable=False, comment='企微 UserID'), sa.Column('role_id', sa.String(36), sa.ForeignKey('roles.id', ondelete='CASCADE'), nullable=False, comment='角色 ID'), sa.Column('source', sa.String(50), nullable=False, comment='角色来源:auto/tag/ehr/manual'), sa.Column('assigned_by', sa.String(100), nullable=True, comment='分配者'), sa.Column('assigned_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), comment='分配时间'), sa.Column('expires_at', sa.DateTime(timezone=True), nullable=True, comment='过期时间'), sa.UniqueConstraint('employee_id', 'role_id', name='uq_user_role'), ) # 创建索引 op.create_index('idx_user_roles_employee_id', 'user_roles', ['employee_id']) op.create_index('idx_user_roles_role_id', 'user_roles', ['role_id']) # 3. 创建 role_mapping_rules 表 op.create_table( 'role_mapping_rules', sa.Column('id', sa.String(36), primary_key=True), sa.Column('role_id', sa.String(36), sa.ForeignKey('roles.id', ondelete='CASCADE'), nullable=False, comment='目标角色 ID'), sa.Column('source_type', sa.String(50), nullable=False, comment='来源类型:wecom_tag/ehr_position'), sa.Column('source_value', sa.String(200), nullable=False, comment='来源值:标签名/岗位关键词'), sa.Column('priority', sa.Integer(), nullable=False, server_default='0', comment='优先级'), sa.Column('is_active', sa.Boolean(), nullable=False, server_default='1', comment='是否启用'), sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), comment='创建时间'), ) # 创建索引 op.create_index('idx_role_mapping_rules_role_id', 'role_mapping_rules', ['role_id']) op.create_index('idx_role_mapping_rules_source_type', 'role_mapping_rules', ['source_type']) # 4. 预置三个基础角色 # 注意:使用 op.execute 直接插入数据,因为 server_default 不适用于 Python 端生成的 UUID # PostgreSQL 使用 NOW() 替代 SQLite 的 datetime('now') op.execute(""" INSERT INTO roles (id, name, display_name, description, permissions, is_default, created_at, updated_at) VALUES ('role_user_001', 'user', '用户', '所有在职员工默认角色,可提交工单、查看进度、浏览知识库', '["ticket.create", "ticket.view", "knowledge.view"]', TRUE, NOW(), NOW()), ('role_agent_001', 'agent', '坐席', 'IT支持人员,可处理会话、使用AI辅助、管理工单', '["conversation.manage", "ticket.assign", "knowledge.edit", "ai.wingman"]', FALSE, NOW(), NOW()), ('role_admin_001', 'admin', '管理员', '系统管理员,可配置系统、管理权限、查看数据分析', '["system.config", "user.manage", "role.manage", "analytics.view"]', FALSE, NOW(), NOW()) """) # 5. 预置默认映射规则(企微标签 → agent 角色) op.execute(""" INSERT INTO role_mapping_rules (id, role_id, source_type, source_value, priority, is_active, created_at) VALUES ('rule_agent_tag_001', 'role_agent_001', 'wecom_tag', 'IT坐席', 10, TRUE, NOW()), ('rule_agent_ehr_001', 'role_agent_001', 'ehr_position', 'IT支持', 10, TRUE, NOW()), ('rule_agent_ehr_002', 'role_agent_001', 'ehr_position', 'IT运维', 10, TRUE, NOW()), ('rule_agent_ehr_003', 'role_agent_001', 'ehr_position', '技术支持', 10, TRUE, NOW()) """) def downgrade() -> None: """回滚角色系统迁移。""" # 删除 role_mapping_rules 表索引和表 op.drop_index('idx_role_mapping_rules_source_type', table_name='role_mapping_rules') op.drop_index('idx_role_mapping_rules_role_id', table_name='role_mapping_rules') op.drop_table('role_mapping_rules') # 删除 user_roles 表索引和表 op.drop_index('idx_user_roles_role_id', table_name='user_roles') op.drop_index('idx_user_roles_employee_id', table_name='user_roles') op.drop_table('user_roles') # 删除 roles 表 op.drop_table('roles')