""" KPI 表單 Model """ from datetime import datetime from decimal import Decimal from typing import Optional, List, TYPE_CHECKING from sqlalchemy import String, Integer, DateTime, Text, Numeric, ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.database import Base if TYPE_CHECKING: from app.models.employee import Employee from app.models.kpi_period import KPIPeriod from app.models.department import Department from app.models.kpi_item import KPIItem from app.models.kpi_review_log import KPIReviewLog # KPI 表單狀態 class KPISheetStatus: DRAFT = "draft" # 草稿 PENDING = "pending" # 待審核 APPROVED = "approved" # 已核准 SELF_EVAL = "self_eval" # 自評中 MANAGER_EVAL = "manager_eval" # 主管評核中 COMPLETED = "completed" # 已完成 SETTLED = "settled" # 已結算 # 狀態轉換規則 VALID_STATUS_TRANSITIONS = { KPISheetStatus.DRAFT: [KPISheetStatus.PENDING], KPISheetStatus.PENDING: [KPISheetStatus.APPROVED, KPISheetStatus.DRAFT], KPISheetStatus.APPROVED: [KPISheetStatus.SELF_EVAL], KPISheetStatus.SELF_EVAL: [KPISheetStatus.MANAGER_EVAL], KPISheetStatus.MANAGER_EVAL: [KPISheetStatus.COMPLETED], KPISheetStatus.COMPLETED: [KPISheetStatus.SETTLED], KPISheetStatus.SETTLED: [], } class KPISheet(Base): """KPI 表單""" __tablename__ = "KPI_D_sheets" id: Mapped[int] = mapped_column(primary_key=True) employee_id: Mapped[int] = mapped_column(ForeignKey("KPI_D_employees.id"), nullable=False) period_id: Mapped[int] = mapped_column(ForeignKey("KPI_D_periods.id"), nullable=False) department_id: Mapped[int] = mapped_column(ForeignKey("KPI_D_departments.id"), nullable=False) status: Mapped[str] = mapped_column(String(20), default=KPISheetStatus.DRAFT) # 提交資訊 submitted_at: Mapped[Optional[datetime]] = mapped_column(DateTime) # 審核資訊 approved_by: Mapped[Optional[int]] = mapped_column(ForeignKey("KPI_D_employees.id")) approved_at: Mapped[Optional[datetime]] = mapped_column(DateTime) approve_comment: Mapped[Optional[str]] = mapped_column(Text) # 退回資訊 rejected_by: Mapped[Optional[int]] = mapped_column(ForeignKey("KPI_D_employees.id")) rejected_at: Mapped[Optional[datetime]] = mapped_column(DateTime) reject_reason: Mapped[Optional[str]] = mapped_column(Text) # 自評資訊 self_eval_at: Mapped[Optional[datetime]] = mapped_column(DateTime) # 主管評核資訊 manager_eval_by: Mapped[Optional[int]] = mapped_column(ForeignKey("KPI_D_employees.id")) manager_eval_at: Mapped[Optional[datetime]] = mapped_column(DateTime) manager_eval_comment: Mapped[Optional[str]] = mapped_column(Text) # 分數 total_score: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 4)) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) # Relationships employee: Mapped["Employee"] = relationship( "Employee", back_populates="kpi_sheets", foreign_keys=[employee_id] ) period: Mapped["KPIPeriod"] = relationship("KPIPeriod", back_populates="kpi_sheets") department: Mapped["Department"] = relationship("Department") items: Mapped[List["KPIItem"]] = relationship( "KPIItem", back_populates="sheet", cascade="all, delete-orphan" ) review_logs: Mapped[List["KPIReviewLog"]] = relationship( "KPIReviewLog", back_populates="sheet", cascade="all, delete-orphan" ) approver: Mapped[Optional["Employee"]] = relationship("Employee", foreign_keys=[approved_by]) rejecter: Mapped[Optional["Employee"]] = relationship("Employee", foreign_keys=[rejected_by]) manager_evaluator: Mapped[Optional["Employee"]] = relationship("Employee", foreign_keys=[manager_eval_by]) def can_transition_to(self, new_status: str) -> bool: """檢查是否可以轉換到指定狀態""" return new_status in VALID_STATUS_TRANSITIONS.get(self.status, []) def __repr__(self) -> str: return f""