""" KPI API """ from typing import Optional, List from fastapi import APIRouter, Depends, HTTPException, status, Query from sqlalchemy.orm import Session from app.api.deps import get_db, get_current_user, get_current_manager from app.models.employee import Employee from app.models.kpi_sheet import KPISheet from app.models.kpi_period import KPIPeriod from app.models.kpi_template import KPITemplate, KPIPreset from app.services.kpi_service import KPISheetService from app.services.notify_service import NotifyService from app.schemas.kpi_sheet import ( KPISheetCreate, KPISheetResponse, KPISheetListItem, ApproveRequest, RejectRequest, SelfEvalRequest, ManagerEvalRequest, KPIPeriodResponse, KPITemplateResponse, KPIPresetResponse, ) from app.schemas.common import PaginatedResponse, MessageResponse router = APIRouter(prefix="/api/kpi", tags=["KPI"]) # ==================== KPI 表單 ==================== @router.get("/sheets", response_model=List[KPISheetListItem]) def list_sheets( period_id: Optional[int] = Query(None), department_id: Optional[int] = Query(None), status: Optional[str] = Query(None), skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=500), db: Session = Depends(get_db), current_user: Employee = Depends(get_current_user), ): """查詢 KPI 表單清單""" service = KPISheetService(db) sheets = service.get_multi( period_id=period_id, department_id=department_id, status=status, skip=skip, limit=limit, ) return sheets @router.get("/sheets/my", response_model=List[KPISheetListItem]) def get_my_sheets( db: Session = Depends(get_db), current_user: Employee = Depends(get_current_user), ): """取得我的 KPI 表單""" service = KPISheetService(db) return service.get_my_sheets(current_user.id) @router.get("/sheets/pending", response_model=List[KPISheetListItem]) def get_pending_sheets( db: Session = Depends(get_db), current_user: Employee = Depends(get_current_manager), ): """取得待審核的 KPI 表單(主管用)""" service = KPISheetService(db) return service.get_pending_for_manager(current_user.id) @router.post("/sheets", response_model=KPISheetResponse, status_code=status.HTTP_201_CREATED) def create_sheet( data: KPISheetCreate, db: Session = Depends(get_db), current_user: Employee = Depends(get_current_user), ): """建立 KPI 表單""" service = KPISheetService(db) return service.create(current_user, data) @router.post("/sheets/copy-from-previous", response_model=KPISheetResponse, status_code=status.HTTP_201_CREATED) def copy_from_previous( period_id: int, db: Session = Depends(get_db), current_user: Employee = Depends(get_current_user), ): """從上期複製 KPI""" service = KPISheetService(db) return service.copy_from_previous(current_user, period_id) @router.post("/sheets/apply-preset/{preset_id}", response_model=KPISheetResponse, status_code=status.HTTP_201_CREATED) def apply_preset( preset_id: int, period_id: int, db: Session = Depends(get_db), current_user: Employee = Depends(get_current_user), ): """套用預設組合""" service = KPISheetService(db) return service.apply_preset(current_user, period_id, preset_id) @router.get("/sheets/{sheet_id}", response_model=KPISheetResponse) def get_sheet( sheet_id: int, db: Session = Depends(get_db), current_user: Employee = Depends(get_current_user), ): """取得單一 KPI 表單""" service = KPISheetService(db) sheet = service.get_by_id(sheet_id) if not sheet: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={"code": "KPI004", "message": "KPI 表單不存在"}, ) return sheet @router.delete("/sheets/{sheet_id}", response_model=MessageResponse) def delete_sheet( sheet_id: int, db: Session = Depends(get_db), current_user: Employee = Depends(get_current_user), ): """刪除 KPI 表單""" service = KPISheetService(db) sheet = service.get_by_id(sheet_id) if not sheet: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={"code": "KPI004", "message": "KPI 表單不存在"}, ) if sheet.employee_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail={"code": "AUTH003", "message": "權限不足"}, ) service.delete(sheet) return MessageResponse(message="刪除成功") # ==================== 審核流程 ==================== @router.post("/sheets/{sheet_id}/submit", response_model=KPISheetResponse) def submit_sheet( sheet_id: int, db: Session = Depends(get_db), current_user: Employee = Depends(get_current_user), ): """提交 KPI 審核""" service = KPISheetService(db) notify_service = NotifyService(db) sheet = service.get_by_id(sheet_id) if not sheet: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={"code": "KPI004", "message": "KPI 表單不存在"}, ) if sheet.employee_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail={"code": "AUTH003", "message": "只能提交自己的 KPI"}, ) result = service.submit(sheet, current_user) notify_service.notify_kpi_submitted(result) return result @router.post("/sheets/{sheet_id}/approve", response_model=KPISheetResponse) def approve_sheet( sheet_id: int, data: ApproveRequest, db: Session = Depends(get_db), current_user: Employee = Depends(get_current_manager), ): """審核通過""" service = KPISheetService(db) notify_service = NotifyService(db) sheet = service.get_by_id(sheet_id) if not sheet: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={"code": "KPI004", "message": "KPI 表單不存在"}, ) # 檢查是否為該員工的主管 if sheet.employee.manager_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail={"code": "AUTH003", "message": "只能審核直屬部屬的 KPI"}, ) result = service.approve(sheet, current_user, data.comment) notify_service.notify_kpi_approved(result) return result @router.post("/sheets/{sheet_id}/reject", response_model=KPISheetResponse) def reject_sheet( sheet_id: int, data: RejectRequest, db: Session = Depends(get_db), current_user: Employee = Depends(get_current_manager), ): """退回""" service = KPISheetService(db) notify_service = NotifyService(db) sheet = service.get_by_id(sheet_id) if not sheet: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={"code": "KPI004", "message": "KPI 表單不存在"}, ) # 檢查是否為該員工的主管 if sheet.employee.manager_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail={"code": "AUTH003", "message": "只能審核直屬部屬的 KPI"}, ) result = service.reject(sheet, current_user, data.reason) notify_service.notify_kpi_rejected(result, data.reason) return result # ==================== 評核 ==================== @router.post("/sheets/{sheet_id}/self-eval", response_model=KPISheetResponse) def self_eval( sheet_id: int, data: SelfEvalRequest, db: Session = Depends(get_db), current_user: Employee = Depends(get_current_user), ): """員工自評""" service = KPISheetService(db) notify_service = NotifyService(db) sheet = service.get_by_id(sheet_id) if not sheet: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={"code": "KPI004", "message": "KPI 表單不存在"}, ) if sheet.employee_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail={"code": "AUTH003", "message": "只能自評自己的 KPI"}, ) result = service.self_eval(sheet, current_user, data) notify_service.notify_self_eval_completed(result) return result @router.post("/sheets/{sheet_id}/manager-eval", response_model=KPISheetResponse) def manager_eval( sheet_id: int, data: ManagerEvalRequest, db: Session = Depends(get_db), current_user: Employee = Depends(get_current_manager), ): """主管評核""" service = KPISheetService(db) notify_service = NotifyService(db) sheet = service.get_by_id(sheet_id) if not sheet: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={"code": "KPI004", "message": "KPI 表單不存在"}, ) # 檢查是否為該員工的主管 if sheet.employee.manager_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail={"code": "AUTH003", "message": "只能評核直屬部屬的 KPI"}, ) result = service.manager_eval(sheet, current_user, data) notify_service.notify_manager_eval_completed(result) return result # ==================== 期間 ==================== @router.get("/periods", response_model=List[KPIPeriodResponse]) def list_periods(db: Session = Depends(get_db)): """取得 KPI 期間列表""" periods = db.query(KPIPeriod).order_by(KPIPeriod.start_date.desc()).all() return periods @router.get("/periods/current", response_model=KPIPeriodResponse) def get_current_period(db: Session = Depends(get_db)): """取得當前 KPI 期間""" period = ( db.query(KPIPeriod) .filter(KPIPeriod.status.in_(["setting", "self_eval", "manager_eval"])) .order_by(KPIPeriod.start_date.desc()) .first() ) if not period: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={"code": "PERIOD002", "message": "目前沒有進行中的 KPI 期間"}, ) return period # ==================== 範本 ==================== @router.get("/templates", response_model=List[KPITemplateResponse]) def list_templates( category: Optional[str] = Query(None), db: Session = Depends(get_db), ): """取得 KPI 範本列表""" query = db.query(KPITemplate).filter(KPITemplate.is_active == True) if category: query = query.filter(KPITemplate.category == category) return query.all() @router.get("/presets", response_model=List[KPIPresetResponse]) def list_presets(db: Session = Depends(get_db)): """取得 KPI 預設組合列表""" presets = db.query(KPIPreset).filter(KPIPreset.is_active == True).all() return presets