"""工作负载 API。""" from __future__ import annotations from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel, Field from sqlalchemy.ext.asyncio import AsyncSession from ..core.database import get_db from ..dependencies import ( get_current_user, get_tenant_id, require_manager_user, require_roles, require_self_or_manager, ) from ..models import User from ..services import workload as workload_service router = APIRouter(prefix="/workloads", tags=["workloads"]) class WorkloadHoursUpdateRequest(BaseModel): max_weekly_hours: float = Field(..., gt=0, le=120) class AIOptimizePreviewRequest(BaseModel): project_id: Optional[int] = None max_moves: int = Field(default=20, ge=1, le=100) class AIOptimizeApplyRequest(BaseModel): proposals: list[dict] @router.get("/") async def get_team_workloads( project_id: Optional[int] = Query(None), tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(require_roles("BOSS", "ADMIN")), db: AsyncSession = Depends(get_db), ): _ = current_user rows = await workload_service.get_team_workload_board( db, tenant_id=tenant_id, project_id=project_id, ) total = len(rows) avg_ratio = round(sum(float(r.get("workload_ratio", 0)) for r in rows) / total, 2) if total else 0.0 overloaded = sum(1 for r in rows if float(r.get("workload_ratio", 0)) > 100) idle = sum(1 for r in rows if float(r.get("workload_ratio", 0)) < 70) return { "items": rows, "total": total, "summary": { "avg_ratio": avg_ratio, "overloaded_count": overloaded, "idle_count": idle, "normal_count": max(0, total - overloaded - idle), }, } @router.get("/summary") async def get_workload_summary( tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): _ = current_user rows = await workload_service.get_team_workload_board(db, tenant_id=tenant_id) total = len(rows) avg_ratio = round(sum(float(r.get("workload_ratio", 0)) for r in rows) / total, 2) if total else 0.0 return { "team_size": total, "avg_ratio": avg_ratio, "overloaded_count": sum(1 for r in rows if float(r.get("workload_ratio", 0)) > 100), "normal_count": sum( 1 for r in rows if 70 <= float(r.get("workload_ratio", 0)) <= 100 ), "idle_count": sum(1 for r in rows if float(r.get("workload_ratio", 0)) < 70), } @router.get("/{user_id}") async def get_user_workload( user_id: int, tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(require_self_or_manager), db: AsyncSession = Depends(get_db), ): _ = current_user item = await workload_service.get_user_personal_workload(db, tenant_id, user_id) if not item: raise HTTPException(status_code=404, detail="User workload not found") return item @router.put("/{user_id}") async def update_weekly_hours( user_id: int, body: WorkloadHoursUpdateRequest, tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(require_self_or_manager), db: AsyncSession = Depends(get_db), ): _ = current_user result = await workload_service.update_user_max_weekly_hours( db, tenant_id=tenant_id, user_id=user_id, max_weekly_hours=body.max_weekly_hours, ) if not result: raise HTTPException(status_code=404, detail="User not found") return result @router.get("/{user_id}/tasks") async def get_user_workload_tasks( user_id: int, status_value: Optional[str] = Query(None, alias="status"), priority: Optional[str] = Query(None), tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(require_self_or_manager), db: AsyncSession = Depends(get_db), ): _ = current_user items = await workload_service.list_user_assigned_tasks( db, tenant_id=tenant_id, user_id=user_id, status=status_value, priority=priority, ) return {"items": items, "total": len(items)} @router.get("/{user_id}/trend") async def get_user_workload_trend( user_id: int, days: int = Query(7, ge=1, le=30), tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(require_self_or_manager), db: AsyncSession = Depends(get_db), ): _ = current_user items = await workload_service.get_user_workload_trend( db, tenant_id=tenant_id, user_id=user_id, days=days, ) return {"items": items, "total": len(items)} @router.post("/ai-optimize") async def ai_optimize_preview( body: AIOptimizePreviewRequest, tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), ): _ = current_user proposals = await workload_service.build_ai_optimize_preview( db, tenant_id=tenant_id, project_id=body.project_id, max_moves=body.max_moves, ) return {"proposals": proposals, "total": len(proposals)} @router.post("/ai-optimize/apply") async def apply_ai_optimize( body: AIOptimizeApplyRequest, tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), ): _ = current_user result = await workload_service.apply_ai_optimize_plan( db, tenant_id=tenant_id, proposals=body.proposals, ) return result