feat(backend): 新增 /dashboard/user-operations/detail 操作明细下钻接口
- 按人+操作类型(receive/transfer/record)查询明细:任务/产品/备注/时间 - record 明细与该人统计口径一致(名下任务手动备注,排除系统自动)
This commit is contained in:
@ -11,6 +11,7 @@ from app.services.dashboard_service import (
|
|||||||
get_completed_tasks, CompletedTask,
|
get_completed_tasks, CompletedTask,
|
||||||
get_rejected_tasks, RejectedTask,
|
get_rejected_tasks, RejectedTask,
|
||||||
get_user_operations, UserOperation,
|
get_user_operations, UserOperation,
|
||||||
|
get_user_operation_detail, OperationDetail,
|
||||||
get_people_workload, PersonWorkload,
|
get_people_workload, PersonWorkload,
|
||||||
get_people_history, PersonHistoryRecord,
|
get_people_history, PersonHistoryRecord,
|
||||||
search_product_messages, ProductMessageList,
|
search_product_messages, ProductMessageList,
|
||||||
@ -83,6 +84,22 @@ async def user_operations(
|
|||||||
return await get_user_operations(db, since=since_dt, until=until_dt)
|
return await get_user_operations(db, since=since_dt, until=until_dt)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/user-operations/detail", response_model=list[OperationDetail])
|
||||||
|
async def user_operations_detail(
|
||||||
|
user_id: str = Query(..., description="人员ID(username)"),
|
||||||
|
action_type: str = Query(..., description="操作类型: receive/transfer/record"),
|
||||||
|
since: str | None = Query(None, description="起始日期 ISO"),
|
||||||
|
until: str | None = Query(None, description="截止日期 ISO"),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""人员操作明细下钻 — 某人在指定时段的接收/转交/上传备注明细"""
|
||||||
|
since_dt = datetime.fromisoformat(since) if since else None
|
||||||
|
until_dt = datetime.fromisoformat(until) if until else None
|
||||||
|
return await get_user_operation_detail(
|
||||||
|
db, user_id, action_type, since=since_dt, until=until_dt,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/people-workload", response_model=list[PersonWorkload])
|
@router.get("/people-workload", response_model=list[PersonWorkload])
|
||||||
async def people_workload(
|
async def people_workload(
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
|
|||||||
@ -72,6 +72,14 @@ class UserOperation(BaseModel):
|
|||||||
total: int = 0 # 总操作次数
|
total: int = 0 # 总操作次数
|
||||||
|
|
||||||
|
|
||||||
|
class OperationDetail(BaseModel):
|
||||||
|
task_name: str # 任务/工序名
|
||||||
|
product_sn: str # 产品身份证
|
||||||
|
material_name: str # 设备名称
|
||||||
|
remark: str | None # 备注/说明
|
||||||
|
time: str # 操作时间 ISO(BEIJING_TZ)
|
||||||
|
|
||||||
|
|
||||||
class PersonDevice(BaseModel):
|
class PersonDevice(BaseModel):
|
||||||
product_id: str
|
product_id: str
|
||||||
serial_number: str # 16位HEX身份证
|
serial_number: str # 16位HEX身份证
|
||||||
@ -583,6 +591,90 @@ async def get_user_operations(
|
|||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 人员操作明细(点击数字下钻 — 接收/转交/上传备注)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
async def get_user_operation_detail(
|
||||||
|
db: AsyncSession,
|
||||||
|
user_id: str,
|
||||||
|
action_type: str,
|
||||||
|
since: datetime | None = None,
|
||||||
|
until: datetime | None = None,
|
||||||
|
) -> list[OperationDetail]:
|
||||||
|
"""查询某人在指定时段内的某类操作明细。
|
||||||
|
|
||||||
|
action_type:
|
||||||
|
- receive: 接收(task_logs.action_type='receive')
|
||||||
|
- transfer: 转交(task_logs.action_type='complete')
|
||||||
|
- record: 上传备注(该人名下任务的手动备注,排除系统自动生成的)
|
||||||
|
"""
|
||||||
|
from app.models.task_log import TaskLog
|
||||||
|
from app.models.task import Task, TaskRecord
|
||||||
|
from app.models.product import Product
|
||||||
|
from app.core.time_utils import BEIJING_TZ
|
||||||
|
|
||||||
|
def _to_iso(dt) -> str:
|
||||||
|
if not dt:
|
||||||
|
return ""
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
from datetime import timezone as dt_timezone
|
||||||
|
dt = dt.replace(tzinfo=dt_timezone.utc).astimezone(BEIJING_TZ)
|
||||||
|
else:
|
||||||
|
dt = dt.astimezone(BEIJING_TZ)
|
||||||
|
return dt.isoformat()
|
||||||
|
|
||||||
|
items: list[OperationDetail] = []
|
||||||
|
|
||||||
|
if action_type in ("receive", "transfer"):
|
||||||
|
act = "receive" if action_type == "receive" else "complete"
|
||||||
|
stmt = (
|
||||||
|
select(TaskLog.created_at, Task.task_name, Product.serial_number, Product.material_name, TaskLog.remark)
|
||||||
|
.join(Task, TaskLog.task_id == Task.id)
|
||||||
|
.join(Product, Task.product_id == Product.id)
|
||||||
|
.where(TaskLog.operator_id == user_id, TaskLog.action_type == act)
|
||||||
|
.order_by(TaskLog.created_at.desc())
|
||||||
|
)
|
||||||
|
if since:
|
||||||
|
stmt = stmt.where(TaskLog.created_at >= since)
|
||||||
|
if until:
|
||||||
|
stmt = stmt.where(TaskLog.created_at <= until)
|
||||||
|
rows = (await db.execute(stmt)).all()
|
||||||
|
for r in rows:
|
||||||
|
items.append(OperationDetail(
|
||||||
|
task_name=r[1] or "",
|
||||||
|
product_sn=r[2] or "",
|
||||||
|
material_name=r[3] or "",
|
||||||
|
remark=r[4] or "",
|
||||||
|
time=_to_iso(r[0]),
|
||||||
|
))
|
||||||
|
elif action_type == "record":
|
||||||
|
stmt = (
|
||||||
|
select(TaskRecord.created_at, Task.task_name, Product.serial_number, Product.material_name, TaskRecord.remark)
|
||||||
|
.join(Task, TaskRecord.task_id == Task.id)
|
||||||
|
.join(Product, Task.product_id == Product.id)
|
||||||
|
.where(
|
||||||
|
Task.assignee_id == user_id,
|
||||||
|
or_(TaskRecord.remark.is_(None), ~TaskRecord.remark.like("[%")),
|
||||||
|
)
|
||||||
|
.order_by(TaskRecord.created_at.desc())
|
||||||
|
)
|
||||||
|
if since:
|
||||||
|
stmt = stmt.where(TaskRecord.created_at >= since)
|
||||||
|
if until:
|
||||||
|
stmt = stmt.where(TaskRecord.created_at <= until)
|
||||||
|
rows = (await db.execute(stmt)).all()
|
||||||
|
for r in rows:
|
||||||
|
items.append(OperationDetail(
|
||||||
|
task_name=r[1] or "",
|
||||||
|
product_sn=r[2] or "",
|
||||||
|
material_name=r[3] or "",
|
||||||
|
remark=r[4] or "",
|
||||||
|
time=_to_iso(r[0]),
|
||||||
|
))
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 人员负载(按人聚合在制品设备 — 独立「人员看板」)
|
# 人员负载(按人聚合在制品设备 — 独立「人员看板」)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user