Files
hospital_performance/backend/app/models/models.py
2026-02-28 15:06:52 +08:00

589 lines
34 KiB
Python

"""
数据模型模块
"""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import (
String, Text, Integer, Numeric, Boolean, DateTime, ForeignKey, Enum as SQLEnum,
Index, CheckConstraint
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from enum import Enum
from app.core.database import Base
class DeptType(Enum):
"""科室类型"""
CLINICAL_SURGICAL = "clinical_surgical" # 手术临床科室
CLINICAL_NONSURGICAL_WARD = "clinical_nonsurgical_ward" # 非手术有病房科室
CLINICAL_NONSURGICAL_NOWARD = "clinical_nonsurgical_noward" # 非手术无病房科室
MEDICAL_TECH = "medical_tech" # 医技科室
MEDICAL_AUXILIARY = "medical_auxiliary" # 医辅科室
NURSING = "nursing" # 护理单元
ADMIN = "admin" # 行政科室
FINANCE = "finance" # 财务科室
LOGISTICS = "logistics" # 后勤保障科室
class BSCDimension(Enum):
"""平衡计分卡维度"""
FINANCIAL = "financial" # 财务维度
CUSTOMER = "customer" # 客户维度
INTERNAL_PROCESS = "internal_process" # 内部流程维度
LEARNING_GROWTH = "learning_growth" # 学习与成长维度
class StaffStatus(Enum):
"""员工状态"""
ACTIVE = "active" # 在职
LEAVE = "leave" # 休假
RESIGNED = "resigned" # 离职
RETIRED = "retired" # 退休
class AssessmentStatus(Enum):
"""考核状态"""
DRAFT = "draft" # 草稿
SUBMITTED = "submitted" # 已提交
REVIEWED = "reviewed" # 已审核
FINALIZED = "finalized" # 已确认
REJECTED = "rejected" # 已驳回
class IndicatorType(Enum):
"""指标类型"""
QUALITY = "quality" # 质量指标
QUANTITY = "quantity" # 数量指标
EFFICIENCY = "efficiency" # 效率指标
SERVICE = "service" # 服务指标
COST = "cost" # 成本指标
class Department(Base):
"""科室信息表"""
__tablename__ = "departments"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(100), nullable=False, comment="科室名称")
code: Mapped[str] = mapped_column(String(20), unique=True, nullable=False, comment="科室编码")
dept_type: Mapped[DeptType] = mapped_column(SQLEnum(DeptType, values_callable=lambda x: [e.value for e in x]), nullable=False, comment="科室类型")
parent_id: Mapped[Optional[int]] = mapped_column(ForeignKey("departments.id"), nullable=True, comment="上级科室")
level: Mapped[int] = mapped_column(Integer, default=1, comment="层级")
sort_order: Mapped[int] = mapped_column(Integer, default=0, comment="排序")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否启用")
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="描述")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
parent: Mapped[Optional["Department"]] = relationship("Department", remote_side=[id], backref="children")
staff: Mapped[List["Staff"]] = relationship("Staff", back_populates="department")
__table_args__ = (
Index("idx_dept_type", "dept_type"),
Index("idx_dept_parent", "parent_id"),
)
class Staff(Base):
"""员工信息表"""
__tablename__ = "staff"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
employee_id: Mapped[str] = mapped_column(String(20), unique=True, nullable=False, comment="工号")
name: Mapped[str] = mapped_column(String(50), nullable=False, comment="姓名")
department_id: Mapped[int] = mapped_column(ForeignKey("departments.id"), nullable=False, comment="所属科室")
position: Mapped[str] = mapped_column(String(50), nullable=False, comment="职位")
title: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, comment="职称")
phone: Mapped[Optional[str]] = mapped_column(String(20), nullable=True, comment="联系电话")
email: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment="邮箱")
base_salary: Mapped[float] = mapped_column(Numeric(10, 2), default=0, comment="基本工资")
performance_ratio: Mapped[float] = mapped_column(Numeric(5, 2), default=1.0, comment="绩效系数")
status: Mapped[StaffStatus] = mapped_column(SQLEnum(StaffStatus, values_callable=lambda x: [e.value for e in x]), default=StaffStatus.ACTIVE, comment="状态")
hire_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="入职日期")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
department: Mapped["Department"] = relationship("Department", back_populates="staff")
assessments: Mapped[List["Assessment"]] = relationship("Assessment", foreign_keys="Assessment.staff_id", back_populates="staff")
salary_records: Mapped[List["SalaryRecord"]] = relationship("SalaryRecord", back_populates="staff")
__table_args__ = (
Index("idx_staff_dept", "department_id"),
Index("idx_staff_status", "status"),
)
class Indicator(Base):
"""考核指标表"""
__tablename__ = "indicators"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(100), nullable=False, comment="指标名称")
code: Mapped[str] = mapped_column(String(20), unique=True, nullable=False, comment="指标编码")
indicator_type: Mapped[IndicatorType] = mapped_column(SQLEnum(IndicatorType, values_callable=lambda x: [e.value for e in x]), nullable=False, comment="指标类型")
bs_dimension: Mapped[BSCDimension] = mapped_column(SQLEnum(BSCDimension, values_callable=lambda x: [e.value for e in x]), nullable=False, comment="平衡计分卡维度")
weight: Mapped[float] = mapped_column(Numeric(5, 2), default=1.0, comment="权重")
max_score: Mapped[float] = mapped_column(Numeric(5, 2), default=100.0, comment="最高分值")
target_value: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), nullable=True, comment="目标值")
target_unit: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, comment="目标值单位")
calculation_method: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="计算方法/公式")
assessment_method: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="考核方法")
deduction_standard: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="扣分标准")
data_source: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment="数据来源")
applicable_dept_types: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="适用科室类型 (JSON 数组)")
is_veto: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否一票否决指标")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否启用")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
assessment_details: Mapped[List["AssessmentDetail"]] = relationship("AssessmentDetail", back_populates="indicator")
__table_args__ = (
Index("idx_indicator_type", "indicator_type"),
CheckConstraint("weight > 0", name="ck_indicator_weight"),
)
class Assessment(Base):
"""考核记录表"""
__tablename__ = "assessments"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
staff_id: Mapped[int] = mapped_column(ForeignKey("staff.id"), nullable=False, comment="员工ID")
period_year: Mapped[int] = mapped_column(Integer, nullable=False, comment="考核年度")
period_month: Mapped[int] = mapped_column(Integer, nullable=False, comment="考核月份")
period_type: Mapped[str] = mapped_column(String(20), default="monthly", comment="考核周期类型")
total_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0, comment="总分")
weighted_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0, comment="加权得分")
status: Mapped[AssessmentStatus] = mapped_column(SQLEnum(AssessmentStatus, values_callable=lambda x: [e.value for e in x]), default=AssessmentStatus.DRAFT, comment="状态")
assessor_id: Mapped[Optional[int]] = mapped_column(ForeignKey("staff.id"), nullable=True, comment="考核人")
reviewer_id: Mapped[Optional[int]] = mapped_column(ForeignKey("staff.id"), nullable=True, comment="审核人")
submit_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="提交时间")
review_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="审核时间")
remark: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="备注")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
staff: Mapped["Staff"] = relationship("Staff", foreign_keys=[staff_id], back_populates="assessments")
assessor: Mapped[Optional["Staff"]] = relationship("Staff", foreign_keys=[assessor_id])
reviewer: Mapped[Optional["Staff"]] = relationship("Staff", foreign_keys=[reviewer_id])
details: Mapped[List["AssessmentDetail"]] = relationship("AssessmentDetail", back_populates="assessment", cascade="all, delete-orphan")
__table_args__ = (
Index("idx_assessment_staff", "staff_id"),
Index("idx_assessment_period", "period_year", "period_month"),
Index("idx_assessment_status", "status"),
)
class AssessmentDetail(Base):
"""考核明细表"""
__tablename__ = "assessment_details"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
assessment_id: Mapped[int] = mapped_column(ForeignKey("assessments.id"), nullable=False, comment="考核记录ID")
indicator_id: Mapped[int] = mapped_column(ForeignKey("indicators.id"), nullable=False, comment="指标ID")
actual_value: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), nullable=True, comment="实际值")
score: Mapped[float] = mapped_column(Numeric(5, 2), default=0, comment="得分")
evidence: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="佐证材料")
remark: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="备注")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
assessment: Mapped["Assessment"] = relationship("Assessment", back_populates="details")
indicator: Mapped["Indicator"] = relationship("Indicator", back_populates="assessment_details")
__table_args__ = (
Index("idx_detail_assessment", "assessment_id"),
Index("idx_detail_indicator", "indicator_id"),
)
class SalaryRecord(Base):
"""工资核算记录表"""
__tablename__ = "salary_records"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
staff_id: Mapped[int] = mapped_column(ForeignKey("staff.id"), nullable=False, comment="员工ID")
period_year: Mapped[int] = mapped_column(Integer, nullable=False, comment="年度")
period_month: Mapped[int] = mapped_column(Integer, nullable=False, comment="月份")
base_salary: Mapped[float] = mapped_column(Numeric(10, 2), default=0, comment="基本工资")
performance_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0, comment="绩效得分")
performance_bonus: Mapped[float] = mapped_column(Numeric(10, 2), default=0, comment="绩效奖金")
deduction: Mapped[float] = mapped_column(Numeric(10, 2), default=0, comment="扣款")
allowance: Mapped[float] = mapped_column(Numeric(10, 2), default=0, comment="补贴")
total_salary: Mapped[float] = mapped_column(Numeric(10, 2), default=0, comment="应发工资")
status: Mapped[str] = mapped_column(String(20), default="pending", comment="状态")
remark: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="备注")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
staff: Mapped["Staff"] = relationship("Staff", back_populates="salary_records")
__table_args__ = (
Index("idx_salary_staff", "staff_id"),
Index("idx_salary_period", "period_year", "period_month"),
)
class PlanStatus(Enum):
"""计划状态"""
DRAFT = "draft" # 草稿
PENDING = "pending" # 待审批
APPROVED = "approved" # 已批准
REJECTED = "rejected" # 已驳回
ACTIVE = "active" # 执行中
COMPLETED = "completed" # 已完成
CANCELLED = "cancelled" # 已取消
class User(Base):
"""系统用户表"""
__tablename__ = "users"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
password_hash: Mapped[str] = mapped_column(String(255), nullable=False, comment="密码哈希")
staff_id: Mapped[Optional[int]] = mapped_column(ForeignKey("staff.id"), nullable=True, comment="关联员工")
role: Mapped[str] = mapped_column(String(20), default="staff", comment="角色")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否启用")
last_login: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="最后登录")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
__table_args__ = (
Index("idx_user_username", "username"),
)
class PlanLevel(Enum):
"""计划层级"""
HOSPITAL = "hospital" # 医院级
DEPARTMENT = "department" # 科室级
INDIVIDUAL = "individual" # 个人级
class PerformancePlan(Base):
"""绩效计划表"""
__tablename__ = "performance_plans"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
plan_name: Mapped[str] = mapped_column(String(200), nullable=False, comment="计划名称")
plan_code: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="计划编码")
plan_level: Mapped[PlanLevel] = mapped_column(SQLEnum(PlanLevel, values_callable=lambda x: [e.value for e in x]), nullable=False, comment="计划层级")
plan_year: Mapped[int] = mapped_column(Integer, nullable=False, comment="计划年度")
plan_month: Mapped[Optional[int]] = mapped_column(Integer, nullable=True, comment="计划月份 (月度计划)")
plan_type: Mapped[str] = mapped_column(String(20), default="annual", comment="计划类型 (annual/monthly)")
department_id: Mapped[Optional[int]] = mapped_column(ForeignKey("departments.id"), nullable=True, comment="所属科室 ID")
staff_id: Mapped[Optional[int]] = mapped_column(ForeignKey("staff.id"), nullable=True, comment="责任人 ID")
parent_plan_id: Mapped[Optional[int]] = mapped_column(ForeignKey("performance_plans.id"), nullable=True, comment="上级计划 ID")
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="计划描述")
strategic_goals: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="战略目标")
key_initiatives: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="关键举措")
status: Mapped[PlanStatus] = mapped_column(SQLEnum(PlanStatus, values_callable=lambda x: [e.value for e in x]), default=PlanStatus.DRAFT, comment="状态")
submitter_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id"), nullable=True, comment="提交人 ID")
submit_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="提交时间")
approver_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id"), nullable=True, comment="审批人 ID")
approve_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="审批时间")
approve_remark: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="审批意见")
version: Mapped[int] = mapped_column(Integer, default=1, comment="版本号")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否启用")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
department: Mapped[Optional["Department"]] = relationship("Department", backref="performance_plans")
staff: Mapped[Optional["Staff"]] = relationship("Staff", backref="performance_plans")
parent_plan: Mapped[Optional["PerformancePlan"]] = relationship("PerformancePlan", remote_side=[id], backref="child_plans")
submitter: Mapped[Optional["User"]] = relationship("User", foreign_keys=[submitter_id])
approver: Mapped[Optional["User"]] = relationship("User", foreign_keys=[approver_id])
kpi_relations: Mapped[List["PlanKpiRelation"]] = relationship("PlanKpiRelation", back_populates="plan", cascade="all, delete-orphan")
__table_args__ = (
Index("idx_plan_level", "plan_level"),
Index("idx_plan_year", "plan_year"),
Index("idx_plan_department", "department_id"),
Index("idx_plan_status", "status"),
)
class PlanKpiRelation(Base):
"""计划指标关联表"""
__tablename__ = "plan_kpi_relations"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
plan_id: Mapped[int] = mapped_column(ForeignKey("performance_plans.id"), nullable=False, comment="计划 ID")
indicator_id: Mapped[int] = mapped_column(ForeignKey("indicators.id"), nullable=False, comment="指标 ID")
target_value: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), nullable=True, comment="目标值")
target_unit: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, comment="目标值单位")
weight: Mapped[float] = mapped_column(Numeric(5, 2), default=1.0, comment="权重")
scoring_method: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, comment="评分方法")
scoring_params: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="评分参数 (JSON)")
remark: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="备注")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
plan: Mapped["PerformancePlan"] = relationship("PerformancePlan", back_populates="kpi_relations")
indicator: Mapped["Indicator"] = relationship("Indicator", backref="plan_relations")
__table_args__ = (
Index("idx_relation_plan", "plan_id"),
Index("idx_relation_indicator", "indicator_id"),
Index("idx_relation_unique", "plan_id", "indicator_id", unique=True),
)
class MenuType(Enum):
"""菜单类型"""
MENU = "menu" # 菜单
BUTTON = "button" # 按钮
class Menu(Base):
"""系统菜单表"""
__tablename__ = "menus"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
parent_id: Mapped[Optional[int]] = mapped_column(ForeignKey("menus.id"), nullable=True, comment="父菜单 ID")
menu_type: Mapped[MenuType] = mapped_column(SQLEnum(MenuType, values_callable=lambda x: [e.value for e in x]), default=MenuType.MENU, comment="菜单类型")
menu_name: Mapped[str] = mapped_column(String(100), nullable=False, comment="菜单名称")
menu_icon: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, comment="菜单图标")
path: Mapped[str] = mapped_column(String(200), nullable=False, comment="路由路径")
component: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, comment="组件路径")
permission: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment="权限标识")
sort_order: Mapped[int] = mapped_column(Integer, default=0, comment="排序")
is_visible: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可见")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否启用")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
parent: Mapped[Optional["Menu"]] = relationship("Menu", remote_side=[id], backref="children")
__table_args__ = (
Index("idx_menu_parent", "parent_id"),
Index("idx_menu_type", "menu_type"),
Index("idx_menu_visible", "is_visible"),
)
class TemplateType(Enum):
"""模板类型"""
GENERAL = "general" # 通用模板
SURGICAL = "surgical" # 手术临床科室
NON_SURGICAL_WARD = "nonsurgical_ward" # 非手术有病房科室
NON_SURGICAL_NOWARD = "nonsurgical_noward" # 非手术无病房科室
MEDICAL_TECH = "medical_tech" # 医技科室
NURSING = "nursing" # 护理单元
ADMIN = "admin" # 行政科室
LOGISTICS = "logistics" # 后勤科室
class IndicatorTemplate(Base):
"""考核指标模板表"""
__tablename__ = "indicator_templates"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
template_name: Mapped[str] = mapped_column(String(200), nullable=False, comment="模板名称")
template_code: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="模板编码")
template_type: Mapped[TemplateType] = mapped_column(SQLEnum(TemplateType, values_callable=lambda x: [e.value for e in x]), nullable=False, comment="模板类型")
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="模板描述")
dimension_weights: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="维度权重 (JSON)")
assessment_cycle: Mapped[str] = mapped_column(String(20), default="monthly", comment="考核周期")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否启用")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
indicators: Mapped[List["TemplateIndicator"]] = relationship("TemplateIndicator", back_populates="template", cascade="all, delete-orphan")
__table_args__ = (
Index("idx_template_type", "template_type"),
Index("idx_template_active", "is_active"),
)
class TemplateIndicator(Base):
"""模板指标关联表"""
__tablename__ = "template_indicators"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
template_id: Mapped[int] = mapped_column(ForeignKey("indicator_templates.id"), nullable=False, comment="模板 ID")
indicator_id: Mapped[int] = mapped_column(ForeignKey("indicators.id"), nullable=False, comment="指标 ID")
category: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment="指标分类 (二级指标)")
target_value: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), nullable=True, comment="目标值")
target_unit: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, comment="目标值单位")
weight: Mapped[float] = mapped_column(Numeric(5, 2), default=1.0, comment="权重")
scoring_method: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, comment="评分方法")
scoring_params: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="评分参数 (JSON)")
sort_order: Mapped[int] = mapped_column(Integer, default=0, comment="排序")
remark: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="备注")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
template: Mapped["IndicatorTemplate"] = relationship("IndicatorTemplate", back_populates="indicators")
indicator: Mapped["Indicator"] = relationship("Indicator", backref="template_relations")
__table_args__ = (
Index("idx_ti_template", "template_id"),
Index("idx_ti_indicator", "indicator_id"),
Index("idx_ti_unique", "template_id", "indicator_id", unique=True),
)
class DeptTypeDimensionWeight(Base):
"""科室类型BSC维度权重配置表"""
__tablename__ = "dept_type_dimension_weights"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
dept_type: Mapped[DeptType] = mapped_column(SQLEnum(DeptType, values_callable=lambda x: [e.value for e in x]), nullable=False, comment="科室类型")
financial_weight: Mapped[float] = mapped_column(Numeric(5, 2), default=0.60, comment="财务维度权重")
customer_weight: Mapped[float] = mapped_column(Numeric(5, 2), default=0.15, comment="客户维度权重")
internal_process_weight: Mapped[float] = mapped_column(Numeric(5, 2), default=0.20, comment="内部流程维度权重")
learning_growth_weight: Mapped[float] = mapped_column(Numeric(5, 2), default=0.05, comment="学习成长维度权重")
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="描述说明")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否启用")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
__table_args__ = (
Index("idx_weight_dept_type", "dept_type"),
Index("idx_weight_active", "is_active"),
)
# ==================== 满意度调查模块 ====================
class SurveyStatus(Enum):
"""调查状态"""
DRAFT = "draft" # 草稿
PUBLISHED = "published" # 已发布
CLOSED = "closed" # 已结束
ARCHIVED = "archived" # 已归档
class SurveyType(Enum):
"""调查类型"""
INPATIENT = "inpatient" # 住院患者满意度
OUTPATIENT = "outpatient" # 门诊患者满意度
INTERNAL = "internal" # 内部员工满意度
DEPARTMENT = "department" # 科室间满意度
class Survey(Base):
"""满意度调查问卷表"""
__tablename__ = "surveys"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
survey_name: Mapped[str] = mapped_column(String(200), nullable=False, comment="调查名称")
survey_code: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="调查编码")
survey_type: Mapped[SurveyType] = mapped_column(SQLEnum(SurveyType, values_callable=lambda x: [e.value for e in x]), nullable=False, comment="调查类型")
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="调查描述")
target_departments: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="目标科室(JSON数组)")
total_questions: Mapped[int] = mapped_column(Integer, default=0, comment="题目总数")
status: Mapped[SurveyStatus] = mapped_column(SQLEnum(SurveyStatus, values_callable=lambda x: [e.value for e in x]), default=SurveyStatus.DRAFT, comment="状态")
start_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="开始日期")
end_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="结束日期")
is_anonymous: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否匿名")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否启用")
created_by: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id"), nullable=True, comment="创建人")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间")
# 关系
questions: Mapped[List["SurveyQuestion"]] = relationship("SurveyQuestion", back_populates="survey", cascade="all, delete-orphan")
responses: Mapped[List["SurveyResponse"]] = relationship("SurveyResponse", back_populates="survey", cascade="all, delete-orphan")
__table_args__ = (
Index("idx_survey_type", "survey_type"),
Index("idx_survey_status", "status"),
Index("idx_survey_dates", "start_date", "end_date"),
)
class QuestionType(Enum):
"""题目类型"""
SINGLE_CHOICE = "single_choice" # 单选题
MULTIPLE_CHOICE = "multiple_choice" # 多选题
SCORE = "score" # 评分题(1-5分)
TEXT = "text" # 文本题
class SurveyQuestion(Base):
"""调查问卷题目表"""
__tablename__ = "survey_questions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
survey_id: Mapped[int] = mapped_column(ForeignKey("surveys.id"), nullable=False, comment="调查ID")
question_text: Mapped[str] = mapped_column(Text, nullable=False, comment="题目内容")
question_type: Mapped[QuestionType] = mapped_column(SQLEnum(QuestionType, values_callable=lambda x: [e.value for e in x]), nullable=False, comment="题目类型")
options: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="选项(JSON数组)")
score_max: Mapped[int] = mapped_column(Integer, default=5, comment="最高分值(评分题)")
is_required: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否必答")
sort_order: Mapped[int] = mapped_column(Integer, default=0, comment="排序")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
# 关系
survey: Mapped["Survey"] = relationship("Survey", back_populates="questions")
answers: Mapped[List["SurveyAnswer"]] = relationship("SurveyAnswer", back_populates="question", cascade="all, delete-orphan")
__table_args__ = (
Index("idx_question_survey", "survey_id"),
)
class SurveyResponse(Base):
"""调查问卷回答记录表"""
__tablename__ = "survey_responses"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
survey_id: Mapped[int] = mapped_column(ForeignKey("surveys.id"), nullable=False, comment="调查ID")
department_id: Mapped[Optional[int]] = mapped_column(ForeignKey("departments.id"), nullable=True, comment="评价科室")
respondent_type: Mapped[str] = mapped_column(String(20), default="patient", comment="回答者类型")
respondent_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True, comment="回答者ID(员工时)")
respondent_phone: Mapped[Optional[str]] = mapped_column(String(20), nullable=True, comment="回答者手机")
total_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0, comment="总得分")
max_score: Mapped[float] = mapped_column(Numeric(5, 2), default=0, comment="最高可能得分")
satisfaction_rate: Mapped[float] = mapped_column(Numeric(5, 2), default=0, comment="满意度比例")
ip_address: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, comment="IP地址")
user_agent: Mapped[Optional[str]] = mapped_column(String(500), nullable=True, comment="用户代理")
submitted_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="提交时间")
# 关系
survey: Mapped["Survey"] = relationship("Survey", back_populates="responses")
department: Mapped[Optional["Department"]] = relationship("Department", backref="survey_responses")
answers: Mapped[List["SurveyAnswer"]] = relationship("SurveyAnswer", back_populates="response", cascade="all, delete-orphan")
__table_args__ = (
Index("idx_response_survey", "survey_id"),
Index("idx_response_dept", "department_id"),
Index("idx_response_time", "submitted_at"),
)
class SurveyAnswer(Base):
"""调查问卷回答明细表"""
__tablename__ = "survey_answers"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
response_id: Mapped[int] = mapped_column(ForeignKey("survey_responses.id"), nullable=False, comment="回答记录ID")
question_id: Mapped[int] = mapped_column(ForeignKey("survey_questions.id"), nullable=False, comment="题目ID")
answer_value: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment="回答值")
score: Mapped[float] = mapped_column(Numeric(5, 2), default=0, comment="得分")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
# 关系
response: Mapped["SurveyResponse"] = relationship("SurveyResponse", back_populates="answers")
question: Mapped["SurveyQuestion"] = relationship("SurveyQuestion", back_populates="answers")
__table_args__ = (
Index("idx_answer_response", "response_id"),
Index("idx_answer_question", "question_id"),
)