liuwb 2 месяцев назад
Родитель
Сommit
3ea4e9033e
21 измененных файлов с 880 добавлено и 27 удалено
  1. 186 0
      docs/qms/IQC开发流程与注意事项.md
  2. 240 0
      docs/qms/codex-dev-playbook.md
  3. 50 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/IpqcTaskController.java
  4. 16 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/vo/IpqcTaskPageReqVO.java
  5. 24 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/vo/IpqcTaskProcessInfoVO.java
  6. 65 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/vo/IpqcTaskRespVO.java
  7. 14 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/vo/IpqcTaskStartReqVO.java
  8. 28 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/dal/mysql/ipqc/IpqcTaskMapper.java
  9. 12 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/ipqc/IpqcTaskService.java
  10. 58 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/ipqc/IpqcTaskServiceImpl.java
  11. 88 0
      yudao-module-qms/src/main/resources/mapper/ipqc/IpqcTaskMapper.xml
  12. 5 1
      yudao-server/src/main/resources/application.yaml
  13. 0 0
      yudao-ui/yudao-ui-admin-vue3/IQC来料检验任务流程开发说明.md
  14. 0 0
      yudao-ui/yudao-ui-admin-vue3/Order_Change_流程开发说明.md
  15. 35 0
      yudao-ui/yudao-ui-admin-vue3/src/api/qms/ipqc/task/index.ts
  16. 0 1
      yudao-ui/yudao-ui-admin-vue3/src/config/qmsModules.ts
  17. 0 1
      yudao-ui/yudao-ui-admin-vue3/src/router/index.ts
  18. 13 4
      yudao-ui/yudao-ui-admin-vue3/src/router/modules/remaining.ts
  19. 11 17
      yudao-ui/yudao-ui-admin-vue3/src/store/modules/qms/task.ts
  20. 24 0
      yudao-ui/yudao-ui-admin-vue3/src/utils/routerHelper.ts
  21. 11 3
      yudao-ui/yudao-ui-admin-vue3/src/views/system/menu/index.vue

+ 186 - 0
docs/qms/IQC开发流程与注意事项.md

