Files
012-kaopeilian/backend/app/api/v1/tasks.py
yuliang_guo 616bb7185e
Some checks failed
continuous-integration/drone/push Build is failing
fix: 任务中心标签页显示真实任务数量
- 移除硬编码的任务数量(12/5/28/3)
- 加载所有任务后统计各状态数量
- 后端任务API page_size限制调整为500
2026-01-31 18:48:26 +08:00

284 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
任务管理API
"""
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, Query, Request
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_db, get_current_user, require_admin_or_manager
from app.schemas.base import ResponseModel, PaginatedResponse
from app.schemas.task import TaskCreate, TaskUpdate, TaskResponse, TaskStatsResponse
from app.services.task_service import task_service
from app.services.system_log_service import system_log_service
from app.schemas.system_log import SystemLogCreate
from app.models.user import User
router = APIRouter(prefix="/manager/tasks", tags=["Tasks"], redirect_slashes=False)
@router.post("", response_model=ResponseModel[TaskResponse], summary="创建任务")
async def create_task(
task_in: TaskCreate,
request: Request,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_admin_or_manager)
):
"""创建新任务"""
task = await task_service.create_task(db, task_in, current_user.id)
# 记录任务创建日志
await system_log_service.create_log(
db,
SystemLogCreate(
level="INFO",
type="api",
message=f"创建任务: {task.title}",
user_id=current_user.id,
user=current_user.username,
ip=request.client.host if request.client else None,
path="/api/v1/manager/tasks",
method="POST",
user_agent=request.headers.get("user-agent")
)
)
# 构建响应
courses = [link.course.name for link in task.course_links]
# 安全获取枚举值(兼容字符串和枚举类型)
priority_val = task.priority.value if hasattr(task.priority, 'value') else task.priority
status_val = task.status.value if hasattr(task.status, 'value') else task.status
completed_count = sum(
1 for a in task.assignments
if (a.status.value if hasattr(a.status, 'value') else a.status) == "completed"
)
return ResponseModel(
data=TaskResponse(
id=task.id,
title=task.title,
description=task.description,
priority=priority_val,
status=status_val,
creator_id=task.creator_id,
deadline=task.deadline,
requirements=task.requirements,
progress=task.progress,
created_at=task.created_at,
updated_at=task.updated_at,
courses=courses,
assigned_count=len(task.assignments),
completed_count=completed_count
)
)
@router.get("", response_model=ResponseModel[PaginatedResponse[TaskResponse]], summary="获取任务列表")
async def get_tasks(
status: Optional[str] = Query(None, description="任务状态筛选"),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=500),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_admin_or_manager)
):
"""获取任务列表"""
tasks, total = await task_service.get_tasks(db, status, page, page_size)
# 构建响应
items = []
for task in tasks:
# 安全获取枚举值
priority_val = task.priority.value if hasattr(task.priority, 'value') else task.priority
status_val = task.status.value if hasattr(task.status, 'value') else task.status
# 使用已加载的关联数据(通过 selectinload
courses = [link.course.name for link in task.course_links] if task.course_links else []
completed_count = sum(
1 for a in task.assignments
if (a.status.value if hasattr(a.status, 'value') else a.status) == "completed"
) if task.assignments else 0
items.append(TaskResponse(
id=task.id,
title=task.title,
description=task.description,
priority=priority_val,
status=status_val,
creator_id=task.creator_id,
deadline=task.deadline,
requirements=task.requirements,
progress=task.progress,
created_at=task.created_at,
updated_at=task.updated_at,
courses=courses,
assigned_count=len(task.assignments) if task.assignments else 0,
completed_count=completed_count
))
return ResponseModel(
data=PaginatedResponse.create(
items=items,
total=total,
page=page,
page_size=page_size
)
)
@router.get("/stats", response_model=ResponseModel[TaskStatsResponse], summary="获取任务统计")
async def get_task_stats(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_admin_or_manager)
):
"""获取任务统计数据"""
stats = await task_service.get_task_stats(db)
return ResponseModel(data=stats)
@router.get("/{task_id}", response_model=ResponseModel[TaskResponse], summary="获取任务详情")
async def get_task(
task_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_admin_or_manager)
):
"""获取任务详情"""
task = await task_service.get_task_detail(db, task_id)
if not task:
raise HTTPException(status_code=404, detail="任务不存在")
courses = [link.course.name for link in task.course_links]
return ResponseModel(
data=TaskResponse(
id=task.id,
title=task.title,
description=task.description,
priority=task.priority.value,
status=task.status.value,
creator_id=task.creator_id,
deadline=task.deadline,
requirements=task.requirements,
progress=task.progress,
created_at=task.created_at,
updated_at=task.updated_at,
courses=courses,
assigned_count=len(task.assignments),
completed_count=sum(1 for a in task.assignments if a.status.value == "completed")
)
)
@router.put("/{task_id}", response_model=ResponseModel[TaskResponse], summary="更新任务")
async def update_task(
task_id: int,
task_in: TaskUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_admin_or_manager)
):
"""更新任务"""
task = await task_service.update_task(db, task_id, task_in)
if not task:
raise HTTPException(status_code=404, detail="任务不存在")
# 自动更新任务进度和状态
await task_service.update_task_status(db, task_id)
# 重新加载详情
task_detail = await task_service.get_task_detail(db, task.id)
courses = [link.course.name for link in task_detail.course_links] if task_detail else []
return ResponseModel(
data=TaskResponse(
id=task.id,
title=task.title,
description=task.description,
priority=task.priority.value,
status=task.status.value,
creator_id=task.creator_id,
deadline=task.deadline,
requirements=task.requirements,
progress=task.progress,
created_at=task.created_at,
updated_at=task.updated_at,
courses=courses,
assigned_count=len(task_detail.assignments) if task_detail else 0,
completed_count=sum(1 for a in task_detail.assignments if a.status.value == "completed") if task_detail else 0
)
)
@router.delete("/{task_id}", response_model=ResponseModel, summary="删除任务")
async def delete_task(
task_id: int,
request: Request,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_admin_or_manager)
):
"""删除任务"""
# 先获取任务信息用于日志
task_detail = await task_service.get_task_detail(db, task_id)
task_title = task_detail.title if task_detail else f"ID:{task_id}"
success = await task_service.delete_task(db, task_id)
if not success:
raise HTTPException(status_code=404, detail="任务不存在")
# 记录任务删除日志
await system_log_service.create_log(
db,
SystemLogCreate(
level="INFO",
type="api",
message=f"删除任务: {task_title}",
user_id=current_user.id,
user=current_user.username,
ip=request.client.host if request.client else None,
path=f"/api/v1/manager/tasks/{task_id}",
method="DELETE",
user_agent=request.headers.get("user-agent")
)
)
return ResponseModel(message="任务已删除")
@router.post("/{task_id}/remind", response_model=ResponseModel, summary="发送任务提醒")
async def send_task_reminder(
task_id: int,
request: Request,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_admin_or_manager)
):
"""向未完成任务的成员发送提醒"""
task = await task_service.get_task_detail(db, task_id)
if not task:
raise HTTPException(status_code=404, detail="任务不存在")
# 获取未完成的成员数量
incomplete_count = sum(1 for a in task.assignments if a.status.value != "completed")
if incomplete_count == 0:
return ResponseModel(message="所有成员已完成任务,无需发送提醒")
# 记录提醒日志
await system_log_service.create_log(
db,
SystemLogCreate(
level="INFO",
type="notification",
message=f"发送任务提醒: {task.title},提醒 {incomplete_count}",
user_id=current_user.id,
user=current_user.username,
ip=request.client.host if request.client else None,
path=f"/api/v1/manager/tasks/{task_id}/remind",
method="POST",
user_agent=request.headers.get("user-agent")
)
)
# TODO: 实际发送通知逻辑(通过通知服务)
# 可以调用 notification_service.send_task_reminder(task, incomplete_assignments)
return ResponseModel(message=f"已向 {incomplete_count} 位未完成成员发送提醒")