# 部署记录状态：CANCELLED vs REJECTED 分析

## 当前状态

### CANCELLED（已取消）的使用场景

**唯一使用场景**：审批被拒绝
- 位置：`DeployRecordServiceImpl.updateStatusFromApproval()`
- 代码：`record.setStatus(DeployRecordStatusEnums.CANCELLED);`
- 日志：`"部署记录状态已更新为已取消（审批被拒）"`

**结论**：当前 `CANCELLED` 只用于审批被拒绝的场景。

### 其他状态的使用场景

| 状态 | 使用场景 | 来源 |
|------|---------|------|
| **TERMINATED** | 工作流被手动终止 | 工作流状态 `TERMINATED` |
| **FAILED** | 工作流执行失败 | 工作流状态 `FAILED` |
| **CANCELLED** | 审批被拒绝 | 审批事件 `ApprovalResultEnum.REJECTED` |

## 问题分析

### 问题1：语义不清晰

**当前问题**：
- `CANCELLED`（已取消）语义模糊，不能明确表示是"审批被拒绝"
- 未来如果有手动取消部署功能，会产生歧义

**对比**：
- 工作流层面：`ApprovalResultEnum.REJECTED`（拒绝）
- 部署记录层面：`CANCELLED`（已取消）
- **不一致**：两个层面使用不同的语义

### 问题2：未来扩展性

**未来可能的场景**：
1. **手动取消部署**：用户主动取消正在运行的部署
   - 应该使用什么状态？如果用 `CANCELLED`，会与"审批被拒绝"混淆
2. **超时自动取消**：部署超时自动取消
   - 应该使用什么状态？

## 方案对比

### 方案A：保留 CANCELLED，新增 REJECTED（推荐）

**状态定义**：
```java
public enum DeployRecordStatusEnums {
    // ... 其他状态 ...
    
    /**
     * 审批被拒绝（终态）
     */
    REJECTED("REJECTED", "审批被拒绝"),
    
    /**
     * 已取消（终态）
     * 用于：手动取消部署、超时自动取消等场景
     */
    CANCELLED("CANCELLED", "已取消"),
    
    // ... 其他状态 ...
}
```

**优点**：
1. ✅ 语义清晰：`REJECTED` 明确表示"审批被拒绝"
2. ✅ 与工作流层面保持一致：`ApprovalResultEnum.REJECTED` → `DeployRecordStatusEnums.REJECTED`
3. ✅ 保留扩展性：`CANCELLED` 可用于未来手动取消场景
4. ✅ 状态语义明确，便于理解和维护

**缺点**：
- 需要新增枚举值
- 需要修改相关代码
- 需要数据库迁移（如果已有数据）

**需要修改的地方**：
1. 枚举类：新增 `REJECTED`
2. `DeployRecordServiceImpl.updateStatusFromApproval()`：改为 `REJECTED`
3. `isFinalState()`：添加 `REJECTED`
4. 统计查询：`REJECTED` 计入失败计数
5. `fromWorkflowStatus()`：不需要修改（因为 `REJECTED` 不是从工作流状态转换来的）

### 方案B：直接用 REJECTED 替代 CANCELLED

**状态定义**：
```java
public enum DeployRecordStatusEnums {
    // ... 其他状态 ...
    
    /**
     * 审批被拒绝（终态）
     */
    REJECTED("REJECTED", "审批被拒绝"),
    
    // 删除 CANCELLED
}
```

**优点**：
1. ✅ 语义清晰
2. ✅ 与工作流层面保持一致
3. ✅ 简化状态模型

**缺点**：
1. ❌ 失去扩展性：如果未来需要手动取消功能，需要再添加状态
2. ❌ 如果未来需要区分"审批被拒绝"和"手动取消"，需要再次修改

### 方案C：保持现状（不推荐）

**优点**：
- 无需修改代码

**缺点**：
1. ❌ 语义不清晰
2. ❌ 与工作流层面不一致
3. ❌ 未来扩展困难

## 推荐方案

**推荐采用方案A：保留 CANCELLED，新增 REJECTED**

### 理由

1. **语义清晰**：
   - `REJECTED`：明确表示"审批被拒绝"
   - `CANCELLED`：用于"手动取消"等场景

2. **状态映射清晰**：
   ```
   审批层面：ApprovalResultEnum.REJECTED
       ↓
   部署记录：DeployRecordStatusEnums.REJECTED
   ```

3. **未来扩展性**：
   - 如果未来需要手动取消部署功能，可以直接使用 `CANCELLED`
   - 如果不需要手动取消，`CANCELLED` 可以保留但不用

4. **状态完整性**：
   - 终态包括：`SUCCESS`、`FAILED`、`REJECTED`、`CANCELLED`、`TERMINATED`、`PARTIAL_SUCCESS`
   - 语义清晰，便于理解和维护

## 实施建议

### 步骤1：新增 REJECTED 枚举

```java
/**
 * 审批被拒绝（终态）
 */
REJECTED("REJECTED", "审批被拒绝"),
```

### 步骤2：修改审批状态更新逻辑

```java
} else {
    // 审批被拒绝，更新为审批被拒绝
    record.setStatus(DeployRecordStatusEnums.REJECTED);
    log.info("部署记录状态已更新为审批被拒绝: id={}, workflowInstanceId={}", 
            record.getId(), workflowInstanceId);
}
```

### 步骤3：更新终态判断

```java
private boolean isFinalState(DeployRecordStatusEnums status) {
    return status == DeployRecordStatusEnums.SUCCESS 
            || status == DeployRecordStatusEnums.FAILED
            || status == DeployRecordStatusEnums.REJECTED  // 新增
            || status == DeployRecordStatusEnums.CANCELLED
            || status == DeployRecordStatusEnums.TERMINATED
            || status == DeployRecordStatusEnums.PARTIAL_SUCCESS;
}
```

### 步骤4：更新统计查询

```sql
-- 在统计查询中，REJECTED 计入失败
SUM(CASE WHEN dr.status IN ('FAILED', 'REJECTED', 'CANCELLED', 'TERMINATED', 'PARTIAL_SUCCESS') 
    OR (dr.status = 'CREATED' AND TIMESTAMPDIFF(MINUTE, dr.create_time, NOW()) > 30) 
    THEN 1 ELSE 0 END) as failedCount
```

### 步骤5：数据库迁移（可选）

如果已有审批被拒绝的记录，状态为 `CANCELLED`，可以考虑：
- 保留现有数据（`CANCELLED` 继续表示审批被拒绝）
- 或者迁移数据（将 `CANCELLED` 迁移为 `REJECTED`）

## 结论

**推荐新增 `REJECTED` 状态**，用于明确表示"审批被拒绝"，与工作流层面的 `ApprovalResultEnum.REJECTED` 保持一致。

**保留 `CANCELLED` 状态**，用于未来可能的手动取消部署功能。

这样既保证了语义清晰，又保留了扩展性。

