feat(backend): 产品列表返回当前人滞留时长 active_duration_hours
- ProductResponse 增加 active_duration_hours(小时) 字段 - get_all_products 批量计算每个产品活跃任务(WIP/PENDING)最早接手时间到现在的时长 - 复用 get_people_workload 相同的北京时间口径,前端无需额外请求
This commit is contained in:
@ -541,6 +541,34 @@ async def get_all_products(
|
||||
has_img = bool(row[3] and row[3] != "[]" and row[3] != "null")
|
||||
latest_record_map[row[0]] = (row[1], row[2], has_img, row[4])
|
||||
|
||||
# 🔧 当前人滞留时长:每个产品活跃任务(WIP/PENDING)最早接手时间 → 小时
|
||||
active_duration_map: dict[uuid.UUID, float] = {}
|
||||
if product_ids:
|
||||
from sqlalchemy import func as sa_func
|
||||
from app.core.time_utils import get_beijing_time, BEIJING_TZ
|
||||
start_stmt = (
|
||||
select(
|
||||
Task.product_id,
|
||||
sa_func.min(sa_func.coalesce(Task.received_at, Task.created_at)),
|
||||
)
|
||||
.where(
|
||||
Task.product_id.in_(product_ids),
|
||||
Task.status.in_(["WIP", "PENDING"]),
|
||||
)
|
||||
.group_by(Task.product_id)
|
||||
)
|
||||
start_result = await db.execute(start_stmt)
|
||||
now = get_beijing_time()
|
||||
for row in start_result:
|
||||
start = row[1]
|
||||
if start is None:
|
||||
continue
|
||||
if start.tzinfo is None:
|
||||
start = start.replace(tzinfo=BEIJING_TZ)
|
||||
else:
|
||||
start = start.astimezone(BEIJING_TZ)
|
||||
active_duration_map[row[0]] = round((now - start).total_seconds() / 3600, 1)
|
||||
|
||||
return [
|
||||
ProductResponse(
|
||||
id=p.id,
|
||||
@ -575,6 +603,7 @@ async def get_all_products(
|
||||
merged_name_map.get(latest_record_map.get(p.id, (None, None, False, None))[3])
|
||||
if latest_record_map.get(p.id, (None, None, False, None))[3] else None
|
||||
),
|
||||
active_duration_hours=active_duration_map.get(p.id),
|
||||
)
|
||||
for p in products
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user