# 审批引擎 - 状态机文档

> **版本**: v0.3
> **最后更新**: 2026-04-30
> **维护者**: 后端团队

---

## 📋 概述

本文档覆盖审批引擎中两个相互联动的状态机：

1. **流程实例状态机** — 对应 `approval_instances.status`，字段类型 `InstanceStatus`
2. **审批任务状态机** — 对应 `approval_tasks.status`，字段类型 `ApprovalTaskStatus`

任务状态变化通过 `ApprovalTaskAction`（24 种动作）驱动，并按节点完成情况反向推动实例状态流转。

---

## 1. 流程实例状态机

### 1.1 状态定义

| 状态代码 | 中文名 | 说明 | 终态 |
|---------|--------|------|-----|
| `RUNNING` | 审批中 | 流程执行中 | ❌ |
| `SUSPENDED` | 已暂停 | 流程暂停，等待管理员恢复（节点审批人解析失败 / 管理员手动暂停） | ❌ |
| `APPROVED` | 已通过 | 审批通过 | ✅ |
| `REJECTED` | 已拒绝 | 审批拒绝 | ✅ |
| `WITHDRAWN` | 已撤回 | 发起人撤回 | ✅ |
| `TERMINATED` | 已终止 | 管理员强制终止 | ✅ |
| `FAILED` | 失败 | 系统错误导致流程失败 | ✅ |

### 1.2 状态流转图

```mermaid
stateDiagram-v2
    [*] --> RUNNING: 发起
    RUNNING --> APPROVED: 全部节点通过
    RUNNING --> REJECTED: 任一关键节点驳回
    RUNNING --> WITHDRAWN: 发起人撤回
    RUNNING --> TERMINATED: 管理员强制终止
    RUNNING --> FAILED: Temporal 工作流执行错误
    RUNNING --> SUSPENDED: 管理员暂停 / 审批人解析失败
    SUSPENDED --> RUNNING: 管理员恢复（resumeWithApprovers）
    SUSPENDED --> TERMINATED: 暂停态被强制终止
    APPROVED --> [*]
    REJECTED --> [*]
    WITHDRAWN --> [*]
    TERMINATED --> [*]
    FAILED --> [*]
```

### 1.3 合法流转表

| 当前状态 | 可流转到 | 触发动作 | 执行者 | 前置条件 |
|---------|---------|---------|--------|---------|
| `RUNNING` | `APPROVED` | 流程完成 | 系统 | 所有节点 COMPLETED 且无 REJECT |
| `RUNNING` | `REJECTED` | 关键节点驳回 | 审批人 | 节点 mode 不允许"驳回后继续" |
| `RUNNING` | `WITHDRAWN` | 撤回 | 发起人 | 流程未到终态、撤回规则允许 |
| `RUNNING` | `TERMINATED` | 强制终止 | 管理员 | `approval:admin` 权限 |
| `RUNNING` | `FAILED` | 系统失败 | 系统 | Temporal 工作流抛错 |
| `RUNNING` | `SUSPENDED` | 暂停 | 管理员 / workflow 自动 | `approval:admin` 权限；或节点审批人解析失败由 workflow 自动挂起（ERR-20260501-004） |
| `SUSPENDED` | `RUNNING` | 恢复 | 管理员 | `approval:admin` 权限；通过 `POST /admin/:instanceId/resume-with-approvers` 注入 `approverIds` 触发 Temporal `resumeWithApprovers` signal |
| `SUSPENDED` | `TERMINATED` | 终止 | 管理员 | `approval:admin` 权限 |
| `FAILED` | — | — | — | 终态。需新发起流程，不支持原地恢复（v2.x） |

---

## 2. 任务状态机（`ApprovalTaskStatus`）

### 2.1 状态定义（10 个）

