Parcourir la source

来料检验任务列表及流程

liuwb il y a 2 mois
Parent
commit
7ad85d91b7
30 fichiers modifiés avec 1419 ajouts et 445 suppressions
  1. 207 0
      IQC来料检验任务流程开发说明.md
  2. 151 0
      IQC来料检验任务流程开发说明.md** (continued)
  3. 199 0
      Order_Change_流程开发说明.md
  4. BIN
      Order_Change_流程开发说明.md:Zone.Identifier
  5. 7 0
      yudao-module-qms/pom.xml
  6. 59 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/IqcTaskController.java
  7. 3 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcApplyDetailRespVO.java
  8. 54 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcTaskDetailRespVO.java
  9. 18 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcTaskPageReqVO.java
  10. 33 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcTaskProcessInfoVO.java
  11. 51 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcTaskRespVO.java
  12. 14 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcTaskStartReqVO.java
  13. 53 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/dal/mysql/iqc/IqcTaskMapper.java
  14. 44 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/IqcTaskService.java
  15. 103 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/IqcTaskServiceImpl.java
  16. 36 0
      yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/listener/IqcTaskStatusListener.java
  17. 1 0
      yudao-module-qms/src/main/resources/mapper/iqc/IqcApplyMapper.xml
  18. 87 0
      yudao-module-qms/src/main/resources/mapper/iqc/IqcTaskMapper.xml
  19. 1 1
      yudao-server/src/main/java/cn/iocoder/yudao/server/YudaoServerApplication.java
  20. 2 0
      yudao-ui/yudao-ui-admin-vue3/src/api/qms/iqc/apply/index.ts
  21. 50 0
      yudao-ui/yudao-ui-admin-vue3/src/api/qms/iqc/task/index.ts
  22. 0 112
      yudao-ui/yudao-ui-admin-vue3/src/config/modules.ts
  23. 10 112
      yudao-ui/yudao-ui-admin-vue3/src/config/qmsModules.ts
  24. 3 4
      yudao-ui/yudao-ui-admin-vue3/src/router/modules/remaining.ts
  25. 23 0
      yudao-ui/yudao-ui-admin-vue3/src/store/modules/qms/task.ts
  26. 7 1
      yudao-ui/yudao-ui-admin-vue3/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
  27. 10 3
      yudao-ui/yudao-ui-admin-vue3/src/views/qms/ApplicationForm.vue
  28. 0 164
      yudao-ui/yudao-ui-admin-vue3/src/views/qms/TaskForm.vue
  29. 51 48
      yudao-ui/yudao-ui-admin-vue3/src/views/qms/TaskList.vue
  30. 142 0
      yudao-ui/yudao-ui-admin-vue3/src/views/qms/iqc/task/ProcessDetailForm.vue

+ 207 - 0
IQC来料检验任务流程开发说明.md

@@ -0,0 +1,207 @@
+# IQC 来料检验任务流程接入工作流开发说明(参照 oa_leave)
+
+> 目标:参照请假流程(`oa_leave`)的实现方式,将 IQC 来料检验任务接入工作流,实现任务与流程实例关联、流程状态落库、发起/详情/列表页面对齐,以及流程结束结果监听与业务状态回写。
+
+---
+
+## 0. 流程节点(已确认)
+- 来料检验
+- 领导审批
+- SQE 处理
+
+---
+
+## 1. 流程实例关联字段设计
+
+### 1.1 业务表字段(已确认)
+任务数据当前来自子表:`qms_qcp_insappnentry`(主表:`qms_qcp_inspecapplyn`)。
+
+任务子表使用字段:
+- `process_instance_id`:流程实例 ID(用于与工作流关联)
+- `flowstate`:流程状态(未发起 / 审批中 / 审批完成 / 已驳回 / 已取消)
+
+> 字段名称已确认使用 `process_instance_id` / `flowstate`。
+
+### 1.2 关联关系
+`qms_qcp_insappnentry.process_instance_id` 对应流程引擎历史流程实例表:
+
+- 表:`ACT_HI_PROCINST`
+- 字段:`PROC_INST_ID_`
+
+即:
+- `qms_qcp_insappnentry.process_instance_id` ↔ `ACT_HI_PROCINST.PROC_INST_ID_`
+
+---
+
+## 2. 流程状态字段与回调更新策略
+
+### 2.1 状态字段
+建议使用:
+- `flowstate`:流程状态
+- `FINSPECTSTATUS`:检验状态(业务状态,列表展示用)
+
+示例状态:
+- `flowstate`:未发起 / 审批中 / 审批完成 / 已驳回 / 已取消
+- `FINSPECTSTATUS`:待检验 / 检验中 / 检验完成
+
+### 2.2 状态更新方式
+通过 Listener 监听流程回调并更新 `flowstate`。  
+业务状态 `FINSPECTSTATUS` 可在流程节点触发或流程结束时统一更新。
+
+#### 2.2.1 流程提交后:更新为“审批中”
+```sql
+update qms_qcp_insappnentry
+set flowstate = '审批中'
+where id = '{<TaskEntryId>}';
+```
+
+#### 2.2.2 流程通过后:更新为“审批完成”
+```sql
+update qms_qcp_insappnentry
+set flowstate = '审批完成',
+    FINSPECTSTATUS = '检验完成'
+where id = '{<TaskEntryId>}';
+```
+
+#### 2.2.3 流程取消:回退为“待检验”
+```sql
+update qms_qcp_insappnentry
+set flowstate = '已取消',
+    FINSPECTSTATUS = '待检验'
+where id = '{<TaskEntryId>}';
+```
+
+#### 2.2.4 流程驳回:更新为“检验中”
+```sql
+update qms_qcp_insappnentry
+set flowstate = '已驳回',
+    FINSPECTSTATUS = '检验中'
+where id = '{<TaskEntryId>}';
+```
+
+---
+
+## 3. 后端业务逻辑开发(参照 oa_leave)
+
+### 3.1 参考类
+请假流程后端参考:
+- `BpmOALeaveController`
+- `BpmOALeaveServiceImpl`
+
+IQC 任务流程需实现类似结构的 Controller + Service,并按“业务表 + 流程”方式接入。
+
+### 3.2 流程模型编号(PROCESS_KEY)
+请假:`oa_leave`  
+IQC 任务:`qms_iqc_task`
+
+> 该 Key 必须与流程模型实际配置一致。
+
+### 3.3 创建流程实例
+使用 `BpmProcessInstanceApi` 创建流程实例:
+- `#createProcessInstance(...)`
+
+发起 IQC 任务流程时建议完成:
+- 创建流程实例
+- 回写 `qms_qcp_insappnentry.process_instance_id`
+- 更新 `flowstate = '审批中'`
+
+---
+
+## 4. 前端业务逻辑开发(参照 oa_leave)
+
+### 4.1 参考页面
+请假模块前端参考:
+- 发起:`leave/create.vue`
+- 详情:`leave/detail.vue`
+- 列表:`leave/index.vue`
+
+### 4.2 IQC 任务页面对齐建议
+可沿用现有 QMS 任务页面逻辑:
+- 列表:`TaskList.vue`(已存在)
+- 表单:`TaskForm.vue`(已存在)
+
+接入流程后建议实现:
+- 列表 “开始检验 / 继续检验” → 发起流程(create)
+- 详情查看 → 进入流程详情(detail)
+
+---
+
+## 5. 路由配置(参照 remaining.ts 的请假流程配置)
+
+为 IQC 任务流程新增隐藏路由(create/detail),用于:
+- 从任务列表发起流程
+- 在流程流转/审批中查看业务详情
+
+示例(路径可按实际模块调整):
+```ts
+{
+  path: '/qms',
+  component: Layout,
+  name: 'qms',
+  meta: { hidden: true },
+  children: [
+    {
+      path: 'iqc/task/create',
+      component: () => import('@/views/qms/TaskForm.vue'),
+      name: 'IqcTaskCreate',
+      meta: {
+        noCache: true,
+        hidden: true,
+        canTo: true,
+        title: '发起 IQC 任务',
+        activeMenu: '/qms/iqc/task/list'
+      }
+    },
+    {
+      path: 'iqc/task/detail/:id',
+      component: () => import('@/views/qms/TaskForm.vue'),
+      name: 'IqcTaskDetail',
+      meta: {
+        noCache: true,
+        hidden: true,
+        canTo: true,
+        title: '查看 IQC 任务',
+        activeMenu: '/qms/iqc/task/list'
+      }
+    }
+  ]
+}
+```
+
+---
+
+## 6. 流程结束回调监听与业务状态更新(必须)
+
+### 6.1 监听器机制
+审批结束时(通过 / 不通过 / 取消),后端必须监听最终结果并更新业务表状态。
+
+请假流程实现参考:
+- `BpmOALeaveStatusListener`
+(继承 `BpmProcessInstanceStatusEventListener`)
+
+### 6.2 IQC 任务监听器要求
+需新增监听器(示例命名:`BpmIqcTaskStatusListener`),继承:
+- `BpmProcessInstanceStatusEventListener`
+
+监听流程结束结果并更新:
+- `qms_qcp_insappnentry.flowstate`
+- `qms_qcp_insappnentry.FINSPECTSTATUS`(通过=检验完成;取消=待检验;驳回=检验中)
+
+---
+
+## 7. 交付清单(Checklist)
+
+### 7.1 后端
+- [ ] 定义 `PROCESS_KEY = "qms_iqc_task"`
+- [ ] Controller(参照 `BpmOALeaveController`)
+- [ ] ServiceImpl(参照 `BpmOALeaveServiceImpl`)
+- [ ] 调用 `BpmProcessInstanceApi#createProcessInstance(...)` 创建流程实例
+- [ ] 回写 `process_instance_id`
+- [ ] 提交后/结束后 `flowstate` 更新逻辑(建议走 Listener)
+- [ ] 新增流程结束监听器(继承 `BpmProcessInstanceStatusEventListener`)
+
+### 7.2 前端
+- [ ] 任务列表 “开始检验/继续检验” 触发流程发起
+- [ ] create 页面(复用 `TaskForm.vue` 或新建)
+- [ ] detail 页面(复用 `TaskForm.vue` 或新建)
+- [ ] `router/modules/remaining.ts` 增加 IQC 任务 create/detail 路由

+ 151 - 0
IQC来料检验任务流程开发说明.md** (continued)

