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:
2025-12-11 16:20:57 +08:00
commit f810ddc2ea
48 changed files with 4950 additions and 0 deletions

204
tests/factories.py Normal file
View File

@@ -0,0 +1,204 @@
"""
測試資料工廠
"""
from datetime import date
from typing import Optional
from sqlalchemy.orm import Session
from app.core.security import get_password_hash
from app.models.department import Department
from app.models.employee import Employee
from app.models.kpi_period import KPIPeriod
from app.models.kpi_template import KPITemplate
from app.models.kpi_sheet import KPISheet
from app.models.kpi_item import KPIItem
class DepartmentFactory:
"""部門工廠"""
_counter = 0
@classmethod
def create(
cls,
db: Session,
code: Optional[str] = None,
name: Optional[str] = None,
level: str = "DEPT",
parent_id: Optional[int] = None,
) -> Department:
cls._counter += 1
dept = Department(
code=code or f"DEPT{cls._counter:03d}",
name=name or f"測試部門{cls._counter}",
level=level,
parent_id=parent_id,
is_active=True,
)
db.add(dept)
db.flush()
return dept
class EmployeeFactory:
"""員工工廠"""
_counter = 0
@classmethod
def create(
cls,
db: Session,
employee_no: Optional[str] = None,
name: Optional[str] = None,
department_id: Optional[int] = None,
manager_id: Optional[int] = None,
role: str = "employee",
status: str = "active",
) -> Employee:
cls._counter += 1
# 如果沒有指定部門,建立一個
if not department_id:
dept = DepartmentFactory.create(db)
department_id = dept.id
employee = Employee(
employee_no=employee_no or f"EMP{cls._counter:05d}",
name=name or f"測試員工{cls._counter}",
email=f"test{cls._counter}@example.com",
password_hash=get_password_hash("password123"),
department_id=department_id,
manager_id=manager_id,
job_title="工程師",
status=status,
role=role,
)
db.add(employee)
db.flush()
return employee
class KPIPeriodFactory:
"""KPI 期間工廠"""
_counter = 0
@classmethod
def create(
cls,
db: Session,
code: Optional[str] = None,
status: str = "setting",
year: int = 2024,
half: int = 2,
) -> KPIPeriod:
cls._counter += 1
if half == 1:
start = date(year, 1, 1)
end = date(year, 6, 30)
setting_end = date(year, 1, 14)
else:
start = date(year, 7, 1)
end = date(year, 12, 31)
setting_end = date(year, 7, 14)
period = KPIPeriod(
code=code or f"{year}H{half}_{cls._counter}",
name=f"{year}{'' if half == 1 else ''}半年",
start_date=start,
end_date=end,
setting_start=start,
setting_end=setting_end,
status=status,
)
db.add(period)
db.flush()
return period
class KPITemplateFactory:
"""KPI 範本工廠"""
_counter = 0
@classmethod
def create(
cls,
db: Session,
code: Optional[str] = None,
name: Optional[str] = None,
category: str = "financial",
) -> KPITemplate:
cls._counter += 1
template = KPITemplate(
code=code or f"TPL{cls._counter:03d}",
name=name or f"測試 KPI 範本 {cls._counter}",
category=category,
default_weight=20,
level0_desc="未達標",
level1_desc="基本達成",
level2_desc="達成目標",
level3_desc="挑戰目標",
level4_desc="超越目標",
is_active=True,
)
db.add(template)
db.flush()
return template
class KPISheetFactory:
"""KPI 表單工廠"""
@classmethod
def create(
cls,
db: Session,
employee: Optional[Employee] = None,
period: Optional[KPIPeriod] = None,
status: str = "draft",
with_items: bool = True,
) -> KPISheet:
if not employee:
employee = EmployeeFactory.create(db)
if not period:
period = KPIPeriodFactory.create(db)
sheet = KPISheet(
employee_id=employee.id,
period_id=period.id,
department_id=employee.department_id,
status=status,
)
db.add(sheet)
db.flush()
# 建立 KPI 項目(權重總和 100%
if with_items:
weights = [30, 25, 25, 20]
categories = ["financial", "customer", "internal", "learning"]
for i, (weight, category) in enumerate(zip(weights, categories)):
template = KPITemplateFactory.create(db, category=category)
item = KPIItem(
sheet_id=sheet.id,
template_id=template.id,
sort_order=i,
name=f"KPI 項目 {i + 1}",
category=category,
weight=weight,
level0_criteria="未達標",
level1_criteria="基本達成",
level2_criteria="達成目標",
level3_criteria="挑戰目標",
level4_criteria="超越目標",
)
db.add(item)
db.flush()
return sheet