@@ -0,0 +1,186 @@
+# IQC 开发流程与注意事项(AI 友好版)
+
+> 目标:让 AI/新人能够快速定位 IQC 功能的页面、接口、数据库字段映射和流程联动。
+
+---
+
+## 1. 模块范围
+- **IQC 来料检验申请**:创建/编辑/查看申请单(主表 + 子表)。
+- **IQC 来料检验任务**:基于申请子表生成任务列表,触发流程。
+- **IQC 检验单编辑**:在流程中编辑检验明细(新页面)。
+- **流程引擎**:Flowable(流程实例、任务、历史)。
+
+---
+
+## 2. 前端页面与路由
+
+### 2.1 申请相关
+- 列表:`/qms/iqc/apply/list`
+  - 页面:`yudao-ui/yudao-ui-admin-vue3/src/views/qms/ApplicationList.vue`
+- 表单:`/qms/iqc/apply/new | /edit/:id | /view/:id`
+  - 页面:`yudao-ui/yudao-ui-admin-vue3/src/views/qms/ApplicationForm.vue`
+
+### 2.2 任务与流程相关
+- 任务列表:`/qms/iqc/task/list`
+  - 页面:`yudao-ui/yudao-ui-admin-vue3/src/views/qms/TaskList.vue`
+- 流程详情(审批详情页):
+  - 页面:`yudao-ui/yudao-ui-admin-vue3/src/views/qms/iqc/task/ProcessDetailForm.vue`
+  - 当前只展示 **申请信息 + 备注**,并提供“打开编辑页面”按钮。
+
+### 2.3 检验单编辑页(新页面)
+- `/qms/iqc/task/edit/:taskId`
+- `/s5/iqc/iqc/task/edit/:taskId`
+- 页面:`yudao-ui/yudao-ui-admin-vue3/src/views/qms/iqc/task/InspectBillEdit.vue`
+
+---
+
+## 3. 关键接口清单
+
+### 3.1 申请(IQC Apply)
+- `GET /qms/iqc-apply/page` 申请列表
+- `GET /qms/iqc-apply/get?id=...` 申请详情
+- `POST /qms/iqc-apply/create` 新建
+- `PUT /qms/iqc-apply/update` 更新
+- `DELETE /qms/iqc-apply/delete?id=...` 删除
+
+### 3.2 任务(IQC Task)
+- `GET /qms/iqc-task/page` 任务列表
+- `GET /qms/iqc-task/get?id=...` 任务详情
+- `POST /qms/iqc-task/start` 开始检验(创建流程实例)
+
+### 3.3 检验单编辑(Inspect Bill)
+- `GET /qms/iqc-task/inspect-bill/main?taskId=...` 主表信息
+- `GET /qms/iqc-task/detail-list?taskId=...` 明细列表
+- `POST /qms/iqc-task/inspect-bill/save` 保存主表+明细
+
+---
+
+## 4. 数据表与字段映射
+
+### 4.1 申请主表(qms_qcp_inspecapplyn)
+- `id`:主键
+- `FBILLNO`:申请单号(前端 `id`)
+- `FAPPLYUSER`:申请人 ID
+- `FAPPLYTIME`:申请时间
+- `FBILLTYPE`:业务类型
+- `FCOMMENT`:**总体备注**(申请列表显示)
+
+### 4.2 申请子表 / 任务来源表(qms_qcp_insappnentry)
+- `glid`:关联主表 `id`
+- `FSRCORDERNUM`:物料编码(前端 `materialCode`)
+- `FSRCORDERTYPE`:物料名称(前端 `materialName`)
+- `FLOTNUMBER`:批次(前端 `batch`)
+- `FAPPLYQTY`:数量(前端 `quantity`)
+- `FUNIT`:单位(前端 `unit`)
+- `FINSPECTSTATUS`:检验状态(待检验/检验中/检验完成)
+- `FSUPPLIER`:**供应商编码**(前端 `supplierCode`)
+- `process_instance_id`:流程实例
+- `flowstate`:流程状态
+
+### 4.3 检验单主表(qms_qcp_inspbill)
+- `hid`:关联任务 ID(`qms_qcp_insappnentry.id`)
+- `FCOMMENT`:临时存 JSON:`{"A":"...","B":"...","C":"...","D":"..."}`
+
+### 4.4 检验单明细表(qms_qcp_inspbilllist)
+- `billid`:主表 ID
+- `jyxm/bz/sx/xx`:A/B/C/D 的临时字段映射
+
+---
+
+## 5. 字段映射表(前端 ↔ DB)
+
+### 5.1 申请明细(ApplicationForm.detail)
+| 前端字段 | 表字段 | 说明 |
+|---|---|---|
+| materialCode | FSRCORDERNUM | 物料编码 |
+| materialName | FSRCORDERTYPE | 物料名称 |
+| specification | (暂无) | 当前未映射 |
+| batch | FLOTNUMBER | 批次 |
+| inspectStatus | FINSPECTSTATUS | 检验状态 |
+| quantity | FAPPLYQTY | 数量 |
+| unit | FUNIT | 单位 |
+| supplierCode | FSUPPLIER | 供应商编码 |
+
+### 5.2 申请列表(ApplicationList)
+- `remark` ← `qms_qcp_inspecapplyn.FCOMMENT`(总体备注)
+- `status`:由子表汇总计算(见 6.1)
+
+### 5.3 任务列表(TaskList)
+- `supplierCode` ← `qms_qcp_insappnentry.FSUPPLIER`
+- `applicationId` ← `qms_qcp_inspecapplyn.FBILLNO`
+- `status`:由 `FINSPECTSTATUS/FBILLSTATUS` 映射
+
+---
+
+## 6. 流程联动(BPM)
+
+### 6.1 流程 Key
+- `PROCESS_KEY = qms_iqc_task`
+- 定义在:`yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/IqcTaskServiceImpl.java`
+
+### 6.2 启动流程
+- 入口:`POST /qms/iqc-task/start`
+- 行为:
+  - 创建流程实例
+  - 回写 `process_instance_id`
+  - 更新 `flowstate = 审批中`
+  - 更新 `FINSPECTSTATUS = 检验中`
+
+### 6.3 流程结束回写
+- 通过:`flowstate = 审批完成`,`FINSPECTSTATUS = 检验完成`
+- 取消:`flowstate = 已取消`,`FINSPECTSTATUS = 待检验`
+- 驳回:`flowstate = 已驳回`,`FINSPECTSTATUS = 检验中`
+
+---
+
+## 7. 前端配置关键点(AI 容易踩坑)
+
+1) **申请列表 vs 任务列表**
+- 申请列表显示 **总体备注**(`FCOMMENT`)
+- 任务列表显示 **供应商编码**(`FSUPPLIER`)
+
+2) **状态角标**
+- `ApplicationForm.vue` 已移除状态角标(不显示“待检验/检验中/检验完成”)。
+
+3) **检验明细编辑入口**
+- 审批详情页(流程详情)不再内嵌明细表。
+- 通过“打开编辑页面”进入 `InspectBillEdit.vue`。
+
+4) **通过前强制保存**
+- `ProcessInstanceOperationButton.vue` 对 `qms_iqc_task` 做了保存拦截:
+  - “通过”前会调用检验单保存接口
+  - 保存失败会阻止通过
+
+---
+
+## 8. 常见问题与排查
+
+- 列表字段不显示:检查 **前端字段 key**、**后端 VO 字段名**、**Mapper SQL 字段别名** 是否一致。
+- 明细更新不生效:当前是“先删后插”,ID 会重新生成;确认 `update` 逻辑是否走到。
+- 流程未发起:`process_instance_id` 为空时会提示“请先开始检验”。
+- 申请时间格式:前端使用时间戳(`value-format: 'x'`),后端用 `LocalDateTime` 接收。
+
+---
+
+## 9. 关键代码位置索引
+
+- 前端配置:`yudao-ui/yudao-ui-admin-vue3/src/config/qmsModules.ts`
+- 申请表单:`yudao-ui/yudao-ui-admin-vue3/src/views/qms/ApplicationForm.vue`
+- 任务列表:`yudao-ui/yudao-ui-admin-vue3/src/views/qms/TaskList.vue`
+- 审批详情:`yudao-ui/yudao-ui-admin-vue3/src/views/qms/iqc/task/ProcessDetailForm.vue`
+- 检验单编辑:`yudao-ui/yudao-ui-admin-vue3/src/views/qms/iqc/task/InspectBillEdit.vue`
+
+- 申请 Mapper:`yudao-module-qms/src/main/resources/mapper/iqc/IqcApplyMapper.xml`
+- 任务 Mapper:`yudao-module-qms/src/main/resources/mapper/iqc/IqcTaskMapper.xml`
+- 任务 Service:`yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/IqcTaskServiceImpl.java`
+
+---
+
+## 10. 建议的开发顺序(可复用)
+1. 先确定 DB 字段映射(主/子表)
+2. 后端 VO + Mapper SQL + Service
+3. 前端 API 类型对齐
+4. 列表字段 key 与后端字段别名一致
+5. 流程 start/回写字段校验
+6. 审批详情 + 编辑页联调
+

+ 240 - 0
docs/qms/codex-dev-playbook.md

