Commit Graph

157 Commits

Author SHA1 Message Date
74d404cc11 feat(analytics): 设备流转甘特图数据底座(lead_time/started_at/task_type 主次标识)
- FlowDevice 新增 lead_time(生命周期总时长)、started_at(最早介入时间)
- FlowSeries.data 改为区间数组 [device_index, start_offset, end_offset, task_name, duration, is_main]
- get_flow_compare 按任务生成相对 T0 的偏移区间,task_type=SPAWN 标记为分支
2026-08-14 11:44:41 +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
e5829c7d06 fix(backend): 修复并发任务聚合——当前位置汇总全部负责人、最新动态携带操作人、状态聚合补齐 REJECTED 2026-08-13 11:17:10 +08:00
b8ade13b61 fix: TaskRecordResponse改用field_validator — 不再污染ORM对象导致图片列表写回VARCHAR报错
根因: model_validate直接obj.images=json.loads()把ORM对象的字符串改成列表
     SQLAlchemy autoflush时把列表写回VARCHAR列 → DataError
修复: field_validator(mode=before)在序列化层转换, 不修改ORM源对象
2026-08-13 09:03:17 +08:00
abc8999358 hotfix: get_product_by_serial补全func导入 — 修复500错误 2026-08-13 09:01:34 +08:00
d8d42e9613 feat: 上游显示真实发送人 — 从task_logs追溯created_by 2026-08-12 17:52:35 +08:00
2170e2e9cf fix: 转交通知中product_sn始终为空 — product查询移到通知循环之前
根因: transfer_task中 product_result 查询在通知创建循环之后
     通知内容 product_sn 引用未初始化的 product 变量 → 始终为空字符串
修复: 将 product 查询提前到 flush() 之后、通知循环之前
2026-08-12 17:33:32 +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
e7bfdc261b fix: TaskRecord也加入管理员代办审计标记 — 看板最新动态可见代理操作
新增 _admin_proxy_note() 辅助函数
receive/transfer 的 TaskRecord.remark 自动拼接 [管理员xx代办]
这样看板\最新动态\列就能显示管理员代操作记录
2026-08-12 17:11:07 +08:00
e511952ea0 feat: 最新动态列支持图片展示 — 纯文字/纯图片/图文混合三种模式
后端: ProductResponse 新增 latest_record_has_images
前端: 📷图标 + 纯图片时显示"图片记录" + Tooltip标注[含图片]
2026-08-12 16:01:24 +08:00
6016f92dd0 hotfix: func→sa_func 解决局部变量遮蔽 2026-08-12 15:52:06 +08:00
b21df33e0a hotfix: 补充 product_service.py 顶部缺失的 func 导入 2026-08-12 15:50:01 +08:00
163d9c20bb feat: 任务全景表格新增"最新动态"列
后端:
  - ProductResponse 新增 latest_record_time/content 字段
  - get_all_products 批量查询活跃任务(WIP/PENDING)的最新 task_records
  - 使用窗口函数 ROW_NUMBER() PARTITION BY product_id

前端:
  - AdminTasksPage: grid-cols-12→grid-cols-[14]
  - 表头+数据行新增"最新动态"列
  - Tooltip悬浮显示完整内容, 截断+灰字二级排版
2026-08-12 15:43:33 +08:00
1fdc607c07 feat: 管理员代工模式 — 前端权限放开 + 后端审计留痕
前端:
  - WorkspaceArea 新增 currentUserRole prop
  - isAssignee/canRecall: SUPER_ADMIN/SUPERVISOR 上帝视角直接放行
  - 管理员代操作时显示黄色提示条: 正在以管理员身份代[原负责人]操作
  - detail.vue: loadCurrentUser 提取并保存 user.role

后端:
  - _create_task_log 新增 task_assignee_id 参数
  - operator_id!=task_assignee_id 时自动拼接 [管理员xx代办操作]
  - 12处调用点全部传入 task_assignee_id
  - receive_task 位置赋值确认为 task.assignee_id(非 operator_id)
2026-08-12 15:24:50 +08:00
49cbf22854 fix: 修复or_局部导入遮蔽模块级导入导致UnboundLocalError + Drawer改用size
1. product_service.py: 删除函数内两处 from sqlalchemy import or_
   Python解析器看到函数内的import就把or_当局部变量, 在赋值前使用就报UnboundLocalError
   模块顶部已有 import or_, 函数内无需重复导入

