""" 数据模型模块 """ 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"), )