@@ -0,0 +1,240 @@
+# Codex 提示词(WSL + VS Code + 芋道/Yudao)前后端开发与自校验规范
+
+> 用途:把本文件放入项目(建议 `docs/codex-dev-playbook.md` 或根目录 `CODEX_DEV_PROMPT.md`),用于驱动 Codex **自行开发 + 自行校验**。  
+> 目标:在当前系统(WSL2 + VS Code Remote-WSL + 多模块 Maven + pnpm 前端)下,快速、稳定地完成“需求→代码→联调→验收”。
+
+---
+
+## 0. 你的角色与输出要求(给 Codex 的总指令)
+
+你是项目的“全栈工程助手”,需要在不遗漏工程化细节的前提下完成开发任务。每次改动必须满足:
+
+- **最小可跑通**:后端接口可启动可调用;前端页面可访问可返回列表/详情。
+- **工程一致性**:遵循芋道项目分层与命名规范;模块边界清晰。
+- **自校验**:每次交付都包含 *如何验证*(命令/URL/返回示例/截图点位)。
+- **不走捷径**:不要用临时 hardcode 绕开权限/租户/路由,除非明确写在“临时方案”并给出回滚方式。
+
+输出格式固定为:
+
+1) 你要做什么(任务拆解清单)  
+2) 你改了哪些文件(文件路径列表 + 关键 diff)  
+3) 你如何验证(命令 + 预期结果)  
+4) 风险与回滚(潜在影响 + 如何撤销)
+
+---
+
+## 1. 环境与工具链硬约束(避免踩坑)
+
+### 1.1 WSL 下运行后端:禁止“单文件运行”
+- **禁止**使用 VS Code Code Runner 的 *Run Code*(它只会 `javac` 单文件,会导致 `org.springframework.boot` 等依赖找不到)。
+- **必须**使用 Maven/IDEA/Java 扩展的 *Run Java* 或 Maven 命令启动。
+
+### 1.2 多模块 Maven:在哪个目录执行很关键
+- 聚合根目录不一定声明 `spring-boot` 插件,根目录直接 `mvn spring-boot:run` 可能失败。
+- 推荐命令(任选其一):
+  - `cd yudao-server && mvn -DskipTests spring-boot:run`
+  - 或:`mvn -pl yudao-server -am -DskipTests org.springframework.boot:spring-boot-maven-plugin:run`
+
+### 1.3 新增模块后:必须 install 一次
+- 新增 `yudao-module-xxx` 或改依赖后:
+  - `mvn -pl yudao-module-xxx -am -DskipTests install`
+- 否则 VS Code / Maven 会提示本地仓库缺 jar(`~/.m2` 不存在)。
+
+### 1.4 前端 baseURL:必须指向后端,不要请求打到 5173
+- `.env.local` 必须配置(示例):
+  - `VITE_BASE_URL='http://127.0.0.1:48080'`
+  - `VITE_API_URL=/admin-api`
+- 如验证码接口 404(打到 5173),优先检查 baseURL 与代理。
+
+---
+
+## 2. 项目开发总流程(强制遵循的“从 0 到 1”)
+
+当你接到一个业务模块(如 IQC 申请列表)需求时,按以下顺序执行:
+
+### 2.1 先定“接口契约”(VO),再定“数据映射”(DO)
+1) 明确前端需要的字段与含义(含显示名/枚举中文/时间格式)。  
+2) **先创建 VO**:`*RespVO` / `*PageReqVO` 等。  
+3) 之后再考虑 DO / entity / 表字段映射(如果需要)。
+
+### 2.2 模块边界:优先拆独立模块
+- 建议将业务放入独立模块,例如:
+  - `yudao-module-qms`(质量相关)
+- 目的:隔离依赖、便于发布、避免污染 `system` 等核心模块。
+
+### 2.3 后端开发顺序(固定)
+1) Controller:定义路由与入参(分页、筛选)。  
+2) Service:定义业务接口与实现(先跑通空分页也行)。  
+3) Mapper + XML:落地 SQL(再逐步补齐 join/聚合)。  
+4) 权限/租户:对齐项目现有注解与过滤器,不要私自绕过。  
+5) Swagger/Knife4j:确保接口可在文档里调通。
+
+### 2.4 前端开发顺序(固定)
+1) API 封装:`src/api/...` 定义请求与返回类型。  
+2) 页面与路由:保证 list/detail/edit 等页面可访问。  
+3) 菜单/权限:如走动态路由,确保后端菜单配置可生成路由。  
+4) 联调:Network 检查 URL 是否正确(不是 5173)。
+
+---
+
+## 3. 动态路由 / 菜单(最常见 404 的根因与规则)
+
+### 3.1 菜单 path 规则(硬规则)
+- **子菜单 path 不要以 “/” 开头**,并且必须相对父菜单。
+- 否则最终路由拼接会错,导致“返回列表 404”。
+
+### 3.2 动态路由缓存
+- 前端通常缓存 `roleRouters`(或类似字段)。
+- **菜单/路由改动后必须清缓存**(localStorage/sessionStorage)并刷新,否则仍旧 404。
+
+### 3.3 “返回列表 404”的诊断 checklist
+当你发现“新增/编辑/查看后返回列表 404”:
+1) 打开控制台打印当前注册 routes,确认是否存在 `/xxx/list`。  
+2) 若只有 new/edit/view,而没有 list,则:  
+   - 要么 list 页面没注册路由  
+   - 要么动态路由菜单没下发 list  
+3) 修复后清缓存再验证。
+
+---
+
+## 4. IQC 申请列表的字段口径与 SQL 聚合规范(可复用模板)
+
+### 4.1 列表主键与显示
+- 返回给前端的 `id` 用 `FBILLNO`(或按需求指定的业务单号),而非数据库自增 id(除非前端明确要求)。
+
+### 4.2 申请人字段
+- `FAPPLYUSER` 存用户 id,列表显示需要 join `system_users.id -> system_users.username`。
+
+### 4.3 状态聚合(示例规则)
+- 主表状态由子表汇总得出(示例优先级):
+  1) 若任一子表为“检验中” → 主表“检验中”
+  2) 否则若所有子表“检验完成” → 主表“检验完成”
+  3) 否则 → 主表“待检验”
+- 需要同时返回:子表总数、完成数、检验中数(便于前端展示与筛选)。
+
+> 注意:状态中文化建议由后端返回中文(避免前端二次映射出错),除非项目规范要求前端映射。
+
+---
+
+## 5. 交付时必须包含的自校验(不写=未完成)
+
+### 5.1 后端自检命令(必须给出)
+- 编译:
+  - `mvn -pl yudao-server -am -DskipTests package`
+- 新模块依赖安装(如新增模块):
+  - `mvn -pl yudao-module-qms -am -DskipTests install`
+- 启动:
+  - `cd yudao-server && mvn -DskipTests spring-boot:run`
+
+### 5.2 接口自检(必须给出)
+- 以 Swagger/Knife4j 或 curl 方式,提供:
+  - URL
+  - Header(token、tenant-id)
+  - 请求参数示例
+  - 返回 JSON 示例(至少包含关键字段)
+
+### 5.3 前端自检(必须给出)
+- 启动:
+  - `pnpm i`
+  - `pnpm dev`
+- 检查点:
+  1) Network 请求 baseURL 是否指向后端端口
+  2) 列表页能否加载
+  3) 新增/编辑/查看返回列表是否 404
+  4) 清缓存后动态路由是否生效
+
+---
+
+## 6. Codex 开发提示词模板(复制即用)
+
+> 你可以直接把下面这一段当作每次给 Codex 的 Prompt 开头。
+
+### 6.1 通用开发 Prompt
+- 任务:在当前项目(WSL + VS Code Remote-WSL + 多模块 Maven + pnpm 前端)下,实现【填业务功能】。
+- 约束:
+  1) 禁止单文件运行 Spring Boot;必须通过 Maven 启动与编译校验。
+  2) 若新增模块或依赖,必须执行 `mvn -pl <module> -am -DskipTests install`。
+  3) 前端 baseURL 必须指向 `http://127.0.0.1:48080`(或项目实际端口)。
+  4) 动态路由:子菜单 path 不能以 `/` 开头;改菜单后清缓存验证。
+- 输出要求(必须逐项给出):
+  1) 任务拆解清单
+  2) 修改文件路径列表 + 关键 diff
+  3) 自校验命令与预期结果(后端/前端/接口)
+  4) 风险与回滚方案
+
+### 6.2 IQC 申请列表 Prompt(示例)
+- 任务:实现 IQC 来料检验“申请列表”接口与前端页面联调,要求:
+  - 列表主键返回 `FBILLNO`(作为 `id`)。
+  - `FAPPLYUSER` 关联 `system_users` 显示申请人 username。
+  - 状态从子表聚合得到,并返回中文:待检验/检验中/检验完成。
+  - 返回分页字段符合芋道分页规范。
+- 交付:接口可在 Swagger 调通,前端列表可加载且“返回列表”不 404。
+
+---
+
+## 7. 快速故障定位(遇到问题先按这里排)
+
+### 7.1 后端启动类报错
+- `package org.springframework.boot does not exist`:
+  - 你用错了运行方式(单文件 javac),改用 Maven 启动。
+- `No plugin found for prefix 'spring-boot'`:
+  - 你在聚合根目录运行了,cd 到 `yudao-server` 或用插件全坐标。
+- “缺 jar / m2 不存在”:
+  - 新增模块未 install:`mvn -pl <module> -am -DskipTests install`
+
+### 7.2 前端接口 404
+- 请求打到 5173:
+  - baseURL 未配置或 env 没生效,检查 `.env.local`,重启 dev。
+
+### 7.3 页面返回列表 404
+- routes 没有 list:
+  - 路由未注册或动态菜单未下发 list。
+- 改菜单没生效:
+  - 清 `roleRouters` 等缓存,硬刷新。
+
+---
+
+## 8. 你必须遵循的“完成定义”(Definition of Done)
+
+只有同时满足以下条件才算完成:
+
+---
+
+## 9. 今日问题记录(IPQC 任务 / 2026-01-22~23)
+
+### 9.1 后端 500:多租户自动拼接 tenant_id
+- 现象:`/admin-api/qms/ipqc-task/page` 报 500,日志里出现 `Unknown column 'tenant_id'`。
+- 根因:多租户拦截器自动追加 `tenant_id`,但 `WorkOrdMaster / ItemMaster / mes_morder / qms_gcjyd` 无该字段。
+- 处理:在 `yudao-server/src/main/resources/application.yaml` 的 `yudao.tenant.ignore-tables` 增加这四张表,避免注入。
+
+### 9.2 后端 404:接口未注册
+- 现象:请求命中 `127.0.0.1:48080` 仍返回 `{"code":404,"msg":"请求地址不存在:admin-api/qms/ipqc-task/page"}`。
+- 根因:本地运行的 `yudao-module-qms` jar 不是最新,`IpqcTaskController` 未被打包进运行时。
+- 处理:
+  1) 先执行 `mvn -pl yudao-module-qms -am -DskipTests install`  
+  2) 再重启 `yudao-server`
+  3) 可用 `jar tf ~/.m2/repository/.../yudao-module-qms-*.jar | rg IpqcTaskController` 验证
+
+### 9.3 后端启动失败:端口占用
+- 现象:启动时报 `Port 48080 was already in use`。
+- 处理:停止旧进程(`ss -ltnp | rg :48080` 找 PID,再 `kill <pid>`),再启动。
+
+### 9.4 VS Code JMX 刷新失败提示
+- 现象:`Failed to refresh live data from process service:jmx...`
+- 结论:仅 VS Code Spring Boot Dashboard 监控失败,不影响接口功能,可忽略或重连监控。
+
+### 9.5 验证方法(最小闭环)
+- 后端直连验证:  
+  `curl "http://127.0.0.1:48080/admin-api/qms/ipqc-task/page?pageNo=1&pageSize=20&status=pending"`
+- 预期返回:  
+  未登录返回 `401`;登录后应返回 `code=0` 且有分页结构。
+
+- [ ] 后端 `mvn -pl yudao-server -am -DskipTests package` 通过  
+- [ ] 后端启动后接口可访问(Swagger/curl)  
+- [ ] 前端启动后列表页可打开、可加载数据  
+- [ ] 新增/编辑/查看后返回列表不 404  
+- [ ] 文档包含:改动文件列表 + 验证步骤 + 回滚方式  
+
+---
+
+> 维护建议:每做完一个模块,把“字段口径/状态规则/路由菜单坑点”沉淀在本文件的对应章节,逐步形成团队的工程手册。

