feat: 宏观状态动态从主干任务获取+任务树assignee中文姓名映射(后端+前端)

This commit is contained in:
2026-08-10 11:25:44 +08:00
parent c0fe7c94bb
commit b43bded0ef
4 changed files with 65 additions and 2 deletions

View File

@ -78,6 +78,7 @@ class ProductScanResponse(BaseModel):
created_at: datetime
top_level_tasks: list[TaskSummaryResponse] = []
task_tree: list[TaskResponse] = []
assignee_names: dict[str, str] = {} # 🔧 username→中文姓名映射
model_config = {"from_attributes": True}

View File

@ -108,6 +108,17 @@ async def get_product_by_serial(db: AsyncSession, serial_number: str) -> Product
# 获取完整任务树(递归嵌套,供前端渲染十字矩阵树状图)
task_tree = await _load_task_tree(db, product.id)
# 🔧 收集任务树中所有 assignee_id → 查中文姓名映射
assignee_ids: set[str] = set()
def _collect_ids(tasks):
for t in tasks:
if t.assignee_id: assignee_ids.add(t.assignee_id)
if t.child_tasks: _collect_ids(t.child_tasks)
for t in top_tasks:
if t.assignee_id: assignee_ids.add(t.assignee_id)
_collect_ids(task_tree)
assignee_names = _lookup_display_names(list(assignee_ids))
return ProductScanResponse(
id=product.id,
serial_number=product.serial_number,
@ -128,6 +139,7 @@ async def get_product_by_serial(db: AsyncSession, serial_number: str) -> Product
TaskSummaryResponse.model_validate(t) for t in top_tasks
],
task_tree=task_tree,
assignee_names=assignee_names, # 🔧 username→中文姓名
)
@ -408,6 +420,53 @@ async def get_all_products(
for row in task_result:
macro_map[row[0]] = prio_to_status.get(row[1], None)
# 🔧 动态宏观状态:只从主干任务中获取最高优先级任务的 task_name
overall_names: dict[uuid.UUID, str] = {}
if product_ids:
from sqlalchemy import func as sa_func2, case as sa_case2
# 先找每个产品中优先级最高的主干任务ID
main_prio_stmt = (
select(
Task.product_id,
sa_func2.max(sa_case2(
(Task.status == "WIP", 3),
(Task.status == "PENDING", 2),
(Task.status == "COMPLETED", 1),
else_=0,
)).label("prio"),
)
.where(
Task.product_id.in_(product_ids),
sa_func2.or_(
Task.parent_task_id.is_(None),
Task.task_type.in_(["TRANSFER", "RECOVERY"]),
),
)
.group_by(Task.product_id)
).subquery()
# 再 join 回去拿 task_name
name_stmt = (
select(Task.product_id, Task.task_name)
.join(main_prio_stmt, sa_func2.and_(
Task.product_id == main_prio_stmt.c.product_id,
sa_case2(
(Task.status == "WIP", 3),
(Task.status == "PENDING", 2),
(Task.status == "COMPLETED", 1),
else_=0,
) == main_prio_stmt.c.prio,
sa_func2.or_(
Task.parent_task_id.is_(None),
Task.task_type.in_(["TRANSFER", "RECOVERY"]),
),
))
.order_by(Task.created_at.desc())
.limit(1)
)
name_result = await db.execute(name_stmt)
for row in name_result:
overall_names[row[0]] = row[1]
return [
ProductResponse(
id=p.id,
@ -427,8 +486,8 @@ async def get_all_products(
else name_map.get(p.current_location_id) if p.current_location_id
else None
),
macro_status=macro_map.get(p.id) or p.status, # 优先任务树状态,兜底产品状态
overall_status=p.overall_status,
macro_status=macro_map.get(p.id) or p.status,
overall_status=overall_names.get(p.id) or p.overall_status, # 🔧 动态主干任务名优先
status=p.status,
created_at=p.created_at,
)