feat(analytics): 规格型号带物料名 + 设备字典(/options 数据契约扩展)
- 后端新增 SpecModelOption/DeviceOption schema - get_analytics_options 返回规格型号的物料名(同型号取首个非空名)+ 关联设备列表 - 前端同步 SpecModelOption/DeviceOption 类型
This commit is contained in:
@ -70,9 +70,22 @@ class AssigneeOption(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
class SpecModelOption(BaseModel):
|
||||
spec_model: str
|
||||
material_name: str
|
||||
|
||||
|
||||
class DeviceOption(BaseModel):
|
||||
product_sn: str
|
||||
external_serial: str | None
|
||||
material_name: str
|
||||
spec_model: str
|
||||
|
||||
|
||||
class AnalyticsOptions(BaseModel):
|
||||
assignees: list[AssigneeOption]
|
||||
spec_models: list[str]
|
||||
spec_models: list[SpecModelOption]
|
||||
devices: list[DeviceOption]
|
||||
|
||||
|
||||
class DeviceRecord(BaseModel):
|
||||
@ -478,11 +491,12 @@ async def get_analytics_options(
|
||||
) -> AnalyticsOptions:
|
||||
"""返回筛选栏选项,支持动态联动:
|
||||
- 传入 spec_models:只返回碰过这些型号的人;
|
||||
- 传入 assignee_ids:只返回这些人处理过的型号。"""
|
||||
- 传入 assignee_ids:只返回这些人处理过的型号;
|
||||
- devices:根据筛选条件返回关联设备(无筛选则返回最近流转的设备)。"""
|
||||
from app.models.task import Task
|
||||
from app.models.product import Product
|
||||
|
||||
# 负责人:从 tasks 去重(可选按 spec_models 精确过滤)
|
||||
# ── 负责人:从 tasks 去重(可选按 spec_models 过滤) ──
|
||||
if spec_models:
|
||||
assignee_stmt = (
|
||||
select(Task.assignee_id)
|
||||
@ -512,23 +526,78 @@ async def get_analytics_options(
|
||||
for aid in distinct_assignees
|
||||
]
|
||||
|
||||
# 规格型号:从 products 去重(可选按 assignee_ids 过滤)
|
||||
# ── 规格型号 + 物料名(可选按 assignee_ids 过滤;同型号取首个非空物料名) ──
|
||||
if assignee_ids:
|
||||
spec_stmt = (
|
||||
select(Product.spec_model)
|
||||
select(Product.spec_model, Product.material_name)
|
||||
.join(Task, Task.product_id == Product.id)
|
||||
.where(
|
||||
Product.spec_model.isnot(None),
|
||||
func.trim(Product.spec_model) != "",
|
||||
Task.assignee_id.in_(assignee_ids),
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
else:
|
||||
spec_stmt = (
|
||||
select(Product.spec_model)
|
||||
select(Product.spec_model, Product.material_name)
|
||||
.where(Product.spec_model.isnot(None), func.trim(Product.spec_model) != "")
|
||||
.distinct()
|
||||
)
|
||||
spec_rows = (await db.execute(spec_stmt.distinct())).all()
|
||||
spec_models = sorted({r[0] for r in spec_rows if r[0]})
|
||||
spec_rows = (await db.execute(spec_stmt)).all()
|
||||
spec_map: dict[str, str] = {}
|
||||
for row in spec_rows:
|
||||
sm = row[0]
|
||||
mn = (row[1] or "").strip()
|
||||
if sm not in spec_map:
|
||||
spec_map[sm] = mn
|
||||
elif mn and not spec_map[sm]:
|
||||
spec_map[sm] = mn
|
||||
spec_model_opts = [
|
||||
SpecModelOption(spec_model=sm, material_name=spec_map[sm])
|
||||
for sm in sorted(spec_map.keys())
|
||||
]
|
||||
|
||||
return AnalyticsOptions(assignees=assignees, spec_models=spec_models)
|
||||
# ── 设备字典(可选按 assignee_ids / spec_models 过滤;无筛选返回最近流转 100 台) ──
|
||||
if assignee_ids or spec_models:
|
||||
device_stmt = select(
|
||||
Product.serial_number, Product.external_serial,
|
||||
Product.material_name, Product.spec_model,
|
||||
)
|
||||
if assignee_ids:
|
||||
device_stmt = device_stmt.join(
|
||||
Task, Task.product_id == Product.id
|
||||
).where(Task.assignee_id.in_(assignee_ids))
|
||||
if spec_models:
|
||||
device_stmt = device_stmt.where(Product.spec_model.in_(spec_models))
|
||||
device_stmt = device_stmt.distinct()
|
||||
else:
|
||||
latest_subq = (
|
||||
select(
|
||||
Task.product_id,
|
||||
func.max(func.coalesce(Task.received_at, Task.created_at)).label("latest"),
|
||||
)
|
||||
.group_by(Task.product_id)
|
||||
.subquery()
|
||||
)
|
||||
device_stmt = (
|
||||
select(
|
||||
Product.serial_number, Product.external_serial,
|
||||
Product.material_name, Product.spec_model,
|
||||
)
|
||||
.join(latest_subq, latest_subq.c.product_id == Product.id)
|
||||
.order_by(latest_subq.c.latest.desc())
|
||||
.limit(100)
|
||||
)
|
||||
device_rows = (await db.execute(device_stmt)).all()
|
||||
devices = [
|
||||
DeviceOption(
|
||||
product_sn=r[0] or "",
|
||||
external_serial=r[1] or None,
|
||||
material_name=r[2] or "",
|
||||
spec_model=r[3] or "",
|
||||
)
|
||||
for r in device_rows
|
||||
]
|
||||
|
||||
return AnalyticsOptions(assignees=assignees, spec_models=spec_model_opts, devices=devices)
|
||||
|
||||
@ -71,9 +71,22 @@ export interface AssigneeOption {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface SpecModelOption {
|
||||
spec_model: string;
|
||||
material_name: string;
|
||||
}
|
||||
|
||||
export interface DeviceOption {
|
||||
product_sn: string;
|
||||
external_serial: string | null;
|
||||
material_name: string;
|
||||
spec_model: string;
|
||||
}
|
||||
|
||||
export interface AnalyticsOptions {
|
||||
assignees: AssigneeOption[];
|
||||
spec_models: string[];
|
||||
spec_models: SpecModelOption[];
|
||||
devices: DeviceOption[];
|
||||
}
|
||||
|
||||
// ─── 查询参数 ──────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user