+ 50 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/IpqcTaskController.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.qms.controller.admin.ipqc;
+
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskPageReqVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskStartReqVO;
+import cn.iocoder.yudao.module.qms.service.ipqc.IpqcTaskService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - IPQC 过程检验任务")
+@RestController
+@RequestMapping("/qms/ipqc-task")
+@Validated
+public class IpqcTaskController {
+
+    @Resource
+    private IpqcTaskService ipqcTaskService;
+
+    @GetMapping("/page")
+    @Operation(summary = "获取过程检验任务分页")
+    @PreAuthorize("@ss.hasPermission('qms:ipqc:task:list')")
+    public CommonResult<PageResult<IpqcTaskRespVO>> getIpqcTaskPage(@Valid IpqcTaskPageReqVO pageReqVO) {
+        return success(ipqcTaskService.getIpqcTaskPage(pageReqVO));
+    }
+
+    @PostMapping("/start")
+    @Operation(summary = "发起过程检验任务流程")
+    @PreAuthorize("@ss.hasPermission('qms:ipqc:task:list')")
+    public CommonResult<String> startIpqcTaskProcess(@Valid @RequestBody IpqcTaskStartReqVO reqVO) {
+        String processInstanceId = ipqcTaskService.startIpqcTaskProcess(reqVO.getId());
+        if (processInstanceId == null) {
+            return CommonResult.error(GlobalErrorCodeConstants.NOT_FOUND.getCode(), "任务不存在");
+        }
+        return success(processInstanceId);
+    }
+}

