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:
9
app/schemas/__init__.py
Normal file
9
app/schemas/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
Pydantic Schemas
|
||||
"""
|
||||
from app.schemas.auth import *
|
||||
from app.schemas.employee import *
|
||||
from app.schemas.kpi_sheet import *
|
||||
from app.schemas.kpi_item import *
|
||||
from app.schemas.notification import *
|
||||
from app.schemas.common import *
|
||||
44
app/schemas/auth.py
Normal file
44
app/schemas/auth.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
認證 Schemas
|
||||
"""
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
"""登入請求"""
|
||||
|
||||
employee_no: str
|
||||
password: str
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
"""Token 回應"""
|
||||
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class RefreshTokenRequest(BaseModel):
|
||||
"""更新 Token 請求"""
|
||||
|
||||
refresh_token: str
|
||||
|
||||
|
||||
class UserInfo(BaseModel):
|
||||
"""使用者資訊"""
|
||||
|
||||
id: int
|
||||
employee_no: str
|
||||
name: str
|
||||
email: str
|
||||
department_id: int
|
||||
department_name: str
|
||||
job_title: Optional[str]
|
||||
role: str
|
||||
is_manager: bool
|
||||
is_admin: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
37
app/schemas/common.py
Normal file
37
app/schemas/common.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
共用 Schemas
|
||||
"""
|
||||
from typing import Generic, TypeVar, Optional, List
|
||||
from pydantic import BaseModel
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class ErrorDetail(BaseModel):
|
||||
"""錯誤詳情"""
|
||||
|
||||
code: str
|
||||
message: str
|
||||
details: Optional[dict] = None
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""錯誤回應"""
|
||||
|
||||
error: ErrorDetail
|
||||
|
||||
|
||||
class PaginatedResponse(BaseModel, Generic[T]):
|
||||
"""分頁回應"""
|
||||
|
||||
items: List[T]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
pages: int
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
"""訊息回應"""
|
||||
|
||||
message: str
|
||||
74
app/schemas/dashboard.py
Normal file
74
app/schemas/dashboard.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
儀表板 Schemas
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ProgressStats(BaseModel):
|
||||
"""進度統計"""
|
||||
|
||||
total: int
|
||||
draft: int
|
||||
pending: int
|
||||
approved: int
|
||||
self_eval: int
|
||||
manager_eval: int
|
||||
completed: int
|
||||
|
||||
|
||||
class DistributionItem(BaseModel):
|
||||
"""分佈項目"""
|
||||
|
||||
label: str
|
||||
count: int
|
||||
percentage: float
|
||||
|
||||
|
||||
class TrendItem(BaseModel):
|
||||
"""趨勢項目"""
|
||||
|
||||
period: str
|
||||
average_score: float
|
||||
completed_count: int
|
||||
|
||||
|
||||
class DashboardAlertResponse(BaseModel):
|
||||
"""儀表板警示回應"""
|
||||
|
||||
id: int
|
||||
alert_type: str
|
||||
severity: str
|
||||
title: str
|
||||
description: Optional[str]
|
||||
related_sheet_id: Optional[int]
|
||||
related_employee_id: Optional[int]
|
||||
is_resolved: bool
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class DashboardProgressResponse(BaseModel):
|
||||
"""儀表板進度回應"""
|
||||
|
||||
period_code: str
|
||||
period_name: str
|
||||
stats: ProgressStats
|
||||
completion_rate: float
|
||||
|
||||
|
||||
class DashboardDistributionResponse(BaseModel):
|
||||
"""儀表板分佈回應"""
|
||||
|
||||
by_department: List[DistributionItem]
|
||||
by_status: List[DistributionItem]
|
||||
by_score_range: List[DistributionItem]
|
||||
|
||||
|
||||
class DashboardTrendsResponse(BaseModel):
|
||||
"""儀表板趨勢回應"""
|
||||
|
||||
trends: List[TrendItem]
|
||||
87
app/schemas/employee.py
Normal file
87
app/schemas/employee.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
員工 Schemas
|
||||
"""
|
||||
from datetime import date, datetime
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
|
||||
class DepartmentBase(BaseModel):
|
||||
"""部門基本資訊"""
|
||||
|
||||
id: int
|
||||
code: str
|
||||
name: str
|
||||
level: str
|
||||
|
||||
|
||||
class DepartmentResponse(DepartmentBase):
|
||||
"""部門回應"""
|
||||
|
||||
parent_id: Optional[int]
|
||||
is_active: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class EmployeeBase(BaseModel):
|
||||
"""員工基本資訊"""
|
||||
|
||||
id: int
|
||||
employee_no: str
|
||||
name: str
|
||||
email: str
|
||||
|
||||
|
||||
class EmployeeSimple(EmployeeBase):
|
||||
"""員工簡要資訊"""
|
||||
|
||||
job_title: Optional[str]
|
||||
role: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class EmployeeResponse(EmployeeBase):
|
||||
"""員工回應"""
|
||||
|
||||
department_id: int
|
||||
department: DepartmentBase
|
||||
manager_id: Optional[int]
|
||||
manager: Optional[EmployeeBase]
|
||||
job_title: Optional[str]
|
||||
role: str
|
||||
status: str
|
||||
hire_date: Optional[date]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class EmployeeCreate(BaseModel):
|
||||
"""建立員工"""
|
||||
|
||||
employee_no: str
|
||||
name: str
|
||||
email: EmailStr
|
||||
password: str
|
||||
department_id: int
|
||||
manager_id: Optional[int] = None
|
||||
job_title: Optional[str] = None
|
||||
role: str = "employee"
|
||||
hire_date: Optional[date] = None
|
||||
|
||||
|
||||
class EmployeeUpdate(BaseModel):
|
||||
"""更新員工"""
|
||||
|
||||
name: Optional[str] = None
|
||||
email: Optional[EmailStr] = None
|
||||
department_id: Optional[int] = None
|
||||
manager_id: Optional[int] = None
|
||||
job_title: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
74
app/schemas/kpi_item.py
Normal file
74
app/schemas/kpi_item.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
KPI 項目 Schemas
|
||||
"""
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class KPIItemBase(BaseModel):
|
||||
"""KPI 項目基本資訊"""
|
||||
|
||||
name: str
|
||||
category: str # financial, customer, internal, learning
|
||||
weight: int = Field(..., ge=1, le=100, description="權重百分比 (1-100)")
|
||||
|
||||
|
||||
class KPIItemCreate(KPIItemBase):
|
||||
"""建立 KPI 項目"""
|
||||
|
||||
template_id: Optional[int] = None
|
||||
level0_criteria: Optional[str] = None
|
||||
level1_criteria: Optional[str] = None
|
||||
level2_criteria: Optional[str] = None
|
||||
level3_criteria: Optional[str] = None
|
||||
level4_criteria: Optional[str] = None
|
||||
|
||||
|
||||
class KPIItemUpdate(BaseModel):
|
||||
"""更新 KPI 項目"""
|
||||
|
||||
name: Optional[str] = None
|
||||
category: Optional[str] = None
|
||||
weight: Optional[int] = Field(None, ge=1, le=100)
|
||||
level0_criteria: Optional[str] = None
|
||||
level1_criteria: Optional[str] = None
|
||||
level2_criteria: Optional[str] = None
|
||||
level3_criteria: Optional[str] = None
|
||||
level4_criteria: Optional[str] = None
|
||||
|
||||
|
||||
class KPIItemResponse(KPIItemBase):
|
||||
"""KPI 項目回應"""
|
||||
|
||||
id: int
|
||||
sheet_id: int
|
||||
template_id: Optional[int]
|
||||
sort_order: int
|
||||
level0_criteria: Optional[str]
|
||||
level1_criteria: Optional[str]
|
||||
level2_criteria: Optional[str]
|
||||
level3_criteria: Optional[str]
|
||||
level4_criteria: Optional[str]
|
||||
self_eval_level: Optional[int]
|
||||
self_eval_note: Optional[str]
|
||||
final_level: Optional[int]
|
||||
final_note: Optional[str]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class SelfEvalItem(BaseModel):
|
||||
"""自評項目"""
|
||||
|
||||
id: int
|
||||
level: int = Field(..., ge=0, le=4, description="自評等級 (0-4)")
|
||||
note: Optional[str] = None
|
||||
|
||||
|
||||
class ManagerEvalItem(BaseModel):
|
||||
"""主管評核項目"""
|
||||
|
||||
id: int
|
||||
level: int = Field(..., ge=0, le=4, description="評核等級 (0-4)")
|
||||
note: Optional[str] = None
|
||||
163
app/schemas/kpi_sheet.py
Normal file
163
app/schemas/kpi_sheet.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
KPI 表單 Schemas
|
||||
"""
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.schemas.employee import EmployeeSimple
|
||||
from app.schemas.kpi_item import KPIItemCreate, KPIItemResponse, SelfEvalItem, ManagerEvalItem
|
||||
|
||||
|
||||
class KPIPeriodBase(BaseModel):
|
||||
"""KPI 期間基本資訊"""
|
||||
|
||||
id: int
|
||||
code: str
|
||||
name: str
|
||||
|
||||
|
||||
class KPIPeriodResponse(KPIPeriodBase):
|
||||
"""KPI 期間回應"""
|
||||
|
||||
start_date: str
|
||||
end_date: str
|
||||
setting_start: str
|
||||
setting_end: str
|
||||
self_eval_start: Optional[str]
|
||||
self_eval_end: Optional[str]
|
||||
manager_eval_start: Optional[str]
|
||||
manager_eval_end: Optional[str]
|
||||
status: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class KPISheetCreate(BaseModel):
|
||||
"""建立 KPI 表單"""
|
||||
|
||||
period_id: int
|
||||
items: List[KPIItemCreate]
|
||||
|
||||
|
||||
class KPISheetUpdate(BaseModel):
|
||||
"""更新 KPI 表單"""
|
||||
|
||||
items: Optional[List[KPIItemCreate]] = None
|
||||
|
||||
|
||||
class KPISheetResponse(BaseModel):
|
||||
"""KPI 表單回應"""
|
||||
|
||||
id: int
|
||||
employee: EmployeeSimple
|
||||
period: KPIPeriodBase
|
||||
department_id: int
|
||||
status: str
|
||||
items: List[KPIItemResponse]
|
||||
|
||||
# 提交資訊
|
||||
submitted_at: Optional[datetime]
|
||||
|
||||
# 審核資訊
|
||||
approved_by: Optional[int]
|
||||
approved_at: Optional[datetime]
|
||||
approve_comment: Optional[str]
|
||||
|
||||
# 退回資訊
|
||||
rejected_by: Optional[int]
|
||||
rejected_at: Optional[datetime]
|
||||
reject_reason: Optional[str]
|
||||
|
||||
# 自評資訊
|
||||
self_eval_at: Optional[datetime]
|
||||
|
||||
# 主管評核資訊
|
||||
manager_eval_by: Optional[int]
|
||||
manager_eval_at: Optional[datetime]
|
||||
manager_eval_comment: Optional[str]
|
||||
|
||||
# 分數
|
||||
total_score: Optional[Decimal]
|
||||
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class KPISheetListItem(BaseModel):
|
||||
"""KPI 表單列表項目"""
|
||||
|
||||
id: int
|
||||
employee: EmployeeSimple
|
||||
period: KPIPeriodBase
|
||||
status: str
|
||||
total_score: Optional[Decimal]
|
||||
submitted_at: Optional[datetime]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ApproveRequest(BaseModel):
|
||||
"""審核通過請求"""
|
||||
|
||||
comment: Optional[str] = None
|
||||
|
||||
|
||||
class RejectRequest(BaseModel):
|
||||
"""退回請求"""
|
||||
|
||||
reason: str
|
||||
|
||||
|
||||
class SelfEvalRequest(BaseModel):
|
||||
"""自評請求"""
|
||||
|
||||
items: List[SelfEvalItem]
|
||||
|
||||
|
||||
class ManagerEvalRequest(BaseModel):
|
||||
"""主管評核請求"""
|
||||
|
||||
items: List[ManagerEvalItem]
|
||||
comment: Optional[str] = None
|
||||
|
||||
|
||||
# KPI 範本相關
|
||||
class KPITemplateResponse(BaseModel):
|
||||
"""KPI 範本回應"""
|
||||
|
||||
id: int
|
||||
code: str
|
||||
name: str
|
||||
category: str
|
||||
description: Optional[str]
|
||||
default_weight: int
|
||||
level0_desc: str
|
||||
level1_desc: str
|
||||
level2_desc: str
|
||||
level3_desc: str
|
||||
level4_desc: str
|
||||
is_active: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class KPIPresetResponse(BaseModel):
|
||||
"""KPI 預設組合回應"""
|
||||
|
||||
id: int
|
||||
code: str
|
||||
name: str
|
||||
description: Optional[str]
|
||||
items: List[KPITemplateResponse]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
47
app/schemas/notification.py
Normal file
47
app/schemas/notification.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
通知 Schemas
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class NotificationResponse(BaseModel):
|
||||
"""通知回應"""
|
||||
|
||||
id: int
|
||||
type: str
|
||||
title: str
|
||||
content: Optional[str]
|
||||
related_sheet_id: Optional[int]
|
||||
is_read: bool
|
||||
read_at: Optional[datetime]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class NotificationPreferenceResponse(BaseModel):
|
||||
"""通知偏好回應"""
|
||||
|
||||
email_enabled: bool
|
||||
in_app_enabled: bool
|
||||
reminder_days_before: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class NotificationPreferenceUpdate(BaseModel):
|
||||
"""更新通知偏好"""
|
||||
|
||||
email_enabled: Optional[bool] = None
|
||||
in_app_enabled: Optional[bool] = None
|
||||
reminder_days_before: Optional[int] = None
|
||||
|
||||
|
||||
class UnreadCountResponse(BaseModel):
|
||||
"""未讀數量回應"""
|
||||
|
||||
count: int
|
||||
Reference in New Issue
Block a user