feat(backend): 新增 /dashboard/user-operations 人员操作统计接口
- add_task_record 补 action_type=record 日志,使上传备注可统计到人 - get_user_operations 按人聚合 接收(receive)/转交(complete)/上传备注(record) 次数,时间筛选,中文名映射 - 复用 get_display_names,不新增数据库字段
This commit is contained in:
@ -10,6 +10,7 @@ from app.services.dashboard_service import (
|
|||||||
get_wip_tasks, WipTask,
|
get_wip_tasks, WipTask,
|
||||||
get_completed_tasks, CompletedTask,
|
get_completed_tasks, CompletedTask,
|
||||||
get_rejected_tasks, RejectedTask,
|
get_rejected_tasks, RejectedTask,
|
||||||
|
get_user_operations, UserOperation,
|
||||||
get_people_workload, PersonWorkload,
|
get_people_workload, PersonWorkload,
|
||||||
get_people_history, PersonHistoryRecord,
|
get_people_history, PersonHistoryRecord,
|
||||||
search_product_messages, ProductMessageList,
|
search_product_messages, ProductMessageList,
|
||||||
@ -70,6 +71,18 @@ async def rejected_tasks(
|
|||||||
return await get_rejected_tasks(db, since=since_dt, until=until_dt, limit=limit)
|
return await get_rejected_tasks(db, since=since_dt, until=until_dt, limit=limit)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/user-operations", response_model=list[UserOperation])
|
||||||
|
async def user_operations(
|
||||||
|
since: str | None = Query(None, description="起始日期 ISO 如 2026-08-01T00:00:00"),
|
||||||
|
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_operations(db, 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),
|
||||||
|
|||||||
@ -63,6 +63,15 @@ class RejectedTask(BaseModel):
|
|||||||
rejected_at: str | None # 驳回时间 ISO(BEIJING_TZ)
|
rejected_at: str | None # 驳回时间 ISO(BEIJING_TZ)
|
||||||
|
|
||||||
|
|
||||||
|
class UserOperation(BaseModel):
|
||||||
|
user_id: str # 登录名 username
|
||||||
|
user_name: str # 中文姓名
|
||||||
|
receive_count: int = 0 # 接收次数
|
||||||
|
transfer_count: int = 0 # 转交次数(action=complete)
|
||||||
|
record_count: int = 0 # 上传备注次数(action=record)
|
||||||
|
total: int = 0 # 总操作次数
|
||||||
|
|
||||||
|
|
||||||
class PersonDevice(BaseModel):
|
class PersonDevice(BaseModel):
|
||||||
product_id: str
|
product_id: str
|
||||||
serial_number: str # 16位HEX身份证
|
serial_number: str # 16位HEX身份证
|
||||||
@ -465,6 +474,68 @@ async def get_rejected_tasks(
|
|||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 人员操作统计(接收/转交/上传备注 — 按人聚合,时间可筛选)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
async def get_user_operations(
|
||||||
|
db: AsyncSession,
|
||||||
|
since: datetime | None = None,
|
||||||
|
until: datetime | None = None,
|
||||||
|
) -> list[UserOperation]:
|
||||||
|
"""上帝视角 — 统计每个人员的操作次数(按时段过滤 task_logs)。
|
||||||
|
|
||||||
|
操作口径:
|
||||||
|
- 接收: action_type='receive'
|
||||||
|
- 转交: action_type='complete'(完工并移交下一道工序)
|
||||||
|
- 上传备注: action_type='record'(工人手动追加的进度记录)
|
||||||
|
"""
|
||||||
|
from app.models.task_log import TaskLog
|
||||||
|
|
||||||
|
rcv = func.count().filter(TaskLog.action_type == "receive")
|
||||||
|
cpl = func.count().filter(TaskLog.action_type == "complete")
|
||||||
|
rcd = func.count().filter(TaskLog.action_type == "record")
|
||||||
|
total_expr = rcv + cpl + rcd
|
||||||
|
|
||||||
|
stmt = (
|
||||||
|
select(TaskLog.operator_id, rcv.label("receive"), cpl.label("complete"), rcd.label("record"))
|
||||||
|
.where(
|
||||||
|
TaskLog.action_type.in_(["receive", "complete", "record"]),
|
||||||
|
TaskLog.operator_id.isnot(None),
|
||||||
|
)
|
||||||
|
.group_by(TaskLog.operator_id)
|
||||||
|
.order_by(total_expr.desc())
|
||||||
|
)
|
||||||
|
if since:
|
||||||
|
stmt = stmt.where(TaskLog.created_at >= since)
|
||||||
|
if until:
|
||||||
|
stmt = stmt.where(TaskLog.created_at <= until)
|
||||||
|
|
||||||
|
result = await db.execute(stmt)
|
||||||
|
rows = result.all()
|
||||||
|
|
||||||
|
ids = [r[0] for r in rows if r[0]]
|
||||||
|
name_map: dict[str, str] = {}
|
||||||
|
if ids:
|
||||||
|
from app.services.mom_cache import get_display_names
|
||||||
|
name_map = get_display_names(ids)
|
||||||
|
|
||||||
|
items: list[UserOperation] = []
|
||||||
|
for row in rows:
|
||||||
|
receive = row[1] or 0
|
||||||
|
transfer = row[2] or 0
|
||||||
|
record = row[3] or 0
|
||||||
|
items.append(UserOperation(
|
||||||
|
user_id=row[0],
|
||||||
|
user_name=name_map.get(row[0], row[0]),
|
||||||
|
receive_count=receive,
|
||||||
|
transfer_count=transfer,
|
||||||
|
record_count=record,
|
||||||
|
total=receive + transfer + record,
|
||||||
|
))
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 人员负载(按人聚合在制品设备 — 独立「人员看板」)
|
# 人员负载(按人聚合在制品设备 — 独立「人员看板」)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@ -1049,6 +1049,16 @@ async def add_task_record(
|
|||||||
db.add(record)
|
db.add(record)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
|
|
||||||
|
# 🔧 操作日志:记录一次「上传备注」操作(供人员操作统计)
|
||||||
|
if current_user:
|
||||||
|
from app.models.task_log import TaskLog
|
||||||
|
db.add(TaskLog(
|
||||||
|
task_id=task_id,
|
||||||
|
operator_id=current_user.get("username") or current_user.get("sub") or "",
|
||||||
|
action_type="record",
|
||||||
|
remark=f"上传备注: {(data.remark or '')[:100]}",
|
||||||
|
))
|
||||||
|
|
||||||
# 🚀 留言通知:给任务当前负责人发送提醒(不给自己发)
|
# 🚀 留言通知:给任务当前负责人发送提醒(不给自己发)
|
||||||
if (
|
if (
|
||||||
current_user
|
current_user
|
||||||
|
|||||||
Reference in New Issue
Block a user