@@ -0,0 +1,151 @@
+通过 Listener 监听流程回调并更新 `flowstate`。  
+业务状态 `FINSPECTSTATUS` 可在流程节点触发或流程结束时统一更新。
+
+#### 2.2.1 流程提交后:更新为“审批中”
+```sql
+update qms_qcp_insappnentry
+set flowstate = '审批中'
+where id = '{<TaskEntryId>}';
+```
+
+#### 2.2.2 流程结束后:更新为“审批完成”
+```sql
+update qms_qcp_insappnentry
+set flowstate = '审批完成',
+    FINSPECTSTATUS = '检验完成'
+where id = '{<TaskEntryId>}';
+```
+
+#### 2.2.3 流程驳回/取消(示例)
+```sql
+update qms_qcp_insappnentry
+set flowstate = '已驳回'
+where id = '{<TaskEntryId>}';
+```
+
+---
+
+## 3. 后端业务逻辑开发(参照 oa_leave)
+
+### 3.1 参考类
+请假流程后端参考:
+- `BpmOALeaveController`
+- `BpmOALeaveServiceImpl`
+
+IQC 任务流程需实现类似结构的 Controller + Service,并按“业务表 + 流程”方式接入。
+
+### 3.2 流程模型编号(PROCESS_KEY)
+请假:`oa_leave`  
+IQC 任务(示例):`iqc_task`
+
+> 该 Key 必须与流程模型实际配置一致。
+
+### 3.3 创建流程实例
+使用 `BpmProcessInstanceApi` 创建流程实例:
+- `#createProcessInstance(...)`
+
+发起 IQC 任务流程时建议完成:
+- 创建流程实例
+- 回写 `qms_qcp_insappnentry.process_instance_id`
+- 更新 `flowstate = '审批中'`
+
+---
+
+## 4. 前端业务逻辑开发(参照 oa_leave)
+
+### 4.1 参考页面
+请假模块前端参考:
+- 发起:`leave/create.vue`
+- 详情:`leave/detail.vue`
+- 列表:`leave/index.vue`
+
+### 4.2 IQC 任务页面对齐建议
+可沿用现有 QMS 任务页面逻辑:
+- 列表:`TaskList.vue`(已存在)
+- 表单:`TaskForm.vue`(已存在)
+
+接入流程后建议实现:
+- 列表 “开始检验 / 继续检验” → 发起流程(create)
+- 详情查看 → 进入流程详情(detail)
+
+---
+
+## 5. 路由配置(参照 remaining.ts 的请假流程配置)
+
+为 IQC 任务流程新增隐藏路由(create/detail),用于:
+- 从任务列表发起流程
+- 在流程流转/审批中查看业务详情
+
+示例(路径可按实际模块调整):
+```ts
+{
+  path: '/qms',
+  component: Layout,
+  name: 'qms',
+  meta: { hidden: true },
+  children: [
+    {
+      path: 'iqc/task/create',
+      component: () => import('@/views/qms/TaskForm.vue'),
+      name: 'IqcTaskCreate',
+      meta: {
+        noCache: true,
+        hidden: true,
+        canTo: true,
+        title: '发起 IQC 任务',
+        activeMenu: '/qms/iqc/task/list'
+      }
+    },
+    {
+      path: 'iqc/task/detail/:id',
+      component: () => import('@/views/qms/TaskForm.vue'),
+      name: 'IqcTaskDetail',
+      meta: {
+        noCache: true,
+        hidden: true,
+        canTo: true,
+        title: '查看 IQC 任务',
+        activeMenu: '/qms/iqc/task/list'
+      }
+    }
+  ]
+}
+```
+
+---
+
+## 6. 流程结束回调监听与业务状态更新(必须)
+
+### 6.1 监听器机制
+审批结束时(通过 / 不通过 / 取消),后端必须监听最终结果并更新业务表状态。
+
+请假流程实现参考:
+- `BpmOALeaveStatusListener`
+(继承 `BpmProcessInstanceStatusEventListener`)
+
+### 6.2 IQC 任务监听器要求
+需新增监听器(示例命名:`BpmIqcTaskStatusListener`),继承:
+- `BpmProcessInstanceStatusEventListener`
+
+监听流程结束结果并更新:
+- `qms_qcp_insappnentry.flowstate`
+- `qms_qcp_insappnentry.FINSPECTSTATUS`(按业务规则设置为“检验完成”或“待检验”)
+
+---
+
+## 7. 交付清单(Checklist)
+
+### 7.1 后端
+- [ ] 定义 `PROCESS_KEY = "iqc_task"`(以实际模型为准)
+- [ ] Controller(参照 `BpmOALeaveController`)
+- [ ] ServiceImpl(参照 `BpmOALeaveServiceImpl`)
+- [ ] 调用 `BpmProcessInstanceApi#createProcessInstance(...)` 创建流程实例
+- [ ] 回写 `process_instance_id`
+- [ ] 提交后/结束后 `flowstate` 更新逻辑(建议走 Listener)
+- [ ] 新增流程结束监听器(继承 `BpmProcessInstanceStatusEventListener`)
+
+### 7.2 前端
+- [ ] 任务列表 “开始检验/继续检验” 触发流程发起
+- [ ] create 页面(复用 `TaskForm.vue` 或新建)
+- [ ] detail 页面(复用 `TaskForm.vue` 或新建)
+- [ ] `router/modules/remaining.ts` 增加 IQC 任务 create/detail 路由

+ 199 - 0
Order_Change_流程开发说明.md

@@ -0,0 +1,199 @@
+# 订单变更流程(Order_Change)接入工作流开发说明(参照 oa_leave)
+
+> 目标:参照请假流程(`oa_leave`)的实现方式,继续开发订单变更流程(`Order_Change`)的前后端,实现业务表与流程实例关联、流程状态落库、发起/详情/列表页面对齐,以及流程结束结果监听与业务状态回写。
+
+---
+
+## 1. 流程实例关联字段设计
+
+### 1.1 业务表字段
+业务表:`crm_seorder`
+
+字段:`process_instance_id`  
+用途:关联流程实例。
+
+### 1.2 关联关系
+`crm_seorder.process_instance_id` 对应流程引擎历史流程实例表:
+
+- 表:`ACT_HI_PROCINST`
+- 字段:`PROC_INST_ID_`
+
+即:
+
+- `crm_seorder.process_instance_id` ↔ `ACT_HI_PROCINST.PROC_INST_ID_`
+
+---
+
+## 2. 流程状态字段与回调更新策略
+
+### 2.1 状态字段
+业务表:`crm_seorder`
+
+字段:`flowstate`  
+用途:记录流程状态(例如:未发起 / 审批中 / 审批完成)。
+
+### 2.2 状态更新方式
+通过 Listener 监听流程引擎回调,并更新 `flowstate`。
+
+#### 2.2.1 流程提交后:更新为“审批中”
+```sql
+update crm_seorder
+set flowstate='审批中'
+where id='{<InstanceId>}'
+```
+
+#### 2.2.2 流程结束后:更新为“审批完成”,并重置订单明细进度为 0
+```sql
+update crm_seorder
+set flowstate='审批完成'
+where id='{<InstanceId>}';
+
+update crm_seorderentry
+set progress=0
+where seorder_id='{<InstanceId>}'
+```
+
+---
+
+## 3. 后端业务逻辑开发(参照 oa_leave)
+
+### 3.1 参考类
+请假流程后端可参考:
+
+- `BpmOALeaveController`
+- `BpmOALeaveServiceImpl`
+
+订单变更流程需实现类似结构的 Controller + Service,并按“业务表 + 流程”方式接入。
+
+### 3.2 流程模型编号(PROCESS_KEY)
+- 请假:`oa_leave`
+- 订单变更:`Order_Change`
+
+订单变更模块需定义静态变量:
+- `PROCESS_KEY = "Order_Change"`
+
+### 3.3 创建流程实例
+使用 `BpmProcessInstanceApi` 提供的创建方法:
+
+- `#createProcessInstance(...)`:用于创建流程实例
+
+订单变更发起时建议完成:
+- 创建流程实例
+- 回写 `crm_seorder.process_instance_id`
+- 同步更新 `flowstate`(建议通过 listener 统一处理,或创建成功后立即设置)
+
+---
+
+## 4. 前端业务逻辑开发(参照 oa_leave,但发起模式保持不变)
+
+### 4.1 参考页面
+请假模块前端可参考:
+
+- 发起界面:`leave/create.vue`
+- 详情界面:`leave/detail.vue`
+- 列表界面:`leave/index.vue`
+
+### 4.2 发起模式要求(订单变更特有约束)
+订单变更流程的发起模式不可修改,仍需:
+
+- 在订单行点击“变更”按钮发起流程  
+- 点击按钮后,可改为打开订单变更流程标签页  
+- 剩余操作形式(创建、查看、表单结构)可与请假流程一致
+
+---
+
+## 5. 路由配置(参照 remaining.ts 的请假流程配置)
+
+### 5.1 请假 create/detail 路由定义示例
+请假业务流程表单在 `router/modules/remaining.ts` 中定义 `create.vue` 和 `detail.vue` 路由,示例:
+
+```ts
+{
+  path: '/bpm',
+  component: Layout,
+  name: 'bpm',
+  meta: {
+    hidden: true
+  },
+  children: [
+    {
+      path: 'oa/leave/create',
+      component: () => import('@/views/bpm/oa/leave/create.vue'),
+      name: 'OALeaveCreate',
+      meta: {
+        noCache: true,
+        hidden: true,
+        canTo: true,
+        title: '发起 OA 请假',
+        activeMenu: '/bpm/oa/leave'
+      }
+    },
+    {
+      path: 'oa/leave/detail',
+      component: () => import('@/views/bpm/oa/leave/detail.vue'),
+      name: 'OALeaveDetail',
+      meta: {
+        noCache: true,
+        hidden: true,
+        canTo: true,
+        title: '查看 OA 请假',
+        activeMenu: '/bpm/oa/leave'
+      }
+    }
+  ]
+}
+```
+
+### 5.2 订单变更路由要求
+为订单变更流程新增类似的隐藏路由(create/detail),用于:
+
+- 从订单行按钮跳转打开流程发起页面(create)
+- 在流程流转/审批中查看业务详情(detail)
+
+---
+
+## 6. 流程结束回调监听与业务状态更新(必须)
+
+### 6.1 监听器机制
+审批结束时(通过 / 不通过 / 取消),后端必须监听最终结果,并更新业务表状态。
+
+请假流程实现参考:
+
+- `BpmOALeaveStatusListener`
+
+它继承框架封装的监听器抽象类:
+
+- `BpmProcessInstanceStatusEventListener`
+
+该监听器特点:
+- 监听流程实例最终结束状态
+- 流程实例结束时回调通知最终结果(通过/不通过/取消)
+
+### 6.2 订单变更监听器要求
+订单变更业务接入工作流并需要监听审批结果时,必须:
+
+- 继承 `BpmProcessInstanceStatusEventListener`
+- 实现订单变更监听器(示例命名:`BpmOrderChangeStatusListener`)
+- 在流程结束回调里更新:
+  - `crm_seorder.flowstate`
+  - `crm_seorderentry.progress`(如需求:流程结束后置 0)
+
+---
+
+## 7. 交付清单(Checklist)
+
+### 7.1 后端
+- [ ] 定义 `PROCESS_KEY = "Order_Change"`
+- [ ] Controller(参照 `BpmOALeaveController`)
+- [ ] ServiceImpl(参照 `BpmOALeaveServiceImpl`)
+- [ ] 调用 `BpmProcessInstanceApi#createProcessInstance(...)` 创建流程实例
+- [ ] 回写 `crm_seorder.process_instance_id`
+- [ ] 提交后/结束后 `flowstate` 更新逻辑(建议走 Listener)
+- [ ] 新增流程结束监听器(继承 `BpmProcessInstanceStatusEventListener`)
+
+### 7.2 前端
+- [ ] 订单行“变更”按钮发起流程(保持发起模式不变)
+- [ ] 新增 create 页面(参照 `leave/create.vue`)
+- [ ] 新增 detail 页面(参照 `leave/detail.vue`)
+- [ ](可选)新增列表页面(参照 `leave/index.vue`)
+- [ ] 在 `router/modules/remaining.ts` 增加订单变更 create/detail 路由

