feat: 新增个人效能统计接口 /dashboard/my-stats(支持自选时段)
移动端「个人中心 → 工作统计」的数据源。现有接口拿不到这份数据: /dashboard/people-history 支持 assignee_id 但只返回 WIP/PENDING/COMPLETED, 不含 REJECTED;/dashboard/rejected-tasks 则根本没有 assignee_id 参数 —— 「今日被驳回数」按现有接口无论如何都过滤不到个人。故补一个薄桥接端点。 服务端 get_my_stats(db, assignee_id, since, until) 返回两组指标: 生产战绩(按 Task.assignee_id 归因) - tasks_completed / tasks_rejected:status 判定 + completed_at 落在区间, 与 get_dashboard_stats 的 t_done_q 同一口径,只多了 assignee_id 过滤 - products_touched:按 product_id 去重,同一台设备做多道工序只算一台 操作统计(与 PC /dashboard/user-operations 严格同口径) - receive/transfer:task_logs 的 receive / complete,按 operator_id 归因 - record:task_records 按 Task.assignee_id 归因,排除 '[' 开头的系统自动备注 - 前两项归因 operator_id、第三项归因 assignee_id 是 PC 端既有口径, 此处刻意保持一致,便于工人自查的数与主管看到的面板对得上 端点侧新增 _parse_bound():裸时间字符串(无时区偏移)按北京时间解释, 否则会被当作服务器本地时间,边界整体偏 8 小时,出现「选了今日却统计到 昨天下午」。since/until 缺省为「本月 1 日 ~ 此刻」。
This commit is contained in:
@ -5,8 +5,10 @@ from fastapi import APIRouter, Depends, Query
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.database import get_db
|
||||
from app.core.time_utils import BEIJING_TZ
|
||||
from app.services.dashboard_service import (
|
||||
get_dashboard_stats, DashboardStats,
|
||||
get_my_stats, MyStats,
|
||||
get_wip_tasks, WipTask,
|
||||
get_completed_tasks, CompletedTask,
|
||||
get_rejected_tasks, RejectedTask,
|
||||
@ -22,6 +24,18 @@ from app.services.dashboard_service import (
|
||||
router = APIRouter(prefix="/dashboard", tags=["管理看板"])
|
||||
|
||||
|
||||
def _parse_bound(value: str | None) -> datetime | None:
|
||||
"""解析 ISO 时间边界。
|
||||
|
||||
裸时间(无时区偏移)按**北京时间**解释 —— 否则会被当作服务器本地时间,
|
||||
在边界上整体偏移 8 小时,出现「选了今日却统计到昨天下午」这类错位。
|
||||
"""
|
||||
if not value:
|
||||
return None
|
||||
dt = datetime.fromisoformat(value)
|
||||
return dt.replace(tzinfo=BEIJING_TZ) if dt.tzinfo is None else dt
|
||||
|
||||
|
||||
@router.get("/stats", response_model=DashboardStats)
|
||||
async def dashboard_stats(
|
||||
since: str | None = Query(None, description="起始日期 ISO 如 2026-08-01T00:00:00"),
|
||||
@ -39,6 +53,26 @@ async def dashboard_stats(
|
||||
return await get_dashboard_stats(db, since=since_dt, until=until_dt)
|
||||
|
||||
|
||||
@router.get("/my-stats", response_model=MyStats)
|
||||
async def my_stats(
|
||||
assignee_id: str = Query(..., description="负责人ID(移动端传当前登录用户 username)"),
|
||||
since: str | None = Query(None, description="起始时间 ISO(含时区偏移,如 2026-09-01T00:00:00+08:00);缺省=本月 1 日"),
|
||||
until: str | None = Query(None, description="截止时间 ISO(含时区偏移);缺省=此刻"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
一线工人个人效能 — 移动端「工作统计」页,支持自选时段。
|
||||
|
||||
- 生产战绩(完成 / 被驳回 / 参与产品)按本人名下任务归因;
|
||||
- 操作统计(接收 / 转交 / 上传备注)与 PC /dashboard/user-operations 严格同口径,
|
||||
工人自查的数与主管看到的面板对得上。
|
||||
|
||||
⚠️ since/until 必须带时区偏移。移动端发的是北京时间 (+08:00),
|
||||
不带偏移的裸字符串会被当成"本地时间"导致边界偏移 8 小时。
|
||||
"""
|
||||
return await get_my_stats(db, assignee_id, since=_parse_bound(since), until=_parse_bound(until))
|
||||
|
||||
|
||||
@router.get("/wip-tasks", response_model=list[WipTask])
|
||||
async def wip_tasks(
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
|
||||
Reference in New Issue
Block a user