2026-02-12 14:24:05 +08:00

159 lines
5.7 KiB
Python

"""项目管理路由"""
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, UserRole, ProjectType,
ProjectStatus, PhaseGroup, WorkType
)
from schemas import ProjectCreate, ProjectUpdate, ProjectOut
from auth import get_current_user, require_role
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_role(UserRole.OWNER, UserRole.SUPERVISOR))
):
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_role(UserRole.OWNER, UserRole.SUPERVISOR))
):
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.post("/{project_id}/complete")
def complete_project(
project_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(require_role(UserRole.OWNER))
):
"""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}