2. AdminDashboard.tsx: Drawer width→size (Antd v6 deprecation)
2026-08-12 15:05:39 +08:00
729de138cd feat: 在制品附加信息补全 — 设备名称|规格型号|序列号|身份证
后端: WipTask 新增 spec_model, 查询关联 Product.spec_model
前端: 第二行格式改为 物料|规格|序列号:xxx|身份证:xxx
2026-08-12 14:43:20 +08:00
9460a8492a feat: 在制品列表信息层级重构 + SLA预警色 + 点击跳转
1. 信息层级重构:
   主信息: [状态Tag] 任务名 | 负责人: 张三  3h
   附加信息: 物料名 SN:25022 ...A1B2C3  08-12 10:00
   新增后端 material_name 字段, 前端卡片式布局

2. SLA滞留预警色:
   <12h → 绿色(正常) | 12-24h → 橙色(警告) | >24h → 红色加粗(危险)

3. 交互: 点击卡片新窗口打开对应产品扫码详情页
2026-08-12 14:33:56 +08:00
42544d7485 fix: get_wip_tasks 解包错误 — 新增 external_serial 后 rows 变3列但解包仍是2变量 2026-08-12 14:28:00 +08:00
079b65e761 feat: 在制品加业务序列号 + 追溯码改名为身份证
1. 在制品行: 显示 external_serial(业务序列号)
   优先 external_serial → 兜底 product_sn
   旧: 装配 A1B2C3D4  张三
   新: 装配 25022  张三

2. 留言板: 追溯码 → 身份证
   旧: 追溯码: 000000000000000D
   新: 身份证: 000000000000000D

3. 后端: WipTask 新增 external_serial 字段
   关联查询 Product.external_serial
2026-08-12 14:23:23 +08:00
ff7c791d49 fix: 留言板区分业务序列号与系统追溯码
语义修正:
  Product.serial_number  = 16位HEX系统追溯码(如000000000000000D)
  Product.external_serial = 业务产品序列号(如25022)

后端:
  - ProductMessageItem 新增 external_serial 字段
  - search_product_messages 关联查询 Product.external_serial
  - 关键词搜索也支持按 external_serial 匹配

前端显示:
  样品升降台V1J · 序列号: 25022          ← 醒目
  追溯码: 000000000000000D               ← 10px浅灰小字
2026-08-12 14:20:38 +08:00
c14bbeb891 fix: 3个逻辑/UI Bug修复
Bug1 — 任务总数与进度条不匹配:
  tasks_total 从全库历史总数改为 PENDING+WIP+COMPLETED(时段)+REJECTED(时段)
  确保进度条4段之和 = 显示的总数, 不再出现11≠9的缺口

Bug2 — 在制品排序混乱:
  移除DB层order_by, 改为Python统一按duration_hours降序
  无视status, 纯数值排序, 滞留最久排最前
  取limit*2条后在Python中排序再截断

Bug3 — 留言板产品显示割裂:
  [Tag:SN] 物料名 → 物料名 (SN: A1B2C3D4...)
  物料名加粗, SN等宽灰色, 阅读更顺畅
2026-08-12 14:13:26 +08:00
32c234fea1 fix: 在制品按滞留时间降序 + 留言抽屉UI美化
1. 在制品排序: nulls_first → nulls_last
   已接收任务按receipt_at升序(最早=滞留最久)排最前
   未接收任务(null)排最后

2. 留言抽屉UI重设计:
   - 卡片式布局: 彩色头像圆+边框阴影+hover效果
   - 头像颜色按姓名首字自动分配7色
   - Input.Search替代普通Input+Search图标
   - 灰色背景区分卡片, 时间格式改为YYYY-MM-DD HH:mm
   - 手动分页按钮替代Antd分页器
2026-08-12 13:59:39 +08:00
ca195192b6 fix: 协同留言时间改为北京时间
根因: ProductMessage.created_at 使用 datetime.utcnow (UTC),
      与其他所有模型使用的 get_beijing_time 不一致。
      看板留言抽屉显示的时间比实际晚8小时。

修复:
  1. message.py: DateTime → DateTime(timezone=True)
     default 从 datetime.utcnow → get_beijing_time
  2. dashboard_service.py: 兜底转换已有数据
     naive UTC → replace(tzinfo=UTC) → astimezone(Beijing)
2026-08-12 13:55:33 +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
c3f3a5e291 fix: 看板动态时间改为北京时间 + 操作人显示中文姓名
1. 时区修复:
   - database.py: PG连接池会话级设置 TimeZone=Asia/Shanghai
   - dashboard_service.py: 格式化时间显式 astimezone(BEIJING_TZ)
   - 兜底: tzinfo为None时默认当作北京时间处理

2. 操作人中文姓名:
   - 收集所有operator_id → 调用mom_cache批量翻译
   - 优先中文姓名 → 英文用户名兜底
   - operator_id为None时不再显示"系统",留空