| 状态代码 | 中文名 | 说明 | 终态 |
|---------|--------|------|-----|
| `CREATED` | 已创建 | 任务由节点实例化生成，但尚未派发 | ❌ |
| `PENDING` | 待领取 | 进入审批人候选池，等待领取或自动分配 | ❌ |
| `CLAIMED` | 已领取 | 候选人主动领取，锁定到该用户 | ❌ |
| `RESERVED` | 已保留 | 系统按规则预占（轮询/优先级），可被释放 | ❌ |
| `ASSIGNED` | 已指派 | 直接派发到固定用户（无候选池）| ❌ |
| `IN_PROGRESS` | 处理中 | 审批人开始操作（已查看/已编辑表单字段）| ❌ |
| `COMPLETED` | 已完成 | 审批人执行了 APPROVE / REJECT / RETURN 等终结动作 | ✅ |
| `CANCELLED` | 已取消 | 节点级取消（如或签其他人已通过、流程跳过此节点）| ✅ |
| `SUSPENDED` | 已暂停 | 实例被暂停时任务进入冻结态，恢复后回到原状态 | ❌ |
| `WITHDRAWN` | 已撤回 | 发起人撤回 / 审批人撤回 / 管理员终止时连带 | ✅ |

> 终态后任务不可再被操作（除 `ADMIN_REASSIGN` 等管理员强制操作）。

### 2.2 任务状态流转

```mermaid
stateDiagram-v2
    [*] --> CREATED: 节点实例化
    CREATED --> PENDING: 进入候选池
    CREATED --> ASSIGNED: 直接指派
    PENDING --> CLAIMED: 候选人领取
    PENDING --> RESERVED: 系统预占
    PENDING --> ASSIGNED: 委托/转发
    RESERVED --> CLAIMED: 转为领取
    RESERVED --> PENDING: 释放
    CLAIMED --> IN_PROGRESS: 开始处理
    ASSIGNED --> IN_PROGRESS: 开始处理
    IN_PROGRESS --> COMPLETED: APPROVE/REJECT/RETURN
    PENDING --> CANCELLED: 节点跳过/或签他人完成
    CLAIMED --> CANCELLED: 同上
    ASSIGNED --> CANCELLED: 同上
    IN_PROGRESS --> CANCELLED: 同上
    PENDING --> SUSPENDED: 实例暂停
    CLAIMED --> SUSPENDED: 实例暂停
    ASSIGNED --> SUSPENDED: 实例暂停
    IN_PROGRESS --> SUSPENDED: 实例暂停
    SUSPENDED --> PENDING: 实例恢复
    SUSPENDED --> CLAIMED: 实例恢复
    SUSPENDED --> ASSIGNED: 实例恢复
    SUSPENDED --> IN_PROGRESS: 实例恢复
    PENDING --> WITHDRAWN: 流程撤回/终止
    CLAIMED --> WITHDRAWN
    ASSIGNED --> WITHDRAWN
    IN_PROGRESS --> WITHDRAWN
    COMPLETED --> [*]
    CANCELLED --> [*]
    WITHDRAWN --> [*]
```

---

## 3. 任务动作矩阵（`ApprovalTaskAction`，24 种）

每行：动作 → 适用任务状态 → 完成后任务状态 → 对实例状态的连带影响。

### 3.1 基本审批动作

| 动作 | 适用 task.status | 完成后 task.status | 对实例的影响 |
|------|------------------|--------------------|-------------|
| `SUBMIT` | — | — | 由发起方触发，创建实例（`InstanceStatus = RUNNING`）|
| `APPROVE` | `CLAIMED` / `ASSIGNED` / `IN_PROGRESS` | `COMPLETED` | 节点完成；最后一个节点完成 → 实例 `APPROVED` |
| `REJECT` | 同上 | `COMPLETED` | 关键节点 → 实例 `REJECTED`（终态）|
| `RETURN` | 同上 | `COMPLETED` | 当前节点回退到指定上游节点；实例保持 `RUNNING` |
| `FORWARD` | 同上 | 转新任务，本任务保持原状或并发 | 实例 `RUNNING` 不变 |

### 3.2 撤回动作

| 动作 | 适用 task.status | 完成后 task.status | 对实例的影响 |
|------|------------------|--------------------|-------------|
| `WITHDRAW` | 由发起人触发，作用于实例上 | 所有未终结任务 → `WITHDRAWN` | 实例 → `WITHDRAWN` |
| `APPROVER_WITHDRAW` | `COMPLETED` 之后 + 满足审批人撤回规则 | 该任务 → 重新激活为 `CLAIMED` 或 `ASSIGNED`，下游若已完成则连带回退 | 实例保持 `RUNNING` |

### 3.3 加减签动作

