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
|
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):
|
class AnalyticsOptions(BaseModel):
|
||||||
assignees: list[AssigneeOption]
|
assignees: list[AssigneeOption]
|
||||||
spec_models: list[str]
|
spec_models: list[SpecModelOption]
|
||||||
|
devices: list[DeviceOption]
|
||||||
|
|
||||||
|
|
||||||
class DeviceRecord(BaseModel):
|
class DeviceRecord(BaseModel):
|
||||||
@ -478,11 +491,12 @@ async def get_analytics_options(
|
|||||||
) -> AnalyticsOptions:
|
) -> AnalyticsOptions:
|
||||||
"""返回筛选栏选项,支持动态联动:
|
"""返回筛选栏选项,支持动态联动:
|
||||||
- 传入 spec_models:只返回碰过这些型号的人;
|
- 传入 spec_models:只返回碰过这些型号的人;
|
||||||
- 传入 assignee_ids:只返回这些人处理过的型号。"""
|
- 传入 assignee_ids:只返回这些人处理过的型号;
|
||||||
|
- devices:根据筛选条件返回关联设备(无筛选则返回最近流转的设备)。"""
|
||||||
from app.models.task import Task
|
from app.models.task import Task
|
||||||
from app.models.product import Product
|
from app.models.product import Product
|
||||||
|
|
||||||
# 负责人:从 tasks 去重(可选按 spec_models 精确过滤)
|
# ── 负责人:从 tasks 去重(可选按 spec_models 过滤) ──
|
||||||
if spec_models:
|
if spec_models:
|
||||||
assignee_stmt = (
|
assignee_stmt = (
|
||||||
select(Task.assignee_id)
|
select(Task.assignee_id)
|
||||||
@ -512,23 +526,78 @@ async def get_analytics_options(
|
|||||||
for aid in distinct_assignees
|
for aid in distinct_assignees
|
||||||
]
|
]
|
||||||
|
|
||||||
# 规格型号:从 products 去重(可选按 assignee_ids 过滤)
|
# ── 规格型号 + 物料名(可选按 assignee_ids 过滤;同型号取首个非空物料名) ──
|
||||||
if assignee_ids:
|
if assignee_ids:
|
||||||
spec_stmt = (
|
spec_stmt = (
|
||||||
select(Product.spec_model)
|
select(Product.spec_model, Product.material_name)
|
||||||
.join(Task, Task.product_id == Product.id)
|
.join(Task, Task.product_id == Product.id)
|
||||||
.where(
|
.where(
|
||||||
Product.spec_model.isnot(None),
|
Product.spec_model.isnot(None),
|
||||||
func.trim(Product.spec_model) != "",
|
func.trim(Product.spec_model) != "",
|
||||||
Task.assignee_id.in_(assignee_ids),
|
Task.assignee_id.in_(assignee_ids),
|
||||||
)
|
)
|
||||||
|
.distinct()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
spec_stmt = (
|
spec_stmt = (
|
||||||
select(Product.spec_model)
|
select(Product.spec_model, Product.material_name)
|
||||||
.where(Product.spec_model.isnot(None), func.trim(Product.spec_model) != "")
|
.where(Product.spec_model.isnot(None), func.trim(Product.spec_model) != "")
|
||||||
|
.distinct()
|
||||||
)
|
)
|
||||||
spec_rows = (await db.execute(spec_stmt.distinct())).all()
|
spec_rows = (await db.execute(spec_stmt)).all()
|
||||||
spec_models = sorted({r[0] for r in spec_rows if r[0]})
|
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;
|
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 {
|
export interface AnalyticsOptions {
|
||||||
assignees: AssigneeOption[];
|
assignees: AssigneeOption[];
|
||||||
spec_models: string[];
|
spec_models: SpecModelOption[];
|
||||||
|
devices: DeviceOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── 查询参数 ──────────────────────────────────────────────
|
// ─── 查询参数 ──────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user