Files
KPI-management/app/models/kpi_sheet.py
DonaldFang 方士碩 f810ddc2ea Initial commit: KPI Management System Backend
Features:
- FastAPI backend with JWT authentication
- MySQL database with SQLAlchemy ORM
- KPI workflow: draft → pending → approved → evaluation → completed
- Ollama LLM API integration for AI features
- Gitea API integration for version control
- Complete API endpoints for KPI, dashboard, notifications

Tables: KPI_D_* prefix naming convention

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-11 16:20:57 +08:00

104 lines
4.2 KiB
Python

"""
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"<KPISheet id={self.id} employee={self.employee_id} period={self.period_id} status={self.status}>"