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>
104 lines
4.2 KiB
Python
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}>"
|