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>
This commit is contained in:
103
app/models/kpi_sheet.py
Normal file
103
app/models/kpi_sheet.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
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}>"
|
||||
Reference in New Issue
Block a user