From d8d42e96133e3baa5fc8f5c17ac3de2aaa61e415 Mon Sep 17 00:00:00 2001 From: duxingchen Date: Wed, 12 Aug 2026 17:52:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=8A=E6=B8=B8=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=E5=8F=91=E9=80=81=E4=BA=BA=20=E2=80=94=20?= =?UTF-8?q?=E4=BB=8Etask=5Flogs=E8=BF=BD=E6=BA=AFcreated=5Fby?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/schemas/task.py | 2 + backend/app/services/product_service.py | 40 +++++++++++++++++++ .../pages/scan/components/WorkspaceArea.vue | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/backend/app/schemas/task.py b/backend/app/schemas/task.py index 4e524ee..d63627a 100644 --- a/backend/app/schemas/task.py +++ b/backend/app/schemas/task.py @@ -116,6 +116,7 @@ class TaskSummaryResponse(BaseModel): received_at: datetime | None = None completed_at: datetime | None = None created_at: datetime + created_by: str | None = None # 谁创建的(从task_logs追溯) model_config = {"from_attributes": True} @@ -140,6 +141,7 @@ class TaskResponse(BaseModel): created_at: datetime child_tasks: list[TaskResponse] = [] records: list[TaskRecordResponse] = [] + created_by: str | None = None # 谁创建的(从task_logs追溯) model_config = {"from_attributes": True} diff --git a/backend/app/services/product_service.py b/backend/app/services/product_service.py index 47d31cb..931cb5f 100644 --- a/backend/app/services/product_service.py +++ b/backend/app/services/product_service.py @@ -42,6 +42,7 @@ def _task_to_response(task: Task) -> TaskResponse: created_at=task.created_at, child_tasks=[_task_to_response(c) for c in task.child_tasks], records=[TaskRecordResponse.model_validate(r) for r in (task.records or [])], + created_by=getattr(task, "created_by", None), ) @@ -85,15 +86,54 @@ async def get_product_by_serial(db: AsyncSession, serial_number: str) -> Product # 🔧 收集任务树中所有 assignee_id → 查中文姓名映射 assignee_ids: set[str] = set() + all_task_ids: list[uuid.UUID] = [] def _collect_ids(tasks): for t in tasks: + all_task_ids.append(t.id) if t.assignee_id: assignee_ids.add(t.assignee_id) if t.child_tasks: _collect_ids(t.child_tasks) for t in top_tasks: + all_task_ids.append(t.id) if t.assignee_id: assignee_ids.add(t.assignee_id) _collect_ids(task_tree) assignee_names = _lookup_display_names(list(assignee_ids)) + # 🔧 批量查 task_logs: 谁创建了每个任务 + creator_map: dict[uuid.UUID, str] = {} + if all_task_ids: + from app.models.task_log import TaskLog + # 每个 task 取最早的 create 日志的 operator_id + sub = ( + select( + TaskLog.task_id, + TaskLog.operator_id, + func.row_number().over( + partition_by=TaskLog.task_id, + order_by=TaskLog.created_at.asc() + ).label("rn") + ) + .where( + TaskLog.task_id.in_(all_task_ids), + TaskLog.action_type == "create" + ) + ).subquery() + log_result = await db.execute( + select(sub.c.task_id, sub.c.operator_id).where(sub.c.rn == 1) + ) + for row in log_result: + if row[1]: + creator_map[row[0]] = row[1] + + # 注入 created_by 到 task_tree 和 top_tasks + def _inject_creator(tasks): + for t in tasks: + t.created_by = creator_map.get(t.id) + if t.child_tasks: + _inject_creator(t.child_tasks) + _inject_creator(task_tree) + for t in top_tasks: + t.created_by = creator_map.get(t.id) + return ProductScanResponse( id=product.id, serial_number=product.serial_number, diff --git a/track-uniapp/src/pages/scan/components/WorkspaceArea.vue b/track-uniapp/src/pages/scan/components/WorkspaceArea.vue index c1451ab..1260cde 100644 --- a/track-uniapp/src/pages/scan/components/WorkspaceArea.vue +++ b/track-uniapp/src/pages/scan/components/WorkspaceArea.vue @@ -61,7 +61,7 @@ {{ isNestedSpawn ? '⬆ 上游协助 (嵌套)' : '⬆ 上游工序' }} - {{ parentTaskOf(lockedTask).task_name }} → {{ formatUserName(parentTaskOf(lockedTask).assignee_id) || '—' }} + {{ parentTaskOf(lockedTask).task_name }} → {{ formatUserName(lockedTask.created_by) || formatUserName(parentTaskOf(lockedTask).assignee_id) || '—' }} ⬇ 下游分支 ({{ lockedTask.child_tasks.length }})