| 动作 | 适用 task.status | 完成后 task.status | 对实例的影响 |
|------|------------------|--------------------|-------------|
| `ADD_SIGN` | `CLAIMED` / `ASSIGNED` / `IN_PROGRESS` | 当前任务保持原态；新增子任务 `PENDING` 或 `ASSIGNED` | 实例 `RUNNING` |
| `REMOVE_SIGN` | 当节点为会签/或签且未完成时 | 被减签的子任务 → `CANCELLED` | 实例 `RUNNING`；若剩余审批人完成条件已满足，节点直接 `COMPLETED` |

### 3.4 任务领取/委托动作

| 动作 | 适用 task.status | 完成后 task.status | 对实例的影响 |
|------|------------------|--------------------|-------------|
| `CLAIM` | `PENDING` / `RESERVED` | `CLAIMED` | 无 |
| `UNCLAIM` | `CLAIMED` / `RESERVED` | `PENDING` | 无 |
| `DELEGATE` | `CLAIMED` / `ASSIGNED` / `IN_PROGRESS` | 原任务 → `CANCELLED`，生成 `delegationType` 标记的新任务给被委托人 | 无 |

### 3.5 系统动作

| 动作 | 适用 task.status | 完成后 task.status | 对实例的影响 |
|------|------------------|--------------------|-------------|
| `AUTO_APPROVE` | `PENDING` / `CLAIMED` / `ASSIGNED` / `IN_PROGRESS` | `COMPLETED`（`autoApproved=true`）| 同 `APPROVE` |
| `AUTO_REJECT` | 同上 | `COMPLETED` | 同 `REJECT` |
| `ESCALATE` | 任意未终结状态 | 任务保持，重新指派到上级 | 实例 `RUNNING` |
| `REMIND` | 任意未终结状态 | 任务保持，`reminderCount += 1`、`lastReminderAt` 更新 | 无 |

### 3.6 管理员动作

| 动作 | 适用 task.status | 完成后 task.status | 对实例的影响 |
|------|------------------|--------------------|-------------|
| `ADMIN_APPROVE` | 任意未终结 | `COMPLETED`（log 中 `targetUserId` 记录原审批人）| 同 `APPROVE` |
| `ADMIN_REJECT` | 任意未终结 | `COMPLETED` | 同 `REJECT` |
| `ADMIN_TERMINATE` | 实例级 | 所有未终结任务 → `WITHDRAWN` | 实例 → `TERMINATED` |
| `ADMIN_REASSIGN` | 任意未终结 | 任务保持，重新分配 `assignee` | 实例 `RUNNING` |
| `ADMIN_RESUME` | 实例级（仅 `SUSPENDED`）| 当前节点注入 `approverIds`，按正常审批继续 | 实例 `SUSPENDED` → `RUNNING`；底层审计日志复用 `ADMIN_REASSIGN`，响应 `action` 字段返回 `'ADMIN_RESUME'` |

### 3.7 其他动作

| 动作 | 用途 | 对状态的影响 |
|------|------|-------------|
| `COMPLETE` | 系统级完成（与 APPROVE 区分用于 SERVICE_TASK / SCRIPT_TASK 等非审批节点）| 任务 → `COMPLETED` |
| `CANCEL` | 节点级取消（或签他人已通过） | 任务 → `CANCELLED` |
| `COMMENT` | 仅写日志，不改状态 | 无 |
| `EXECUTE` | USER_TASK 中的"执行"操作（填写字段）| 任务 → `IN_PROGRESS` |

---

## 4. 关键交互行为

### 4.1 退回（`RETURN`）

- 当前节点任务 `COMPLETED`，实例保持 `RUNNING`
- 工作流引擎将流程指针回退到指定上游节点；该节点已 `COMPLETED` 的任务被打 `WITHDRAWN`，重新生成 `PENDING` 任务
- 实例终态不变

### 4.2 加签（`ADD_SIGN`）

- 不产生新节点实例；在当前节点实例下追加子任务（`taskType` 视加签策略）
- 节点完成判断条件按 `ApprovalTaskType` 重新计算（如或签：任一通过即整体完成；会签：全部通过）

### 4.3 委托（`DELEGATE`）

- 原任务 `CANCELLED`，新任务 `delegatedFrom` 指向原任务的 `assignee`，并写 `delegationType`（`PERSONAL`/`AUTO`/`ROLE_BASED` 等）
- 不影响实例状态

