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_rejected_tasks, RejectedTask,
|
||||
get_user_operations, UserOperation,
|
||||
get_user_operation_detail, OperationDetail,
|
||||
get_people_workload, PersonWorkload,
|
||||
get_people_history, PersonHistoryRecord,
|
||||
search_product_messages, ProductMessageList,
|
||||
@ -83,6 +84,22 @@ async def user_operations(
|
||||
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])
|
||||
async def people_workload(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
|
||||
@ -72,6 +72,14 @@ class UserOperation(BaseModel):
|
||||
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):
|
||||
product_id: str
|
||||
serial_number: str # 16位HEX身份证
|
||||
@ -583,6 +591,90 @@ async def get_user_operations(
|
||||
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