+ 16 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/vo/IpqcTaskPageReqVO.java

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IPQC 过程检验任务分页 Request VO")
+@Data
+public class IpqcTaskPageReqVO extends PageParam {
+
+    @Schema(description = "任务状态(pending/processing/completed)")
+    private String status;
+
+    @Schema(description = "工厂编号/域", example = "1001")
+    private String factoryId;
+}

+ 24 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/vo/IpqcTaskProcessInfoVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IPQC 过程检验任务流程信息 VO")
+@Data
+public class IpqcTaskProcessInfoVO {
+
+    @Schema(description = "任务编号")
+    private Long id;
+
+    @Schema(description = "单据编号")
+    private String applicationId;
+
+    @Schema(description = "生产订单/工单号")
+    private String workOrder;
+
+    @Schema(description = "物料编码")
+    private String materialCode;
+
+    @Schema(description = "批次")
+    private String batch;
+}

+ 65 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/vo/IpqcTaskRespVO.java

@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IPQC 过程检验任务 Response VO")
+@Data
+public class IpqcTaskRespVO {
+
+    @Schema(description = "任务编号")
+    private Long id;
+
+    @Schema(description = "单据编号")
+    private String applicationId;
+
+    @Schema(description = "任务状态(pending/processing/completed)")
+    private String status;
+
+    @Schema(description = "生产订单/工单号")
+    private String workOrder;
+
+    @Schema(description = "工序")
+    private String process;
+
+    @Schema(description = "工步")
+    private String step;
+
+    @Schema(description = "线体")
+    private String line;
+
+    @Schema(description = "工位")
+    private String station;
+
+    @Schema(description = "设备")
+    private String equipment;
+
+    @Schema(description = "物料编码")
+    private String materialCode;
+
+    @Schema(description = "物料名称")
+    private String materialName;
+
+    @Schema(description = "批次")
+    private String batch;
+
+    @Schema(description = "负责人")
+    private String responsibleName;
+
+    @Schema(description = "优先级(high/medium/low)")
+    private String priority;
+
+    @Schema(description = "开始时间")
+    private LocalDateTime startTime;
+
+    @Schema(description = "完成时间")
+    private LocalDateTime completeTime;
+
+    @Schema(description = "备注")
+    private String remark;
+
+    @Schema(description = "流程实例编号")
+    private String processInstanceId;
+}

+ 14 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/ipqc/vo/IpqcTaskStartReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IPQC 过程检验任务发起流程 Request VO")
+@Data
+public class IpqcTaskStartReqVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "任务编号不能为空")
+    private Long id;
+}

+ 28 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/dal/mysql/ipqc/IpqcTaskMapper.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.qms.dal.mysql.ipqc;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskPageReqVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskProcessInfoVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskRespVO;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+@Mapper
+public interface IpqcTaskMapper {
+
+    @TenantIgnore
+    Page<IpqcTaskRespVO> selectTaskPage(Page<IpqcTaskRespVO> page, @Param("req") IpqcTaskPageReqVO req);
+
+    @TenantIgnore
+    IpqcTaskProcessInfoVO selectTaskProcessInfo(@Param("id") Long id);
+
+    default PageResult<IpqcTaskRespVO> selectTaskPage(IpqcTaskPageReqVO reqVO) {
+        Page<IpqcTaskRespVO> page = MyBatisUtils.buildPage(reqVO);
+        IPage<IpqcTaskRespVO> result = selectTaskPage(page, reqVO);
+        return new PageResult<>(result.getRecords(), result.getTotal());
+    }
+}

+ 12 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/ipqc/IpqcTaskService.java

@@ -0,0 +1,12 @@
+package cn.iocoder.yudao.module.qms.service.ipqc;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskPageReqVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskRespVO;
+
+public interface IpqcTaskService {
+
+    PageResult<IpqcTaskRespVO> getIpqcTaskPage(IpqcTaskPageReqVO pageReqVO);
+
+    String startIpqcTaskProcess(Long taskId);
+}

+ 58 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/ipqc/IpqcTaskServiceImpl.java

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.qms.service.ipqc;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskPageReqVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskProcessInfoVO;
+import cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskRespVO;
+import cn.iocoder.yudao.module.qms.dal.mysql.ipqc.IpqcTaskMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Service
+public class IpqcTaskServiceImpl implements IpqcTaskService {
+
+    public static final String PROCESS_KEY = "qms_ipqc_task";
+
+    @Resource
+    private IpqcTaskMapper ipqcTaskMapper;
+
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
+
+    @Override
+    public PageResult<IpqcTaskRespVO> getIpqcTaskPage(IpqcTaskPageReqVO pageReqVO) {
+        return ipqcTaskMapper.selectTaskPage(pageReqVO);
+    }
+
+    @Override
+    public String startIpqcTaskProcess(Long taskId) {
+        if (taskId == null) {
+            return null;
+        }
+        IpqcTaskProcessInfoVO taskInfo = ipqcTaskMapper.selectTaskProcessInfo(taskId);
+        if (taskInfo == null) {
+            return null;
+        }
+
+        Map<String, Object> variables = new HashMap<>();
+        variables.put("taskId", taskInfo.getId());
+        variables.put("applicationId", taskInfo.getApplicationId());
+        variables.put("workOrder", taskInfo.getWorkOrder());
+        variables.put("materialCode", taskInfo.getMaterialCode());
+        variables.put("batch", taskInfo.getBatch());
+
+        Long userId = getLoginUserId();
+        return processInstanceApi.createProcessInstance(userId,
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(PROCESS_KEY)
+                        .setBusinessKey(String.valueOf(taskInfo.getId()))
+                        .setVariables(variables));
+    }
+}

+ 88 - 0
yudao-module-qms/src/main/resources/mapper/ipqc/IpqcTaskMapper.xml

