Files
track/backend/app/api/v1/endpoints/tasks.py

228 lines
7.8 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 __future__ import annotations
import uuid
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.schemas.task import (
TaskCreate,
TaskUpdate,
TaskCompleteRequest,
TaskRejectRequest,
TaskTransferRequest,
TaskTransferBranch,
SubtaskCreate,
TaskRecordCreate,
TaskResponse,
TaskCompleteResponse,
TaskTransferResponse,
TaskSummaryResponse,
TaskListResponse,
)
from app.services import task_service
router = APIRouter(prefix="/tasks", tags=["任务管理"])
# ============================================================
# 任务 CRUD
# ============================================================
@router.get("/", response_model=TaskListResponse)
async def list_tasks(
product_id: str | None = Query(None, description="按产品ID筛选"),
assignee_id: str | None = Query(None, description="按负责人ID筛选逻辑外键→老系统"),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=200),
db: AsyncSession = Depends(get_db),
):
"""获取任务列表,可按产品/负责人筛选(只返回顶层任务)"""
pid = uuid.UUID(product_id) if product_id else None
return await task_service.get_all_tasks(db, product_id=pid, assignee_id=assignee_id, skip=skip, limit=limit)
@router.get("/{task_id}", response_model=TaskResponse)
async def get_task(
task_id: str,
db: AsyncSession = Depends(get_db),
):
"""
获取任务详情 — 递归包含所有层级的子任务。
前端可根据此结果渲染完整的任务树。
"""
return await task_service.get_task(db, uuid.UUID(task_id))
@router.post("/", response_model=TaskResponse, status_code=201)
async def create_task_endpoint(
data: TaskCreate,
db: AsyncSession = Depends(get_db),
):
"""创建任务"""
return await task_service.create_task(db, data)
@router.patch("/{task_id}", response_model=TaskResponse)
async def update_task_endpoint(
task_id: str,
data: TaskUpdate,
db: AsyncSession = Depends(get_db),
):
"""更新任务"""
return await task_service.update_task(db, uuid.UUID(task_id), data)
# ============================================================
# 核心卡点逻辑:任务完成 / 转交
# ============================================================
@router.post("/{task_id}/complete", response_model=TaskCompleteResponse)
async def complete_task_endpoint(
task_id: str,
request: TaskCompleteRequest,
db: AsyncSession = Depends(get_db),
):
"""
**核心接口:完成任务 + 可选创建下一步任务(转交)**
卡点逻辑:
1. 检查当前任务是否已完成(幂等保护)
2. 查询所有 `notify_parent_on_complete=True` 的子任务
→ 如果存在未完成的,返回 HTTP 400「请等待相关子任务完成」
3. 全部通过后,标记任务为 completed写入操作日志
4. 若提供了 `next_task_name` + `next_assignee_id`,自动创建下一步任务
典型场景:
- 某个加工步骤完成,需要检查所有必须的前置工序(子任务)是否已完成
- 完成后自动创建下一步任务并指定负责人
"""
return await task_service.complete_task(
db, uuid.UUID(task_id), request
)
# ============================================================
# 核心业务 1确认接收 (PENDING → WIP)
# ============================================================
@router.post("/{task_id}/receive", response_model=TaskResponse)
async def receive_task_endpoint(
task_id: str,
operator_id: str | None = Query(None, description="操作人ID"),
db: AsyncSession = Depends(get_db),
):
"""
**确认接收任务。**
校验:只有状态为 PENDING 的任务可接收。
动作:将状态改为 WIP记录 received_at 为当前时间。
"""
return await task_service.receive_task(
db, uuid.UUID(task_id), operator_id
)
# ============================================================
# 核心业务 2品质驳回 (→ REJECTED + 返工闭环)
# ============================================================
@router.post("/{task_id}/reject", response_model=TaskResponse)
async def reject_task_endpoint(
task_id: str,
request: TaskRejectRequest,
operator_id: str | None = Query(None, description="操作人ID"),
db: AsyncSession = Depends(get_db),
):
"""
**品质驳回:将任务标记为 REJECTED自动创建返工任务。**
防呆闭环逻辑:
1. 将当前任务状态改为 REJECTED记录 reject_reason 和 completed_at。
2. 查找上一道工序的负责人(父任务的 assignee_id
3. 为该负责人新建返工任务is_rework=True, status=PENDING
"""
return await task_service.reject_task(
db, uuid.UUID(task_id), request, operator_id
)
# ============================================================
# 核心业务 3完工并裂变转交 (→ COMPLETED + 多路裂变)
# ============================================================
@router.post("/{task_id}/transfer", response_model=TaskTransferResponse)
async def transfer_task_endpoint(
task_id: str,
request: TaskTransferRequest,
operator_id: str | None = Query(None, description="操作人ID"),
db: AsyncSession = Depends(get_db),
):
"""
**完工并裂变转交:完成当前任务,批量创建下一道工序任务。**
动作 1闭环当前节点
- 将当前任务状态改为 COMPLETED记录 completed_at。
动作 2解析下家
- 遍历 next_assignees 列表。
- 如果包含 'virtual_warehouse',则将 Product 的 current_location_id 设为仓库。
- 为每一个 assignee_id 新建 PENDING 任务。
裂变逻辑:
- next_assignees > 1 → 多路裂变,新任务挂在当前任务下形成树状分支。
- 当前任务是子任务 → 单路转交也保持在同一父任务下。
- 否则 → 顶层同级转交。
"""
return await task_service.transfer_task(
db, uuid.UUID(task_id), request, operator_id
)
# ============================================================
# 无限层级子任务
# ============================================================
@router.post("/{task_id}/subtasks", response_model=TaskResponse, status_code=201)
async def create_subtask_endpoint(
task_id: str,
data: SubtaskCreate,
db: AsyncSession = Depends(get_db),
):
"""
**创建子任务:支持无限层级嵌套。**
新子任务将自动继承父任务的 product_id。
若父任务已完成,拒绝创建。
"""
return await task_service.create_subtask(
db, uuid.UUID(task_id), data
)
# ============================================================
# 查询产品顶层任务(便捷接口)
# ============================================================
@router.get("/by-product/{product_id}", response_model=list[TaskSummaryResponse])
async def get_tasks_by_product(
product_id: str,
db: AsyncSession = Depends(get_db),
):
"""获取指定产品的顶层任务列表(不含子任务嵌套)"""
return await task_service.get_top_level_tasks(db, uuid.UUID(product_id))
# ============================================================
# 任务进度记录 — 备注/传图
# ============================================================
@router.patch("/{task_id}/records", response_model=TaskResponse)
async def add_task_record_endpoint(
task_id: str,
data: TaskRecordCreate,
db: AsyncSession = Depends(get_db),
):
"""追加进度记录(备注+图片),不改变任务状态"""
return await task_service.add_task_record(db, uuid.UUID(task_id), data)