feat(backend): 新增 /dashboard/wip-matrix 在制品交叉聚合接口

- 规格型号×人员/工序 的设备数量聚合(WIP/PENDING任务)
- dimension 参数: assignee(中文姓名)/task_name(工序名)
- 返回扁平数组含 count + assignees(该交叉点主负责人列表)
This commit is contained in:
2026-08-28 16:25:20 +08:00
parent d9ec15b72f
commit 7547a886cc
2 changed files with 75 additions and 0 deletions

View File

@ -12,6 +12,7 @@ from app.services.dashboard_service import (
get_rejected_tasks, RejectedTask,
get_user_operations, UserOperation,
get_user_operation_detail, OperationDetail,
get_wip_matrix, WipMatrixRow,
get_people_workload, PersonWorkload,
get_people_history, PersonHistoryRecord,
search_product_messages, ProductMessageList,
@ -100,6 +101,15 @@ async def user_operations_detail(
)
@router.get("/wip-matrix", response_model=list[WipMatrixRow])
async def wip_matrix(
dimension: str = Query("assignee", description="聚合维度: assignee(人员) / task_name(工序)"),
db: AsyncSession = Depends(get_db),
):
"""在制品分布透视表 — 规格型号 × 人员/工序 的设备数量交叉聚合"""
return await get_wip_matrix(db, dimension=dimension)
@router.get("/people-workload", response_model=list[PersonWorkload])
async def people_workload(
db: AsyncSession = Depends(get_db),

View File

@ -80,6 +80,13 @@ class OperationDetail(BaseModel):
time: str # 操作时间 ISOBEIJING_TZ
class WipMatrixRow(BaseModel):
spec_model: str # 规格型号Y 轴)
dimension_key: str # 人员姓名 或 工序名称X 轴)
count: int = 0 # 该交叉点的设备数量
assignees: list[str] = [] # 该交叉点涉及的主负责人(中文名,去重)
class PersonDevice(BaseModel):
product_id: str
serial_number: str # 16位HEX身份证
@ -593,6 +600,64 @@ async def get_user_operations(
return items
# ============================================================
# 在制品分布透视表WIP Matrix规格型号 × 人员/工序)
# ============================================================
async def get_wip_matrix(
db: AsyncSession,
dimension: str = "assignee",
) -> list[WipMatrixRow]:
"""在制品交叉聚合Y=规格型号X=人员 或 工序,单元格=设备数量。
dimension:
- assignee: 按任务负责人聚合dimension_key 为中文姓名)
- task_name: 按工序名聚合dimension_key 为工序名,附主负责人)
"""
from app.models.task import Task, TASK_STATUS_WIP, TASK_STATUS_PENDING
from app.models.product import Product
dim_expr = Task.task_name if dimension == "task_name" else Task.assignee_id
stmt = (
select(
Product.spec_model,
dim_expr,
func.count(Product.id),
func.array_agg(func.distinct(Task.assignee_id)),
)
.join(Task, Task.product_id == Product.id)
.where(Task.status.in_([TASK_STATUS_WIP, TASK_STATUS_PENDING]))
.group_by(Product.spec_model, dim_expr)
.order_by(Product.spec_model, dim_expr)
)
result = await db.execute(stmt)
rows = result.all()
# 收集负责人 ID → 中文名
raw_ids: set[str] = set()
for r in rows:
for aid in (r[3] or []):
if aid:
raw_ids.add(aid)
name_map: dict[str, str] = {}
if raw_ids:
from app.services.mom_cache import get_display_names
name_map = get_display_names(list(raw_ids))
items: list[WipMatrixRow] = []
for spec, dim_key, cnt, assignee_ids in rows:
dim_display = name_map.get(dim_key or "", dim_key or "未分配") if dimension == "assignee" else (dim_key or "")
assignees = [name_map.get(a, a) for a in (assignee_ids or []) if a] or []
items.append(WipMatrixRow(
spec_model=spec or "未知型号",
dimension_key=dim_display,
count=cnt or 0,
assignees=assignees,
))
return items
# ============================================================
# 人员操作明细(点击数字下钻 — 接收/转交/上传备注)
# ============================================================