@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.qms.dal.mysql.ipqc.IpqcTaskMapper">
+
+    <select id="selectTaskPage" resultType="cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskRespVO">
+        SELECT
+            fr.recid AS id,
+            fr.Drawing AS applicationId,
+            fr.WorkOrd AS workOrder,
+            fr.Typed AS process,
+            fr.WoTyped AS step,
+            fr.Project AS line,
+            NULL AS station,
+            NULL AS equipment,
+            fr.ItemNum AS materialCode,
+            im.Descr AS materialName,
+            fr.LotSerial AS batch,
+            fr.lbrvar AS responsibleName,
+            CASE
+                WHEN mo.MaterialSituation IN ('high', 'H', 'A', '1') THEN 'high'
+                WHEN mo.MaterialSituation IN ('medium', 'M', 'B', '2') THEN 'medium'
+                WHEN mo.MaterialSituation IN ('low', 'L', 'C', '3') THEN 'low'
+                ELSE 'medium'
+            END AS priority,
+            fr.OrdDate AS startTime,
+            fr.DueDate AS completeTime,
+            fr.Remark AS remark,
+            NULL AS processInstanceId,
+            CASE d.status_rank
+                WHEN 3 THEN 'processing'
+                WHEN 2 THEN 'completed'
+                ELSE 'pending'
+            END AS status
+        FROM
+        (
+            SELECT
+                `Domain`,
+                WorkOrd,
+                ItemNum,
+                LotSerial,
+                SUM(IFNULL(QtyOrded, 0))     AS sum_QtyOrded,
+                SUM(IFNULL(QtyCompleted, 0)) AS sum_QtyCompleted,
+                COUNT(*)                     AS merge_cnt,
+                MIN(recid)                   AS first_recid
+            FROM WorkOrdMaster
+            WHERE IFNULL(Status, '') &lt;&gt; ''
+              AND Status &lt;&gt; 'c'
+            GROUP BY `Domain`, WorkOrd, ItemNum, LotSerial
+        ) AS g
+        INNER JOIN WorkOrdMaster fr
+            ON fr.recid = g.first_recid
+        LEFT JOIN ItemMaster im
+            ON im.ItemNum = fr.ItemNum
+           AND im.`Domain` = fr.`Domain`
+        LEFT JOIN mes_morder mo
+            ON mo.morder_no = fr.WorkOrd
+           AND mo.factory_id = fr.`Domain`
+        LEFT JOIN (
+            SELECT
+                lydjbh,
+                scph,
+                MAX(
+                    CASE
+                        WHEN `status` = '检验中' THEN 3
+                        WHEN `status` = '检验完成' THEN 2
+                        ELSE 1
+                    END
+                ) AS status_rank
+            FROM qms_gcjyd
+            GROUP BY lydjbh, scph
+        ) d
+            ON d.scph = fr.LotSerial
+           AND d.lydjbh = fr.WorkOrd
+        ORDER BY fr.OrdDate DESC, fr.recid DESC
+    </select>
+
+    <select id="selectTaskProcessInfo" resultType="cn.iocoder.yudao.module.qms.controller.admin.ipqc.vo.IpqcTaskProcessInfoVO">
+        SELECT
+            a.recid AS id,
+            a.Drawing AS applicationId,
+            a.WorkOrd AS workOrder,
+            a.ItemNum AS materialCode,
+            a.LotSerial AS batch
+        FROM WorkOrdMaster a
+        WHERE a.recid = #{id}
+    </select>
+
+</mapper>

+ 5 - 1
yudao-server/src/main/resources/application.yaml

@@ -320,6 +320,10 @@ yudao:
       - ASNBOLShipperDetail
       - crm_seorder
       - crm_seorderentry
+      - WorkOrdMaster
+      - ItemMaster
+      - mes_morder
+      - qms_gcjyd
     ignore-caches:
       - user_role_ids
       - permission_menu_ids
@@ -355,4 +359,4 @@ yudao:
     message-bus:
       type: redis # 消息总线的类型
 
-debug: false
+debug: false

+ 0 - 0
yudao-ui/yudao-ui-admin-vue3/IQC来料检验任务流程开发说明.md


+ 0 - 0
yudao-ui/yudao-ui-admin-vue3/Order_Change_流程开发说明.md


+ 35 - 0
yudao-ui/yudao-ui-admin-vue3/src/api/qms/ipqc/task/index.ts

@@ -0,0 +1,35 @@
+import request from '@/config/axios'
+
+export interface IpqcTaskPageReqVO extends PageParam {
+  status?: string
+  factoryId?: string
+}
+
+export interface IpqcTaskRespVO {
+  id: number | string
+  applicationId?: string
+  status: string
+  workOrder?: string
+  process?: string
+  step?: string
+  line?: string
+  station?: string
+  equipment?: string
+  materialCode?: string
+  materialName?: string
+  batch?: string
+  responsibleName?: string
+  priority?: string
+  startTime?: string | number
+  completeTime?: string | number
+  remark?: string
+  processInstanceId?: string
+}
+
+export const getIpqcTaskPage = (params: IpqcTaskPageReqVO) => {
+  return request.get<PageResult<IpqcTaskRespVO[]>>({ url: '/qms/ipqc-task/page', params })
+}
+
+export const startIpqcTaskProcess = (id: number | string) => {
+  return request.post<string>({ url: '/qms/ipqc-task/start', data: { id } })
+}

+ 0 - 1
yudao-ui/yudao-ui-admin-vue3/src/config/qmsModules.ts

