"""项目管理路由""" from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from sqlalchemy import func as sa_func from typing import List, Optional from database import get_db from models import ( User, Project, Submission, ProjectType, ProjectStatus, PhaseGroup, WorkType ) from schemas import ProjectCreate, ProjectUpdate, ProjectOut from auth import get_current_user, require_permission router = APIRouter(prefix="/api/projects", tags=["项目管理"]) def enrich_project(p: Project, db: Session) -> ProjectOut: """将项目对象转为带计算字段的输出""" # 累计提交秒数(仅有秒数的提交) total_secs = db.query(sa_func.sum(Submission.total_seconds)).filter( Submission.project_id == p.id, Submission.total_seconds > 0 ).scalar() or 0 target = p.target_total_seconds progress = round(total_secs / target * 100, 1) if target > 0 else 0 # 损耗 = 测试损耗 + 超产损耗 test_secs = db.query(sa_func.sum(Submission.total_seconds)).filter( Submission.project_id == p.id, Submission.work_type == WorkType.TEST ).scalar() or 0 overproduction = max(0, total_secs - target) waste = test_secs + overproduction waste_rate = round(waste / target * 100, 1) if target > 0 else 0 leader_name = p.leader.name if p.leader else None return ProjectOut( id=p.id, name=p.name, project_type=p.project_type.value if hasattr(p.project_type, 'value') else p.project_type, status=p.status.value if hasattr(p.status, 'value') else p.status, leader_id=p.leader_id, leader_name=leader_name, current_phase=p.current_phase.value if hasattr(p.current_phase, 'value') else p.current_phase, episode_duration_minutes=p.episode_duration_minutes, episode_count=p.episode_count, target_total_seconds=target, estimated_completion_date=p.estimated_completion_date, actual_completion_date=p.actual_completion_date, contract_amount=p.contract_amount, created_at=p.created_at, total_submitted_seconds=round(total_secs, 1), progress_percent=progress, waste_seconds=round(waste, 1), waste_rate=waste_rate, ) @router.get("/", response_model=List[ProjectOut]) def list_projects( status: Optional[str] = Query(None), project_type: Optional[str] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): q = db.query(Project) if status: q = q.filter(Project.status == ProjectStatus(status)) if project_type: q = q.filter(Project.project_type == ProjectType(project_type)) projects = q.order_by(Project.created_at.desc()).all() return [enrich_project(p, db) for p in projects] @router.post("/", response_model=ProjectOut) def create_project( req: ProjectCreate, db: Session = Depends(get_db), current_user: User = Depends(require_permission("project:create")) ): project = Project( name=req.name, project_type=ProjectType(req.project_type), leader_id=req.leader_id, current_phase=PhaseGroup(req.current_phase), episode_duration_minutes=req.episode_duration_minutes, episode_count=req.episode_count, estimated_completion_date=req.estimated_completion_date, contract_amount=req.contract_amount, ) db.add(project) db.commit() db.refresh(project) return enrich_project(project, db) @router.get("/{project_id}", response_model=ProjectOut) def get_project( project_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): p = db.query(Project).filter(Project.id == project_id).first() if not p: raise HTTPException(status_code=404, detail="项目不存在") return enrich_project(p, db) @router.put("/{project_id}", response_model=ProjectOut) def update_project( project_id: int, req: ProjectUpdate, db: Session = Depends(get_db), current_user: User = Depends(require_permission("project:edit")) ): p = db.query(Project).filter(Project.id == project_id).first() if not p: raise HTTPException(status_code=404, detail="项目不存在") if req.name is not None: p.name = req.name if req.project_type is not None: p.project_type = ProjectType(req.project_type) if req.status is not None: p.status = ProjectStatus(req.status) if req.leader_id is not None: p.leader_id = req.leader_id if req.current_phase is not None: p.current_phase = PhaseGroup(req.current_phase) if req.episode_duration_minutes is not None: p.episode_duration_minutes = req.episode_duration_minutes if req.episode_count is not None: p.episode_count = req.episode_count if req.estimated_completion_date is not None: p.estimated_completion_date = req.estimated_completion_date if req.actual_completion_date is not None: p.actual_completion_date = req.actual_completion_date if req.contract_amount is not None: p.contract_amount = req.contract_amount db.commit() db.refresh(p) return enrich_project(p, db) @router.delete("/{project_id}") def delete_project( project_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_permission("project:delete")) ): """删除项目及其关联数据""" from models import OutsourceCost, AIToolCostAllocation, CostOverride, SubmissionHistory p = db.query(Project).filter(Project.id == project_id).first() if not p: raise HTTPException(status_code=404, detail="项目不存在") # 删除关联数据 subs = db.query(Submission).filter(Submission.project_id == project_id).all() for s in subs: db.query(SubmissionHistory).filter(SubmissionHistory.submission_id == s.id).delete() db.query(Submission).filter(Submission.project_id == project_id).delete() db.query(OutsourceCost).filter(OutsourceCost.project_id == project_id).delete() db.query(AIToolCostAllocation).filter(AIToolCostAllocation.project_id == project_id).delete() db.query(CostOverride).filter(CostOverride.project_id == project_id).delete() db.delete(p) db.commit() return {"message": "项目已删除"} @router.post("/{project_id}/complete") def complete_project( project_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_permission("project:complete")) ): """Owner 手动确认项目完成""" p = db.query(Project).filter(Project.id == project_id).first() if not p: raise HTTPException(status_code=404, detail="项目不存在") from datetime import date p.status = ProjectStatus.COMPLETED p.actual_completion_date = date.today() db.commit() return {"message": "项目已标记为完成", "project_id": project_id}