From e5e2a395e78af804b7ad0ba789e3224f0de3c217 Mon Sep 17 00:00:00 2001 From: duxingchen Date: Thu, 6 Aug 2026 12:53:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=92=A4=E5=9B=9E=E9=93=BE=E5=BC=8F=E6=8E=A5?= =?UTF-8?q?=E5=8A=9B:=20CANCELED=E5=90=8E=E7=94=9F=E6=88=90WIP=E5=AD=90?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E8=BF=98=E7=BB=99=E6=93=8D=E4=BD=9C=E4=BA=BA?= =?UTF-8?q?+TreeCanvas=20CANCELED=E8=A7=86=E4=B8=BA=E4=B8=B2=E8=A1=8C?= =?UTF-8?q?=E6=8D=A2=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/task_service.py | 58 ++++++++++--------- .../src/pages/scan/components/TreeCanvas.vue | 2 +- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/backend/app/services/task_service.py b/backend/app/services/task_service.py index 34ba79c..94f73b9 100644 --- a/backend/app/services/task_service.py +++ b/backend/app/services/task_service.py @@ -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) # ============================================================ diff --git a/track-uniapp/src/pages/scan/components/TreeCanvas.vue b/track-uniapp/src/pages/scan/components/TreeCanvas.vue index 0a89b24..b9f0c18 100644 --- a/track-uniapp/src/pages/scan/components/TreeCanvas.vue +++ b/track-uniapp/src/pages/scan/components/TreeCanvas.vue @@ -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: [] });