@@ -101,7 +101,6 @@ export const QMS_MODULES: Record<string, any> = {
     systemTitle: 'IPQC 过程检验管理',
     defaultRoute: '/qms/ipqc/task/list',
     navItems: [
-      { key: 'applications', label: '过程检验申请', path: '/qms/ipqc/apply/list' },
       { key: 'tasks', label: '过程检验任务', path: '/qms/ipqc/task/list' },
       { key: 'favorites', label: '我的关注', path: '/qms/ipqc/favorites' },
       { key: 'help', label: '帮助', path: '/qms/ipqc/help' }

+ 0 - 1
yudao-ui/yudao-ui-admin-vue3/src/router/index.ts

@@ -16,7 +16,6 @@ const staticRouteNames = collectRouteNames(remainingRouter as RouteRecordRaw[])
 
 const qmsFallbackRoutes: RouteRecordRaw[] = [
   { path: 'iqc/apply/list', name: 'IqcApplicationList', component: () => import('@/views/qms/ApplicationList.vue'), meta: { module: 'iqc', title: '来料检验申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
-  { path: 'ipqc/apply/list', name: 'IpqcApplicationList', component: () => import('@/views/qms/ApplicationList.vue'), meta: { module: 'ipqc', title: '过程检验申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
   { path: 'fqc/apply/list', name: 'FqcApplicationList', component: () => import('@/views/qms/ApplicationList.vue'), meta: { module: 'fqc', title: '成品检验申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
   { path: 'iqc/task/list', name: 'IqcTaskList', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'iqc', title: '来料检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
   { path: 'ipqc/task/list', name: 'IpqcTaskList', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'ipqc', title: '过程检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },

+ 13 - 4
yudao-ui/yudao-ui-admin-vue3/src/router/modules/remaining.ts

@@ -734,10 +734,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
       { path: 'iqc/favorites', name: 'IqcFavorites', component: () => import('@/views/qms/Favorites.vue'), meta: { module: 'iqc', title: '我的关注', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       { path: 'iqc/help', name: 'IqcHelp', component: () => import('@/views/qms/Help.vue'), meta: { module: 'iqc', title: '帮助', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       // IPQC 过程检验
-      { path: 'ipqc/apply/list', name: 'IpqcApplicationList', component: () => import('@/views/qms/ApplicationList.vue'), meta: { module: 'ipqc', title: '过程检验申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
-      { path: 'ipqc/apply/new', name: 'IpqcApplicationNew', component: () => import('@/views/qms/ApplicationForm.vue'), meta: { module: 'ipqc', title: '新建申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms', mode: 'create' } },
-      { path: 'ipqc/apply/edit/:id', name: 'IpqcApplicationEdit', component: () => import('@/views/qms/ApplicationForm.vue'), meta: { module: 'ipqc', title: '编辑申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms', mode: 'edit' } },
-      { path: 'ipqc/apply/view/:id', name: 'IpqcApplicationView', component: () => import('@/views/qms/ApplicationForm.vue'), meta: { module: 'ipqc', title: '查看申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms', mode: 'view' } },
       { path: 'ipqc/task/list', name: 'IpqcTaskList', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'ipqc', title: '过程检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       { path: 'ipqc/favorites', name: 'IpqcFavorites', component: () => import('@/views/qms/Favorites.vue'), meta: { module: 'ipqc', title: '我的关注', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       { path: 'ipqc/help', name: 'IpqcHelp', component: () => import('@/views/qms/Help.vue'), meta: { module: 'ipqc', title: '帮助', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
@@ -771,6 +767,19 @@ const remainingRouter: AppRouteRecordRaw[] = [
     ]
   },
   {
+    // S6 菜单路径兼容,避免点击侧边栏 404(IPQC 过程检验)
+    path: '/s6',
+    component: Layout,
+    name: 'S6Center',
+    meta: { hidden: true },
+    children: [
+      { path: 'ipqc/task/list', name: 'S6IpqcTaskList', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'ipqc', title: '过程检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/s6' } },
+      { path: 'ipqc/qms/ipqc/task/list', name: 'S6IpqcTaskListCompat', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'ipqc', title: '过程检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/s6' } },
+      { path: 'ipqc/favorites', name: 'S6IpqcFavorites', component: () => import('@/views/qms/Favorites.vue'), meta: { module: 'ipqc', title: '我的关注', noCache: true, hidden: true, canTo: true, activeMenu: '/s6' } },
+      { path: 'ipqc/help', name: 'S6IpqcHelp', component: () => import('@/views/qms/Help.vue'), meta: { module: 'ipqc', title: '帮助', noCache: true, hidden: true, canTo: true, activeMenu: '/s6' } }
+    ]
+  },
+  {
     path: '/s0',
     component: Layout,
     name: 'S0Center',

+ 11 - 17
yudao-ui/yudao-ui-admin-vue3/src/store/modules/qms/task.ts

@@ -1,6 +1,7 @@
 import { defineStore } from 'pinia'
 import { QMS_MODULE_CODES } from '@/config/qmsModules'
 import { getIqcTaskPage, startIqcTaskProcess } from '@/api/qms/iqc/task'
+import { getIpqcTaskPage, startIpqcTaskProcess } from '@/api/qms/ipqc/task'
 
 const createDefaultFilters = () => ({
   keyword: '', searchMode: 'AND', statusList: [], supplierIds: [], materialCodes: [], batchList: [],
@@ -11,23 +12,12 @@ const createDefaultFilters = () => ({
 const createDefaultPagination = () => ({ currentPage: 1, pageSize: 20, total: 0 })
 
 const createTaskSamples = () => ({
-  iqc: [
-    { id: 'IQCT001', applicationId: 'IQC001', status: 'pending', materialCode: 'M001', materialName: '电阻', specification: '1KΩ ±5%', supplierName: '供应商A', supplierCode: 'SUP001', batch: 'B001', batchCount: 1, location: 'A01-01', responsibleId: '1', responsibleName: '张三', priority: 'high', startTime: null, completeTime: null, remark: '精密电阻,需要特殊检验', arrivalTime: '2024-01-15 08:30:00', quantity: 100, inspectionSpecVersion: 'V1.2' },
-    { id: 'IQCT002', applicationId: 'IQC002', status: 'processing', materialCode: 'M002', materialName: '电容', specification: '100μF 25V', supplierName: '供应商B', supplierCode: 'SUP002', batch: 'B002', batchCount: 2, location: 'A01-02', responsibleId: '2', responsibleName: '李四', priority: 'medium', startTime: '2024-01-15 10:00:00', completeTime: null, remark: '电解电容,常规检验', arrivalTime: '2024-01-15 09:15:00', quantity: 50, inspectionSpecVersion: 'V1.2' },
-    { id: 'IQCT003', applicationId: 'IQC003', status: 'completed', materialCode: 'M003', materialName: '电感', specification: '10mH ±10%', supplierName: '供应商C', supplierCode: 'SUP003', batch: 'B003', batchCount: 1, location: 'A01-03', responsibleId: '3', responsibleName: '王五', priority: 'low', startTime: '2024-01-14 09:00:00', completeTime: '2024-01-14 16:30:00', remark: '电感器件,已完成检验', arrivalTime: '2024-01-14 08:30:00', quantity: 200, inspectionSpecVersion: 'V1.1' }
-  ],
-  ipqc: [
-    { id: 'IPQCT001', applicationId: 'IPQC001', status: 'pending', workOrder: 'WO-202401-001', process: '焊接', step: '点焊', line: 'L01', station: 'W02', equipment: 'EQ-09', materialCode: 'M1201', materialName: '电池模组', batch: 'P20240116A', batchCount: 2, responsibleId: '3', responsibleName: '王五', priority: 'high', startTime: null, completeTime: null, remark: '首件检验,需工程师确认', shift: '夜班', inspectionSpecVersion: 'V2.0' },
-    { id: 'IPQCT002', applicationId: 'IPQC002', status: 'processing', workOrder: 'WO-202401-009', process: '组装', step: '装配', line: 'L03', station: 'W05', equipment: 'EQ-22', materialCode: 'M1402', materialName: '控制板', batch: 'P20240115B', batchCount: 1, responsibleId: '4', responsibleName: '赵六', priority: 'medium', startTime: '2024-01-16 09:20:00', completeTime: null, remark: '巡检批次', shift: '中班', inspectionSpecVersion: 'V2.0' }
-  ],
-  fqc: [
-    { id: 'FQCT001', applicationId: 'FQC001', status: 'pending', productCode: 'P-900-XL', productName: '蓝牙耳机', productModel: 'Pro X', customer: 'ABC Corp', packageBatch: 'BX001', releaseStatus: '未判定', responsibleId: '5', responsibleName: '钱七', priority: 'high', startTime: null, completeTime: null, remark: '客户特采,仅限样机', inspectionSpecVersion: 'V3.4' },
-    { id: 'FQCT002', applicationId: 'FQC002', status: 'processing', productCode: 'P-700-M', productName: '智能手环', productModel: 'Mini', customer: 'XYZ Ltd', packageBatch: 'BX016', releaseStatus: '检验中', responsibleId: '2', responsibleName: '李四', priority: 'medium', startTime: '2024-01-16 14:20:00', completeTime: null, remark: '抽检批次', inspectionSpecVersion: 'V3.3' },
-    { id: 'FQCT003', applicationId: 'FQC001', status: 'completed', productCode: 'P-900-XL', productName: '蓝牙耳机', productModel: 'Pro X', customer: 'ABC Corp', packageBatch: 'BX000', releaseStatus: '合格', responsibleId: '6', responsibleName: '孙八', priority: 'low', startTime: '2024-01-15 13:00:00', completeTime: '2024-01-15 21:40:00', remark: '批量抽检完成', inspectionSpecVersion: 'V3.3' }
-  ]
+  iqc: [],
+  ipqc: [],
+  fqc: []
 })
 
-const REMOTE_MODULES = new Set(['iqc'])
+const REMOTE_MODULES = new Set(['iqc', 'ipqc'])
 const isRemoteModule = (module: string) => REMOTE_MODULES.has(module)
 
 export const useQmsTaskStore = defineStore('qmsTask', {
@@ -77,7 +67,9 @@ export const useQmsTaskStore = defineStore('qmsTask', {
           pageSize: pagination.pageSize,
           status: status && status !== 'all' ? status : undefined
         }
-        const pageResult = await getIqcTaskPage(params)
+        const pageResult = module === 'ipqc'
+          ? await getIpqcTaskPage(params)
+          : await getIqcTaskPage(params)
         const pageData: any = (pageResult as any)?.data ?? pageResult
         this.tasks[module] = pageData?.list || []
         this.pagination[module].total = pageData?.total || 0
@@ -90,7 +82,9 @@ export const useQmsTaskStore = defineStore('qmsTask', {
     setActiveTab(module: string, tab: string) { this.activeTabs[module] = tab },
     async startInspection(module: string, taskId: string) {
       if (isRemoteModule(module)) {
-        const result: any = await startIqcTaskProcess(taskId)
+        const result: any = module === 'ipqc'
+          ? await startIpqcTaskProcess(taskId)
+          : await startIqcTaskProcess(taskId)
         return result?.data ?? result
       }
       return new Promise((resolve, reject) => {

+ 24 - 0
yudao-ui/yudao-ui-admin-vue3/src/utils/routerHelper.ts

@@ -70,6 +70,13 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
   const res: AppRouteRecordRaw[] = []
   const modulesRoutesKeys = Object.keys(modules)
   for (const route of routes) {
+    const routePath = (route.path || '').toLowerCase()
+    const routeName = String(route.name || '')
+    const isIpqcApplyRoute =
+      (routePath.includes('ipqc') && routePath.includes('apply')) || routeName.includes('过程检验申请')
+    if (isIpqcApplyRoute) {
+      continue
+    }
     // 1. 生成 meta 菜单元数据
     const meta = {
       title: route.name,
@@ -81,6 +88,23 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
         route.children.length > 0 &&
         (route.alwaysShow !== undefined ? route.alwaysShow : true)
     } as any
+    if (!meta.module) {
+      if (routePath.includes('ipqc') || routeName.includes('过程检验') || routeName.toLowerCase().includes('ipqc')) {
+        meta.module = 'ipqc'
+      } else if (
+        routePath.includes('iqc') ||
+        routeName.includes('来料检验') ||
+        routeName.toLowerCase().includes('iqc')
+      ) {
+        meta.module = 'iqc'
+      } else if (
+        routePath.includes('fqc') ||
+        routeName.includes('成品检验') ||
+        routeName.toLowerCase().includes('fqc')
+      ) {
+        meta.module = 'fqc'
+      }
+    }
     // 特殊逻辑:如果后端配置的 MenuDO.component 包含 ?,则表示需要传递参数
     // 此时,我们需要解析参数,并且将参数放到 meta.query 中
     // 这样,后续在 Vue 文件中,可以通过 const { currentRoute } = useRouter() 中,通过 meta.query 获取到参数

+ 11 - 3
yudao-ui/yudao-ui-admin-vue3/src/views/system/menu/index.vue

@@ -84,7 +84,7 @@
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
-  <MenuForm ref="formRef" @success="getList" />
+  <MenuForm ref="formRef" @success="handleMenuSaved" />
 </template>
 <script lang="tsx" setup>
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
@@ -272,9 +272,11 @@ const toggleExpandAll = () => {
 }
 
 /** 刷新菜单缓存按钮操作 */
-const refreshMenu = async () => {
+const refreshMenu = async (silent = false) => {
   try {
-    await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
+    if (!silent) {
+      await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
+    }
     // 清空,从而触发刷新
     wsCache.delete(CACHE_KEY.USER)
     wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
@@ -283,6 +285,12 @@ const refreshMenu = async () => {
   } catch {}
 }
 
+/** 菜单保存成功:刷新列表并自动更新菜单缓存 */
+const handleMenuSaved = async () => {
+  await getList()
+  await refreshMenu(true)
+}
+
 /** 删除按钮操作 */
 const handleDelete = async (id: number) => {
   try {