2026-08-12 13:28:38 +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
d59f732a0e fix: 数据库会话事务安全兜底
get_db() 依赖注入增加 try/except/rollback/finally:
- 请求处理中发生异常时自动 rollback
- 无论成功或异常,finally 中确保 close 释放连接
- 防止异常导致连接泄漏或脏事务残留
2026-08-12 12:04:45 +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
69f3e35d14 fix: Dashboard统计数据修复 + 生产环境SECRET_KEY强制校验
1. Dashboard统计Bug修复
   - Task统计改用TASK_STATUS_PENDING/WIP/COMPLETED大写常量
   - 旧代码使用小写"pending"/"in_progress"永远匹配不到数据
   - 修复后tasks_pending/tasks_in_progress/tasks_completed返回真实值

2. 生产环境SECRET_KEY强制校验
   - 新增model_validator:DEBUG=False且SECRET_KEY为默认值时抛出ValueError
   - 阻止使用默认密钥部署到生产环境
2026-08-12 12:03:07 +08:00
cc199081f9 perf: CTE任务树加载器 + MOM跨库查询缓存
消除两个核心N+1性能瓶颈:

1. CTE任务树加载器 (task_tree_loader.py)
   - PostgreSQL Recursive CTE一次性加载完整任务树
   - 无论树深度多大,仅2条SQL(CTE + records selectinload)
   - set_committed_value安全注入,避免Session脏数据
   - 修复add_task_record双重加载问题
   - 移除get_all_tasks中冗余的selectinload(child_tasks)

2. MOM跨库查询缓存 (mom_cache.py)
   - 零依赖TTL内存缓存(threading.RLock + time.monotonic)
   - 参数化ANY(:user_ids)替代OR拼接LIKE(防注入)
   - get_all_products中3次调用共享缓存,2h TTL内零跨库查询
2026-08-12 12:03:02 +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
b97dfaa95d fix(backend): 宏观状态权限校验 — 根除 current_user 空值绕过漏洞
Bug: if current_user: 在 Python 中 None/空dict均为falsy,
      一旦 current_user 为空则整个权限块被跳过直接放行。

修复:
1. if not current_user → 直接 raise 401
2. 反转 SUPER_ADMIN 判断: if user_role != 'SUPER_ADMIN' 进入校验
   (避免 if/else/pass 空分支带来的逻辑歧义)
3. main_task is not None 显式判断 (替代隐式 truthy)
2026-08-11 17:39:30 +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
de47d27cfb fix(backend): _recalc_product_location 修复 func.or_() → or_()
func 是 SQL 函数调用(如 func.now()),不能替代 WHERE 条件中的 or_ 运算符。
这会导致结束协助分支时 SQL 抛错 500。
2026-08-11 10:05:13 +08:00
718e205f53 feat(backend): 完工转交智能父节点继承算法
- complete_task: MAIN转交保持平级(parent_task_id),SPAWN转交认当前为父(task.id)
- 基因严格继承: task_type不会篡位成MAIN
2026-08-11 10:02:01 +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
b14773f81b feat: 产品协同留言板 — ProductMessage 模型 + API + 迁移
- 新增 ProductMessage 模型,挂载在 products.id(ON DELETE CASCADE)
- GET/POST /products/{id}/messages 接口
- 注册到 Alembic 自动发现
2026-08-10 17:37:01 +08:00
e326f8d311 fix: 修复任务完工/结束 500 + 后端基因继承
- 移除 task.child_tasks 的 ORM 属性直接赋值(导致 SQLAlchemy 状态机崩溃)
- complete_task 创建 next_task 时补上 task_type=task.task_type 基因继承
2026-08-10 17:36:54 +08:00
2440e9bdaa fix: 修复产品删除 500 — ImportError + FK 自引用约束
- 修正 TaskLog 导入路径:app.models.task → app.models.task_log
- 删除任务前先切断 parent_task_id 自引用外键
- 删除产品前先切断 parent_product_id 自引用外键
- 真正执行 delete(TaskRecord) 和 delete(TaskLog) 替代空 select
2026-08-10 17:36:48 +08:00
34724e478c fix: SQL语法错误—func.and_()/or_()改为sqlalchemy.and_()/or_() 2026-08-10 11:41:10 +08:00
e447de6cfa feat: 当前位置100%动态主干化—与宏观状态同源,只跟主分支,静态字段仅兜底 2026-08-10 11:32:07 +08:00
b43bded0ef feat: 宏观状态动态从主干任务获取+任务树assignee中文姓名映射(后端+前端) 2026-08-10 11:25:44 +08:00
60af3b9998 feat: 物料选择全量展示(移除成品/半成品限制) 2026-08-10 11:02:22 +08:00