# 维修工单业务流程优化文档

## 目录
1. [概述](#概述)
2. [修改内容总结](#修改内容总结)
3. [业务流程梳理](#业务流程梳理)  
4. [工作流集成](#工作流集成)
5. [状态流转逻辑](#状态流转逻辑)
6. [外协ID字段映射问题解决](#外协id字段映射问题解决)
7. [Web端功能简化](#web端功能简化)
8. [接口文档](#接口文档)
9. [数据表结构](#数据表结构)

---

## 概述

本次优化基于原有故障维修工单系统，完善了审批流程、PDA端状态检查、主管确认等关键业务功能，并与Warm-flow工作流框架深度集成，实现了完整的维修工单生命周期管理。

### 核心改进
- **完善审批机制**：增加工单审批环节，确保维修申请的合规性
- **状态流转优化**：明确各阶段状态转换规则，防止非法操作
- **工作流集成**：与Warm-flow框架深度整合，实现标准化流程管控
- **PDA端适配**：保持PDA代码不变的前提下，完善后端状态检查逻辑
- **外协ID映射修复**：解决PDA端outsrcId与后端outsourcingId的字段映射问题
- **Web端功能简化**：Web端专注于审批功能，避免重复信息录入
- **查询逻辑优化**：修复多条记录查询异常问题

---

## 修改内容总结

### ✅ 已完成功能
1. **完整的审批流程** - Web端审批，PDA端状态检查
2. **状态流转控制** - 严格的状态检查和转换逻辑  
3. **工作流集成** - 与Warm-flow框架深度整合
4. **主管确认机制** - 维修质量的最终把关
5. **循环维修机制** - 确认不通过自动返回维修环节
6. **质量控制闭环** - 从申请到确认的完整质量管控体系
7. **PDA端适配** - 保持原有PDA代码不变
8. **代码质量保障** - 通过全面审核，确保与PDA标准完全一致
9. **外协ID映射修复** - 解决PDA端outsrcId与后端outsourcingId的字段映射问题
10. **Web端功能简化** - Web端专注审批，PDA端负责工单创建和维修，避免重复录入
11. **查询逻辑优化** - 使用instance_activity_id确保获取最新活动记录

### 🔄 业务流程
PDA创建工单 → Web审批 → PDA维修 → Web确认 → 工单完成

### 📊 技术特点
- **代码复用性高** - 模块化设计，便于扩展
- **状态管理严格** - 防止非法操作和数据不一致
- **工作流标准化** - 符合企业流程管控要求
- **接口设计清晰** - 前后端分离，易于维护
- **PDA标准兼容** - 完全符合原有PDA客户端要求
- **字段映射完善** - 解决不同端字段类型差异问题
- **职责分离明确** - 各端专注自身核心功能，提高效率

---

## 业务流程梳理

### 整体流程图
```
PDA端创建工单 → 工作流启动 → Web端审批 → PDA端维修 → Web端主管确认 → 工单完成
     ↓              ↓           ↓            ↓             ↓
   申请阶段      待审批状态    审批通过     维修完成      确认完成
```

### 各端操作详情

#### PDA端操作
1. **创建工单** (`insertFaultInstsanceActivity`)
   - 选择设备
   - 填写故障信息（故障类型、描述、涉及操作）
   - 选择维修类型（内部维修/外协维修）
   - 如果选择外协，选择外协单位（传递outsrcId）
   - 上传故障图片
   - 提交后自动启动工作流并生成DmsFaultInstanceActivity记录

2. **获取工单** (`getBillsFaultInstance4Repair`)
   - 查询待维修工单列表
   - 检查审批状态

3. **开始维修** (`startRepair`)
   - 检查审批状态（必须为"2"通过）
   - 更新工单状态为"1"维修中
   - 记录维修开始时间

4. **完成维修** (`completeRepair`)
   - 填写维修内容、措施、维修人员
   - 上传维修后图片
   - 更新工单状态为"2"维修完成
   - 设置确认状态为"0"待确认

#### Web端操作
1. **审批处理** (`/dmsFaultInstanceActivity?type=approval`)
   - 通过我的待办跳转到审批页面
   - 查看PDA端创建的工单详细信息（只读）
   - 使用submitVerify组件完成审批
   - **注意**：Web端不创建工单，不编辑工单信息，专注于审批功能

2. **主管确认** (`/confirm`)
   - 查看PDA端完成的维修结果
   - 确认维修质量
   - 选择确认结果：
     - **确认通过**：工单完成，流程结束
     - **确认不通过**：工单返回维修环节，形成循环

### 业务流程简化原则

1. **职责分离**：
   - **PDA端**：工单创建、维修执行、信息录入
   - **Web端**：审批决策、质量确认

2. **避免重复录入**：
   - 工单信息只在PDA端录入一次
   - Web端只读显示，不允许编辑

3. **按创建时间倒序显示**：
   - 工单实例信息按创建时间倒序排列
   - 确保最新的维修记录优先显示

---

## 外协ID字段映射问题解决

### 问题描述
1. **字段类型不匹配**：PDA端使用 `outsrcId`（int类型），后端期望 `outsourcingId`（Long类型）
2. **数据表结构差异**：`dms_bills_fault_instance` 表没有 `outsourcingId` 字段，该字段在 `dms_fault_instance_activity` 表中
3. **查询逻辑问题**：查询最新活动记录时使用了错误的字段排序，导致返回多条记录

### 解决方案

#### 1. 字段转换处理
```java
// 在DmsBillsFaultInstanceServiceImpl.insertByBo()中添加转换逻辑
if (bo.getOutsrcId() != null && bo.getOutsrcId() > 0) {
    bo.setOutsourcingId(Long.valueOf(bo.getOutsrcId()));
}
```

#### 2. Bo类字段扩展
```java
// 在DmsBillsFaultInstanceBo中添加字段
/**
 * 外协ID（PDA端传递的int类型，需要转换为outsourcingId）
 */
private Integer outsrcId;
```

#### 3. 查询逻辑修复
```sql
-- 修复前：可能返回多条相同 process_step_order 的记录
and dfia.process_step_order = (select max(process_step_order) ...)

-- 修复后：确保返回唯一的最新记录
and dfia.instance_activity_id = (select max(instance_activity_id) ...)
```

#### 4. PDA端兼容性处理
```java
// 在DmsMobileController中处理字段转换
if (voInstance.getOutsourcingId() != null) {
    try {
        mobileInstance.setOutsrcId(Math.toIntExact(voInstance.getOutsourcingId()));
    } catch (ArithmeticException e) {
        log.warn("外协单位ID转换失败，值过大: {}", voInstance.getOutsourcingId());
        mobileInstance.setOutsrcId(0); // 设置为0作为默认值
    }
} else {
    mobileInstance.setOutsrcId(0); // 当outsourcingId为null时，明确设置为0
}
```

---

## Web端功能简化

### 简化原则
1. **移除创建功能**：Web端不再提供工单创建功能
2. **移除编辑功能**：Web端不再提供工单信息编辑功能
3. **专注审批**：Web端只负责审批和主管确认
4. **只读显示**：所有工单信息以只读方式显示

### 具体修改

#### 1. 页面结构简化
- 移除"提交"和"暂存"按钮
- 移除分步骤的表单编辑界面
- 改为统一的只读信息展示

#### 2. 表单验证简化
```javascript
// 只保留审批相关的验证规则
const rules = reactive({
  approveStatus: [{ required: false, message: '审批状态不能为空', trigger: 'change' }],
  processHandleResolution: [{ required: false, message: '处理意见不能为空', trigger: 'blur' }]
});
```

#### 3. 业务逻辑简化
- 移除工单创建逻辑
- 移除工单编辑逻辑
- 保留审批和确认逻辑

#### 4. 界面展示优化
```vue
<!-- PDA端创建的工单详情（只读显示） -->
<el-card shadow="never">
  <h4 class="form-header">PDA端创建的工单详情（只读）</h4>
  <!-- 所有字段都设置为disabled -->
</el-card>
```

---

## 工作流集成

### 工作流定义
- **流程代码**：`Fault01`
- **节点配置**：
  1. 故障申请（PDA端）
  2. 工单审批（Web端）
  3. 维修处理（PDA端）
  4. 主管确认（Web端）

### 状态映射关系
| 工作流状态 | 工单状态 | 审批状态 | 确认状态 | 说明 |
|-----------|---------|---------|---------|------|
| 故障申请 | 0(待维修) | 1(待审批) | null | PDA端创建工单后 |
| 工单审批 | 0(待维修) | 2(审批通过)/3(审批拒绝) | null | Web端审批后 |
| 维修处理 | 1(维修中)/2(维修完成) | 2(审批通过) | 0(待确认) | PDA端维修过程 |
| 主管确认 | 2(维修完成) | 2(审批通过) | 1(确认完成)/2(确认不通过) | Web端确认 |

---

## 状态流转逻辑

### 状态检查规则
1. **开始维修前检查**：
   - 审批状态必须为"2"（审批通过）
   - 工单状态必须为"0"（待维修）

2. **完成维修后状态**：
   - 工单状态更新为"2"（维修完成）
   - 确认状态设置为"0"（待确认）

3. **主管确认规则**：
   - 确认通过：工单完成，流程结束
   - 确认不通过：返回维修环节，形成循环

### 循环维修机制
- 主管确认不通过时，工单状态回到"0"（待维修）
- PDA端可以重新开始维修
- 系统创建新的活动记录，保持历史记录完整性

---

## 接口文档

### PDA端接口

#### 1. 创建故障工单
- **接口**：`POST /dms/mobile/insertFaultInstsanceActivity`
- **参数**：
  ```json
  {
    "deviceId": 20033,
    "outsrcId": 1,  // PDA端传递的外协ID
    "faultType": "1",
    "faultDescription": "设备故障描述",
    "repairType": "2"
  }
  ```

#### 2. 获取工单详情
- **接口**：`GET /dms/mobile/getBillsFaultInstance4Repair/{repairInstanceId}`
- **返回**：包含转换后的outsrcId字段

#### 3. 开始维修
- **接口**：`POST /dms/mobile/startRepair`
- **状态检查**：审批状态必须为"2"

#### 4. 完成维修
- **接口**：`POST /dms/mobile/completeRepair`
- **状态更新**：工单状态→"2"，确认状态→"0"

### Web端接口

#### 1. 审批工单
- **接口**：`POST /dms/dmsBillsFaultInstance/approve`
- **参数**：
  ```json
  {
    "repairInstanceId": "工单ID",
    "approveStatus": "2",  // 2-通过，3-拒绝
    "message": "审批意见"
  }
  ```

#### 2. 主管确认
- **接口**：`POST /dms/dmsBillsFaultInstance/confirm`
- **参数**：
  ```json
  {
    "repairInstanceId": "工单ID",
    "confirmResult": "1",  // 1-确认通过，2-确认不通过
    "confirmUser": "确认人"
  }
  ```

---

## 数据表结构

### 主要表结构

#### 1. dms_bills_fault_instance（故障工单主表）
- `repair_instance_id`：工单主键
- `bills_status`：工单状态（0待维修，1维修中，2维修完成）
- `approve_status`：审批状态（1待审批，2审批通过，3审批拒绝）
- `repair_confirm`：确认状态（0待确认，1确认完成，2确认不通过）

#### 2. dms_fault_instance_activity（故障工单活动表）
- `instance_activity_id`：活动主键（自增ID）
- `repair_instance_id`：关联工单主键
- `outsourcing_id`：外协单位ID（Long类型）
- `process_step_order`：流程步骤顺序

### 字段映射关系
| PDA端字段 | 后端字段 | 数据类型转换 | 说明 |
|----------|---------|-------------|------|
| outsrcId | outsourcingId | int → Long | 外协单位ID |
| deviceId | machineId | int → Long | 设备ID |
| repairInstanceId | repairInstanceId | String → Long | 工单ID |

---

## 🚀 后续优化建议
1. 添加工单超时预警机制
2. 完善维修知识库集成
3. 增加维修效率统计分析
4. 支持批量操作功能
5. 前端常量统一管理（避免硬编码）
6. 添加外协单位管理功能
7. 完善维修历史记录查询和统计分析
8. 优化查询性能，添加适当索引
9. 完善异常处理和日志记录
10. 添加数据备份和恢复机制

## 问题背景
在维修工单系统的使用过程中，发现了以下问题：
1. PDA端显示的工单数量与数据库实际记录不符
2. 点击工单时出现SQL查询异常：`Expected one result (or null) to be returned by selectOne(), but found: 2`
3. 外协ID字段映射问题：PDA端传递`outsrcId`(int)，后端期望`outsourcingId`(Long)
4. Web端和PDA端功能重复，用户需要在两个端重复录入信息

## 根本原因分析
1. **查询逻辑问题**：使用`MAX(process_step_order)`查询最新记录时，在循环维修场景下会返回多条相同最大值的记录
2. **字段映射不匹配**：PDA端使用`outsrcId`(int)，后端使用`outsourcingId`(Long)
3. **业务流程设计问题**：Web端和PDA端都在创建和编辑工单信息
4. **数据库异常处理**：在某些边界情况下，查询可能返回多条记录导致系统异常

## 解决方案

### 1. 数据库查询优化
**问题**：`selectFaultInstanceJoinFirstAndDeviceById`查询可能返回多条记录

**解决方案**：
- 修改SQL查询逻辑，使用更精确的条件确保返回唯一记录
- 添加备用查询方案，当主查询失败时自动切换

```xml
<!-- 优化后的查询SQL -->
<select id="selectFaultInstanceJoinFirstAndDeviceById" parameterType="Long" resultMap="DmsBillsFaultInstanceResult">
    select dbfi.repair_instance_id, dbfi.fault_source_type, dbfi.fault_source_id,
           -- 其他字段...
    from dms_bills_fault_instance dbfi
        left join prod_base_machine_info dbdl on dbfi.machine_id=dbdl.machine_id
        left join dms_fault_instance_activity dfia on (dbfi.repair_instance_id=dfia.repair_instance_id
            and dfia.instance_activity_id = (
                select max(instance_activity_id)
                from dms_fault_instance_activity dfia2
                where dfia2.repair_instance_id = dbfi.repair_instance_id
                and dfia2.process_step_order = (
                    select max(process_step_order)
                    from dms_fault_instance_activity dfia3
                    where dfia3.repair_instance_id = dbfi.repair_instance_id
                )
            ))
    where dbfi.repair_instance_id = #{repairInstanceId}
</select>
```

### 2. 防重复机制
**问题**：在网络不稳定或重试场景下，可能重复插入维修记录

**解决方案**：
- 在开始维修前检查是否已存在相同步骤的记录
- 添加幂等性保护，避免重复操作

```java
// 防重复检查
MPJLambdaWrapper<DmsFaultInstanceActivity> checkWrapper = JoinWrappers.lambda(DmsFaultInstanceActivity.class)
    .eq(DmsFaultInstanceActivity::getRepairInstanceId, repairInstanceId)
    .eq(DmsFaultInstanceActivity::getProcessStepOrder, 2L);
Long existingCount = dmsFaultInstanceActivityMapper.selectCount(checkWrapper);
if (existingCount > 0) {
    log.warn("工单已经开始维修，无需重复操作，repairInstanceId: {}", repairInstanceId);
    return 1; // 返回成功，避免重复操作
}
```

### 3. 异常处理机制
**问题**：当查询返回多条记录时，系统直接抛出异常

**解决方案**：
- 添加异常捕获和备用查询方案
- 提供降级处理机制

```java
try {
    // 主查询方案
    DmsBillsFaultInstanceVo dmsBillsFaultInstance = baseMapper.selectFaultInstanceJoinFirstAndDeviceById(repairInstanceId);
    return dmsBillsFaultInstance;
} catch (Exception e) {
    if (e.getMessage() != null && e.getMessage().contains("Expected one result")) {
        log.warn("检测到查询返回多条记录异常，尝试备用查询方案，repairInstanceId: {}", repairInstanceId);
        
        // 备用方案：分别查询工单基本信息和最新活动记录
        DmsBillsFaultInstanceVo workOrder = baseMapper.selectVoById(repairInstanceId);
        // 查询最新的活动记录并合并数据
        // ...
        return workOrder;
    }
    throw new ServiceException("查询工单详情失败：" + e.getMessage());
}
```

### 4. 数据清理建议
如果数据库中已经存在重复或异常数据，建议执行以下清理操作：

```sql
-- 1. 查找重复的活动记录
SELECT repair_instance_id, process_step_order, COUNT(*) as count
FROM dms_fault_instance_activity 
GROUP BY repair_instance_id, process_step_order 
HAVING COUNT(*) > 1;

-- 2. 保留最新的记录，删除重复记录（谨慎操作，建议先备份）
-- 这里需要根据具体情况制定清理策略
```

### 5. 外协ID字段映射修复
**问题**：PDA端传递`outsrcId`(int)，后端期望`outsourcingId`(Long)

**解决方案**：
1. 在`DmsBillsFaultInstanceBo`中添加`outsrcId`字段用于接收PDA端数据
2. 在`insertByBo`方法中实现字段转换
3. 在返回数据时进行反向转换，确保PDA端兼容性

```java
// 在DmsBillsFaultInstanceBo中添加字段
private Integer outsrcId; // PDA端传递的外协ID

// 在insertByBo方法中转换
if (bo.getOutsrcId() != null && bo.getOutsrcId() > 0) {
    bo.setOutsourcingId(Long.valueOf(bo.getOutsrcId()));
}
```

### 6. Web端功能简化
**问题**：Web端和PDA端功能重复，造成用户困惑

**解决方案**：
1. **PDA端职责**：工单创建、维修执行、信息录入
2. **Web端职责**：审批决策、质量确认
3. **简化流程**：PDA创建工单 → Web审批 → PDA维修 → Web确认 → 工单完成

#### Web端修改内容：
- 移除工单创建和编辑功能
- 所有工单信息设为只读显示
- 专注于审批和确认功能

### 7. 业务流程重新设计

#### 优化后的流程：
```
1. PDA端创建工单
   ├── 录入故障信息
   ├── 上传故障图片
   └── 提交审批

2. Web端审批
   ├── 查看工单详情（只读）
   ├── 审批决策（通过/拒绝）
   └── 推送通知

3. PDA端维修执行
   ├── 接收审批通知
   ├── 开始维修
   ├── 记录维修过程
   ├── 更换零部件记录
   ├── 上传维修图片
   └── 完成维修

4. Web端质量确认
   ├── 查看维修结果（只读）
   ├── 确认维修质量
   └── 工单完成
```

## 技术实现细节

### 字段映射关系
| PDA端字段 | 后端字段 | 数据类型转换 | 说明 |
|----------|---------|-------------|------|
| outsrcId | outsourcingId | int → Long | 外协单位ID |
| deviceId | machineId | int → Long | 设备ID |

### 关键代码修改点
1. **DmsBillsFaultInstanceServiceImpl.java**：
   - 添加outsrcId到outsourcingId转换逻辑
   - 增加防重复检查机制
   - 添加异常处理和备用查询方案
   
2. **DmsBillsFaultInstanceMapper.xml**：
   - 优化查询逻辑，确保返回唯一记录
   
3. **Web端Vue页面**：
   - 简化功能，移除创建编辑逻辑
   - 改为只读显示模式

### 监控和日志
- 添加详细的调试日志，跟踪outsourcingId的传递过程
- 记录查询异常和备用方案的使用情况
- 监控重复操作的发生频率

## 预期效果
1. ✅ 解决SQL查询返回多条记录的异常
2. ✅ 修复外协ID字段映射问题
3. ✅ 简化Web端功能，避免重复录入
4. ✅ 明确各端职责分工
5. ✅ 保持PDA代码完全不变
6. ✅ 提高系统稳定性和用户体验
7. ✅ 增强异常处理能力
8. ✅ 提供数据清理和恢复机制

## 风险控制
1. **数据安全**：所有数据库操作都在事务中执行
2. **向下兼容**：保持PDA端接口不变
3. **降级处理**：提供备用查询方案
4. **监控告警**：记录异常情况便于排查
5. **回滚机制**：保留原有逻辑作为备用方案

## 后续优化建议
1. 考虑添加数据库索引优化查询性能
2. 实现自动数据清理机制
3. 添加更完善的监控和告警系统
4. 考虑引入分布式锁防止并发问题
5. 优化工作流程，减少不必要的步骤