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:
@ -63,6 +63,15 @@ class RejectedTask(BaseModel):
|
||||
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):
|
||||
product_id: str
|
||||
serial_number: str # 16位HEX身份证
|
||||
@ -465,6 +474,68 @@ async def get_rejected_tasks(
|
||||
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)
|
||||
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 (
|
||||
current_user
|
||||
|
||||
Reference in New Issue
Block a user