撤回链式接力: CANCELED后生成WIP子任务还给操作人+TreeCanvas CANCELED视为串行换行

This commit is contained in:
2026-08-06 12:53:23 +08:00
parent 616638a6c8
commit e5e2a395e7
2 changed files with 32 additions and 28 deletions

View File

@ -313,43 +313,47 @@ async def end_task(
async def recall_task(
db: AsyncSession, task_id: uuid.UUID, operator_id: str | None = None
) -> TaskResponse:
"""撤回 PENDING 转交任务:状态标记为 CANCELED恢复父任务为 WIP"""
"""撤回 PENDING 转交标记为 CANCELED以被撤回节点为父生成接力新任务给操作人"""
task = await _get_task_or_404(db, task_id)
if task.status != TASK_STATUS_PENDING:
raise HTTPException(status_code=409, detail="只有待接收(PENDING)的任务可以撤回")
# 标记作废(不物理删除)
now = get_beijing_time()
# 1. 废掉当前待接收任务
task.status = TASK_STATUS_CANCELED
task.completed_at = get_beijing_time()
# 写入撤回记录
task.completed_at = now
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)
remark=f"撤回转交「{task.task_name}」→ {task.assignee_id}")
db.add(TaskRecord(task_id=task.id, remark=f"[撤回] 转交至 {task.assignee_id} 已撤回", images="[]"))
# 恢复父任务
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
# 2. 生成接力新任务(以撤回节点为父,还给操作人)
recovery = Task(
product_id=task.product_id,
parent_task_id=task.id,
task_name=task.task_name,
assignee_id=operator_id,
status=TASK_STATUS_WIP,
notify_parent_on_complete=False,
is_rework=False,
remark=f"撤回「{task.task_name}」后重新接手",
)
db.add(recovery)
await db.flush()
await _create_task_log(db, recovery.id, action_type="create", operator_id=operator_id,
remark=f"撤回接力:撤回「{task.task_name}」→ {task.assignee_id} 后重新指派给 {operator_id}")
db.add(TaskRecord(task_id=recovery.id, remark=f"[重新接手] 撤回转交后系统自动生成接力节点", images="[]"))
# 3. 更新产品位置
product_result = await db.execute(select(Product).where(Product.id == task.product_id))
product = product_result.scalar_one_or_none()
if product and operator_id:
product.current_location_id = operator_id
await db.commit()
if task.parent_task_id:
refreshed = await _get_task_or_404(db, task.parent_task_id)
return _to_response(refreshed)
return _to_response(task)
await db.refresh(recovery)
return _to_response(recovery)
# ============================================================

View File

@ -69,7 +69,7 @@ export default {
// 工业拓扑父WIP→并行(同Y)父COMPLETED→串行(换行)
// 后端保证 COMPLETED 后不可 spawn故 COMPLETED 的子任务必为转交产物
const parent = this.taskDataMap[t.parent_task_id];
const isSerial = !t.parent_task_id || (parent && parent.status === 'COMPLETED');
const isSerial = !t.parent_task_id || (parent && (parent.status === 'COMPLETED' || parent.status === 'CANCELED'));
const rowIdx = isSerial ? (parentRowIdx >= 0 ? parentRowIdx + 1 : rows.length) : parentRowIdx;
while (rows.length <= rowIdx) rows.push({ y: 0, tasks: [] });