Files
track-LICA/backend/app/api/v1/endpoints/screen.py
duxingchen 394e1e39c3 feat(分组权限): 统计接口接入数据范围(dashboard / analytics / screen 共 20 个路由)
这三组接口此前是「上帝视角」且**匿名可访问**,现在全部挂 get_data_scope ——
既要求登录、又按业务分组范围过滤。顺带堵上了 AGENTS.md 点名的风险:
/dashboard/people-history/export 此前匿名即可批量导出全员工时台账。

dashboard(11 个函数 / 24 处注入)
· Product 主体 → product_where();Task、TaskLog 主体 → 先 join(Product) 再 task_where()
· 「卡片数字」与「下钻明细」成对出现的地方用同一谓词,避免「按钮显示 2、点开却是 0 条」
· get_user_operations 的 func.count() 改为 func.count(TaskLog.id) —— 显式化,不依赖
  join 形状(当前是 many-to-one 不会放大,但这样写更稳)
· unread_notif **刻意不过滤**:Notification.task_id 可空,按 Product 过滤会漏掉
  无任务关联的提醒(就地注释说明)
· get_my_stats 本轮不动 —— 它按本人归因,语义上不受分组影响

analytics(4 个函数 / 9 条语句)
· get_analytics_options 的 4 条独立语句全部处理 —— 它是筛选栏下拉的选项源,
  不过滤的话维修组能在下拉里看到生产组的人(最易漏的一处)
· get_device_records 的 product_id 查号是安全闸:范围外 SN 查不出 → 直接返回 []

screen(3 个函数)
· month_production **不做特判** —— 维修组的「本月生产数」本来就该是 0
· get_wip_distribution 按范围裁剪工序柱子,但坚持「恒 0 才裁、有数必现」,
  保证 total == sum(items) 在任何 scope 下都成立

实测(17 个端点):超管全部 200、匿名全部 401。
造 1 生产 + 1 售后产品后:
  超管   products=2 / wip-matrix 2 行 / options 2 个型号
  生产组 products=1 / wip-matrix 1 行 / options 1 个型号
  维修组 products=1 / wip-matrix 1 行 / options 1 个型号
列表与统计口径一致;未分组用户在过渡期开关下仍走 ungrouped_fallback。
测试数据已还原。
2026-09-21 17:22:01 +08:00

66 lines
2.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""大屏 API — 面向管理层**日常运营与督导**的轻量聚合接口
视角:当月吞吐 / 当前卡点 / 系统活跃度。
与 /dashboard 的区别:/dashboard 面向 PC 后台明细下钻(返回大列表),
/screen 只返回图表直接可用的扁平聚合数据,字段少、无分页、供高频轮询。
⚠️ 2026-09 起不再是「上帝视角」:三个端点都挂了 get_data_scope
结果按当前用户的业务分组范围过滤(超管不受限),且不再允许匿名访问。
"""
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.deps import get_data_scope
from app.services.data_scope_service import DataScope
from app.services.screen_service import (
get_monthly_metrics, MonthlyMetrics,
get_wip_distribution, WipDistributionResponse,
get_active_users, ActiveUsersResponse,
)
router = APIRouter(prefix="/screen", tags=["大屏统计"])
@router.get("/monthly-metrics", response_model=MonthlyMetrics)
async def monthly_metrics(
db: AsyncSession = Depends(get_db),
scope: DataScope = Depends(get_data_scope),
):
"""
当月吞吐 — 大屏顶部四张数字卡(按当前用户的数据范围)。
返回:本月生产流转 / 本月已入库 / 本月已出库 / 本月返厂回流。
统计区间为北京时间当月 1 日 00:00 至此刻。
"""
return await get_monthly_metrics(db, scope)
@router.get("/wip-distribution", response_model=WipDistributionResponse)
async def wip_distribution(
db: AsyncSession = Depends(get_db),
scope: DataScope = Depends(get_data_scope),
):
"""
工序积压分布 — 当前数据范围内未完结设备按 overall_status 聚合的**纯数量**。
返回固定阶段列表(含 0 值),保证柱状图类目稳定、不因缺数据而塌陷;
工序柱本身也按数据范围裁剪(不属于本组阶段的工序不画)。
"""
return await get_wip_distribution(db, scope)
@router.get("/active-users", response_model=ActiveUsersResponse)
async def active_users(
top_n: int = Query(5, ge=1, le=20, description="返回的活跃人员数量"),
db: AsyncSession = Depends(get_db),
scope: DataScope = Depends(get_data_scope),
):
"""
本月系统使用活跃度排行 — 接收 / 转交 / 上传备注次数(按当前用户的数据范围)。
桥接 /dashboard/user-operations 的统计口径,仅返回本月确实有操作的人员。
"""
return await get_active_users(db, scope, top_n=top_n)