diff --git a/backend/app/schemas/product.py b/backend/app/schemas/product.py index b8a6961..2173588 100644 --- a/backend/app/schemas/product.py +++ b/backend/app/schemas/product.py @@ -55,6 +55,9 @@ class ProductResponse(BaseModel): overall_status: str | None = None status: str created_at: datetime + # 🔧 最新动态 — 该产品活跃任务的最新记录 + latest_record_time: datetime | None = None + latest_record_content: str | None = None model_config = {"from_attributes": True} diff --git a/backend/app/services/product_service.py b/backend/app/services/product_service.py index b371a87..47a5888 100644 --- a/backend/app/services/product_service.py +++ b/backend/app/services/product_service.py @@ -461,6 +461,32 @@ async def get_all_products( merged_location_ids = list(set(static_location_ids + dynamic_location_ids)) merged_name_map = _lookup_display_names(merged_location_ids) + # 🔧 批量查询每个产品活跃任务的最新记录 + latest_record_map: dict[uuid.UUID, tuple] = {} + if product_ids: + from app.models.task import TaskRecord as TR + wip_pending_ids = select(Task.id).where( + and_( + Task.product_id.in_(product_ids), + Task.status.in_(["WIP", "PENDING"]), + ) + ).subquery() + ranked = ( + select(TR.task_id, TR.remark, TR.created_at, Task.product_id, + func.row_number().over( + partition_by=Task.product_id, + order_by=TR.created_at.desc() + ).label("rn")) + .join(Task, TR.task_id == Task.id) + .where(Task.id.in_(select(wip_pending_ids.c.id))) + ).subquery() + rec_result = await db.execute( + select(ranked.c.product_id, ranked.c.created_at, ranked.c.remark) + .where(ranked.c.rn == 1) + ) + for row in rec_result: + latest_record_map[row[0]] = (row[1], row[2]) + return [ ProductResponse( id=p.id, @@ -474,10 +500,8 @@ async def get_all_products( category=p.category, material_type=p.material_type, parent_product_id=p.parent_product_id, - # 🔧 当前位置:动态主干assignee优先 → 静态兜底 current_location_id=( - main_assignees.get(p.id) # 动态主干 - or p.current_location_id # 静态兜底 + main_assignees.get(p.id) or p.current_location_id ), current_location_name=( "仓库" if (main_assignees.get(p.id) or p.current_location_id) == "virtual_warehouse" @@ -489,6 +513,8 @@ async def get_all_products( overall_status=overall_names.get(p.id) or p.overall_status, status=p.status, created_at=p.created_at, + latest_record_time=latest_record_map.get(p.id, (None, None))[0], + latest_record_content=latest_record_map.get(p.id, (None, None))[1], ) for p in products ] diff --git a/frontend/src/pages/admin/AdminTasksPage.tsx b/frontend/src/pages/admin/AdminTasksPage.tsx index f54ea75..da9caf9 100644 --- a/frontend/src/pages/admin/AdminTasksPage.tsx +++ b/frontend/src/pages/admin/AdminTasksPage.tsx @@ -5,6 +5,7 @@ import { Search, Loader2, Package, ChevronDown, ChevronRight, Warehouse, GitBranch, X, } from "lucide-react"; +import { Tooltip } from "antd"; import api from "../../services/api"; import { scanProduct } from "../../services/productApi"; import { @@ -368,14 +369,15 @@ export default function AdminTasksPage() { {isOpen && (