add backend source code
This commit is contained in:
68
backend/alembic/env.py
Normal file
68
backend/alembic/env.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Alembic环境配置
|
||||
"""
|
||||
from logging.config import fileConfig
|
||||
from sqlalchemy import pool
|
||||
from sqlalchemy.engine import Connection
|
||||
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||
from alembic import context
|
||||
|
||||
from app.core.config import settings
|
||||
from app.models.models import Base
|
||||
|
||||
config = context.config
|
||||
|
||||
# 使用 settings 中的数据库 URL
|
||||
config.set_main_option("sqlalchemy.url", settings.DATABASE_URL)
|
||||
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
target_metadata = Base.metadata
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
"""离线模式运行迁移"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def do_run_migrations(connection: Connection) -> None:
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
async def run_async_migrations() -> None:
|
||||
"""异步模式运行迁移"""
|
||||
connectable = async_engine_from_config(
|
||||
config.get_section(config.config_ini_section, {}),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
|
||||
await connectable.dispose()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""在线模式运行迁移"""
|
||||
import asyncio
|
||||
asyncio.run(run_async_migrations())
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
182
backend/alembic/versions/001_initial.py
Normal file
182
backend/alembic/versions/001_initial.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: initial
|
||||
Revises:
|
||||
Create Date: 2024-01-01 00:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'initial'
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 创建科室表
|
||||
op.create_table(
|
||||
'departments',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('name', sa.String(length=100), nullable=False, comment='科室名称'),
|
||||
sa.Column('code', sa.String(length=20), nullable=False, comment='科室编码'),
|
||||
sa.Column('dept_type', sa.String(length=50), nullable=False, comment='科室类型'),
|
||||
sa.Column('parent_id', sa.Integer(), nullable=True, comment='上级科室'),
|
||||
sa.Column('level', sa.Integer(), nullable=True, comment='层级'),
|
||||
sa.Column('sort_order', sa.Integer(), nullable=True, comment='排序'),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True, comment='是否启用'),
|
||||
sa.Column('description', sa.Text(), nullable=True, comment='描述'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('code')
|
||||
)
|
||||
op.create_index('idx_dept_type', 'departments', ['dept_type'])
|
||||
op.create_index('idx_dept_parent', 'departments', ['parent_id'])
|
||||
|
||||
# 创建员工表
|
||||
op.create_table(
|
||||
'staff',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('employee_id', sa.String(length=20), nullable=False, comment='工号'),
|
||||
sa.Column('name', sa.String(length=50), nullable=False, comment='姓名'),
|
||||
sa.Column('department_id', sa.Integer(), nullable=False, comment='所属科室'),
|
||||
sa.Column('position', sa.String(length=50), nullable=False, comment='职位'),
|
||||
sa.Column('title', sa.String(length=50), nullable=True, comment='职称'),
|
||||
sa.Column('phone', sa.String(length=20), nullable=True, comment='联系电话'),
|
||||
sa.Column('email', sa.String(length=100), nullable=True, comment='邮箱'),
|
||||
sa.Column('base_salary', sa.Numeric(precision=10, scale=2), nullable=True, comment='基本工资'),
|
||||
sa.Column('performance_ratio', sa.Numeric(precision=5, scale=2), nullable=True, comment='绩效系数'),
|
||||
sa.Column('status', sa.String(length=50), nullable=True, comment='状态'),
|
||||
sa.Column('hire_date', sa.DateTime(), nullable=True, comment='入职日期'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.ForeignKeyConstraint(['department_id'], ['departments.id']),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('employee_id')
|
||||
)
|
||||
op.create_index('idx_staff_dept', 'staff', ['department_id'])
|
||||
op.create_index('idx_staff_status', 'staff', ['status'])
|
||||
|
||||
# 创建指标表
|
||||
op.create_table(
|
||||
'indicators',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('name', sa.String(length=100), nullable=False, comment='指标名称'),
|
||||
sa.Column('code', sa.String(length=20), nullable=False, comment='指标编码'),
|
||||
sa.Column('indicator_type', sa.String(length=50), nullable=False, comment='指标类型'),
|
||||
sa.Column('weight', sa.Numeric(precision=5, scale=2), nullable=True, comment='权重'),
|
||||
sa.Column('max_score', sa.Numeric(precision=5, scale=2), nullable=True, comment='最高分值'),
|
||||
sa.Column('target_value', sa.Numeric(precision=10, scale=2), nullable=True, comment='目标值'),
|
||||
sa.Column('unit', sa.String(length=20), nullable=True, comment='计量单位'),
|
||||
sa.Column('calculation_method', sa.Text(), nullable=True, comment='计算方法'),
|
||||
sa.Column('description', sa.Text(), nullable=True, comment='描述'),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True, comment='是否启用'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('code')
|
||||
)
|
||||
op.create_index('idx_indicator_type', 'indicators', ['indicator_type'])
|
||||
|
||||
# 创建考核记录表
|
||||
op.create_table(
|
||||
'assessments',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('staff_id', sa.Integer(), nullable=False, comment='员工ID'),
|
||||
sa.Column('period_year', sa.Integer(), nullable=False, comment='考核年度'),
|
||||
sa.Column('period_month', sa.Integer(), nullable=False, comment='考核月份'),
|
||||
sa.Column('period_type', sa.String(length=20), nullable=True, comment='考核周期类型'),
|
||||
sa.Column('total_score', sa.Numeric(precision=5, scale=2), nullable=True, comment='总分'),
|
||||
sa.Column('weighted_score', sa.Numeric(precision=5, scale=2), nullable=True, comment='加权得分'),
|
||||
sa.Column('status', sa.String(length=50), nullable=True, comment='状态'),
|
||||
sa.Column('assessor_id', sa.Integer(), nullable=True, comment='考核人'),
|
||||
sa.Column('reviewer_id', sa.Integer(), nullable=True, comment='审核人'),
|
||||
sa.Column('submit_time', sa.DateTime(), nullable=True, comment='提交时间'),
|
||||
sa.Column('review_time', sa.DateTime(), nullable=True, comment='审核时间'),
|
||||
sa.Column('remark', sa.Text(), nullable=True, comment='备注'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.ForeignKeyConstraint(['staff_id'], ['staff.id']),
|
||||
sa.ForeignKeyConstraint(['assessor_id'], ['staff.id']),
|
||||
sa.ForeignKeyConstraint(['reviewer_id'], ['staff.id']),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index('idx_assessment_staff', 'assessments', ['staff_id'])
|
||||
op.create_index('idx_assessment_period', 'assessments', ['period_year', 'period_month'])
|
||||
op.create_index('idx_assessment_status', 'assessments', ['status'])
|
||||
|
||||
# 创建考核明细表
|
||||
op.create_table(
|
||||
'assessment_details',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('assessment_id', sa.Integer(), nullable=False, comment='考核记录ID'),
|
||||
sa.Column('indicator_id', sa.Integer(), nullable=False, comment='指标ID'),
|
||||
sa.Column('actual_value', sa.Numeric(precision=10, scale=2), nullable=True, comment='实际值'),
|
||||
sa.Column('score', sa.Numeric(precision=5, scale=2), nullable=True, comment='得分'),
|
||||
sa.Column('evidence', sa.Text(), nullable=True, comment='佐证材料'),
|
||||
sa.Column('remark', sa.Text(), nullable=True, comment='备注'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.ForeignKeyConstraint(['assessment_id'], ['assessments.id']),
|
||||
sa.ForeignKeyConstraint(['indicator_id'], ['indicators.id']),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index('idx_detail_assessment', 'assessment_details', ['assessment_id'])
|
||||
op.create_index('idx_detail_indicator', 'assessment_details', ['indicator_id'])
|
||||
|
||||
# 创建工资记录表
|
||||
op.create_table(
|
||||
'salary_records',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('staff_id', sa.Integer(), nullable=False, comment='员工ID'),
|
||||
sa.Column('period_year', sa.Integer(), nullable=False, comment='年度'),
|
||||
sa.Column('period_month', sa.Integer(), nullable=False, comment='月份'),
|
||||
sa.Column('base_salary', sa.Numeric(precision=10, scale=2), nullable=True, comment='基本工资'),
|
||||
sa.Column('performance_score', sa.Numeric(precision=5, scale=2), nullable=True, comment='绩效得分'),
|
||||
sa.Column('performance_bonus', sa.Numeric(precision=10, scale=2), nullable=True, comment='绩效奖金'),
|
||||
sa.Column('deduction', sa.Numeric(precision=10, scale=2), nullable=True, comment='扣款'),
|
||||
sa.Column('allowance', sa.Numeric(precision=10, scale=2), nullable=True, comment='补贴'),
|
||||
sa.Column('total_salary', sa.Numeric(precision=10, scale=2), nullable=True, comment='应发工资'),
|
||||
sa.Column('status', sa.String(length=50), nullable=True, comment='状态'),
|
||||
sa.Column('remark', sa.Text(), nullable=True, comment='备注'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.ForeignKeyConstraint(['staff_id'], ['staff.id']),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index('idx_salary_staff', 'salary_records', ['staff_id'])
|
||||
op.create_index('idx_salary_period', 'salary_records', ['period_year', 'period_month'])
|
||||
|
||||
# 创建用户表
|
||||
op.create_table(
|
||||
'users',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('username', sa.String(length=50), nullable=False, comment='用户名'),
|
||||
sa.Column('password_hash', sa.String(length=255), nullable=False, comment='密码哈希'),
|
||||
sa.Column('staff_id', sa.Integer(), nullable=True, comment='关联员工'),
|
||||
sa.Column('role', sa.String(length=20), nullable=True, comment='角色'),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True, comment='是否启用'),
|
||||
sa.Column('last_login', sa.DateTime(), nullable=True, comment='最后登录'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.ForeignKeyConstraint(['staff_id'], ['staff.id']),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('username')
|
||||
)
|
||||
op.create_index('idx_user_username', 'users', ['username'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table('users')
|
||||
op.drop_table('salary_records')
|
||||
op.drop_table('assessment_details')
|
||||
op.drop_table('assessments')
|
||||
op.drop_table('indicators')
|
||||
op.drop_table('staff')
|
||||
op.drop_table('departments')
|
||||
95
backend/alembic/versions/002_template.py
Normal file
95
backend/alembic/versions/002_template.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""添加指标模板表
|
||||
|
||||
Revision ID: 002_template
|
||||
Revises: initial
|
||||
Create Date: 2024-01-02 00:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '002_template'
|
||||
down_revision: Union[str, None] = 'initial'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 创建指标模板表
|
||||
op.create_table(
|
||||
'indicator_templates',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('template_name', sa.String(length=200), nullable=False, comment='模板名称'),
|
||||
sa.Column('template_code', sa.String(length=50), nullable=False, comment='模板编码'),
|
||||
sa.Column('template_type', sa.String(length=30), nullable=False, comment='模板类型'),
|
||||
sa.Column('description', sa.Text(), nullable=True, comment='模板描述'),
|
||||
sa.Column('dimension_weights', sa.Text(), nullable=True, comment='维度权重 (JSON)'),
|
||||
sa.Column('assessment_cycle', sa.String(length=20), nullable=True, comment='考核周期'),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True, comment='是否启用'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('template_code')
|
||||
)
|
||||
op.create_index('idx_template_type', 'indicator_templates', ['template_type'])
|
||||
op.create_index('idx_template_active', 'indicator_templates', ['is_active'])
|
||||
|
||||
# 创建模板指标关联表
|
||||
op.create_table(
|
||||
'template_indicators',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('template_id', sa.Integer(), nullable=False, comment='模板 ID'),
|
||||
sa.Column('indicator_id', sa.Integer(), nullable=False, comment='指标 ID'),
|
||||
sa.Column('category', sa.String(length=100), nullable=True, comment='指标分类'),
|
||||
sa.Column('target_value', sa.Numeric(precision=10, scale=2), nullable=True, comment='目标值'),
|
||||
sa.Column('target_unit', sa.String(length=50), nullable=True, comment='目标值单位'),
|
||||
sa.Column('weight', sa.Numeric(precision=5, scale=2), nullable=True, comment='权重'),
|
||||
sa.Column('scoring_method', sa.String(length=50), nullable=True, comment='评分方法'),
|
||||
sa.Column('scoring_params', sa.Text(), nullable=True, comment='评分参数 (JSON)'),
|
||||
sa.Column('sort_order', sa.Integer(), nullable=True, comment='排序'),
|
||||
sa.Column('remark', sa.Text(), nullable=True, comment='备注'),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.ForeignKeyConstraint(['template_id'], ['indicator_templates.id']),
|
||||
sa.ForeignKeyConstraint(['indicator_id'], ['indicators.id']),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index('idx_ti_template', 'template_indicators', ['template_id'])
|
||||
op.create_index('idx_ti_indicator', 'template_indicators', ['indicator_id'])
|
||||
op.create_index('idx_ti_unique', 'template_indicators', ['template_id', 'indicator_id'], unique=True)
|
||||
|
||||
# 为指标表添加 BSC 维度字段(如果不存在)
|
||||
# 注意:在 PostgreSQL 中,如果字段已存在会报错,需要检查
|
||||
conn = op.get_bind()
|
||||
inspector = sa.inspect(conn)
|
||||
columns = [col['name'] for col in inspector.get_columns('indicators')]
|
||||
|
||||
if 'bs_dimension' not in columns:
|
||||
op.add_column('indicators', sa.Column('bs_dimension', sa.String(length=30), nullable=True, comment='平衡计分卡维度'))
|
||||
|
||||
if 'target_unit' not in columns:
|
||||
op.add_column('indicators', sa.Column('target_unit', sa.String(length=50), nullable=True, comment='目标值单位'))
|
||||
|
||||
if 'assessment_method' not in columns:
|
||||
op.add_column('indicators', sa.Column('assessment_method', sa.Text(), nullable=True, comment='考核方法'))
|
||||
|
||||
if 'deduction_standard' not in columns:
|
||||
op.add_column('indicators', sa.Column('deduction_standard', sa.Text(), nullable=True, comment='扣分标准'))
|
||||
|
||||
if 'data_source' not in columns:
|
||||
op.add_column('indicators', sa.Column('data_source', sa.String(length=100), nullable=True, comment='数据来源'))
|
||||
|
||||
if 'applicable_dept_types' not in columns:
|
||||
op.add_column('indicators', sa.Column('applicable_dept_types', sa.Text(), nullable=True, comment='适用科室类型'))
|
||||
|
||||
if 'is_veto' not in columns:
|
||||
op.add_column('indicators', sa.Column('is_veto', sa.Boolean(), nullable=True, default=False, comment='是否一票否决指标'))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table('template_indicators')
|
||||
op.drop_table('indicator_templates')
|
||||
0
backend/alembic/versions/__init__.py
Normal file
0
backend/alembic/versions/__init__.py
Normal file
Reference in New Issue
Block a user