撤回改为CANCELED状态标记+TaskRecord留痕+TreeCanvas灰度渲染+全部状态映射更新

This commit is contained in:
2026-08-06 12:51:40 +08:00
parent eb97c12bed
commit 616638a6c8
6 changed files with 27 additions and 25 deletions

View File

@ -14,6 +14,7 @@ TASK_STATUS_WIP = "WIP" # 进行中
TASK_STATUS_COMPLETED = "COMPLETED" # 已完成
TASK_STATUS_REJECTED = "REJECTED" # 已驳回
TASK_STATUS_ARCHIVED = "ARCHIVED" # 已入库
TASK_STATUS_CANCELED = "CANCELED" # 已撤回/已作废
class Task(Base):

View File

@ -6,7 +6,7 @@ from sqlalchemy import select, delete
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.models.task import Task, TaskRecord, TASK_STATUS_PENDING, TASK_STATUS_WIP, TASK_STATUS_COMPLETED, TASK_STATUS_REJECTED
from app.models.task import Task, TaskRecord, TASK_STATUS_PENDING, TASK_STATUS_WIP, TASK_STATUS_COMPLETED, TASK_STATUS_REJECTED, TASK_STATUS_CANCELED, TASK_STATUS_ARCHIVED
from app.core.time_utils import get_beijing_time
from app.models.product import Product
from app.models.task_log import TaskLog
@ -313,43 +313,43 @@ async def end_task(
async def recall_task(
db: AsyncSession, task_id: uuid.UUID, operator_id: str | None = None
) -> TaskResponse:
"""撤回 PENDING 转交任务,恢复父任务为 WIP。"""
"""撤回 PENDING 转交任务:状态标记为 CANCELED,恢复父任务为 WIP。"""
task = await _get_task_or_404(db, task_id)
if task.status != TASK_STATUS_PENDING:
raise HTTPException(status_code=409, detail="只有待接收(PENDING)的任务可以撤回")
# 如果有父任务,恢复父任务状态
# 标记作废(不物理删除)
task.status = TASK_STATUS_CANCELED
task.completed_at = get_beijing_time()
# 写入撤回记录
await _create_task_log(db, task_id, action_type="recall", operator_id=operator_id,
remark=f"撤回转交「{task.task_name}」→ {task.assignee_id},任务已作废")
# 同时写入 TaskRecord 留痕
recall_record = TaskRecord(task_id=task.id, remark=f"[撤回] 转交至 {task.assignee_id} 的「{task.task_name}」已撤回", images="[]")
db.add(recall_record)
# 恢复父任务
if task.parent_task_id:
parent = await _get_task_or_404(db, task.parent_task_id)
if parent.status == TASK_STATUS_COMPLETED:
parent.status = TASK_STATUS_WIP
parent.completed_at = None
await _create_task_log(db, parent.id, action_type="recall", operator_id=operator_id,
remark=f"撤回转交「{task.task_name}」,恢复为 WIP")
product_result = await db.execute(select(Product).where(Product.id == parent.product_id))
product = product_result.scalar_one_or_none()
if product and parent.assignee_id:
product.current_location_id = parent.assignee_id
product.overall_status = parent.task_name
await _create_task_log(db, parent.id, action_type="recall", operator_id=operator_id,
remark=f"撤回转交「{task.task_name}」→ {task.assignee_id},恢复父任务为 WIP")
# 先删除关联的 task_logs 和 task_records
await db.execute(delete(TaskLog).where(TaskLog.task_id == task_id))
await db.execute(delete(TaskRecord).where(TaskRecord.task_id == task_id))
await db.flush()
# 删除 PENDING 子任务
await db.delete(task)
await db.commit()
if task.parent_task_id:
parent = await _get_task_or_404(db, task.parent_task_id)
return _to_response(parent)
else:
return _to_response(task)
refreshed = await _get_task_or_404(db, task.parent_task_id)
return _to_response(refreshed)
return _to_response(task)
# ============================================================
@ -396,7 +396,7 @@ async def _check_children_done(db: AsyncSession, task_id: uuid.UUID):
)
children = result.scalars().all()
incomplete = [c for c in children if c.status not in (
TASK_STATUS_COMPLETED, TASK_STATUS_REJECTED, ARCHIVED_STATUS
TASK_STATUS_COMPLETED, TASK_STATUS_REJECTED, TASK_STATUS_CANCELED, ARCHIVED_STATUS
)]
if incomplete:
names = "".join(c.task_name for c in incomplete)