Commit Graph

62 Commits

Author SHA1 Message Date
817183062d feat(webhook): MOM 回调的部门归属分流 —— 只放行 IRIS 与空白值
MOM 现在同时对接 IRIS 与 LICA 两个 Track 实例,按载荷里的 company_name 分流。

实现:
  · MomInboundPayload / MomOutboundPayload 补 company_name 字段
    (不补的话会被 Pydantic 静默丢弃,分流无从谈起)
  · 新增 _is_foreign_company(),在**鉴权之后、匹配产品之前**拦截
  · 拦截时返回 200 + matched=False + reason="ignored_company" —— 与「未命中」
    保持同一契约,避免 MOM 侧把它当成故障反复重推

⚠️ 判定刻意做成「只排除已知的外来公司」(黑名单),而非「白名单只认 IRIS」:
   MOM 在无法确定公司归属时会回落到扁平配置,该配置指向本实例 —— 这类消息的
   company_name 会是空 / 缺失。若按白名单把空白也拒掉,它们就彻底丢了:MOM
   那边已收到 200、认为投递成功,不会再重推。同理,未见过的新值也一律照常处理。

reason 的取值 "ignored_company" 与 LICA 实例(~/track-lica)保持一致 —— MOM 侧
不解析它,但排查时两边日志要对着看,字段名不一致会白白浪费时间。

实测:
  · LICA 载荷      → {"ok":true,"matched":false,"reason":"ignored_company"}
  · IRIS/空串/缺失 → 照常处理
  · 无 X-API-Key 仍返回 401(鉴权未被绕过)
2026-09-22 13:43:52 +08:00
42f6e242b4 fix(qrcode): 二维码接口去鉴权,并把 qrcode 路径排除出审计
前端以 <img src="/api/v1/products/qrcode/{sn}"> 引用该端点,而 <img> 无法携带
Authorization 头 —— 加了鉴权会让所有二维码图片加载失败(页面显示成破图),
并在审计里刷出大量 401。

去鉴权是安全的:本函数不查数据库,只校验长度并把这个字符串渲染成二维码,
没有任何业务数据泄露面(序列号本身就是调用方提供的)。也刻意不支持 ?token=
兜底 —— 把 JWT 放进 URL 会渗进访问日志、浏览器历史与 Referer,比它想解决的
问题更糟。

随之而来的副作用必须一并处理:qrcode 路径命中 _TRACKED_READ_PREFIXES 里的
/api/v1/products 前缀、又不是 bare list,会被判成「查看详情」逐条留痕。列表页
一次渲染就并发拉几十张图,逐条留痕会把审计日志塞满,真正有价值的操作反而被
淹没。故把 /api/v1/products/qrcode 加进 _IGNORED_PREFIXES。

实测:二维码正常加载;审计中不再出现 qrcode 记录。
2026-09-22 13:43:36 +08:00
3f34652b07 fix(security): 核心读接口补齐鉴权(P0 — 无 token 可直接拉取业务数据)
现象:不带任何 Token 请求 /api/v1/products/scan/{sn} 等接口直接 200,
业务数据(产品、任务、订单)可被匿名读取。

同时这也是审计「操作人恒为未认证」的根因:
这些路由只挂了 Depends(get_db),JWT 依赖不执行 →
request.state.audit_user 从未写入 → 中间件只能记成「未认证」。

修复:为 3 个文件共 9 条 GET 路由统一补上 Depends(get_current_user)

  products.py  GET /qrcode/{serial_number}
               GET /scan/{serial_number}      ← 匿名可读产品数据
               GET /                          ← 列表
               GET /{product_id}
               GET /{product_id}/messages
  tasks.py     GET /
               GET /{task_id}
               GET /by-product/{product_id}
  orders.py    GET /

未改动:notifications.py 本就有鉴权;records.py 无 GET 路由。
三个文件原本已 import get_current_user,未新增导入。

