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>
205 lines
5.4 KiB
Python
205 lines
5.4 KiB
Python
"""
|
||
測試資料工廠
|
||
"""
|
||
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
|