### 4.4 撤回（发起人 `WITHDRAW` vs 审批人 `APPROVER_WITHDRAW`）

| 场景 | 触发方 | 影响范围 |
|------|--------|---------|
| 发起人撤回 | 发起人 | 整个实例 → `WITHDRAWN`，所有未终结任务 → `WITHDRAWN` |
| 审批人撤回 | 已完成的审批人 | 仅当前任务回到激活态；下游任务（若已 `COMPLETED`）需级联回退 |

> 注：表单引擎层面的 `FormInstance.status = WITHDRAWN` 必须**反向通知审批引擎**（调用 `approvalService.withdraw(...)`），否则会出现"表单已撤但流程仍在跑"的状态漂移。详见 `form-management/services/instance.service.ts` 撤回链路。

---

### 4.5 挂起与恢复（`SUSPENDED` ↔ `RUNNING`）

**进入 `SUSPENDED` 的两种途径：**

1. **节点审批人解析失败（自动）**：workflow 在节点实例化时调用 activity 解析审批人；若解析返回空集合（如人员离职、规则匹配为空），节点保留 `PENDING` 状态，**实例自动**切到 `SUSPENDED`，等待管理员介入。详见 `temporal/workflows/generic-approval.workflow.ts` `setHandler(resumeWithApproversSignal)` 与 `temporal/activities/approval.activities.ts` 中的 `resolveApprovers` 失败分支（ERR-20260501-004）。
2. **管理员手动暂停**（roadmap，目前仅文档列出）：未通过 API 暴露。

**恢复路径：**

- 管理员调用 `POST /admin/:instanceId/resume-with-approvers`，传入 `approverIds`（必填，至少一个）+ `reason`。
- 服务层向 workflow 投递 Temporal `resumeWithApprovers` signal，payload `{ approverIds, resolvedBy }`。
- workflow 端 `setHandler(resumeWithApproversSignal)` 把 `approverIds` 注入 `state.resumedApprovers`，触发 `condition` 退出，进入正常审批流程。
- 实例 `status: SUSPENDED → RUNNING`，节点重新派发 `PENDING` 任务给注入的审批人。
- 审计日志：底层 `audit_logs.action = 'ADMIN_REASSIGN'`（避免新增枚举），但 API 响应 `action` 字段返回 `'ADMIN_RESUME'`，`riskLevel = 'HIGH'`。前端识别"恢复挂起"应基于响应 `action` 或 `reason` 中"恢复挂起流程"前缀。

**与 `ADMIN_TERMINATE` 的关系：** SUSPENDED 实例也可以直接被 `ADMIN_TERMINATE` 终止（不必先恢复）。

---

## 5. 超时与提醒

`approval_tasks` 上的相关字段：

| 字段 | 说明 |
|------|------|
| `dueDate` | 任务截止时间；为空表示无超时约束 |
| `reminderCount` | 已提醒次数 |
| `lastReminderAt` | 最近一次提醒时间 |

**超时策略**（v2.x 现状）：

- 系统目前**仅记录** `reminderCount` 与 `lastReminderAt`，由外部调度（`ReminderQueue`）触发 `REMIND` 动作通知审批人
- ⚠️ **超时自动通过 / 超时自动驳回 / 超时升级**当前未实现（roadmap P1，详见 `10-roadmap.md`）
- `dueDate` 字段已写入但**不会主动改变任务状态**

---

## 6. 失败恢复

`InstanceStatus = FAILED` 当前为终态：

- 通常由 Temporal workflow 抛错 / 节点解析异常导致
- v2.x **不支持原地 retry / resume**；须人工排查后由发起人重新发起新流程
- 管理员可通过 `ADMIN_TERMINATE` 强制把卡死的 `RUNNING` 实例转为 `TERMINATED`（不会进入 `FAILED`）

> 计划中：`/admin/instances/:id/retry` 接口（roadmap P2）允许在 Temporal workflow 修复后从最近一次 checkpoint 恢复。

---

## 7. 引用

- Schema：`backend/prisma/schema/corp_approval.prisma` 中 `enum ApprovalTaskStatus`、`enum ApprovalTaskAction`、`enum InstanceStatus`
- 服务实现：`backend/src/engines/approval/services/`
- 工作流编排：`backend/src/engines/approval/workflows/`