落地前已排查「是否有意免登」:
- /products/qrcode/{sn} 是 PC 打印标签用,非免登场景
- 外部系统调用走独立通道 /external/products/lookup(X-API-Key 鉴权),
  与内部 /products/* 完全分离
故内部读接口本就应要求登录。

⚠️ 连带影响:二维码标签编码的是前端页面地址(/sn/{序列号}),
   此前任何人用手机相机扫码即可查看产品状态,现在会要求登录。
   若业务需要免登查询,应走带 API-Key 的 /external/products/lookup,
   而不是让内部接口裸奔。
2026-09-21 13:47:37 +08:00
c3667fe00d fix(audit): 刷新令牌记录不再显示「未认证」
问题:审计列表里 POST /auth/refresh 的操作人恒为「未认证」。
根因:本接口刻意不挂 get_current_user —— 能用到这里,正是因为 access token
已过期、请求里没有 Authorization 头,JWT 依赖不执行,request.state 里
从未写入操作人。而"谁在何时尝试刷新"恰恰是该留痕的信息。

修复:
- core/security.py 新增 peek_token_identity(),与 decode_token 的唯一区别是
  关闭过期校验(刷新场景令牌本就过期,若因过期解不出来还是会漏记)。
  签名校验照常进行,伪造令牌解不出任何东西。
  ⚠️ docstring 中明确:该函数只许用于写审计字段,鉴权一律走 get_current_user
- refresh 接口解码 refresh token 取得 sub/username/display_name/role 写入 state。
  refresh token 的载荷与 access token 完全一致,只有 type 字段不同。

附带效果:活动打点读的正是 request.state.audit_user,修复后刷新请求也会
被计为一次活动 —— 语义正确(会刷新说明用户正在使用)。

验证(7/7):正常刷新记到中文人名与角色;过期令牌虽被拒 401 但仍能记到人;
伪造令牌不认人、显示未认证。历史记录不追溯。
2026-09-21 13:05:41 +08:00
1fea30b03b feat(audit): 日活使用统计 + 双 CSV 导出(审计/上下线,列可自定义)
【日活统计:GET /audit/daily-usage】
按【北京时间自然日 × 操作人】聚合,单个 GROUP BY 完成(count(*) FILTER),
不用窗口函数。指标:上线/下线时间、操作次数、登录/登出次数。

⚠️ 上线/下线时间取【当天首次/末次活动】,刻意不取登录时间:
   Refresh Token 有效期 7 天,用户不必每天重新登录。按登录算会出现
   「登录次数 0、上线时间空,但操作次数 35」——报表自相矛盾。
   时间一律按 +08:00 分日与渲染,否则早班(00:00~08:00)操作会掉到前一天。

【CSV 导出:两个端点 + 列自定义】
- /audit/logs/export        整体审计导出,筛选维度与列表页完全一致
- /audit/daily-usage/export 上下线导出,每人一行
- 列清单由后端统一维护并经 /audit/options 下发(log_export_columns /
  usage_export_columns),前端不硬编码表头,避免两端漂移
- ⚠️ 响应带 UTF-8 BOM:Excel 靠它识别编码,否则中文表头全乱码
- 单次上限 5 万行,超出经 X-Export-Truncated 头告知前端明确提示
  (静默截断比报错更危险)
- list_audit_logs 与 export_audit_logs 共用 _log_filters,
  保证「看到的」与「导出的」永远是同一批数据

【前端】
- 审计页页头新增「人员统计」「导出 CSV」两个按钮,现有表格与筛选零改动
- 人员统计走抽屉(AuditUsagePanel):日期范围+快捷键、日活表格、
  上下线次数彩色标签、北京时间渲染、导出前弹列勾选面板
- ExportColumnsModal 为两处导出共用,默认全选
2026-09-21 12:06:43 +08:00
5290d83463 feat: MOM 撤回出库强制回滚通道
MOM 把误点出库的设备物理回滚到仓库时,Track 被动跟随 MOM 的权威物理状态。

【撤回信号识别】
- action / event 里的 revoke / rollback / revert / cancel 子串匹配
  (MOM 侧字段命名尚未冻结,刻意宽松,避免对方改词就整条链路失联)
- 无显式标记但产品正处于「已出库」时,按"已发货设备收到入库回调 = 货回来了"
  隐式判定

【匹配放宽】
- 常规入库仍严格要求 current_location_id == virtual_warehouse
- 撤回信号、或 payload 带 serial 时才放行「已出库」产品 —— 出库回调已把
  location 置为 None,不放宽则撤回必然失配、静默返回 matched=False
- 刻意不给 sku 兜底也无条件放宽:同型号可能多台,放宽会误标到别的设备

【特权通道】
- 强制覆写 overall_status=已入库 + 位置回滚 virtual_warehouse,优先级高于
  task_service 的【绝对物理终态保护】。两者方向刻意相反:那套约束的是
  "车间内部流转不许用工序名抹掉物理终态",而本接口是物理事实的权威来源。
  代码内已留醒目注释,防止后续维护者误加状态互斥校验
- 改用 sync_product_status 统一双字段同步(原先硬编码 status="ARCHIVED"
  绕过了 lifecycle.py 的约定),并把 status 变化一并计入 changed,
  避免"状态与位置本就正确时纠偏不提交"

【撤回留痕】
- 追加「撤回出库(重新入库)」主线节点。名称里的「入库」二字是必须保留的契约:
  product_service._has_warehouse_task 用子串判定仓库节点,若只有"出库"会让
  location==virtual_warehouse 的产品被注入假的「待仓库收货」虚拟节点,
  出现"已入库却在等收货"的自相矛盾

【重构】
- 抽出 _pick_warehouse_log_task / _match_inbound_product 复用,出库回调同步简化
2026-09-21 11:21:03 +08:00
17b2fab5ca fix(audit): 补齐「退出」留痕 + 登录记录显示中文操作人
【问题①:退出动作在审计里完全不可见】
根因:后端没有 logout 接口。前端「退出」只清本地 localStorage、不产生任何
请求,中间件自然无从采集(中间件里早已预留 "logout" 动作映射,但端点没做)。

- 新增 POST /auth/logout,【仅用于留痕】。JWT 无状态,服务端本就没有可吊销
  的会话,该接口不做任何令牌失效动作 —— 它存在的唯一目的,是让审计记下
  「谁在何时退出了系统」。这点在 docstring 里写明,免得日后被误当安全边界。
- 挂 Depends(get_current_user),让 JWT 依赖把操作人写进 request.state,
  从而记录到真实姓名而非「未认证」
- 前端 logout 改为【先上报、后清 token】。顺序不能反:api.ts 的请求拦截器
  是从 localStorage 取 token 的,清掉后就发不出这个请求了。
  刻意不 await、失败也不阻断 —— 用户点「退出」必须退得掉。

【问题②:登录记录的「操作人」显示英文账号而非中文名】
根因:登录接口只写了 request.state.audit_user(账号),没写 audit_display_name
—— 那一刻还不知道显示名。前端按 display_name || user_id 渲染,于是退化成账号。

- 改为登录成功后补写 display_name / role。可行的原因是中间件在 call_next
  返回【之后】才落库,此刻写 request.state 依然能被采集到。
- 失败登录走不到这一步,保持「只有账号可追责」(密码绝不落库),语义不变。
2026-09-21 11:20:59 +08:00
7635802a42 feat(audit): 新增操作审计日志(表/中间件/查询接口)+ 角色常量收敛
背景:系统此前没有操作审计。task_logs 的 task_id 是 NOT NULL 外键,只能挂在
任务上,且全项目仅 4 处写入点 —— 登录、导出、产品增删改、收编完全不留痕。
需求方整理的问题清单里「无审计日志查看页」正源于此:不是没有页面,是没数据。

设计参考 MOM(KCGL) 的 audit_logs / audit_listener,但按 Track 栈做了取舍:

1) 写入时机:MOM 用 SQLAlchemy event listener + 同事务写入,优点是零侵入,
   缺点是**业务回滚时审计一起消失**,而失败/被拒的操作(越权尝试、参数错误)
   恰恰最需要留痕。Track 改为响应生成后用**独立 session** 写入:
   - 业务回滚不影响审计(已验证 422/401 失败操作同样落库)
   - 审计写入失败也不影响业务(全包裹 try/except)
   - 代价:非原子提交,响应后进程立即被 kill 可能丢一条(已注释说明取舍)

2) 采集方式:中间件自动采集写操作 + 导出/下载/打印这类「读但敏感」的 GET。
   路径段推导 module/action/target_id。不做手写埋点,因为手写必然漏 ——
   task_logs 只有 4 处写入点就是前车之鉴。

3) 增量价值:新增 request_id 字段,与 core/logging.py 的结构化日志打通,
   凭一个 ID 就能从审计记录直接跳到那一次接口日志。MOM 无此字段。

4) 敏感信息:details 经 sanitize_details 递归剔除 password/token/secret 等键;
   中间件不读请求体,登录明文密码不会落库(已断言表内无密码痕迹)。

配套改动:
- core/roles.py:角色常量与 is_admin 收敛为单一事实来源。此前同一份
  「管理员角色」规则散在 task_service、products.py 内联判断和前端
  constants/task.ts 三处,已因此发生过「移动端漏判 SUPERVISOR 误挡主管」。
  task_service 改为从 core.roles 导入同名常量,保持既有引用可用。
- core/deps.py:抽出 require_roles/require_admin 可复用依赖,替代内联判断。
- main.py:500 响应显式补 X-Request-ID 头 —— 该响应由 ServerErrorMiddleware
  生成,位于 RequestContextMiddleware 外层,中间件没机会写头。
- auth.py:登录校验前把「尝试的账号」写入 request.state,使登录事件
  (含失败登录)可归属到人,可用于追踪暴力破解。

验证:本地起 PostgreSQL 17 + 迁移后跑端到端测试,32/32 通过
(TestClient 每个请求新建事件循环,与模块级 asyncpg 连接池冲突会报
 "got Future attached to a different loop",故改用 httpx.AsyncClient +
 ASGITransport 单循环;生产 uvicorn 单循环无此问题)。
2026-09-21 02:23:19 +00:00
d666bbd4ed feat: 直接完结通道与报表口径收口(后端)
【直接完结:保留动作,剥离状态】
- transfer_task 权限硬拦截:仅 SUPER_ADMIN / SUPERVISOR。判据取签名保护的
  operator_role,刻意不用客户端可通过 ?operator_id= 伪造的 operator_id
- 直接完结【不再改写】overall_status —— 它只是任务闭环动作,不改变物理状态。
  已出库设备完结后依然是「已出库」,回归 WIP 矩阵的已出库列
- 物理终态保护:create_task / receive_task / transfer_task 三处写入点统一加
  _is_physical_terminal 守卫,禁止用任务名覆写 已入库/在库/已出库。
  历史缺陷:「发货测试」会把设备的「已出库」标识静默抹掉,跨报表口径随之打架

【位置与工序口径】
- _recalc_product_location:已出库且闲置 → 位置清空(货发走就离场)。
  只清「已出库」;已入库/在库的设备确实还在仓库里,位置必须保留
- MOM 出库回调同步清空 current_location_id
- 新增 ProductResponse.current_step:只认活跃主干任务,无活跃任务时返回
  宏观终态或空。不再让已 COMPLETED 的历史工序(扫码出库/测试…)冒充"当前工序"

【售后烙印去死锁】
- 拆分 OUTBOUND_QC_STEPS(发货测试) / AFTER_SALES_REPAIR_STEPS(售后维修)
- resolve_phase_for_step 与 _mark_after_sales_if_reactivated 双双改为仅
  「售后维修」触发 AFTER_SALES,解除「已出库 + 发货测试」被永久烙印的死锁
- PRODUCTION_OVERALL_STEPS 补入「发货测试」,使出厂质检在生产阶段合法
  (否则 _enforce_step_isolation 会把已出库设备的发货测试直接 400)

【游魂收口】
- get_wip_matrix / get_wip_matrix_detail 的 is_terminal 增加"名下再无任何活跃
  任务则视同终结态",让被直接完结的生产设备接受时间筛选,不再恒挂在看板上
  冒充在制。两处必须一字不差同步,否则会出现"矩阵有数、下钻为空"
2026-09-17 17:05:42 +08:00
dcc92b223e feat: 生产看板多维度重构(全量在制品、时间维度联动、口径提示)
本次只含后端看板接口与 PC 端页面。移动端的体验优化已在之前的 12 个提交中
入库,不在本提交范围内。

后端 · 服务层 dashboard_service.py
- 在制品列表不再按 20 条静默截断:此前 `.limit(limit*2)` 预取再排序切片,
  前端又拿数组长度当总数,表现为「卡片 23、列表却写共 20 个」的数据被吞。
  现默认返回全量,limit 降级为防御性上限
- 产品流转「已完结」接入右上角时间筛选(since/until),废弃硬编码「本月」。
  此前选「今天」也会看到整月产出,业务语义分裂;待流转/流转中仍为实时快照。
  Product 表无 completed_at,以名下工序的完成时间锚定「完结时刻」
- 返工任务可见性:未完结卡点永远展示,已了结的仅限本月,且与卡片数字共用
  同一份条件,避免「按钮显示 2、点开共 0 条」
- 驳回明细的「返工给」改为真实溯源:直接查派生出的返工任务当前 assignee_id,
  不再复刻 reject_task 的推算逻辑(返工任务事后被转交时会显示旧名字)

后端 · 接口层 dashboard.py
- /wip-tasks 的 limit 默认 20→500、上限 100→1000
- /my-stats 支持 since/until,新增接收/转交/上传备注口径(与 PC 人员操作统计
  逐字段交叉验证一致)

PC 端
- AdminDashboard: 在制品计数绑定真实总数并加截断提示、列表改为容器内滚动;
  驳回明细只展示「已驳回」行(返工行不再独立成行,其信息已由「返工给」承载);
  「待流转」「待接收」图例新增口径 Tooltip,消除「产品数 > 任务数」的误解;
  移除已失真的时间筛选提示条
- App.tsx: dayjs 中文 locale 上提到应用根。此前散落在 AdminPeoplePage /
  AnalyticsDashboard 两个页面里,而路由是 lazy 分割的 —— 直接打开概览页时
  那两个模块未加载,日期面板就露出 Sep/Su/Mo
- AdminPeoplePage / AnalyticsDashboard: 移除重复的页面级 locale 设置
- dashboardApi.ts: fetchWipTasks 默认值同步为 500
2026-09-16 14:29:56 +08:00
44ca09ae22 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 日 ~ 此刻」。
2026-09-15 15:51:34 +08:00
0d1e45e3fb feat: 管理层数据大屏(后端聚合接口 + 前端全屏页面)
PC 管理端新增独立全屏数据大屏,供管理层查看直通率/产量趋势/不良分布,
替代原 AdminDashboard 上零散的手工下钻。

后端:
- endpoints/screen.py + services/screen_service.py: 大屏聚合接口
  (复用 lifecycle 的售后工序归一,保证统计口径与展示一致)
- router.py: 注册 screen_router

前端:
- pages/admin/ScreenDashboard.tsx: 全屏大屏页(自带鉴权守卫,无侧边栏)
- services/screenApi.ts: 大屏数据接口封装
- components/admin/UserOperationDetailDrawer.tsx: 人员操作明细抽屉,
  由 AdminDashboard 的原生 state 抽成独立组件(含命令式 handle)
- AdminDashboard.tsx: 改为使用该抽屉组件,移除内联的下钻状态
- MatrixBoard.tsx / App.tsx / AdminLayout.tsx: 挂载路由与导航入口
- BaseEChart.tsx: 注册 GaugeChart 与 GraphicComponent
  (graphic 需显式注册,否则饼图中心文字静默不渲染)
2026-09-15 10:57:46 +08:00
70c8357bc0 feat: 后端新增产品收口接口(已入库/已出库,管理员,含扫码节点与操作日志,可反向纠错与强收口) 2026-09-07 13:43:06 +08:00
2d8c937189 feat: 效能分析flow区间携带自然/工作日双时长并支持时间过滤 2026-09-02 10:38:53 +08:00
3019653dfa refactor: 宏观状态统一(待仓库收货/已入库)废弃"在库"; WIP矩阵返回product_name并新增下钻明细 2026-09-02 10:38:49 +08:00
262e28b9f2 fix: 仓储任务节点修复(assignee=None + WAREHOUSE 类型) + 前端防御性渲染
- webhook 生成任务 assignee_id 置 None,不再填中文,避免前端解析报错跳过渲染
- task_type=WAREHOUSE,并同步 isMain 判断(后端+双端前端)使其画在中央主干道
- 前端对扫码入库/出库任务:不请求用户数据,直接显示 📦 + 'MOM 仓储系统'
2026-09-01 17:25:22 +08:00
5407e2135f feat: Webhook 入库/出库后动态生成扫码任务节点与操作日志
- mom-inbound/mom-outbound 在标记已入库/已出库后,追加'扫码入库/扫码出库'主线任务
- 新任务 parent_task_id 取最后一个主线任务,task_type=TRANSFER 保证画在主干道
- 生成 TaskRecord 操作日志,前端流转树最底部长出节点
- 幂等:已存在同名任务则不重复插入
2026-09-01 16:55:13 +08:00
2b889b99d6 fix: webhook 匹配支持 external_serial 外部业务序列号
- mom-inbound/mom-outbound 用 or_ 双字段联合匹配 serial_number 与 external_serial
- MOM 推送 ceshi123 等自定义序列号时也能精准认领产品并闭环状态
2026-09-01 15:25:42 +08:00
73faa1fd93 feat: Track 打通已完成/已入库/已出库状态闭环与外部联动
- 完工转交入库同步 status=COMPLETED;MOM 入库/出库回调同步 status
- 新增 mom-outbound webhook(发货出库标记已出库),lookup 返回 material_id
- VALID_OVERALL_STATUS 新增已出库;update_overall_status 同步 status 字段
- WIP 矩阵区分已入库/待仓库收货;虚拟节点正确处理已出库/转入在库人
2026-09-01 13:53:24 +08:00
41dd257260 feat(透视表): 工序分布包含在库/完成 + 增加时间筛选
- wip-matrix 去掉状态过滤,全量分布,工序包含「在库」(生产完成)
- 后端加 since/until 按任务接手/创建时间过滤
- 前端 MatrixBoard 加 RangePicker 日期筛选 + 全部清除
2026-08-28 16:32:01 +08:00
7547a886cc feat(backend): 新增 /dashboard/wip-matrix 在制品交叉聚合接口
- 规格型号×人员/工序 的设备数量聚合(WIP/PENDING任务)
- dimension 参数: assignee(中文姓名)/task_name(工序名)
- 返回扁平数组含 count + assignees(该交叉点主负责人列表)
2026-08-28 16:25:20 +08:00
907473b18c feat(效能分析): 人员视图也支持自然天/工作日耗时切换
- capability 接口加 mode 参数,natural 用自然小时、workdays 用工作小时
- 人员视图柱状图随顶部按钮切换,排除休息日的耗时
- 切换按钮自动重新请求 capability 与 flow
2026-08-28 15:38:32 +08:00
4447a1f52d feat(效能分析): 轨迹流转图时间轴支持工作日/自然天切换
- flow 接口加 mode 参数,workdays 模式把时间偏移换算为排除周末/节假日的工作小时
- 轨迹流转图的横轴随顶部按钮(自然天/工作日)切换,休息日被压缩
- 按钮切换自动重新请求 flow
2026-08-28 15:34:17 +08:00
ad4b55dece feat(工作日): 新增工作小时计算工具 + 节假日表与管理接口
- time_utils 新增 to_beijing(统一naive按UTC转北京时间) + working_duration_hours(排除周末/节假日)
- Holiday 模型 + alembic 迁移建 holidays 表
- GET/POST/DELETE /holidays 节假日管理接口,可随时配置放假日期
2026-08-28 15:08:05 +08:00
7205de369a feat(backend): 新增 /dashboard/user-operations/detail 操作明细下钻接口
- 按人+操作类型(receive/transfer/record)查询明细:任务/产品/备注/时间
- record 明细与该人统计口径一致(名下任务手动备注,排除系统自动)
2026-08-28 13:36:23 +08:00
b85188e625 feat(backend): 新增 /dashboard/user-operations 人员操作统计接口
- add_task_record 补 action_type=record 日志,使上传备注可统计到人
- get_user_operations 按人聚合 接收(receive)/转交(complete)/上传备注(record) 次数,时间筛选,中文名映射
- 复用 get_display_names,不新增数据库字段
2026-08-28 13:14:09 +08:00
7e00683a21 feat(backend): 新增 /dashboard/rejected-tasks 驳回/返工下钻接口
- RejectedTask schema: 设备/工序/驳回人/返工负责人/原因/时间
- get_rejected_tasks 批量窗口取驳回人(reject log) + 复刻 reject_task 的返工负责人追溯逻辑(create log→父任务→自身)
- 复用 get_display_names 中文名映射、BEIJING_TZ 时间转换
2026-08-28 11:18:58 +08:00
54b5ea00f1 feat: 新增效能分析看板(个人能力图谱 + 设备人员耗时对比)
- 前端:AnalyticsDashboard 页面 + BaseEChart 封装 + analyticsApi,路由与导航
- 后端:/analytics 系列接口(capability/flow/options/device-records)
- 能力图谱单机颗粒度、流转对比按人堆叠识别瓶颈、点击下钻备注、筛选动态联动
- 依赖:引入 echarts,移除 echarts-for-react
2026-08-14 10:19:44 +08:00
91b1426ccb feat(dashboard): 人员工时台账——平铺明细+多维筛选+时间交集+动态工时+备注+CSV导出 2026-08-13 16:04:23 +08:00
c2e6ed240b feat(dashboard): 看板增强——流转完成率下钻明细 + 人员看板(按人聚合在制品设备) 2026-08-13 12:00:12 +08:00
eba0c3be98 fix: 端点层operator_id兜底current_user.username — 管理员代办检测生效
根因: 移动端不传?operator_id=参数, 后端收到None
      _admin_proxy_note(None, assignee) → 不触发标记

修复: 6个端点(receive/reject/transfer/end/recall/spawn)
      operator_id or current_user.get(username) 兜底
2026-08-12 17:19:56 +08:00
3b53db03c1 feat: 看板全局时间筛选 + 协同留言抽屉
任务1 — 后端时间快照逻辑:
  get_dashboard_stats 新增 since/until 参数
  PENDING/WIP/总数 → 永远实时快照(忽略时间筛选)
  COMPLETED/REJECTED → 严格按时段过滤
  完成率基于过滤后的COMPLETED计算

任务2 — 前端时间筛选器:
  Radio.Button: 今天 | 近7天 | 近30天 | 自定义
  DatePicker.RangePicker 自定义区间
  切换时重新拉取 /dashboard/stats

任务3 — 协同留言抽屉:
  后端: GET /dashboard/messages(上帝视角全厂数据)
    JOIN Product → serial_number + material_name
    支持 keyword 搜: SN/物料名/留言人/内容
    按 created_at 倒序
  前端: Drawer + Input.Search + List
    点击"留言"数字打开抽屉
    分页展示: 留言人/内容/SN标签/物料名/时间
2026-08-12 13:50:56 +08:00
39377ae5e4 feat: 看板重构 — 在制品看板 + 留言未读数
核心理念转变:
  旧: 最近操作历史(谁做了什么)
  新: 当前在制品状态(谁的活卡了多久)

后端:
  - DashboardStats 新增 unread_messages(协同留言总数)
  - 新增 GET /dashboard/wip-tasks(在制品端点)
  - 按滞留时间升序排列,PENDING/WIP 任务倒计时
  - 自动计算 duration_hours(已滞留小时数)

前端:
  - 品质卡片: 新增留言未读数(紫色)
  - 在制品看板: 替换原最近动态
    每行显示: 状态徽标 | 任务名+负责人+身份证 | 滞留时长
  - 滞留颜色: 48h+红 | 24h+橙 | 8h+黄 | <8h灰
  - 系统通知可点击跳转
2026-08-12 13:40:08 +08:00
00fae01eed fix: 看板4项体验修复 — 时间/身份证/日期筛选/通知跳转
1. 完整16位产品身份证号显示(不再截断)
2. 日期筛选栏: 今天 | 近7天 | 近30天 | 全部
   - 后端 recent-activity 支持 since/until ISO参数
   - 默认展示今天动态,可按时间范围切换
3. 操作人兜底: TaskLog无operator_id时用任务assignee_id
4. 未读通知可点击跳转通知页面 + 快捷入口增加通知中心
2026-08-12 13:35:23 +08:00
658fc28b9b feat: 管理看板全面改版 — 增强可读性 + 填充空白区域
后端增强:
  - DashboardStats 新增 tasks_rejected, tasks_rework, unread_notifications
  - 新增 GET /dashboard/recent-activity 最近动态端点
  - 关联 Task + Product 表返回完整动态信息

前端改版 (AdminDashboard.tsx):
  - 4 张概览卡片: 产品流转 | 任务状态 | 品质通知 | 完成率
  - 每张卡片含进度条(百分比标注) + 中文说明
  - SVG 环形图展示任务完成率
  - 最近流转动态时间线 (8条)
  - 快捷入口: 创建产品/任务管理/打印配置/扫码干活
  - 底部说明卡片解释"产品 vs 任务"的区别
  - 响应式网格填满全屏, 无空白区域
2026-08-12 13:25:49 +08:00
3a6ab7d756 security: 补全剩余端点鉴权 + 移除硬编码管理员后门
1. 鉴权补全
   - orders.py: create_order 补全 Depends(get_current_user)
   - print.py: print_execute 和 update_printer_config 补全鉴权
   - records.py: update_record 和 delete_record 补全鉴权

2. 安全加固
   - auth_service.py: 移除硬编码超级管理员(IRIS/123321)后门
   - 所有用户统一通过MOM sys_user scrypt密码验证登录
2026-08-12 12:04:40 +08:00
8c54a38f55 security: API鉴权补全 + SQL拼接隐患消除
1. materials.py
   - get_material_groups和get_material_items补全Depends(get_current_user)
   - 移除TYPE_FILTER="1=1"死代码及4处f-string SQL拼接
   - 全部SQL改为纯参数化text()查询

2. notifications.py
   - list_notifications废弃user_id查询参数(越权漏洞)
   - user_id强制从JWT Token解析,防止篡改参数偷看他人通知
   - mark_notification_read补全鉴权
2026-08-12 12:03:12 +08:00
991d713777 feat(backend): 新增留言通知 — add_task_record 自动推送给任务负责人
触发条件:
- 有人对任务添加流转记录(留言/备注)
- 任务有 assignee_id
- 留言人 != 任务负责人 (不给自己发通知)

通知内容:
- title: 💬 收到新留言
- content: 产品[SN]的「任务名」有新留言:{前30字摘要}
- type: COMMENT

额外:
- Notification 模型新增 NOTIFY_COMMENT 常量
- 前端 notify 页新增 COMMENT 图标(💬)和标题(收到新留言)
2026-08-11 18:17:52 +08:00
40a2d87345 fix: 消息通知页不能点进详情 — 补全 product_serial_number
问题: 点击通知卡片跳转 detail?taskId=xxx,但 detail.vue onLoad 只认 serial 参数导致空白页

修复:
1. 后端 NotificationResponse 新增 product_serial_number 字段
2. 后端 notifications API 联表 tasks+products 批量填充 serial
3. 前端 notify/index.vue handleCardTap 优先用 product_serial_number 跳转,兜底从 content 正则解析
4. 前端 detail.vue onLoad 新增 taskId 兜底 → doQueryByTask 反查 product_sn
2026-08-11 15:56:25 +08:00
88dc7381f6 fix(backend): 权限漏洞修复 + 业务逻辑审查修复
1. 宏观状态越权修复 (products.py + product_service.py):
   - update_overall_status 增加权限校验:仅 SUPER_ADMIN 或当前操作该产品主线任务的人可修改,否则 403

2. 任务撤回越权修复 (task_service.py + tasks.py):
   - recall_task 增加校验:操作人必须等于上游任务负责人(谁发出的谁撤回),否则 403
   - 管理员 (SUPER_ADMIN/SUPERVISOR) 直接放行

3. 驳回上游溯源修复 (task_service.py - reject_task):
   - 将 TaskLog (action_type=create) 提升为优先溯源方式,解决协助分支转交后驳回找不到正确发起人的 Bug
   - 保留 parent_task.assignee_id + task.assignee_id 两级兜底

4. complete_task 父节点继承修复:
   - 修复 task_type == 'MAIN' 永不匹配的 Bug(模型无此值)
   - 改为 not parent_task_id or task_type in (TRANSFER, RECOVERY)
2026-08-11 15:05:30 +08:00
d40a8d480e fix(backend): end_task 和 complete_task 补齐权限校验
- end_task 新增 operator_role 参数 + _check_permission 调用
- complete_task 新增 operator_role 参数 + _check_permission 调用
- 两个端点均注入 current_user Depends(get_current_user)
- 非任务负责人且非管理员/主管调用时返回 403
2026-08-11 10:13:40 +08:00
1fe9c3d59d feat: 留言板 API + 零信任安全:后端 Token 鉴权强制覆写 operator_id
- 新增 GET/POST /products/{id}/messages 端点
- POST 创建留言时注入 current_user = Depends(get_current_user)
- operator_id 优先取 Token 中的 username,防止前端越权伪造发言身份
- 同时将 product.current_location_id 显示改为 formatUserName 映射
2026-08-10 17:37:10 +08:00
60af3b9998 feat: 物料选择全量展示(移除成品/半成品限制) 2026-08-10 11:02:22 +08:00
65ff05fb2f feat: 状态渲染修复+中文姓名映射+SUPERVISOR/SUPER_ADMIN权限控制(前端+后端) 2026-08-07 17:26:14 +08:00
beea543fbd fix: 创建产品时自动将当前位置设为当前登录用户(谁创建谁持有) 2026-08-07 15:42:10 +08:00
bc43b5a732 feat: 产品管理 — 当前位置显示真实姓名 + 编辑弹窗 + 删除确认 2026-08-07 15:38:01 +08:00
0de981ea81 fix: OTA检测闭环 — 新增GET /check-update + App端uni.getSystemInfoSync版本对比 2026-08-07 12:35:00 +08:00
bff8787e62 feat: OTA热更新 — 版本检测API + App端WGT下载安装 + app_versions表 2026-08-07 11:49:59 +08:00
b71c5a2d07 feat: 双Token认证(Access 2h/Refresh 7d) + 通知系统(转交/驳回自动推送) 2026-08-07 11:44:04 +08:00
57884d7baa 撤回转交: recall接口删除子任务恢复父WIP + 前端canRecall判断 + 红色撤回按钮 2026-08-06 11:39:22 +08:00