BIN
Order_Change_流程开发说明.md:Zone.Identifier


+ 7 - 0
yudao-module-qms/pom.xml

@@ -39,6 +39,13 @@
             <artifactId>yudao-spring-boot-starter-security</artifactId>
         </dependency>
 
+        <!-- BPM 能力 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-bpm</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
         <!-- DB 能力 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

+ 59 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/IqcTaskController.java

@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.qms.controller.admin.iqc;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskDetailRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskPageReqVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskStartReqVO;
+import cn.iocoder.yudao.module.qms.service.iqc.IqcTaskService;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - IQC 来料检验任务")
+@RestController
+@RequestMapping("/qms/iqc-task")
+@Validated
+public class IqcTaskController {
+
+    @Resource
+    private IqcTaskService iqcTaskService;
+
+    @GetMapping("/page")
+    @Operation(summary = "获取来料检验任务分页")
+    @PreAuthorize("@ss.hasPermission('qms:iqc:task:list')")
+    public CommonResult<PageResult<IqcTaskRespVO>> getIqcTaskPage(@Valid IqcTaskPageReqVO pageReqVO) {
+        return success(iqcTaskService.getIqcTaskPage(pageReqVO));
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获取来料检验任务详情")
+    @PreAuthorize("@ss.hasPermission('qms:iqc:task:list')")
+    public CommonResult<IqcTaskDetailRespVO> getIqcTask(@RequestParam("id") Long id) {
+        return success(iqcTaskService.getIqcTaskDetail(id));
+    }
+
+    @PostMapping("/start")
+    @Operation(summary = "发起来料检验任务流程")
+    @PreAuthorize("@ss.hasPermission('qms:iqc:task:start')")
+    public CommonResult<String> startIqcTaskProcess(@Valid @RequestBody IqcTaskStartReqVO reqVO) {
+        String processInstanceId = iqcTaskService.startIqcTaskProcess(reqVO.getId());
+        if (processInstanceId == null) {
+            return CommonResult.error(GlobalErrorCodeConstants.NOT_FOUND.getCode(), "任务不存在");
+        }
+        return success(processInstanceId);
+    }
+}

+ 3 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcApplyDetailRespVO.java

@@ -21,6 +21,9 @@ public class IqcApplyDetailRespVO {
     @Schema(description = "批次")
     private String batch;
 
+    @Schema(description = "检验状态")
+    private String inspectStatus;
+
     @Schema(description = "数量")
     private BigDecimal quantity;
 

+ 54 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcTaskDetailRespVO.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.qms.controller.admin.iqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IQC 来料检验任务详情 Response VO")
+@Data
+public class IqcTaskDetailRespVO {
+
+    @Schema(description = "任务编号", example = "1024")
+    private Long id;
+
+    @Schema(description = "申请单编号", example = "IQC20240115001")
+    private String applicationId;
+
+    @Schema(description = "申请时间")
+    private LocalDateTime applyTime;
+
+    @Schema(description = "申请人编号")
+    private Long applicantId;
+
+    @Schema(description = "申请人")
+    private String applicantName;
+
+    @Schema(description = "流程实例编号")
+    private String processInstanceId;
+
+    @Schema(description = "流程状态")
+    private String flowState;
+
+    @Schema(description = "检验状态")
+    private String inspectStatus;
+
+    @Schema(description = "来源单号")
+    private String sourceOrderNum;
+
+    @Schema(description = "来源单类型")
+    private String sourceOrderType;
+
+    @Schema(description = "批次")
+    private String batch;
+
+    @Schema(description = "数量")
+    private BigDecimal quantity;
+
+    @Schema(description = "单位")
+    private String unit;
+
+    @Schema(description = "备注")
+    private String remark;
+}

+ 18 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcTaskPageReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.qms.controller.admin.iqc.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - IQC 来料检验任务分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IqcTaskPageReqVO extends PageParam {
+
+    @Schema(description = "任务状态", example = "pending")
+    private String status;
+
+}

+ 33 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcTaskProcessInfoVO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.qms.controller.admin.iqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IQC 来料检验任务流程信息 VO")
+@Data
+public class IqcTaskProcessInfoVO {
+
+    @Schema(description = "任务编号")
+    private Long id;
+
+    @Schema(description = "申请单号")
+    private String applicationId;
+
+    @Schema(description = "申请人编号")
+    private Long applicantId;
+
+    @Schema(description = "来源单号")
+    private String sourceOrderNum;
+
+    @Schema(description = "批次")
+    private String batch;
+
+    @Schema(description = "流程实例编号")
+    private String processInstanceId;
+
+    @Schema(description = "流程状态")
+    private String flowState;
+
+    @Schema(description = "检验状态")
+    private String inspectStatus;
+}

+ 51 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/controller/admin/iqc/vo/IqcTaskRespVO.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.qms.controller.admin.iqc.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IQC 来料检验任务 Response VO")
+@Data
+public class IqcTaskRespVO {
+
+    @Schema(description = "任务编号", example = "1024")
+    private Long id;
+
+    @Schema(description = "申请单编号", example = "IQC20240115001")
+    private String applicationId;
+
+    @Schema(description = "任务状态", example = "pending")
+    private String status;
+
+    @Schema(description = "来源单号")
+    private String sourceOrderNum;
+
+    @Schema(description = "来源单类型")
+    private String sourceOrderType;
+
+    @Schema(description = "批次")
+    private String batch;
+
+    @Schema(description = "数量")
+    private BigDecimal quantity;
+
+    @Schema(description = "单位")
+    private String unit;
+
+    @Schema(description = "申请时间")
+    private LocalDateTime applyTime;
+
+    @Schema(description = "申请人编号")
+    private Long applicantId;
+
+    @Schema(description = "申请人")
+    private String applicantName;
+
+    @Schema(description = "流程实例编号")
+    private String processInstanceId;
+
+    @Schema(description = "备注")
+    private String remark;
+}

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

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

+ 53 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/dal/mysql/iqc/IqcTaskMapper.java

@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.qms.dal.mysql.iqc;
+
+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.iqc.vo.IqcTaskDetailRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskProcessInfoVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskPageReqVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskRespVO;
+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;
+
+/**
+ * IQC 来料检验任务 Mapper
+ */
+@Mapper
+public interface IqcTaskMapper {
+
+    /**
+     * 分页查询
+     */
+    @TenantIgnore
+    Page<IqcTaskRespVO> selectTaskPage(Page<IqcTaskRespVO> page, @Param("req") IqcTaskPageReqVO req);
+
+    /**
+     * 查询流程相关信息
+     */
+    @TenantIgnore
+    IqcTaskProcessInfoVO selectTaskProcessInfo(@Param("id") Long id);
+
+    /**
+     * 查询任务详情
+     */
+    @TenantIgnore
+    IqcTaskDetailRespVO selectTaskDetail(@Param("id") Long id);
+
+    /**
+     * 更新流程相关字段
+     */
+    @TenantIgnore
+    void updateTaskProcessInfo(@Param("id") Long id,
+                               @Param("processInstanceId") String processInstanceId,
+                               @Param("flowState") String flowState,
+                               @Param("inspectStatus") String inspectStatus);
+
+    default PageResult<IqcTaskRespVO> selectTaskPage(IqcTaskPageReqVO reqVO) {
+        Page<IqcTaskRespVO> page = MyBatisUtils.buildPage(reqVO);
+        IPage<IqcTaskRespVO> result = selectTaskPage(page, reqVO);
+        return new PageResult<>(result.getRecords(), result.getTotal());
+    }
+}

+ 44 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/IqcTaskService.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.qms.service.iqc;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskDetailRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskPageReqVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskRespVO;
+
+/**
+ * IQC 来料检验任务 Service 接口
+ */
+public interface IqcTaskService {
+
+    /**
+     * 获取来料检验任务分页
+     *
+     * @param pageReqVO 分页参数
+     * @return 分页结果
+     */
+    PageResult<IqcTaskRespVO> getIqcTaskPage(IqcTaskPageReqVO pageReqVO);
+
+    /**
+     * 获取来料检验任务详情
+     *
+     * @param taskId 任务编号
+     * @return 任务详情
+     */
+    IqcTaskDetailRespVO getIqcTaskDetail(Long taskId);
+
+    /**
+     * 发起来料检验任务流程
+     *
+     * @param taskId 任务编号
+     * @return 流程实例编号
+     */
+    String startIqcTaskProcess(Long taskId);
+
+    /**
+     * 更新任务流程结果
+     *
+     * @param taskId 任务编号
+     * @param processStatus 流程实例状态
+     */
+    void updateIqcTaskProcessStatus(Long taskId, Integer processStatus);
+}

+ 103 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/IqcTaskServiceImpl.java

@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.module.qms.service.iqc;
+
+import cn.hutool.core.util.StrUtil;
+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.bpm.enums.task.BpmProcessInstanceStatusEnum;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskDetailRespVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskPageReqVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskProcessInfoVO;
+import cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskRespVO;
+import cn.iocoder.yudao.module.qms.dal.mysql.iqc.IqcTaskMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+/**
+ * IQC 来料检验任务 Service 实现
+ */
+@Service
+public class IqcTaskServiceImpl implements IqcTaskService {
+
+    public static final String PROCESS_KEY = "qms_iqc_task";
+
+    private static final String FLOW_STATE_RUNNING = "审批中";
+    private static final String FLOW_STATE_APPROVED = "审批完成";
+    private static final String FLOW_STATE_REJECTED = "已驳回";
+    private static final String FLOW_STATE_CANCELED = "已取消";
+
+    private static final String INSPECT_STATUS_PENDING = "待检验";
+    private static final String INSPECT_STATUS_PROCESSING = "检验中";
+    private static final String INSPECT_STATUS_COMPLETED = "检验完成";
+
+    @Resource
+    private IqcTaskMapper iqcTaskMapper;
+
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
+
+    @Override
+    public PageResult<IqcTaskRespVO> getIqcTaskPage(IqcTaskPageReqVO pageReqVO) {
+        return iqcTaskMapper.selectTaskPage(pageReqVO);
+    }
+
+    @Override
+    public IqcTaskDetailRespVO getIqcTaskDetail(Long taskId) {
+        if (taskId == null) {
+            return null;
+        }
+        return iqcTaskMapper.selectTaskDetail(taskId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String startIqcTaskProcess(Long taskId) {
+        IqcTaskProcessInfoVO taskInfo = iqcTaskMapper.selectTaskProcessInfo(taskId);
+        if (taskInfo == null) {
+            return null;
+        }
+        if (StrUtil.isNotBlank(taskInfo.getProcessInstanceId())) {
+            return taskInfo.getProcessInstanceId();
+        }
+
+        Map<String, Object> variables = new HashMap<>();
+        variables.put("taskId", taskInfo.getId());
+        variables.put("applicationId", taskInfo.getApplicationId());
+        variables.put("applicantId", taskInfo.getApplicantId());
+        variables.put("sourceOrderNum", taskInfo.getSourceOrderNum());
+        variables.put("batch", taskInfo.getBatch());
+
+        Long userId = getLoginUserId();
+        String processInstanceId = processInstanceApi.createProcessInstance(userId,
+                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY)
+                        .setBusinessKey(String.valueOf(taskInfo.getId()))
+                        .setVariables(variables));
+
+        iqcTaskMapper.updateTaskProcessInfo(taskId, processInstanceId, FLOW_STATE_RUNNING, INSPECT_STATUS_PROCESSING);
+        return processInstanceId;
+    }
+
+    @Override
+    public void updateIqcTaskProcessStatus(Long taskId, Integer processStatus) {
+        if (taskId == null || processStatus == null) {
+            return;
+        }
+        if (BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(processStatus)) {
+            iqcTaskMapper.updateTaskProcessInfo(taskId, null, FLOW_STATE_APPROVED, INSPECT_STATUS_COMPLETED);
+            return;
+        }
+        if (BpmProcessInstanceStatusEnum.CANCEL.getStatus().equals(processStatus)) {
+            iqcTaskMapper.updateTaskProcessInfo(taskId, null, FLOW_STATE_CANCELED, INSPECT_STATUS_PENDING);
+            return;
+        }
+        if (BpmProcessInstanceStatusEnum.REJECT.getStatus().equals(processStatus)) {
+            iqcTaskMapper.updateTaskProcessInfo(taskId, null, FLOW_STATE_REJECTED, INSPECT_STATUS_PROCESSING);
+        }
+    }
+}

+ 36 - 0
yudao-module-qms/src/main/java/cn/iocoder/yudao/module/qms/service/iqc/listener/IqcTaskStatusListener.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.qms.service.iqc.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.qms.service.iqc.IqcTaskService;
+import cn.iocoder.yudao.module.qms.service.iqc.IqcTaskServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * IQC 来料检验任务流程结果监听器
+ */
+@Component
+public class IqcTaskStatusListener extends BpmProcessInstanceStatusEventListener {
+
+    @Resource
+    private IqcTaskService iqcTaskService;
+
+    @Override
+    protected String getProcessDefinitionKey() {
+        return IqcTaskServiceImpl.PROCESS_KEY;
+    }
+
+    @Override
+    protected void onEvent(BpmProcessInstanceStatusEvent event) {
+        if (event.getBusinessKey() == null) {
+            return;
+        }
+        try {
+            Long taskId = Long.parseLong(event.getBusinessKey());
+            iqcTaskService.updateIqcTaskProcessStatus(taskId, event.getStatus());
+        } catch (NumberFormatException ignore) {
+            // 业务主键不是任务编号时忽略
+        }
+    }
+}

+ 1 - 0
yudao-module-qms/src/main/resources/mapper/iqc/IqcApplyMapper.xml

@@ -86,6 +86,7 @@
             FSRCORDERTYPE AS materialName,
             NULL AS specification,
             FLOTNUMBER AS batch,
+            COALESCE(FINSPECTSTATUS, '待检验') AS inspectStatus,
             FAPPLYQTY AS quantity,
             FUNIT AS unit,
             NULL AS remark

+ 87 - 0
yudao-module-qms/src/main/resources/mapper/iqc/IqcTaskMapper.xml

@@ -0,0 +1,87 @@
+<?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.iqc.IqcTaskMapper">
+
+    <select id="selectTaskPage" resultType="cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskRespVO">
+        SELECT
+            b.id AS id,
+            a.FBILLNO AS applicationId,
+            a.FAPPLYTIME AS applyTime,
+            a.FAPPLYUSER AS applicantId,
+            u.username AS applicantName,
+            a.FCOMMENT AS remark,
+            b.process_instance_id AS processInstanceId,
+            b.FSRCORDERNUM AS sourceOrderNum,
+            b.FSRCORDERTYPE AS sourceOrderType,
+            b.FLOTNUMBER AS batch,
+            b.FAPPLYQTY AS quantity,
+            b.FUNIT AS unit,
+            CASE
+                WHEN COALESCE(b.FINSPECTSTATUS, a.FBILLSTATUS) IN ('processing', '检验中') THEN 'processing'
+                WHEN COALESCE(b.FINSPECTSTATUS, a.FBILLSTATUS) IN ('completed', '检验完成') THEN 'completed'
+                ELSE 'pending'
+            END AS status
+        FROM qms_qcp_insappnentry b
+        LEFT JOIN qms_qcp_inspecapplyn a ON a.id = b.glid
+        LEFT JOIN system_users u ON u.id = a.FAPPLYUSER
+        <where>
+            <if test="req.status != null and req.status != ''">
+                AND
+                CASE
+                    WHEN COALESCE(b.FINSPECTSTATUS, a.FBILLSTATUS) IN ('processing', '检验中') THEN 'processing'
+                    WHEN COALESCE(b.FINSPECTSTATUS, a.FBILLSTATUS) IN ('completed', '检验完成') THEN 'completed'
+                    ELSE 'pending'
+                END = #{req.status}
+            </if>
+        </where>
+        ORDER BY a.FAPPLYTIME DESC, b.id DESC
+    </select>
+
+    <select id="selectTaskProcessInfo" resultType="cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskProcessInfoVO">
+        SELECT
+            b.id AS id,
+            a.FBILLNO AS applicationId,
+            a.FAPPLYUSER AS applicantId,
+            b.FSRCORDERNUM AS sourceOrderNum,
+            b.FLOTNUMBER AS batch,
+            b.process_instance_id AS processInstanceId,
+            b.flowstate AS flowState,
+            b.FINSPECTSTATUS AS inspectStatus
+        FROM qms_qcp_insappnentry b
+        LEFT JOIN qms_qcp_inspecapplyn a ON a.id = b.glid
+        WHERE b.id = #{id}
+    </select>
+
+    <select id="selectTaskDetail" resultType="cn.iocoder.yudao.module.qms.controller.admin.iqc.vo.IqcTaskDetailRespVO">
+        SELECT
+            b.id AS id,
+            a.FBILLNO AS applicationId,
+            a.FAPPLYTIME AS applyTime,
+            a.FAPPLYUSER AS applicantId,
+            u.username AS applicantName,
+            a.FCOMMENT AS remark,
+            b.process_instance_id AS processInstanceId,
+            b.flowstate AS flowState,
+            b.FINSPECTSTATUS AS inspectStatus,
+            b.FSRCORDERNUM AS sourceOrderNum,
+            b.FSRCORDERTYPE AS sourceOrderType,
+            b.FLOTNUMBER AS batch,
+            b.FAPPLYQTY AS quantity,
+            b.FUNIT AS unit
+        FROM qms_qcp_insappnentry b
+        LEFT JOIN qms_qcp_inspecapplyn a ON a.id = b.glid
+        LEFT JOIN system_users u ON u.id = a.FAPPLYUSER
+        WHERE b.id = #{id}
+    </select>
+
+    <update id="updateTaskProcessInfo">
+        UPDATE qms_qcp_insappnentry
+        <set>
+            <if test="processInstanceId != null">process_instance_id = #{processInstanceId},</if>
+            <if test="flowState != null">flowstate = #{flowState},</if>
+            <if test="inspectStatus != null">FINSPECTSTATUS = #{inspectStatus},</if>
+        </set>
+        WHERE id = #{id}
+    </update>
+
+</mapper>

+ 1 - 1
yudao-server/src/main/java/cn/iocoder/yudao/server/YudaoServerApplication.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.server;
+  package cn.iocoder.yudao.server;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;

+ 2 - 0
yudao-ui/yudao-ui-admin-vue3/src/api/qms/iqc/apply/index.ts

@@ -30,6 +30,7 @@ export interface IqcApplyDetailSaveReqVO {
   materialName?: string
   specification?: string
   batch?: string
+  inspectStatus?: string
   quantity?: number
   unit?: string
   remark?: string
@@ -49,6 +50,7 @@ export interface IqcApplyDetailRespVO {
   materialName?: string
   specification?: string
   batch?: string
+  inspectStatus?: string
   quantity?: number
   unit?: string
   remark?: string

+ 50 - 0
yudao-ui/yudao-ui-admin-vue3/src/api/qms/iqc/task/index.ts

@@ -0,0 +1,50 @@
+import request from '@/config/axios'
+
+export interface IqcTaskPageReqVO extends PageParam {
+  status?: string
+}
+
+export interface IqcTaskRespVO {
+  id: number
+  applicationId: string
+  status: string
+  sourceOrderNum?: string
+  sourceOrderType?: string
+  batch?: string
+  quantity?: number
+  unit?: string
+  applyTime?: string | number
+  applicantId?: number | string
+  applicantName?: string
+  processInstanceId?: string
+  remark?: string
+}
+
+export interface IqcTaskDetailRespVO {
+  id: number | string
+  applicationId?: string
+  applyTime?: string | number
+  applicantId?: number | string
+  applicantName?: string
+  processInstanceId?: string
+  flowState?: string
+  inspectStatus?: string
+  sourceOrderNum?: string
+  sourceOrderType?: string
+  batch?: string
+  quantity?: number
+  unit?: string
+  remark?: string
+}
+
+export const getIqcTaskPage = (params: IqcTaskPageReqVO) => {
+  return request.get<PageResult<IqcTaskRespVO[]>>({ url: '/qms/iqc-task/page', params })
+}
+
+export const getIqcTaskDetail = (id: number | string) => {
+  return request.get<IqcTaskDetailRespVO>({ url: `/qms/iqc-task/get?id=${id}` })
+}
+
+export const startIqcTaskProcess = (id: number | string) => {
+  return request.post<string>({ url: '/qms/iqc-task/start', data: { id } })
+}

+ 0 - 112
yudao-ui/yudao-ui-admin-vue3/src/config/modules.ts

@@ -1,112 +0,0 @@
-/**
- * 模块配置
- */
-
-export const DEFAULT_MODULE = 'iqc'
-
-// 模块配置
-const moduleConfigs: Record<string, any> = {
-  iqc: {
-    name: 'IQC检验',
-    applicationList: {
-      title: '检验申请列表',
-      columns: [
-        { type: 'selection', width: 55 },
-        { key: 'applicationNo', label: '申请单号', type: 'applicationLink', width: 180 },
-        { key: 'status', label: '状态', type: 'status', width: 100 },
-        { key: 'applicantName', label: '申请人', width: 120 },
-        { key: 'createTime', label: '申请时间', type: 'datetime', width: 160 },
-        { key: 'materialCode', label: '物料编码', width: 150 },
-        { key: 'materialName', label: '物料名称', minWidth: 200 },
-        { key: 'quantity', label: '数量', type: 'number', width: 100 },
-        { key: 'remark', label: '备注', type: 'tooltip', minWidth: 150 }
-      ],
-      keywordFields: ['申请单号', '物料编码', '物料名称'],
-      actionButtons: {
-        create: '新建申请',
-        import: true,
-        export: true,
-        guide: true
-      }
-    },
-    taskList: {
-      title: '检验任务列表',
-      columns: [
-        { type: 'selection', width: 55 },
-        { key: 'taskNo', label: '任务单号', width: 180 },
-        { key: 'status', label: '状态', type: 'status', width: 100 },
-        { key: 'inspectorName', label: '检验员', width: 120 },
-        { key: 'createTime', label: '创建时间', type: 'datetime', width: 160 },
-        { key: 'materialCode', label: '物料编码', width: 150 },
-        { key: 'materialName', label: '物料名称', minWidth: 200 }
-      ],
-      keywordFields: ['任务单号', '物料编码']
-    },
-    favorites: {
-      title: '我的收藏',
-      emptyText: '暂无收藏内容'
-    },
-    help: {
-      title: '帮助中心',
-      contacts: [
-        { name: '技术支持', phone: '400-xxx-xxxx' }
-      ],
-      faqs: [
-        { question: '如何创建检验申请?', answer: '点击"新建申请"按钮,填写相关信息后提交即可。' },
-        { question: '如何查看检验结果?', answer: '在任务列表中找到对应任务,点击查看详情。' }
-      ]
-    },
-    applicationForm: {
-      title: '检验申请',
-      baseFields: [
-        { key: 'applicantName', label: '申请人', type: 'input', span: 8, required: true },
-        { key: 'applyTime', label: '申请时间', type: 'datetime', span: 8 },
-        { key: 'materialCode', label: '物料编码', type: 'input', span: 8, required: true },
-        { key: 'materialName', label: '物料名称', type: 'input', span: 8, required: true },
-        { key: 'quantity', label: '数量', type: 'number', span: 8, required: true },
-        { key: 'remark', label: '备注', type: 'textarea', span: 24 }
-      ],
-      detail: {
-        title: '检验明细',
-        columns: [
-          { key: 'itemNo', label: '序号', width: 80 },
-          { key: 'checkItem', label: '检验项目', width: 150 },
-          { key: 'standard', label: '标准值', width: 120 },
-          { key: 'result', label: '检验结果', width: 120 },
-          { key: 'conclusion', label: '结论', width: 100 }
-        ],
-        defaultRow: { itemNo: '', checkItem: '', standard: '', result: '', conclusion: '' }
-      }
-    },
-    taskForm: {
-      title: '检验任务',
-      baseFields: [
-        { key: 'taskNo', label: '任务单号', type: 'input', span: 8 },
-        { key: 'inspectorName', label: '检验员', type: 'input', span: 8 },
-        { key: 'createTime', label: '创建时间', type: 'datetime', span: 8 }
-      ],
-      detail: {
-        title: '任务明细',
-        columns: [],
-        defaultRow: {}
-      }
-    }
-  }
-}
-
-/**
- * 获取模块配置
- */
-export const getModuleConfig = (moduleCode: string) => {
-  return moduleConfigs[moduleCode] || moduleConfigs[DEFAULT_MODULE]
-}
-
-/**
- * 获取所有模块
- */
-export const getAllModules = () => {
-  return Object.keys(moduleConfigs).map(code => ({
-    code,
-    name: moduleConfigs[code].name
-  }))
-}

+ 10 - 112
yudao-ui/yudao-ui-admin-vue3/src/config/qmsModules.ts

@@ -29,7 +29,6 @@ export const QMS_MODULES: Record<string, any> = {
         { key: 'id', label: '单据编号', width: 200, type: 'applicationLink' },
         { key: 'applicantName', label: '申请人', width: 100 },
         { key: 'applyTime', label: '申请时间', width: 160, type: 'datetime' },
-        { key: 'status', label: '单据状态', width: 110, type: 'status' },
         { key: 'materialCount', label: '物料数', width: 100, type: 'number' },
         { key: 'remark', label: '备注', minWidth: 200, type: 'tooltip' }
       ],
@@ -53,6 +52,7 @@ export const QMS_MODULES: Record<string, any> = {
           { key: 'materialName', label: '名称', type: 'input', width: 160, required: true },
           { key: 'specification', label: '规格', type: 'input', width: 160 },
           { key: 'batch', label: '批次', type: 'input', width: 140 },
+          { key: 'inspectStatus', label: '状态', type: 'select', width: 120, options: ['待检验', '检验中', '检验完成'] },
           { key: 'quantity', label: '数量', type: 'number', width: 120, min: 0 },
           { key: 'unit', label: '单位', type: 'input', width: 100 },
           { key: 'remark', label: '备注', type: 'input', minWidth: 200 }
@@ -62,6 +62,7 @@ export const QMS_MODULES: Record<string, any> = {
           materialName: '',
           specification: '',
           batch: '',
+          inspectStatus: '待检验',
           quantity: 1,
           unit: 'PCS',
           remark: ''
@@ -77,65 +78,21 @@ export const QMS_MODULES: Record<string, any> = {
         { key: 'processing', label: '检验中' },
         { key: 'completed', label: '已完成' }
       ],
-      keywordFields: ['applicationId', 'materialName'],
+      keywordFields: ['applicationId', 'sourceOrderNum', 'batch'],
       columns: [
         { type: 'selection', width: 55 },
         { key: 'status', label: '状态', width: 90, type: 'status' },
-        { key: 'id', label: '任务编号', width: 130 },
-        { key: 'applicationId', label: '单据编号', width: 140, type: 'taskLink' },
-        { key: 'materialInfo', label: '物料信息', minWidth: 220, type: 'textLines', lines: ['materialCode', 'materialName', 'specification'] },
-        { key: 'supplierInfo', label: '供应商', width: 180, type: 'textLines', lines: ['supplierName', 'supplierCode'] },
+        { key: 'id', label: '任务编号', width: 140 },
+        { key: 'applicationId', label: '单据编号', width: 160, type: 'taskLink' },
+        { key: 'sourceOrderNum', label: '来源单号', width: 160 },
         { key: 'batch', label: '批次', width: 130, type: 'batch' },
-        { key: 'location', label: '仓位', width: 110 },
-        { key: 'responsibleName', label: '负责人', width: 100 },
-        { key: 'priority', label: '优先级', width: 100, type: 'priority' },
-        { key: 'startTime', label: '开始时间', width: 170, type: 'datetime' },
-        { key: 'completeTime', label: '完成时间', width: 170, type: 'datetime' },
+        { key: 'quantity', label: '数量', width: 100 },
+        { key: 'unit', label: '单位', width: 80 },
+        { key: 'applyTime', label: '申请时间', width: 170, type: 'datetime' },
         { key: 'remark', label: '备注', minWidth: 200, type: 'tooltip' }
       ],
-      advancedFilters: [
-        { key: 'statusList', label: '状态', type: 'select', multiple: true, optionsKey: 'taskStatuses' },
-        { key: 'supplierIds', label: '供应商', type: 'select', multiple: true, optionsKey: 'suppliers' },
-        { key: 'materialCodes', label: '物料编码', type: 'select', multiple: true, optionsKey: 'materials' },
-        { key: 'batchList', label: '批次', type: 'select', multiple: true, optionsKey: 'batches' },
-        { key: 'responsibleIds', label: '负责人', type: 'select', multiple: true, optionsKey: 'responsibles' },
-        { key: 'priorities', label: '优先级', type: 'select', multiple: true, options: [
-          { label: '高', value: 'high' },
-          { label: '中', value: 'medium' },
-          { label: '低', value: 'low' }
-        ] },
-        { key: 'timeRange', label: '时间范围', type: 'datetimerange' }
-      ]
+      advancedFilters: []
     },
-    taskForm: {
-      title: '来料检验单',
-      infoItems: [
-        { key: 'supplierName', label: '供应商' },
-        { key: 'materialName', label: '物料' },
-        { key: 'batch', label: '批次' },
-        { key: 'arrivalTime', label: '到货时间', type: 'datetime' },
-        { key: 'inspectionSpecVersion', label: '检规版本' }
-      ],
-      projectColumns: [
-        { key: 'itemName', label: '项目', width: 200 },
-        { key: 'standard', label: '标准', width: 180 },
-        { key: 'upperLimit', label: '上限', width: 120 },
-        { key: 'lowerLimit', label: '下限', width: 120 },
-        { key: 'unit', label: '单位', width: 100 },
-        { key: 'result', label: '结果', width: 140, editable: true },
-        { key: 'decision', label: '判定', width: 120, editable: true, type: 'select', options: ['合格', '不合格'] },
-        { key: 'remark', label: '备注', minWidth: 200, editable: true }
-      ],
-      statisticsFields: [
-        { key: 'sampleSize', label: '样本数' },
-        { key: 'passCount', label: '合格数' },
-        { key: 'failCount', label: '不合格数' },
-        { key: 'result', label: '判定结果' }
-      ],
-      exceptionFields: {
-        typeOptions: ['尺寸偏差', '外观缺陷', '功能异常']
-      }
-    }
   },
   ipqc: {
     code: 'ipqc',
@@ -241,34 +198,6 @@ export const QMS_MODULES: Record<string, any> = {
         { key: 'timeRange', label: '时间范围', type: 'datetimerange' }
       ]
     },
-    taskForm: {
-      title: '过程检验单',
-      infoItems: [
-        { key: 'workOrder', label: '生产订单' },
-        { key: 'process', label: '工序/工步', formatter: (row: any) => `${row.process || ''}/${row.step || ''}` },
-        { key: 'line', label: '线体/工位/设备', formatter: (row: any) => `${row.line || ''} / ${row.station || ''} / ${row.equipment || ''}` },
-        { key: 'shift', label: '班次' },
-        { key: 'inspectionSpecVersion', label: '检规版本' }
-      ],
-      projectColumns: [
-        { key: 'category', label: '类别', width: 140 },
-        { key: 'itemName', label: '项目', width: 200 },
-        { key: 'standard', label: '标准', width: 200 },
-        { key: 'upperLimit', label: '上限', width: 120 },
-        { key: 'lowerLimit', label: '下限', width: 120 },
-        { key: 'unit', label: '单位', width: 100 },
-        { key: 'result', label: '结果', width: 140, editable: true },
-        { key: 'decision', label: '判定', width: 120, editable: true, type: 'select', options: ['合格', '不合格'] },
-        { key: 'remark', label: '备注', minWidth: 200, editable: true }
-      ],
-      statisticsFields: [
-        { key: 'sampleSize', label: '样本数' },
-        { key: 'passCount', label: '合格数' },
-        { key: 'failCount', label: '不合格数' },
-        { key: 'result', label: '判定结果' }
-      ],
-      exceptionFields: { typeOptions: ['制程异常', '设备异常', '人员操作'] }
-    }
   },
   fqc: {
     code: 'fqc',
@@ -373,37 +302,6 @@ export const QMS_MODULES: Record<string, any> = {
         { key: 'timeRange', label: '时间范围', type: 'datetimerange' }
       ]
     },
-    taskForm: {
-      title: '成品检验单',
-      infoItems: [
-        { key: 'productName', label: '成品名称' },
-        { key: 'productModel', label: '型号' },
-        { key: 'customer', label: '客户' },
-        { key: 'salesOrder', label: '销售订单/出货单' },
-        { key: 'packageBatch', label: '包装批次/箱号' },
-        { key: 'inspectionSpecVersion', label: '检规版本' }
-      ],
-      projectColumns: [
-        { key: 'category', label: '类别', width: 140 },
-        { key: 'itemName', label: '项目', width: 200 },
-        { key: 'standard', label: '标准', width: 200 },
-        { key: 'upperLimit', label: '上限', width: 120 },
-        { key: 'lowerLimit', label: '下限', width: 120 },
-        { key: 'unit', label: '单位', width: 100 },
-        { key: 'result', label: '结果', width: 140, editable: true },
-        { key: 'decision', label: '判定', width: 120, editable: true, type: 'select', options: ['合格', '不合格', '特采'] },
-        { key: 'remark', label: '备注', minWidth: 200, editable: true }
-      ],
-      statisticsFields: [
-        { key: 'sampleSize', label: '样本数' },
-        { key: 'passCount', label: '合格数' },
-        { key: 'failCount', label: '不合格数' },
-        { key: 'specialAcceptCount', label: '特采数' },
-        { key: 'result', label: '判定结果' }
-      ],
-      exceptionFields: { typeOptions: ['外观异常', '功能异常', '包装损坏', '标签错误'] },
-      releaseActions: ['放行', '返工', '隔离', '特采']
-    }
   }
 }
 

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

@@ -730,7 +730,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
       { path: 'iqc/apply/edit/:id', name: 'IqcApplicationEdit', component: () => import('@/views/qms/ApplicationForm.vue'), meta: { module: 'iqc', title: '编辑申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms', mode: 'edit' } },
       { path: 'iqc/apply/view/:id', name: 'IqcApplicationView', component: () => import('@/views/qms/ApplicationForm.vue'), meta: { module: 'iqc', title: '查看申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms', mode: 'view' } },
       { 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: 'iqc/task/:id', name: 'IqcTaskDetail', component: () => import('@/views/qms/TaskForm.vue'), meta: { module: 'iqc', title: '检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       { 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 过程检验
@@ -739,7 +738,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
       { 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/task/:id', name: 'IpqcTaskDetail', component: () => import('@/views/qms/TaskForm.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' } },
       // FQC 成品检验
@@ -748,7 +746,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
       { path: 'fqc/apply/edit/:id', name: 'FqcApplicationEdit', component: () => import('@/views/qms/ApplicationForm.vue'), meta: { module: 'fqc', title: '编辑申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms', mode: 'edit' } },
       { path: 'fqc/apply/view/:id', name: 'FqcApplicationView', component: () => import('@/views/qms/ApplicationForm.vue'), meta: { module: 'fqc', title: '查看申请', noCache: true, hidden: true, canTo: true, activeMenu: '/qms', mode: 'view' } },
       { path: 'fqc/task/list', name: 'FqcTaskList', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'fqc', title: '成品检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
-      { path: 'fqc/task/:id', name: 'FqcTaskDetail', component: () => import('@/views/qms/TaskForm.vue'), meta: { module: 'fqc', title: '检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       { path: 'fqc/favorites', name: 'FqcFavorites', component: () => import('@/views/qms/Favorites.vue'), meta: { module: 'fqc', title: '我的关注', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       { path: 'fqc/help', name: 'FqcHelp', component: () => import('@/views/qms/Help.vue'), meta: { module: 'fqc', title: '帮助', noCache: true, hidden: true, canTo: true, activeMenu: '/qms' } },
       // SPEC 检规管理
@@ -766,7 +763,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
     meta: { hidden: true },
     children: [
       { path: 'iqc/iqc/apply/list', name: 'S5IqcApplicationList', component: () => import('@/views/qms/ApplicationList.vue'), meta: { module: 'iqc', title: '来料检验申请', noCache: true, hidden: true, canTo: true, activeMenu: '/s5' } },
-      { path: 'iqc/qms/iqc/apply/list', name: 'S5IqcApplicationListCompat', component: () => import('@/views/qms/ApplicationList.vue'), meta: { module: 'iqc', title: '来料检验申请', noCache: true, hidden: true, canTo: true, activeMenu: '/s5' } }
+      { path: 'iqc/qms/iqc/apply/list', name: 'S5IqcApplicationListCompat', component: () => import('@/views/qms/ApplicationList.vue'), meta: { module: 'iqc', title: '来料检验申请', noCache: true, hidden: true, canTo: true, activeMenu: '/s5' } },
+      { path: 'iqc/iqc/task/list', name: 'S5IqcTaskList', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'iqc', title: '来料检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/s5' } },
+      { path: 'iqc/qms/iqc/task/list', name: 'S5IqcTaskListCompat', component: () => import('@/views/qms/TaskList.vue'), meta: { module: 'iqc', title: '来料检验任务', noCache: true, hidden: true, canTo: true, activeMenu: '/s5' } }
     ]
   },
   {

+ 23 - 0
yudao-ui/yudao-ui-admin-vue3/src/store/modules/qms/task.ts

@@ -1,5 +1,6 @@
 import { defineStore } from 'pinia'
 import { QMS_MODULE_CODES } from '@/config/qmsModules'
+import { getIqcTaskPage, startIqcTaskProcess } from '@/api/qms/iqc/task'
 
 const createDefaultFilters = () => ({
   keyword: '', searchMode: 'AND', statusList: [], supplierIds: [], materialCodes: [], batchList: [],
@@ -26,6 +27,9 @@ const createTaskSamples = () => ({
   ]
 })
 
+const REMOTE_MODULES = new Set(['iqc'])
+const isRemoteModule = (module: string) => REMOTE_MODULES.has(module)
+
 export const useQmsTaskStore = defineStore('qmsTask', {
   state: () => ({
     tasks: createTaskSamples() as Record<string, any[]>,
@@ -36,6 +40,7 @@ export const useQmsTaskStore = defineStore('qmsTask', {
     pagination: QMS_MODULE_CODES.reduce((acc, module) => { acc[module] = createDefaultPagination(); return acc }, {} as Record<string, any>)
   }),
   getters: {
+    isRemoteModule: () => (module: string) => isRemoteModule(module),
     activeTab: (state) => (module: string) => state.activeTabs[module] || 'pending',
     filteredTasks: (state) => (module: string) => {
       const filters = state.filters[module] || createDefaultFilters()
@@ -64,12 +69,30 @@ export const useQmsTaskStore = defineStore('qmsTask', {
   },
   actions: {
     async fetchTasks(module: string) {
+      if (isRemoteModule(module)) {
+        const pagination = this.pagination[module] || createDefaultPagination()
+        const status = this.activeTabs[module]
+        const params = {
+          pageNo: pagination.currentPage,
+          pageSize: pagination.pageSize,
+          status: status && status !== 'all' ? status : undefined
+        }
+        const pageResult = await getIqcTaskPage(params)
+        const pageData: any = (pageResult as any)?.data ?? pageResult
+        this.tasks[module] = pageData?.list || []
+        this.pagination[module].total = pageData?.total || 0
+        return pageData?.list
+      }
       return new Promise((resolve) => {
         setTimeout(() => { const list = this.filteredTasks(module); this.pagination[module].total = list.length; resolve(list) }, 300)
       })
     },
     setActiveTab(module: string, tab: string) { this.activeTabs[module] = tab },
     async startInspection(module: string, taskId: string) {
+      if (isRemoteModule(module)) {
+        const result: any = await startIqcTaskProcess(taskId)
+        return result?.data ?? result
+      }
       return new Promise((resolve, reject) => {
         setTimeout(() => {
           const tasks = this.tasks[module] || []

+ 7 - 1
yudao-ui/yudao-ui-admin-vue3/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue

@@ -25,7 +25,7 @@
           :rules="approveReasonRule"
           label-width="100px"
         >
-          <el-card v-if="runningTask?.formId > 0" class="mb-15px !-mt-10px">
+          <el-card v-if="runningTask?.formId > 0 && !shouldSkipNormalForm" class="mb-15px !-mt-10px">
             <template #header>
               <span class="el-icon-picture-outline"> 填写表单【{{ runningTask?.formName }}】 </span>
             </template>
@@ -1071,6 +1071,9 @@ const getButtonDisplayName = (btnType: OperationButtonType) => {
   return displayName
 }
 
+const PROCESS_SKIP_NORMAL_FORM_KEYS = new Set(['qms_iqc_task'])
+const shouldSkipNormalForm = computed(() => PROCESS_SKIP_NORMAL_FORM_KEYS.has(props.processDefinition?.key))
+
 const loadTodoTask = (task: any) => {
   approveForm.value = {}
   runningTask.value = task
@@ -1089,6 +1092,9 @@ const loadTodoTask = (task: any) => {
 
 /** 校验流程表单 */
 const validateNormalForm = async () => {
+  if (shouldSkipNormalForm.value) {
+    return true
+  }
   if (props.processDefinition?.formType === BpmModelFormType.NORMAL) {
     let valid = true
     try {

+ 10 - 3
yudao-ui/yudao-ui-admin-vue3/src/views/qms/ApplicationForm.vue

@@ -137,9 +137,14 @@ const getDetailComponent = (column: any) => {
 }
 
 const getDetailProps = (column: any) => {
-  if (column.type === 'number') return { min: column.min ?? 0, controlsPosition: 'right', style: 'width: 100%' }
-  if (column.type === 'select') return { placeholder: '请选择', style: 'width: 100%' }
-  return { placeholder: '请输入', style: 'width: 100%' }
+  const baseProps = { disabled: !!column.disabled, style: 'width: 100%' }
+  if (column.type === 'number') {
+    return { ...baseProps, min: column.min ?? 0, controlsPosition: 'right' }
+  }
+  if (column.type === 'select') {
+    return { ...baseProps, placeholder: '请选择' }
+  }
+  return { ...baseProps, placeholder: '请输入' }
 }
 
 const handleAddDetail = () => { formData.details.push(createDetailRow()) }
@@ -181,6 +186,7 @@ const buildPayload = () => ({
     materialName: detail.materialName || '',
     specification: detail.specification || '',
     batch: detail.batch || '',
+    inspectStatus: detail.inspectStatus || '待检验',
     quantity: detail.quantity ?? 0,
     unit: detail.unit || '',
     remark: detail.remark || ''
@@ -200,6 +206,7 @@ const applyResponseToForm = (data: any) => {
       materialName: detail.materialName || '',
       specification: detail.specification || '',
       batch: detail.batch || '',
+      inspectStatus: detail.inspectStatus || '待检验',
       quantity: detail.quantity ?? 0,
       unit: detail.unit || '',
       remark: detail.remark || ''

+ 0 - 164
yudao-ui/yudao-ui-admin-vue3/src/views/qms/TaskForm.vue

@@ -1,164 +0,0 @@
-<template>
-  <div class="page-container" v-loading="loading">
-    <div class="page-header">
-      <h1>{{ formConfig.title }} - {{ taskData?.id || '--' }}</h1>
-      <IqcStatusBadge :status="taskData?.status || 'pending'" />
-    </div>
-
-    <div class="info-card">
-      <el-row :gutter="20">
-        <el-col :span="6">
-          <div class="info-item"><span class="info-label">负责人:</span><span class="info-value">{{ taskData?.responsibleName || '-' }}</span></div>
-        </el-col>
-        <el-col :span="6">
-          <div class="info-item"><span class="info-label">优先级:</span><el-tag size="small" :color="getPriorityColor(taskData?.priority)">{{ getPriorityName(taskData?.priority) }}</el-tag></div>
-        </el-col>
-        <el-col :span="6">
-          <div class="info-item"><span class="info-label">最近更新:</span><span class="info-value">{{ formatDateTime(taskData?.startTime) }}</span></div>
-        </el-col>
-        <el-col :span="6">
-          <div class="info-item"><span class="info-label">检规版本:</span><span class="info-value">{{ taskData?.inspectionSpecVersion || '-' }}</span></div>
-        </el-col>
-      </el-row>
-    </div>
-
-    <div class="steps-container">
-      <el-steps :active="currentStep" finish-status="success">
-        <el-step title="准备" /><el-step title="检验" /><el-step title="完成" />
-      </el-steps>
-    </div>
-
-    <div class="form-container">
-      <div class="form-section">
-        <div class="section-title">基础信息</div>
-        <el-row :gutter="20">
-          <el-col v-for="item in basicInfoItems" :key="item.label" :span="8">
-            <div class="info-item"><span class="info-label">{{ item.label }}:</span><span class="info-value">{{ item.value }}</span></div>
-          </el-col>
-        </el-row>
-      </div>
-
-      <div class="form-section">
-        <div class="section-title">检验项目<div class="section-actions" v-if="isEditable"><el-button size="small" type="primary" @click="handleAddProject"><Icon icon="ep:plus" class="mr-5px" />新增项目</el-button></div></div>
-        <el-table :data="detail.projects" border>
-          <el-table-column v-for="column in formConfig.projectColumns" :key="column.key" :prop="column.key" :label="column.label" :width="column.width" :min-width="column.minWidth">
-            <template #default="{ row }">
-              <template v-if="isEditable && column.editable !== false">
-                <el-input v-if="!column.type || column.type === 'input'" v-model="row[column.key]" placeholder="请输入" />
-                <el-select v-else-if="column.type === 'select'" v-model="row[column.key]" placeholder="请选择">
-                  <el-option v-for="option in column.options || []" :key="option" :label="option" :value="option" />
-                </el-select>
-                <el-input v-else v-model="row[column.key]" placeholder="请输入" />
-              </template>
-              <template v-else>{{ row[column.key] ?? '-' }}</template>
-            </template>
-          </el-table-column>
-          <el-table-column v-if="isEditable" label="操作" width="90">
-            <template #default="{ $index }"><el-button type="danger" link size="small" @click="handleDeleteProject($index)">删除</el-button></template>
-          </el-table-column>
-        </el-table>
-      </div>
-
-      <div class="form-actions">
-        <el-button @click="handleBack">返回列表</el-button>
-        <el-button v-if="isEditable" @click="handleSave" :loading="saving">保存</el-button>
-        <el-button v-if="isEditable" type="primary" @click="handleSubmit" :loading="submitting">提交审核</el-button>
-        <el-button @click="handlePrint">打印</el-button>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-defineOptions({ name: 'IqcTaskForm' })
-
-import { reactive, ref, computed, onMounted } from 'vue'
-import { useRoute, useRouter } from 'vue-router'
-import IqcStatusBadge from '@/components/Qms/StatusBadge.vue'
-import { useQmsTaskStore } from '@/store/modules/qms/task'
-import { useQmsUserStore } from '@/store/modules/qms/user'
-import { getQmsModuleConfig, DEFAULT_QMS_MODULE } from '@/config/qmsModules'
-
-const route = useRoute()
-const router = useRouter()
-const taskStore = useQmsTaskStore()
-const userStore = useQmsUserStore()
-
-const moduleCode = computed(() => (route.meta.module as string) || DEFAULT_QMS_MODULE)
-const formConfig = computed(() => getQmsModuleConfig(moduleCode.value).taskForm)
-
-const loading = ref(false)
-const saving = ref(false)
-const submitting = ref(false)
-const taskData = ref<any>(null)
-const detail = reactive({ projects: [] as any[], statistics: {} as any, exceptions: [] as any[] })
-
-const PRIORITY_MAP: Record<string, string> = { high: '高', medium: '中', low: '低' }
-const PRIORITY_COLOR: Record<string, string> = { high: '#F56C6C', medium: '#E6A23C', low: '#67C23A' }
-const getPriorityName = (priority?: string) => PRIORITY_MAP[priority || ''] || '未知'
-const getPriorityColor = (priority?: string) => PRIORITY_COLOR[priority || ''] || '#909399'
-
-const currentStep = computed(() => {
-  switch (taskData.value?.status) { case 'pending': return 0; case 'processing': return 1; case 'completed': return 2; default: return 0 }
-})
-
-const isEditable = computed(() => {
-  if (!taskData.value) return false
-  const status = taskData.value.status
-  const isResponsible = userStore.isResponsible(taskData.value.responsibleId)
-  return isResponsible && (status === 'pending' || status === 'processing')
-})
-
-const formatDateTime = (value?: string) => { if (!value) return '-'; return String(value).replace('T', ' ').slice(0, 16) }
-
-const basicInfoItems = computed(() => {
-  const infoItems = formConfig.value.infoItems || []
-  const basicInfo = { ...(taskData.value?.detail?.basicInfo || {}), ...(taskData.value || {}) }
-  return infoItems.map((item: any) => {
-    const raw = item.formatter ? item.formatter(basicInfo) : (basicInfo[item.key] ?? taskData.value?.[item.key])
-    return { label: item.label, value: raw || '-' }
-  })
-})
-
-const createDefaultProject = () => {
-  const project: any = {}
-  ;(formConfig.value.projectColumns || []).forEach((column: any) => { project[column.key] = column.default ?? '' })
-  return project
-}
-
-const loadTaskDetail = async () => {
-  loading.value = true
-  try {
-    const task = await taskStore.fetchTaskDetail(moduleCode.value, route.params.id as string)
-    if (!task) { ElMessage.error('检验任务不存在'); handleBack(); return }
-    taskData.value = task
-    detail.projects = task.detail?.projects ? task.detail.projects.map((item: any) => ({ ...item })) : []
-    detail.statistics = { ...(task.detail?.statistics || {}) }
-    detail.exceptions = task.detail?.exceptions ? task.detail.exceptions.map((item: any) => ({ ...item })) : []
-  } catch { ElMessage.error('加载检验单失败') }
-  finally { loading.value = false }
-}
-
-const handleAddProject = () => { detail.projects.push(createDefaultProject()) }
-const handleDeleteProject = (index: number) => { detail.projects.splice(index, 1) }
-const handleBack = () => { router.push(`/qms/${moduleCode.value}/task/list`) }
-const handleSave = async () => { saving.value = true; setTimeout(() => { ElMessage.success('保存成功'); saving.value = false }, 300) }
-const handleSubmit = async () => { submitting.value = true; setTimeout(() => { ElMessage.success('提交成功'); handleBack(); submitting.value = false }, 300) }
-const handlePrint = () => { ElMessage.info('打印功能开发中...') }
-
-onMounted(() => { loadTaskDetail() })
-</script>
-
-<style lang="scss" scoped>
-.page-container { padding: 20px; min-height: calc(100vh - 72px); }
-.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; h1 { font-size: 20px; font-weight: 600; color: #303133; margin: 0; } }
-.info-card { background: #fff; border-radius: 8px; padding: 16px 20px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06); margin-bottom: 20px; }
-.info-item { display: flex; align-items: center; gap: 4px; .info-label { color: #909399; font-size: 13px; } .info-value { color: #303133; font-size: 14px; } }
-.steps-container { background: #fff; border-radius: 8px; padding: 24px 20px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06); margin-bottom: 20px; }
-.form-container { background: #fff; border-radius: 8px; padding: 24px 20px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06); display: flex; flex-direction: column; gap: 24px; }
-.form-section { display: flex; flex-direction: column; gap: 16px; }
-.section-title { display: flex; align-items: center; justify-content: space-between; font-size: 16px; font-weight: 600; color: #303133; }
-.section-actions { display: flex; gap: 8px; }
-.form-actions { display: flex; gap: 12px; justify-content: flex-end; }
-@media (max-width: 768px) { .page-container { padding: 12px; } .form-actions { flex-wrap: wrap; justify-content: flex-start; } }
-</style>

+ 51 - 48
yudao-ui/yudao-ui-admin-vue3/src/views/qms/TaskList.vue

@@ -3,20 +3,10 @@
     <div class="page-header">
       <h1>{{ taskConfig.title }}</h1>
       <div class="header-actions">
-        <el-button @click="handleExport"><Icon icon="ep:download" class="mr-5px" />导出</el-button>
         <el-button plain @click="handleShowGuide"><Icon icon="ep:question-filled" class="mr-5px" />使用指引</el-button>
       </div>
     </div>
 
-    <IqcSearchFilter
-      :filters="currentFilters"
-      :advanced-filters="taskConfig.advancedFilters"
-      :options="filterOptions"
-      :keyword-placeholder="keywordPlaceholder"
-      @search="handleSearch"
-      @reset="handleReset"
-    />
-
     <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="task-tabs">
       <el-tab-pane v-for="tab in taskConfig.tabs" :key="tab.key" :label="tab.label" :name="tab.key">
         <el-table v-loading="loading" :data="tableData" stripe @selection-change="handleSelectionChange">
@@ -46,7 +36,6 @@
                 <IqcPermissionButton v-if="row.status === 'pending'" :responsible-id="row.responsibleId" type="primary" size="small" @click="handleStartInspection(row)">开始检验</IqcPermissionButton>
                 <IqcPermissionButton v-if="row.status === 'processing'" :responsible-id="row.responsibleId" type="primary" size="small" @click="handleContinueInspection(row)">继续检验</IqcPermissionButton>
                 <el-button type="primary" link size="small" @click="handleView(row)">查看</el-button>
-                <el-button v-if="row.status === 'completed'" type="primary" link size="small" @click="handlePrint(row)">打印</el-button>
               </div>
             </template>
           </el-table-column>
@@ -64,7 +53,6 @@ defineOptions({ name: 'IqcTaskList' })
 
 import { computed, ref, watch, onMounted } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
-import IqcSearchFilter from '@/components/Qms/SearchFilter.vue'
 import IqcStatusBadge from '@/components/Qms/StatusBadge.vue'
 import IqcPermissionButton from '@/components/Qms/PermissionButton.vue'
 import { useQmsTaskStore } from '@/store/modules/qms/task'
@@ -79,6 +67,7 @@ const selectedRows = ref<any[]>([])
 
 const moduleCode = computed(() => (route.meta.module as string) || DEFAULT_QMS_MODULE)
 const taskConfig = computed(() => getQmsModuleConfig(moduleCode.value).taskList)
+const useRemoteData = computed(() => taskStore.isRemoteModule(moduleCode.value))
 
 const activeTab = computed({
   get: () => taskStore.activeTab(moduleCode.value),
@@ -86,68 +75,82 @@ const activeTab = computed({
 })
 
 const pagination = computed(() => taskStore.pagination[moduleCode.value])
-const currentFilters = computed(() => taskStore.filters[moduleCode.value])
 const taskColumns = computed(() => taskConfig.value.columns || [])
-const keywordPlaceholder = computed(() => `搜索${(taskConfig.value.keywordFields || ['关键字段']).join('/')}`)
 
 const filteredList = computed(() => taskStore.tasksByStatus(moduleCode.value, activeTab.value))
 const tableData = computed(() => {
   const list = filteredList.value || []
+  if (useRemoteData.value) return list
   const { currentPage, pageSize } = pagination.value
   const start = (currentPage - 1) * pageSize
   return list.slice(start, start + pageSize)
 })
-const totalCount = computed(() => (filteredList.value || []).length)
+const totalCount = computed(() => (useRemoteData.value ? pagination.value.total : (filteredList.value || []).length))
 
 const PRIORITY_MAP: Record<string, string> = { high: '高', medium: '中', low: '低' }
 const PRIORITY_COLOR: Record<string, string> = { high: '#F56C6C', medium: '#E6A23C', low: '#67C23A' }
 const getPriorityName = (priority: string) => PRIORITY_MAP[priority] || '未知'
 const getPriorityColor = (priority: string) => PRIORITY_COLOR[priority] || '#909399'
 
-const filterOptions = computed(() => {
-  const list = taskStore.tasks[moduleCode.value] || []
-  const uniqueOptions = (list: any[], field: string, labelField?: string) => {
-    const map = new Map()
-    list.forEach((item) => { const value = item[field]; if (!value) return; const label = labelField ? item[labelField] : value; if (!map.has(value)) map.set(value, { label, value }) })
-    return Array.from(map.values())
-  }
-  return {
-    taskStatuses: [{ label: '待检验', value: 'pending' }, { label: '检验中', value: 'processing' }, { label: '已完成', value: 'completed' }],
-    suppliers: uniqueOptions(list, 'supplierCode', 'supplierName'),
-    materials: uniqueOptions(list, 'materialCode', 'materialName'),
-    batches: uniqueOptions(list, 'batch'),
-    responsibles: uniqueOptions(list, 'responsibleId', 'responsibleName'),
-    workOrders: uniqueOptions(list, 'workOrder'),
-    customers: uniqueOptions(list, 'customer'),
-    productModels: uniqueOptions(list, 'productModel'),
-    packageBatches: uniqueOptions(list, 'packageBatch')
-  }
-})
-
-const formatDateTime = (value: any) => { if (!value) return '-'; return String(value).replace('T', ' ').slice(0, 16) }
+const formatDateTime = (value: any) => {
+  if (!value) return '-'
+  const date = typeof value === 'number' || /^\d+$/.test(String(value))
+    ? new Date(Number(value))
+    : new Date(String(value))
+  if (Number.isNaN(date.getTime())) return String(value)
+  const yyyy = String(date.getFullYear())
+  const MM = String(date.getMonth() + 1).padStart(2, '0')
+  const dd = String(date.getDate()).padStart(2, '0')
+  const HH = String(date.getHours()).padStart(2, '0')
+  const mm = String(date.getMinutes()).padStart(2, '0')
+  const ss = String(date.getSeconds()).padStart(2, '0')
+  return `${yyyy}-${MM}-${dd} ${HH}:${mm}:${ss}`
+}
 const getColumnProps = (column: any) => {
   if (column.type === 'selection') return { type: 'selection', width: column.width || 55, fixed: column.fixed || false }
   return { prop: column.key, label: column.label, width: column.width, minWidth: column.minWidth, fixed: column.fixed, showOverflowTooltip: column.type === 'tooltip' }
 }
 
 const handleSelectionChange = (selection: any[]) => { selectedRows.value = selection }
-const handleSearch = (payload: any) => { taskStore.updateFilters(moduleCode.value, payload); taskStore.updatePagination(moduleCode.value, { currentPage: 1 }); fetchData() }
-const handleReset = () => { taskStore.resetFilters(moduleCode.value); taskStore.updatePagination(moduleCode.value, { currentPage: 1 }); fetchData() }
-const handleSizeChange = (size: number) => { taskStore.updatePagination(moduleCode.value, { pageSize: size, currentPage: 1 }) }
-const handleCurrentChange = (page: number) => { taskStore.updatePagination(moduleCode.value, { currentPage: page }) }
-const handleTabChange = () => { taskStore.updatePagination(moduleCode.value, { currentPage: 1 }) }
-
-const handleStartInspection = async (row: any) => { try { await taskStore.startInspection(moduleCode.value, row.id); ElMessage.success('开始检验成功'); fetchData() } catch { ElMessage.error('开始检验失败') } }
-const handleContinueInspection = (row: any) => { router.push(`/qms/${moduleCode.value}/task/${row.id}`) }
-const handleView = (row: any) => { router.push(`/qms/${moduleCode.value}/task/${row.id}`) }
-const handlePrint = () => { ElMessage.info('打印功能开发中...') }
-const handleExport = () => { ElMessage.info('导出功能开发中...') }
+const handleSizeChange = (size: number) => { taskStore.updatePagination(moduleCode.value, { pageSize: size, currentPage: 1 }); fetchData() }
+const handleCurrentChange = (page: number) => { taskStore.updatePagination(moduleCode.value, { currentPage: page }); fetchData() }
+const handleTabChange = () => { taskStore.updatePagination(moduleCode.value, { currentPage: 1 }); fetchData() }
+
+const goToProcessDetail = (row: any, processInstanceId?: string) => {
+  const targetId = processInstanceId || row?.processInstanceId
+  if (targetId) {
+    router.push({ name: 'BpmProcessInstanceDetail', query: { id: targetId } })
+    return
+  }
+  ElMessage.warning('流程未发起,请先点击开始检验')
+}
+const handleStartInspection = async (row: any) => {
+  try {
+    const processInstanceId = await taskStore.startInspection(moduleCode.value, row.id)
+    ElMessage.success('开始检验成功')
+    goToProcessDetail(row, processInstanceId)
+  } catch {
+    ElMessage.error('开始检验失败')
+  }
+}
+const handleContinueInspection = async (row: any) => {
+  if (row?.processInstanceId) {
+    goToProcessDetail(row)
+    return
+  }
+  try {
+    const processInstanceId = await taskStore.startInspection(moduleCode.value, row.id)
+    goToProcessDetail(row, processInstanceId)
+  } catch {
+    ElMessage.error('继续检验失败')
+  }
+}
+const handleView = (row: any) => { goToProcessDetail(row) }
 const handleShowGuide = () => { ElMessage.info('使用指引功能开发中...') }
 
 const fetchData = async () => { loading.value = true; try { await taskStore.fetchTasks(moduleCode.value) } catch { ElMessage.error('获取任务列表失败') } finally { loading.value = false } }
 
 watch(moduleCode, () => { fetchData() })
-watch(() => activeTab.value, () => { taskStore.updatePagination(moduleCode.value, { currentPage: 1 }) })
 onMounted(() => { fetchData() })
 </script>
 

+ 142 - 0
yudao-ui/yudao-ui-admin-vue3/src/views/qms/iqc/task/ProcessDetailForm.vue

@@ -0,0 +1,142 @@
+<template>
+  <div class="iqc-process-form" v-loading="loading">
+    <template v-if="detail">
+      <div class="form-section">
+        <div class="section-title">流程信息</div>
+        <el-descriptions :column="3" border>
+          <el-descriptions-item label="流程实例">
+            {{ detail.processInstanceId || '-' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="流程状态">
+            <el-tag v-if="detail.flowState" type="info">{{ detail.flowState }}</el-tag>
+            <span v-else>-</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="检验状态">
+            <el-tag v-if="detail.inspectStatus" type="success">{{ detail.inspectStatus }}</el-tag>
+            <span v-else>-</span>
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+
+      <div class="form-section">
+        <div class="section-title">申请信息</div>
+        <el-descriptions :column="3" border>
+          <el-descriptions-item label="任务编号">
+            {{ detail.id ?? '-' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="单据编号">
+            {{ detail.applicationId || '-' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="申请时间">
+            {{ formatDateTime(detail.applyTime) }}
+          </el-descriptions-item>
+          <el-descriptions-item label="申请人">
+            {{ detail.applicantName || '-' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="来源单号">
+            {{ detail.sourceOrderNum || '-' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="来源类型">
+            {{ detail.sourceOrderType || '-' }}
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+
+      <div class="form-section">
+        <div class="section-title">检验明细</div>
+        <el-table :data="detailRows" border>
+          <el-table-column prop="batch" label="批次" width="160" />
+          <el-table-column prop="quantity" label="数量" width="120" />
+          <el-table-column prop="unit" label="单位" width="100" />
+          <el-table-column prop="sourceOrderNum" label="来源单号" min-width="200" />
+          <el-table-column prop="sourceOrderType" label="来源类型" width="140" />
+        </el-table>
+      </div>
+
+      <div class="form-section">
+        <div class="section-title">备注</div>
+        <el-input
+          :model-value="remarkValue"
+          type="textarea"
+          :rows="3"
+          readonly
+          placeholder="-"
+        />
+      </div>
+    </template>
+    <el-empty v-else description="暂无任务详情" />
+  </div>
+</template>
+
+<script setup lang="ts">
+defineOptions({ name: 'IqcTaskProcessDetailForm' })
+
+import { computed, ref, watch } from 'vue'
+import { getIqcTaskDetail, IqcTaskDetailRespVO } from '@/api/qms/iqc/task'
+
+const props = defineProps<{ id: string }>()
+
+const loading = ref(false)
+const detail = ref<IqcTaskDetailRespVO | null>(null)
+
+const remarkValue = computed(() => detail.value?.remark || '')
+const detailRows = computed(() => {
+  if (!detail.value) return []
+  return [{
+    batch: detail.value.batch || '-',
+    quantity: detail.value.quantity ?? '-',
+    unit: detail.value.unit || '-',
+    sourceOrderNum: detail.value.sourceOrderNum || '-',
+    sourceOrderType: detail.value.sourceOrderType || '-'
+  }]
+})
+
+const formatDateTime = (value: any) => {
+  if (!value) return '-'
+  const date = typeof value === 'number' || /^\d+$/.test(String(value))
+    ? new Date(Number(value))
+    : new Date(String(value))
+  if (Number.isNaN(date.getTime())) return String(value)
+  const yyyy = String(date.getFullYear())
+  const MM = String(date.getMonth() + 1).padStart(2, '0')
+  const dd = String(date.getDate()).padStart(2, '0')
+  const HH = String(date.getHours()).padStart(2, '0')
+  const mm = String(date.getMinutes()).padStart(2, '0')
+  const ss = String(date.getSeconds()).padStart(2, '0')
+  return `${yyyy}-${MM}-${dd} ${HH}:${mm}:${ss}`
+}
+
+const fetchDetail = async () => {
+  if (!props.id) {
+    detail.value = null
+    return
+  }
+  loading.value = true
+  try {
+    const result = await getIqcTaskDetail(props.id)
+    detail.value = (result as any)?.data ?? result
+  } catch {
+    detail.value = null
+    ElMessage.error('获取任务详情失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+watch(() => props.id, () => { fetchDetail() }, { immediate: true })
+</script>
+
+<style scoped lang="scss">
+.iqc-process-form {
+  padding: 4px 0;
+}
+.form-section {
+  margin-bottom: 16px;
+  .section-title {
+    font-size: 14px;
+    font-weight: 600;
+    color: var(--el-text-color-primary);
+    margin-bottom: 10px;
+  }
+}
+</style>