# 审批引擎架构设计

> 技术架构文档 - 系统架构、数据模型、核心设计决策、详细实现方案

---

## 📐 系统架构

### 整体架构

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                              前端层 (Next.js)                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐          │
│  │   审批中心         │  │   发起审批         │  │   审批详情         │          │
│  │ /approval-center  │  │ /approval-center  │  │ /approval/{...}  │          │
│  └──────────────────┘  └──────────────────┘  └──────────────────┘          │
│                                                                              │
│  ┌──────────────────┐  ┌──────────────────┐                                 │
│  │   流程设计器       │  │  流程图/审批历史   │                                 │
│  │  ProcessDesigner  │  │ ApprovalFlowDiagram│                                 │
│  └──────────────────┘  └──────────────────┘                                 │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓ HTTP/REST
┌─────────────────────────────────────────────────────────────────────────────┐
│                              API 网关层                                      │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │  认证 (JWT) │ 权限 (RBAC) │ 限流 │ 日志 │ 响应格式化                  │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│                           API 控制器层 (NestJS)                              │
├─────────────────────────────────────────────────────────────────────────────┤
│  ┌──────────────────────────┐  ┌──────────────────────────┐               │
│  │ ApprovalController       │  │ ProcessAdminController   │               │
│  │ 流程/任务/实例统一入口     │  │ 管理员操作与审计          │               │
│  └──────────────────────────┘  └──────────────────────────┘               │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│                             服务层 (NestJS)                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌────────────────────┐  ┌──────────────────────┐  ┌────────────────────┐   │
│  │ ApprovalService    │  │ AdminApprovalService │  │ DefinitionService  │   │
│  │ 流程/任务/实例处理   │  │ 管理员操作与审计      │  │ 流程定义管理        │   │
│  └────────────────────┘  └──────────────────────┘  └────────────────────┘   │
│                                                                              │
│  ┌────────────────────┐  ┌──────────────────────┐                           │
│  │ ProcessLoaderService│  │ BusinessTypeRegistry │                           │
│  │ 流程配置加载         │  │ 业务类型注册/回调     │                           │
│  └────────────────────┘  └──────────────────────┘                           │
└─────────────────────────────────────────────────────────────────────────────┘
                    ↓                                    ↓
┌────────────────────────────┐          ┌────────────────────────────────────┐
│       数据访问层            │          │         工作流编排层                │
│  ┌──────────────────────┐  │          │  ┌────────────────────────────┐    │
│  │    PrismaService     │  │          │  │    TemporalService         │    │
│  │  - ApprovalDefinition │  │          │  │  - 启动工作流               │    │
│  │  - ApprovalVersion    │  │          │  │  - 发送 Signal             │    │
│  │  - ApprovalInstance   │  │          │  │  - 查询 Query              │    │
│  │  - ApprovalNodeInstance      │  │          │  │  - 管理 Timer              │    │
│  │  - ApprovalTask      │  │          │  └────────────────────────────┘    │
│  │  - ApprovalTaskLog   │  │          │                                    │
│  └──────────────────────┘  │          │  ┌────────────────────────────┐    │
│              ↓             │          │  │   Temporal Worker          │    │
│  ┌──────────────────────┐  │          │  │  - Workflows               │    │
│  │     PostgreSQL       │  │          │  │  - Activities              │    │
│  └──────────────────────┘  │          │  └────────────────────────────┘    │
└────────────────────────────┘          │              ↓                     │
                                        │  ┌────────────────────────────┐    │
                                        │  │    Temporal Server         │    │
                                        │  └────────────────────────────┘    │
                                        └────────────────────────────────────┘
```

### 模块划分

```
backend/src/engines/approval/
├── approval.module.ts                    # 模块定义
├── approval.controller.ts                # 审批引擎 API
├── process-admin.controller.ts           # 管理员接口
├── approval.service.ts                   # 审批流程与任务
├── admin-approval.service.ts             # 管理员操作
├── definition.service.ts                 # 流程定义服务
├── process-loader.service.ts             # 流程配置加载
├── business-type-registry.service.ts     # 业务类型注册
├── dto/
│   ├── approval.dto.ts                   # 请求 DTO
│   └── approval-response.dto.ts          # 响应 DTO
└── temporal/
    ├── temporal.service.ts               # Temporal 接入
    ├── workflows/
    │   ├── generic-approval.workflow.ts  # 通用审批工作流
    │   └── index.ts
    └── activities/
        └── approval.activities.ts        # 审批活动
frontend/src/
├── app/(modules)/approval-center/
│   ├── page.tsx                          # 审批中心（列表）
│   └── submit/[formKey]/page.tsx         # 发起审批
├── app/(engines)/approval/[businessType]/[instanceId]/page.tsx  # 审批详情
├── app/(engines)/approvals/page.tsx      # /approvals 重定向
└── features/approval/
    ├── components/
    │   ├── ApprovalActions.tsx           # 审批操作
    │   ├── ApprovalFlowDiagram.tsx       # 流程图
    │   └── ApprovalHistory.tsx           # 审批历史
    └── designer/
        ├── DingProcessDesigner.tsx       # 钉钉风格设计器
        ├── DingStyleDesigner.tsx         # 钉钉样式设计器
        ├── DingStylePropertyPanel.tsx    # 属性面板
        ├── LarkProcessPreview.tsx        # 飞书风格预览
        ├── ProcessCanvas.tsx             # 画布
        ├── ProcessDesigner.tsx           # 设计器容器
        ├── ProcessNodePalette.tsx        # 节点面板
        ├── ProcessPreview.tsx            # 预览
        └── ProcessPropertyPanel.tsx      # 属性面板
```

### ⚠️ 边界声明

> **审批引擎仅负责「流程编排」与「任务管理」，不负责业务逻辑和表单定义。**

| 审批引擎负责 | 审批引擎不负责 |
|-------------|---------------|
| ✅ 流程定义与版本管理 | ❌ 业务数据存储 |
| ✅ 流程实例执行 | ❌ 业务规则校验 |
| ✅ 审批任务分配与管理 | ❌ 表单结构定义和渲染 |
| ✅ 审批操作处理 | ❌ 表单数据采集和验证 |
| ✅ 超时与催办处理 | ❌ 业务通知内容生成 |
| ✅ 操作日志记录 | ❌ 业务状态更新（通过回调） |

**集成方式**：
- 通过 `businessType` + `businessId` 松耦合关联业务
- 通过事件/回调通知业务系统审批结果
- 业务系统负责更新自身状态

---

## 🗄️ 数据模型设计

### ER 图

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                              数据模型关系图                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ApprovalDefinition (1)                                                       │
│       │                                                                      │
│       │ 1:N                                                                  │
│       ▼                                                                      │
│  ApprovalVersion (N) ─────────────────────────────────────────┐              │
│       │                                                       │              │
│       │ 1:N                                                   │              │
│       ▼                                                       │              │
│  ApprovalInstance (N) ──────────────────────────────────────┐ │              │
│       │                                      │              │ │              │
│       │ 1:N                                  │ N:1          │ │              │
│       ▼                                      ▼              │ │              │
│  ApprovalNodeInstance (N)                         User (发起人)     │ │              │
│       │                                                      │ │              │
│       │ 1:N                                                  │ │              │
│       ▼                                                      │ │              │
│  ApprovalTask (N) ──────────────────────────────────────────┤ │              │
│       │                                      │               │ │              │
│       │ 1:N                                  │ N:1           │ │              │
│       ▼                                      ▼               │ │              │
│  ApprovalTaskLog (N) ────────────── User (操作人)     │ │              │
│                                                              │ │              │
│  ════════════════════════════════════════════════════════════╧═╧══════════   │
│                                                                              │
│  补偿队列表:                                                                  │
│  CallbackRetryQueue (回调重试队列)                                           │
│  ReminderQueue (催办队列)                                                    │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

### 核心实体详细设计

#### 1. ApprovalDefinition（流程定义）

存储流程的元数据。

```typescript
interface ApprovalDefinition {
  id: string;                    // UUID
  key: string;                   // 唯一标识，如 "OVERTIME_APPROVAL"
  name: string;                  // 流程名称
  category: string;              // 分类：HR, FINANCE, PROCUREMENT
  description?: string;          // 描述
  
  // 版本信息
  latestVersion: number;         // 最新版本号
  
  // 多区域/多租户支持
  regionScope?: string;          // 区域范围：CN, US, ALL
  tenantId?: string;             // 租户 ID
  orgScope?: string;             // 组织范围
  
  // 状态
  status: ProcessStatus;         // ACTIVE, SUSPENDED, ARCHIVED
  
  // 审计字段
  createdBy?: string;
  createdAt: Date;
  updatedBy?: string;
  updatedAt: Date;
  
  // 关联
  versions: ApprovalVersion[];
}

enum ProcessStatus {
  ACTIVE = 'ACTIVE',
  SUSPENDED = 'SUSPENDED',
  ARCHIVED = 'ARCHIVED',
}
```

**数据库索引**:
```sql
CREATE UNIQUE INDEX idx_process_definition_key ON approval_definitions(key);
CREATE INDEX idx_process_definition_status ON approval_definitions(status);
CREATE INDEX idx_process_definition_category ON approval_definitions(category);
CREATE INDEX idx_process_definition_org ON approval_definitions(organization_id);
```

#### 2. ApprovalVersion（流程版本）

存储流程的具体版本和配置。

```typescript
interface ApprovalVersion {
  id: string;                    // UUID
  definitionId: string;          // 关联流程定义
  version: number;               // 版本号：1, 2, 3...
  name: string;                  // 版本名称
  
  // 是否为默认版本
  isDefault: boolean;
  
  // 流程模型（核心）
  processModel: ProcessModel;    // JSON - 流程图定义
  
  // 全局配置
  settings: ProcessSettings;     // JSON - 流程全局设置
  
  // 状态
  status: VersionStatus;         // DRAFT, DEPLOYED, SUPERSEDED, ARCHIVED
  
  // 部署信息
  deployedAt?: Date;
  deployedBy?: string;
  changeLog?: string;            // 变更说明
  
  // 审计字段
  createdAt: Date;
  updatedAt: Date;
}

enum VersionStatus {
  DRAFT = 'DRAFT',               // 草稿
  DEPLOYED = 'DEPLOYED',         // 已部署（生效中）
  SUPERSEDED = 'SUPERSEDED',     // 已被新版本取代
  ARCHIVED = 'ARCHIVED',         // 已归档
}
```

**流程模型结构**:

> 💡 **类型共享**: 以下类型定义建议抽取为独立的 TypeScript 类型库 `@ffoa/approval-types`，
> 供前端设计器和后端共同使用，保证两端 schema 一致。详见 [类型库设计](#-类型库设计)。

```typescript
// ==================== 核心模型 ====================

interface ProcessModel {
  nodes: ProcessNode[];
  edges: ProcessEdge[];
}

interface ProcessNode {
  id: string;                    // 节点唯一 ID
  name: string;                  // 节点名称
  type: NodeType;                // 节点类型
  description?: string;          // 节点描述
  position?: { x: number; y: number };  // 设计器中的位置
  
  // ==================== USER_TASK 专用配置 ====================
  
  // 审批人配置
  assignee?: string;             // 审批人表达式
  approvalMode?: ApprovalMode;   // 审批模式
  chainConfig?: ManagerChainConfig;  // 连续上级配置
  
  // 委托/代理配置
  delegationConfig?: DelegationConfig;  // 自动委托配置
  
  // 超时配置
  timeout?: TimeoutConfig;
  
  // 撤回配置
  withdrawConfig?: WithdrawConfig;
  
  // 表单字段配置
  editableFields?: string[];     // 可编辑字段
  requiredFields?: string[];     // 必填字段
  
  // 条件验证器（审批前校验）
  validators?: NodeValidator[];
  
  // 操作权限
  allowedActions?: ApprovalAction[];
  
  // 退回配置
  returnTargets?: string[];      // 可退回的目标节点
  
  // ==================== 钩子配置 ====================
  
  // 前置钩子（进入节点前触发）
  beforeEnterHooks?: NodeHook[];
  
  // 后置钩子（节点完成后触发）
  afterCompleteHooks?: NodeHook[];
  
  // ==================== GATEWAY 专用配置 ====================
  
  // 条件表达式
  condition?: string;
  
  // ==================== SERVICE_TASK 专用配置 ====================
  
  // 服务任务配置
  serviceConfig?: ServiceTaskConfig;
}

interface ProcessEdge {
  id: string;
  source: string;                // 源节点 ID
  target: string;                // 目标节点 ID
  condition?: string;            // 条件表达式
  label?: string;                // 连线标签
  priority?: number;             // 优先级（多条件时）
}

// ==================== 枚举定义 ====================

enum NodeType {
  START = 'START',
  END = 'END',
  USER_TASK = 'USER_TASK',
  SERVICE_TASK = 'SERVICE_TASK',
  EXCLUSIVE_GATEWAY = 'EXCLUSIVE_GATEWAY',
  PARALLEL_GATEWAY = 'PARALLEL_GATEWAY',
  INCLUSIVE_GATEWAY = 'INCLUSIVE_GATEWAY',
  SUB_PROCESS = 'SUB_PROCESS',
}

enum ApprovalMode {
  SINGLE = 'SINGLE',             // 单人审批
  AND = 'AND',                   // 会签（所有人通过）
  OR = 'OR',                     // 或签（任一人通过）
  SEQUENTIAL = 'SEQUENTIAL',     // 顺序审批
}
```

**节点钩子配置**:

```typescript
// ==================== 钩子系统 ====================

/**
 * 节点钩子 - 在节点生命周期的特定时机触发
 * 
 * 使用场景：
 * - beforeEnterHooks: 进入节点前发送通知、记录日志、调用外部系统
 * - afterCompleteHooks: 节点完成后创建后续任务、更新业务数据、触发集成
 */
interface NodeHook {
  id: string;                    // 钩子唯一 ID
  name: string;                  // 钩子名称（用于日志和调试）
  type: HookType;                // 钩子类型
  enabled: boolean;              // 是否启用
  
  // 执行条件（可选，满足条件才执行）
  condition?: string;            // 条件表达式，如 '${amount > 10000}'
  
  // 执行配置
  config: HookConfig;
  
  // 错误处理
  onError: 'ignore' | 'warn' | 'fail';  // 失败时的处理方式
  retryConfig?: {
    maxAttempts: number;
    intervalSeconds: number;
  };
}

enum HookType {
  // 通知类
  SEND_EMAIL = 'SEND_EMAIL',           // 发送邮件
  SEND_DINGTALK = 'SEND_DINGTALK',     // 发送钉钉消息
  SEND_FEISHU = 'SEND_FEISHU',         // 发送飞书消息
  SEND_WEBHOOK = 'SEND_WEBHOOK',       // 调用 Webhook
  
  // 业务类
  CREATE_TASK = 'CREATE_TASK',         // 创建业务任务（如付款任务）
  UPDATE_RECORD = 'UPDATE_RECORD',     // 更新业务记录
  CALL_API = 'CALL_API',               // 调用外部 API
  
  // 流程类
  SET_VARIABLE = 'SET_VARIABLE',       // 设置流程变量
  LOG = 'LOG',                         // 记录日志
  
  // 自定义
  CUSTOM = 'CUSTOM',                   // 自定义钩子（调用注册的处理器）
}

// 钩子配置（根据 type 不同而不同）
type HookConfig = 
  | EmailHookConfig 
  | WebhookHookConfig 
  | CreateTaskHookConfig 
  | UpdateRecordHookConfig
  | CallApiHookConfig
  | SetVariableHookConfig
  | CustomHookConfig;

interface EmailHookConfig {
  templateId: string;            // 邮件模板 ID
  recipients: string[];          // 收件人表达式，如 ['${initiator}', 'hr@company.com']
  cc?: string[];                 // 抄送
}

interface WebhookHookConfig {
  url: string;                   // Webhook URL
  method: 'GET' | 'POST' | 'PUT';
  headers?: Record<string, string>;
  bodyTemplate?: string;         // JSON 模板，支持变量替换
  timeout?: number;              // 超时时间（秒）
}

interface CreateTaskHookConfig {
  taskType: string;              // 任务类型，如 'PAYMENT', 'PROCUREMENT'
  assignee: string;              // 负责人表达式
  title: string;                 // 任务标题模板
  dueDate?: string;              // 截止时间表达式
  data?: Record<string, string>; // 任务数据（支持变量）
}

interface UpdateRecordHookConfig {
  targetType: string;            // 目标类型，如 'FORM_INSTANCE', 'ORDER'
  targetId: string;              // 目标 ID 表达式，如 '${businessId}'
  updates: Record<string, string>; // 更新字段和值
}

interface CallApiHookConfig {
  endpoint: string;              // API 端点
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
  headers?: Record<string, string>;
  body?: Record<string, any>;
  responseMapping?: Record<string, string>;  // 响应映射到流程变量
}

interface SetVariableHookConfig {
  variables: Record<string, string>;  // 变量名 -> 值表达式
}

interface CustomHookConfig {
  handlerName: string;           // 注册的处理器名称
  params?: Record<string, any>;  // 传递给处理器的参数
}
```

**条件验证器**:

```typescript
// ==================== 条件验证器 ====================

/**
 * 节点验证器 - 在审批操作前进行校验
 * 
 * 使用场景：
 * - 金额 > 5000 必须填写理由
 * - 审批人必须修改某个字段
 * - 特定条件下必须上传附件
 */
interface NodeValidator {
  id: string;                    // 验证器唯一 ID
  name: string;                  // 验证器名称（用于错误提示）
  type: ValidatorType;           // 验证器类型
  enabled: boolean;              // 是否启用
  
  // 触发条件（可选，满足条件才进行验证）
  triggerCondition?: string;     // 如 '${action == "APPROVE"}'
  
  // 验证配置
  config: ValidatorConfig;
  
  // 错误信息
  errorMessage: string;          // 验证失败时的提示信息
  errorMessageI18n?: string;     // 国际化 key
}

enum ValidatorType {
  // 字段验证
  FIELD_REQUIRED = 'FIELD_REQUIRED',           // 字段必填
  FIELD_MODIFIED = 'FIELD_MODIFIED',           // 字段必须被修改
  FIELD_VALUE = 'FIELD_VALUE',                 // 字段值校验
  FIELD_PATTERN = 'FIELD_PATTERN',             // 字段正则校验
  
  // 条件验证
  CONDITION = 'CONDITION',                     // 自定义条件表达式
  
  // 附件验证
  ATTACHMENT_REQUIRED = 'ATTACHMENT_REQUIRED', // 必须上传附件
  ATTACHMENT_COUNT = 'ATTACHMENT_COUNT',       // 附件数量校验
  
  // 评论验证
  COMMENT_REQUIRED = 'COMMENT_REQUIRED',       // 必须填写评论
  COMMENT_MIN_LENGTH = 'COMMENT_MIN_LENGTH',   // 评论最小长度
  
  // 自定义验证
  CUSTOM = 'CUSTOM',                           // 自定义验证器
}

type ValidatorConfig =
  | FieldRequiredConfig
  | FieldModifiedConfig
  | FieldValueConfig
  | ConditionConfig
  | AttachmentConfig
  | CommentConfig
  | CustomValidatorConfig;

interface FieldRequiredConfig {
  fieldPath: string;             // 字段路径，如 'formData.reason'
  condition?: string;            // 条件表达式，如 '${formData.amount > 5000}'
}

interface FieldModifiedConfig {
  fieldPath: string;             // 必须被修改的字段
  byApprover?: boolean;          // 是否必须由当前审批人修改
}

interface FieldValueConfig {
  fieldPath: string;
  operator: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'contains';
  value: any;
}

interface ConditionConfig {
  expression: string;            // 条件表达式，返回 true 表示验证通过
}

interface AttachmentConfig {
  minCount?: number;
  maxCount?: number;
  allowedTypes?: string[];       // 允许的文件类型
  maxSizeKb?: number;            // 最大文件大小
}

interface CommentConfig {
  minLength?: number;
  maxLength?: number;
  required?: boolean;
}

interface CustomValidatorConfig {
  validatorName: string;         // 注册的验证器名称
  params?: Record<string, any>;
}
```

**委托/代理配置**:

```typescript
// ==================== 委托/代理机制 ====================

/**
 * 自动委托配置 - 当审批人不可用时自动转派
 * 
 * 使用场景：
 * - 审批人请假时，自动委托给代理人
 * - 夜间/节假日自动转派给值班人员
 * - 审批人长时间未处理，自动转派
 */
interface DelegationConfig {
  enabled: boolean;              // 是否启用自动委托
  
  // 委托规则（按优先级顺序匹配）
  rules: DelegationRule[];
  
  // 默认行为（所有规则都不匹配时）
  defaultBehavior: 'wait' | 'escalate' | 'fallback';
  fallbackAssignee?: string;     // 降级审批人表达式
}

interface DelegationRule {
  id: string;
  name: string;                  // 规则名称
  priority: number;              // 优先级（数字越小优先级越高）
  enabled: boolean;
  
  // 触发条件
  trigger: DelegationTrigger;
  
  // 委托目标
  delegateTo: string;            // 委托目标表达式
  
  // 委托行为
  behavior: DelegationBehavior;
}

interface DelegationTrigger {
  type: DelegationTriggerType;
  config: DelegationTriggerConfig;
}

enum DelegationTriggerType {
  // 基于审批人状态
  USER_ON_LEAVE = 'USER_ON_LEAVE',           // 用户请假中
  USER_INACTIVE = 'USER_INACTIVE',           // 用户已禁用/离职
  USER_UNAVAILABLE = 'USER_UNAVAILABLE',     // 用户设置了不可用
  
  // 基于时间
  TIME_RANGE = 'TIME_RANGE',                 // 特定时间段（如夜间）
  HOLIDAY = 'HOLIDAY',                       // 节假日
  WORKING_HOURS = 'WORKING_HOURS',           // 工作时间外
  
  // 基于超时
  TASK_TIMEOUT = 'TASK_TIMEOUT',             // 任务超时未处理
  
  // 自定义
  CUSTOM = 'CUSTOM',                         // 自定义条件
}

type DelegationTriggerConfig =
  | UserStatusTriggerConfig
  | TimeRangeTriggerConfig
  | TaskTimeoutTriggerConfig
  | CustomTriggerConfig;

interface UserStatusTriggerConfig {
  // 用户状态触发器无需额外配置
}

interface TimeRangeTriggerConfig {
  // 时间范围配置
  startTime?: string;            // 开始时间，如 '18:00'
  endTime?: string;              // 结束时间，如 '09:00'
  timezone?: string;             // 时区，如 'Asia/Shanghai'
  daysOfWeek?: number[];         // 星期几生效，0=周日，1-6=周一到周六
}

interface TaskTimeoutTriggerConfig {
  timeoutHours: number;          // 超时小时数
}

interface CustomTriggerConfig {
  expression: string;            // 自定义条件表达式
}

interface DelegationBehavior {
  // 通知配置
  notifyOriginalAssignee: boolean;  // 是否通知原审批人
  notifyDelegate: boolean;          // 是否通知代理人
  notifyInitiator: boolean;         // 是否通知发起人
  
  // 权限配置
  delegateCanReassign: boolean;     // 代理人是否可以再次转派
  originalCanReclaim: boolean;      // 原审批人是否可以收回
  
  // 记录配置
  recordReason: string;             // 委托原因（用于日志）
}

// ==================== 用户委托设置（全局） ====================

/**
 * 用户级别的委托设置
 * 存储在用户配置中，非流程定义
 */
interface UserDelegationSetting {
  userId: string;
  
  // 启用状态
  enabled: boolean;
  
  // 委托人
  delegateUserId: string;
  
  // 生效时间
  startTime?: Date;              // 开始时间（可选）
  endTime?: Date;                // 结束时间（可选）
  
  // 适用范围
  scope: 'ALL' | 'SPECIFIC';     // 所有流程 或 特定流程
  processKeys?: string[];        // 特定流程 key 列表
  
  // 创建信息
  createdAt: Date;
  reason?: string;               // 委托原因
}
```

**服务任务配置**:

```typescript
// ==================== 服务任务配置 ====================

/**
 * 服务任务 - 自动执行的系统任务
 */
interface ServiceTaskConfig {
  serviceType: ServiceType;
  config: ServiceConfig;
  
  // 错误处理
  onError: 'fail' | 'skip' | 'retry';
  retryConfig?: {
    maxAttempts: number;
    intervalSeconds: number;
    backoffMultiplier?: number;
  };
  
  // 结果处理
  resultMapping?: Record<string, string>;  // 结果映射到流程变量
}

enum ServiceType {
  HTTP_CALL = 'HTTP_CALL',           // HTTP 调用
  DATABASE_QUERY = 'DATABASE_QUERY', // 数据库查询
  SCRIPT = 'SCRIPT',                 // 脚本执行
  MESSAGE_QUEUE = 'MESSAGE_QUEUE',   // 消息队列
  CUSTOM = 'CUSTOM',                 // 自定义服务
}

type ServiceConfig =
  | HttpCallConfig
  | DatabaseQueryConfig
  | ScriptConfig
  | MessageQueueConfig
  | CustomServiceConfig;

interface HttpCallConfig {
  url: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  headers?: Record<string, string>;
  body?: any;
  timeout?: number;
}

interface DatabaseQueryConfig {
  query: string;                 // SQL 或查询表达式
  params?: Record<string, any>;
}

interface ScriptConfig {
  language: 'javascript' | 'python';
  script: string;
  timeout?: number;
}

interface MessageQueueConfig {
  queue: string;
  message: any;
}

interface CustomServiceConfig {
  serviceName: string;
  params?: Record<string, any>;
}
```

**流程全局设置**:

```typescript
interface ProcessSettings {
  // 循环保护
  maxNodeExecutions: number;       // 单节点最大执行次数，默认 10
  maxTotalNodeExecutions: number;  // 流程最大节点执行总数，默认 100
  maxReturnCount: number;          // 最大退回次数，默认 5
  
  // 发起人撤回配置
  withdraw: {
    allowWithdraw: boolean;        // 是否允许撤回，默认 true
    withdrawBeforeNode?: string[]; // 允许撤回的节点范围
    withdrawReason: boolean;       // 是否必须填写撤回原因
  };
  
  // 审批人撤回配置
  approverWithdraw: {
    enabled: boolean;              // 是否启用，默认 true
    timeLimit: number;             // 撤回时限（小时），0 表示不限制
    requireReason: boolean;        // 是否必须填写撤回原因
    notifyInitiator: boolean;      // 撤回时是否通知发起人
  };
  
  // 优先级
  defaultPriority: number;         // 默认优先级，0-3
  
  // 通知配置
  notification: {
    channels: ('EMAIL' | 'DINGTALK' | 'FEISHU' | 'INTERNAL')[];
    templates: Record<string, string>;  // 事件 -> 模板 ID
  };
}
```

**连续上级审批配置**:

```typescript
interface ManagerChainConfig {
  // 终止条件（默认: 'deptHead'）
  stopCondition: 
    | 'deptHead'           // 部门负责人（默认）
    | 'orgHead'            // 组织负责人
    | `role:${string}`     // 指定角色
    | `level:${number}`    // 组织层级
    | `user:${string}`;    // 指定用户
  
  // 行为配置
  includeTerminator: boolean;  // 是否包含终止人审批（默认 true）
  skipSelf: boolean;           // 发起人是管理者时跳过自己（默认 true）
  
  // 自动通过配置
  autoApprove: 'none' | 'sameApprover' | 'initiator' | 'both';  // 默认 'both'
  autoApproveComment?: string;  // 自动通过时的备注
  
  // 安全配置
  maxLevels: number;           // 最大审批层级（默认 10）
  
  // 空审批人处理
  emptyHandler: 
    | 'skip'               // 跳过该级
    | 'fail'               // 流程失败
    | `fallback:user:${string}`; // 降级到指定用户
}
```

**超时配置**:

```typescript
interface TimeoutConfig {
  hours: number;                   // 超时时间（小时）
  remindBeforeHours?: number;      // 超时前提醒（小时）
  
  actions?: TimeoutAction[];       // 超时后的动作
}

interface TimeoutAction {
  type: 'REMIND' | 'ESCALATE' | 'AUTO_APPROVE' | 'AUTO_REJECT';
  
  // REMIND 配置
  maxCount?: number;               // 最大催办次数
  intervalHours?: number;          // 催办间隔（小时）
  
  // ESCALATE 配置
  afterRemindCount?: number;       // 催办几次后升级
  escalateTo?: string;             // 升级目标（审批人表达式）
  
  // AUTO_APPROVE/AUTO_REJECT 配置
  afterHours?: number;             // 多少小时后自动处理
}
```

#### 3. ApprovalInstance（流程实例）

存储流程的运行时数据。

```typescript
interface ApprovalInstance {
  id: string;                    // UUID
  versionId: string;             // 关联流程版本（运行中不变）
  
  // 业务关联
  businessType: string;          // 业务类型：FORM_INSTANCE, OVERTIME, EXPENSE
  businessId: string;            // 业务单据 ID
  businessKey: string;           // 业务单号（可读）
  
  // Temporal 工作流
  workflowId: string;            // Temporal 工作流 ID
  workflowRunId: string;         // Temporal 运行 ID
  
  // 发起人
  initiatorId: string;
  
  // 状态
  status: InstanceStatus;
  currentNodeId?: string;        // 当前节点 ID
  
  // 流程变量
  variables: Record<string, any>;  // 业务数据、条件变量等
  
  // 执行统计
  totalNodeExecutions: number;   // 总节点执行次数
  
  // 结束信息
  endReason?: string;            // APPROVED, REJECTED, WITHDRAWN, ADMIN_TERMINATED, TIMEOUT, FAILED
  endComment?: string;           // 结束备注
  
  // 优先级和截止时间
  priority: number;              // 0-3
  dueDate?: Date;
  
  // 时间戳
  startTime: Date;
  endTime?: Date;
  
  // 关联
  nodeInstances: ApprovalNodeInstance[];
  tasks: ApprovalTask[];
}

enum InstanceStatus {
  RUNNING = 'RUNNING',           // 审批中
  SUSPENDED = 'SUSPENDED',       // 已暂停
  APPROVED = 'APPROVED',         // 已通过（所有节点同意）
  REJECTED = 'REJECTED',         // 已拒绝（某节点驳回）
  WITHDRAWN = 'WITHDRAWN',       // 已撤回（发起人撤回）
  TERMINATED = 'TERMINATED',     // 已终止（管理员强制终止）
  FAILED = 'FAILED',             // 失败（系统错误）
}
```

**数据库索引**:
```sql
CREATE INDEX idx_instance_business ON approval_instances(business_type, business_id);
CREATE INDEX idx_instance_business_key ON approval_instances(business_key);
CREATE INDEX idx_instance_initiator ON approval_instances(initiator_id);
CREATE INDEX idx_instance_status ON approval_instances(status);
CREATE INDEX idx_instance_workflow ON approval_instances(workflow_id);
CREATE INDEX idx_instance_start_time ON approval_instances(start_time DESC);
```

#### 4. ApprovalNodeInstance（节点实例）

记录每个节点的执行情况。

```typescript
interface ApprovalNodeInstance {
  id: string;                    // UUID
  instanceId: string;            // 关联流程实例
  
  // 节点信息
  nodeId: string;                // 节点 ID（对应流程模型）
  nodeName: string;              // 节点名称
  nodeType: NodeType;            // 节点类型
  
  // 状态
  status: NodeStatus;
  
  // 审批配置（快照）
  assignees: string[];           // 解析后的审批人列表
  approvalMode?: ApprovalMode;
  
  // 执行信息
  executionCount: number;        // 执行次数（支持退回重执行）
  result?: NodeResult;           // 执行结果
  
  // 时间戳
  startTime: Date;
  endTime?: Date;
  
  // 关联
  tasks: ApprovalTask[];
}

enum NodeStatus {
  PENDING = 'PENDING',           // 等待执行
  ACTIVE = 'ACTIVE',             // 执行中
  COMPLETED = 'COMPLETED',       // 已完成
  CANCELLED = 'CANCELLED',       // 已取消
  FAILED = 'FAILED',             // 失败
  SKIPPED = 'SKIPPED',           // 跳过（自动通过等）
}

interface NodeResult {
  success: boolean;
  action?: string;               // APPROVED, REJECTED, RETURNED, etc.
  comment?: string;
  data?: Record<string, any>;
}
```

#### 5. ApprovalTask（审批任务）

具体分配给用户的待办任务。

```typescript
interface ApprovalTask {
  id: string;                    // UUID
  instanceId: string;            // 关联流程实例
  nodeInstanceId: string;        // 关联节点实例
  
  // 任务信息
  name: string;                  // 任务名称
  description?: string;          // 任务描述
  type: TaskType;                // 任务类型
  
  // 处理人
  assignee?: string;             // 当前处理人（认领/转发后）
  candidateUsers: string[];      // 候选人列表
  candidateGroups: string[];     // 候选组列表
  
  // 原始负责人（转发前）
  owner?: string;
  
  // 状态
  status: TaskStatus;
  
  // 版本号（乐观锁）
  version: number;
  
  // 表单数据（审批人可编辑的字段）
  formData?: Record<string, any>;
  
  // 优先级和截止时间
  priority: number;
  dueDate?: Date;
  
  // 超时信息
  reminderCount: number;         // 已催办次数
  lastReminderAt?: Date;         // 最后催办时间
  
  // 自动通过标记
  autoApproved: boolean;         // 是否自动通过
  autoApproveReason?: string;    // 自动通过原因
  
  // 时间戳
  createTime: Date;
  claimTime?: Date;              // 认领时间
  endTime?: Date;
  
  // 关联
  actionLogs: ApprovalTaskLog[];
}

enum TaskType {
  APPROVAL = 'APPROVAL',         // 普通审批
  COUNTERSIGN = 'COUNTERSIGN',   // 会签
  OR_SIGN = 'OR_SIGN',           // 或签
  CC = 'CC',                     // 抄送
  SEQUENTIAL = 'SEQUENTIAL',     // 顺序审批中的单个任务
}

enum TaskStatus {
  CREATED = 'CREATED',           // 已创建
  PENDING = 'PENDING',           // 待处理
  CLAIMED = 'CLAIMED',           // 已认领
  IN_PROGRESS = 'IN_PROGRESS',   // 进行中
  COMPLETED = 'COMPLETED',       // 已完成
  CANCELLED = 'CANCELLED',       // 已取消
  WITHDRAWN = 'WITHDRAWN',       // 已撤回
}
```

**数据库索引**:
```sql
-- 待办查询（高频）
CREATE INDEX idx_task_assignee_status ON approval_tasks(assignee, status);
CREATE INDEX idx_task_candidate_status ON approval_tasks USING GIN(candidate_users) WHERE status = 'PENDING';

-- 其他索引
CREATE INDEX idx_task_instance ON approval_tasks(instance_id);
CREATE INDEX idx_task_node_instance ON approval_tasks(node_instance_id);
CREATE INDEX idx_task_status ON approval_tasks(status);
CREATE INDEX idx_task_create_time ON approval_tasks(create_time DESC);
```

#### 6. ApprovalTaskLog（审批动作日志）

记录所有审批操作，用于审计。

```typescript
interface ApprovalTaskLog {
  id: string;                    // UUID
  taskId: string;                // 关联任务
  instanceId: string;            // 关联流程实例（冗余，便于查询）
  
  // 操作信息
  action: ApprovalAction;        // 操作类型
  operatorId: string;            // 操作人
  
  // 操作详情
  comment?: string;              // 操作意见
  targetUserId?: string;         // 转发目标用户
  targetNodeId?: string;         // 退回目标节点
  addSignUsers?: string[];       // 加签用户
  
  // 表单数据变更
  formDataChanges?: Record<string, { old: any; new: any }>;
  
  // 附件
  attachments?: Attachment[];
  
  // 审计信息
  ipAddress?: string;
  userAgent?: string;
  requestId?: string;            // 请求 ID（幂等性）
  
  // 额外信息
  metadata?: Record<string, any>;
  
  // 时间戳
  actionTime: Date;
}

enum ApprovalAction {
  // 基本操作
  APPROVE = 'APPROVE',           // 通过
  REJECT = 'REJECT',             // 驳回
  RETURN = 'RETURN',             // 退回
  FORWARD = 'FORWARD',           // 转发
  
  // 撤回操作
  WITHDRAW = 'WITHDRAW',         // 发起人撤回
  APPROVER_WITHDRAW = 'APPROVER_WITHDRAW',  // 审批人撤回
  
  // 加减签
  ADD_SIGN = 'ADD_SIGN',         // 加签
  REMOVE_SIGN = 'REMOVE_SIGN',   // 减签
  
  // 任务操作
  CLAIM = 'CLAIM',               // 认领
  UNCLAIM = 'UNCLAIM',           // 取消认领
  
  // 系统操作
  AUTO_APPROVE = 'AUTO_APPROVE', // 自动通过
  AUTO_REJECT = 'AUTO_REJECT',   // 自动驳回
  ESCALATE = 'ESCALATE',         // 升级
  REMIND = 'REMIND',             // 催办
  
  // 管理员操作
  ADMIN_APPROVE = 'ADMIN_APPROVE',     // 代审批
  ADMIN_TERMINATE = 'ADMIN_TERMINATE', // 强制结束
}
```

#### 7. 补偿队列表

```typescript
// 回调重试队列
interface CallbackRetryQueue {
  id: string;
  instanceId: string;
  
  // 回调信息
  callbackType: string;          // 回调类型
  callbackUrl?: string;          // 回调 URL（如有）
  payload: Record<string, any>;  // 回调数据
  
  // 重试信息
  retryCount: number;            // 已重试次数
  maxRetries: number;            // 最大重试次数
  nextRetryAt: Date;             // 下次重试时间
  lastError?: string;            // 最后错误信息
  
  // 状态
  status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED';
  
  // 时间戳
  createdAt: Date;
  updatedAt: Date;
}

// 催办队列
interface ReminderQueue {
  id: string;
  taskId: string;
  instanceId: string;
  
  // 催办配置
  reminderType: 'TIMEOUT' | 'MANUAL';
  channels: string[];            // 通知渠道
  
  // 调度信息
  scheduledAt: Date;             // 计划执行时间
  executedAt?: Date;             // 实际执行时间
  
  // 状态
  status: 'SCHEDULED' | 'EXECUTED' | 'CANCELLED';
  
  // 时间戳
  createdAt: Date;
}
```

---

## 🔄 Temporal 工作流设计

### 工作流映射关系

| 审批引擎概念 | Temporal 概念 | 说明 |
|-------------|--------------|------|
| ApprovalInstance | Workflow | 每个流程实例对应一个 Workflow |
| 审批操作 | Signal | 通过 Signal 触发流程推进 |
| 状态查询 | Query | 通过 Query 查询流程状态 |
| 超时处理 | Timer | 使用 workflow.sleep 实现 |
| 重试/补偿 | Activity Retry | 利用 Temporal 内置重试机制 |

### ⚠️ Workflow Determinism 规则（必读）

> **🚨 Hard Rule**: 所有访问外部系统的逻辑，**必须**通过 Activity 执行，**不能**放在 Workflow 里，保证 Workflow 完全 deterministic。

| ✅ 必须放在 Activity | ❌ 禁止放在 Workflow |
|---------------------|---------------------|
| 数据库读写（Prisma） | `prisma.xxx.findMany()` |
| 用户信息查询 | `userService.findById()` |
| 组织架构查询 | `orgService.getManager()` |
| 发送邮件 | `emailService.send()` |
| 调用 Webhook | `fetch()`, `axios()` |
| 调用业务系统 API | `httpService.post()` |
| 读取环境变量 | `process.env.XXX` |
| 获取当前时间 | `new Date()`, `Date.now()` |
| 生成随机数/UUID | `Math.random()`, `uuid()` |

**原因**: Temporal Workflow 可能会被重放（replay）多次，如果包含非确定性操作，重放时会得到不同结果，导致 workflow 状态不一致。

**正确做法**:

```typescript
// ❌ 错误：在 Workflow 中直接查询数据库
async function badWorkflow() {
  const user = await prisma.user.findUnique({ where: { id: '...' } });  // 禁止！
}

// ✅ 正确：通过 Activity 查询
async function goodWorkflow() {
  const user = await activities.getUserById('...');  // Activity 内部调用 prisma
}
```

**例外**: Workflow 内部可以使用以下 Temporal 提供的确定性 API：
- `workflow.sleep()` - 确定性睡眠
- `workflow.condition()` - 等待条件
- `workflow.now()` - 确定性时间（非 `Date.now()`）
- `workflow.uuid4()` - 确定性 UUID（非 `crypto.randomUUID()`）

### Activities 目录

> Workflow 通过 Activity 与外部系统交互（数据库、通知、回调等）。当前所有 Activity 均集中在单文件实现：

| 分类 | 说明 | 所属文件 |
|------|------|----------|
| 节点与任务 | 节点/任务创建与状态更新 | `backend/src/engines/approval/temporal/activities/approval.activities.ts` |
| 审批人解析 | 解析审批人与组织关系 | `backend/src/engines/approval/temporal/activities/approval.activities.ts` |
| 回调与同步 | 业务回调与数据同步 | `backend/src/engines/approval/temporal/activities/approval.activities.ts` |
| 通知与催办 | 催办与通知调度 | `backend/src/engines/approval/temporal/activities/approval.activities.ts` |
| `callbackBusinessSystem` | 回调业务系统 | `approval.activities.ts` |
| `addToRetryQueue` | 回调失败时加入重试队列 | `approval.activities.ts` |
| **日志与审计** | | |
| `createActionLog` | 创建操作日志 | `approval.activities.ts` |
| **钩子执行** | | |
| `executeHook` | 执行单个钩子 | `hook.activities.ts` |
| `executeHooks` | 批量执行钩子（按顺序） | `hook.activities.ts` |
| `sendEmailHook` | 发送邮件钩子 | `hook.activities.ts` |
| `sendWebhookHook` | 调用 Webhook 钩子 | `hook.activities.ts` |
| `createTaskHook` | 创建业务任务钩子 | `hook.activities.ts` |
| `updateRecordHook` | 更新记录钩子 | `hook.activities.ts` |
| `callApiHook` | 调用 API 钩子 | `hook.activities.ts` |
| **验证执行** | | |
| `validateNode` | 执行节点验证器 | `validator.activities.ts` |
| `validateField` | 字段验证 | `validator.activities.ts` |
| `validateCondition` | 条件验证 | `validator.activities.ts` |
| **委托处理** | | |
| `checkDelegation` | 检查是否需要委托 | `delegation.activities.ts` |
| `resolveDelegateUser` | 解析委托人 | `delegation.activities.ts` |
| `executeDelegation` | 执行委托转派 | `delegation.activities.ts` |
| `getUserDelegationSetting` | 获取用户委托设置 | `delegation.activities.ts` |

### Signals 与 Queries 目录

| 类型 | 名称 | 说明 | 参数 |
|------|------|------|------|
| **Signal** | `approve` | 通过审批 | `{ taskId, comment, operatorId }` |
| **Signal** | `reject` | 驳回审批 | `{ taskId, comment, operatorId }` |
| **Signal** | `return` | 退回到指定节点 | `{ taskId, targetNodeId, comment, operatorId }` |
| **Signal** | `forward` | 转发任务 | `{ taskId, targetUserId, comment, operatorId }` |
| **Signal** | `withdraw` | 发起人撤回 | `{ initiatorId, reason }` |
| **Signal** | `approverWithdraw` | 审批人撤回 | `{ taskId, operatorId, reason }` |
| **Signal** | `addSign` | 加签 | `{ taskId, userIds, operatorId }` |
| **Signal** | `claim` | 认领任务 | `{ taskId, operatorId }` |
| **Signal** | `remind` | 手动催办 | `{ taskId, operatorId }` |
| **Signal** | `adminTerminate` | 管理员强制结束 | `{ operatorId, reason }` |
| **Query** | `status` | 查询流程状态 | - |
| **Query** | `currentNode` | 查询当前节点 | - |
| **Query** | `history` | 查询审批历史 | - |
| **Query** | `pendingTasks` | 查询待处理任务 | - |

### 工作流核心类型定义

> 以下类型定义是 Workflow 的核心接口，建议放在 `@ffoa/approval-types` 类型库中。

```typescript
// ==================== 工作流状态 ====================

/**
 * 工作流内部状态
 * 在整个 Workflow 生命周期中维护
 */
interface WorkflowState {
  // 基本信息
  instanceId: string;
  processModel: ProcessModel;
  settings: ProcessSettings;
  variables: Record<string, any>;
  initiatorId: string;
  
  // 运行状态
  status: InstanceStatus;
  currentNodeId: string | null;
  
  // 执行历史
  history: ApprovalHistoryItem[];
  
  // 循环保护计数
  nodeExecutionCounts: Record<string, number>;
  totalNodeExecutions: number;
  
  // Signal 队列
  pendingSignals: PendingSignal[];
  
  // 终止标志
  shouldTerminate: boolean;
  terminateReason: string | null;
}

/**
 * 待处理的 Signal
 */
interface PendingSignal {
  type: SignalType;
  data: SignalData;
  receivedAt: string;  // ISO timestamp
}

type SignalType = 
  | 'approve' 
  | 'reject' 
  | 'return' 
  | 'forward' 
  | 'withdraw'
  | 'approverWithdraw'
  | 'addSign'
  | 'claim'
  | 'remind'
  | 'adminTerminate';

type SignalData = 
  | ApproveSignalData
  | RejectSignalData
  | ReturnSignalData
  | ForwardSignalData
  | WithdrawSignalData
  | AddSignSignalData
  | ClaimSignalData
  | AdminTerminateSignalData;

// ==================== 节点执行结果 ====================

/**
 * 节点执行结果 - 决定流程下一步走向
 */
type NodeExecutionResult =
  | { type: 'NEXT'; nextNodeId: string }
  | { type: 'END'; endStatus?: InstanceStatus }
  | { type: 'RETURN'; returnNodeId: string }
  | { type: 'TERMINATE'; reason?: string };

// ==================== 审批历史 ====================

/**
 * 审批历史记录项
 */
interface ApprovalHistoryItem {
  nodeId: string;
  nodeName: string;
  nodeType: NodeType;
  result: NodeResult;
  timestamp: string;  // ISO timestamp
}

interface NodeResult {
  success: boolean;
  action?: ApprovalAction;
  operator?: string;
  comment?: string;
  data?: Record<string, any>;
}

// ==================== Signal 数据结构 ====================

interface ApproveSignalData {
  taskId: string;
  operatorId: string;
  comment?: string;
  formData?: Record<string, any>;
  attachments?: Attachment[];
  isSystem?: boolean;  // 系统自动通过
}

interface RejectSignalData {
  taskId: string;
  operatorId: string;
  comment?: string;
  isSystem?: boolean;  // 系统自动驳回
}

interface ReturnSignalData {
  taskId: string;
  operatorId: string;
  targetNodeId: string;
  comment?: string;
}

interface ForwardSignalData {
  taskId: string;
  operatorId: string;
  targetUserId: string;
  comment?: string;
}

interface WithdrawSignalData {
  initiatorId: string;
  reason?: string;
}

interface AddSignSignalData {
  taskId: string;
  operatorId: string;
  userIds: string[];
  signType: 'BEFORE' | 'AFTER' | 'PARALLEL';
  comment?: string;
}

interface ClaimSignalData {
  taskId: string;
  operatorId: string;
}

interface AdminTerminateSignalData {
  operatorId: string;
  reason: string;
}

// ==================== 工作流输入输出 ====================

/**
 * 工作流启动输入
 */
interface WorkflowInput {
  instanceId: string;
  processModel: ProcessModel;
  settings: ProcessSettings;
  variables: Record<string, any>;
  initiatorId: string;
  businessType: string;
  businessId: string;
  businessKey: string;
}

/**
 * 工作流执行结果
 */
interface WorkflowResult {
  status: InstanceStatus;
  endReason?: string;
  history: ApprovalHistoryItem[];
  finalVariables: Record<string, any>;
}

// ==================== Query 返回类型 ====================

interface WorkflowStatusQueryResult {
  status: InstanceStatus;
  currentNodeId: string | null;
  totalNodeExecutions: number;
}

interface CurrentNodeQueryResult {
  nodeId: string | null;
  nodeName?: string;
  nodeType?: NodeType;
  startTime?: string;
}

interface PendingTaskInfo {
  taskId: string;
  taskName: string;
  assignee?: string;
  candidateUsers: string[];
  createTime: string;
  dueDate?: string;
}
```

### 工作流定义

```typescript
// workflows/generic-approval.workflow.ts

import {
  defineSignal,
  defineQuery,
  setHandler,
  condition,
  sleep,
  proxyActivities,
} from '@temporalio/workflow';
import type * as activities from '../activities';

// ==================== Signal 定义 ====================

export const approveSignal = defineSignal<[ApproveInput]>('approve');
export const rejectSignal = defineSignal<[RejectInput]>('reject');
export const returnSignal = defineSignal<[ReturnInput]>('return');
export const forwardSignal = defineSignal<[ForwardInput]>('forward');
export const withdrawSignal = defineSignal<[WithdrawInput]>('withdraw');
export const approverWithdrawSignal = defineSignal<[ApproverWithdrawInput]>('approverWithdraw');
export const addSignSignal = defineSignal<[AddSignInput]>('addSign');
export const claimSignal = defineSignal<[ClaimInput]>('claim');
export const remindSignal = defineSignal<[RemindInput]>('remind');
export const adminTerminateSignal = defineSignal<[AdminTerminateInput]>('adminTerminate');

// ==================== Query 定义 ====================

export const statusQuery = defineQuery<WorkflowStatus>('status');
export const currentNodeQuery = defineQuery<CurrentNodeInfo>('currentNode');
export const historyQuery = defineQuery<ApprovalHistoryItem[]>('history');
export const pendingTasksQuery = defineQuery<PendingTaskInfo[]>('pendingTasks');

// ==================== 工作流主函数 ====================

export async function genericApprovalWorkflow(input: WorkflowInput): Promise<WorkflowResult> {
  // 代理 Activities
  const acts = proxyActivities<typeof activities>({
    startToCloseTimeout: '30s',
    retry: {
      initialInterval: '1s',
      maximumInterval: '30s',
      backoffCoefficient: 2,
      maximumAttempts: 5,
    },
  });

  // ==================== 工作流状态 ====================
  
  const state: WorkflowState = {
    instanceId: input.instanceId,
    processModel: input.processModel,
    settings: input.settings,
    variables: input.variables,
    initiatorId: input.initiatorId,
    
    status: 'RUNNING',
    currentNodeId: null,
    
    // 执行历史
    history: [],
    nodeExecutionCounts: {},
    totalNodeExecutions: 0,
    
    // 待处理的 Signal
    pendingSignals: [],
    
    // 标志位
    shouldTerminate: false,
    terminateReason: null,
  };

  // ==================== Signal 处理器 ====================
  
  setHandler(approveSignal, (input) => {
    state.pendingSignals.push({ type: 'approve', data: input });
  });
  
  setHandler(rejectSignal, (input) => {
    state.pendingSignals.push({ type: 'reject', data: input });
  });
  
  setHandler(returnSignal, (input) => {
    state.pendingSignals.push({ type: 'return', data: input });
  });
  
  setHandler(forwardSignal, (input) => {
    state.pendingSignals.push({ type: 'forward', data: input });
  });
  
  setHandler(withdrawSignal, (input) => {
    state.shouldTerminate = true;
    state.terminateReason = 'WITHDRAWN';
    state.pendingSignals.push({ type: 'withdraw', data: input });
  });
  
  setHandler(approverWithdrawSignal, (input) => {
    state.pendingSignals.push({ type: 'approverWithdraw', data: input });
  });
  
  setHandler(addSignSignal, (input) => {
    state.pendingSignals.push({ type: 'addSign', data: input });
  });
  
  setHandler(adminTerminateSignal, (input) => {
    state.shouldTerminate = true;
    state.terminateReason = 'ADMIN_TERMINATED';
    state.pendingSignals.push({ type: 'adminTerminate', data: input });
  });

  // ==================== Query 处理器 ====================
  
  setHandler(statusQuery, () => ({
    status: state.status,
    currentNodeId: state.currentNodeId,
    totalNodeExecutions: state.totalNodeExecutions,
  }));
  
  setHandler(currentNodeQuery, () => ({
    nodeId: state.currentNodeId,
    nodeName: state.processModel.nodes.find(n => n.id === state.currentNodeId)?.name,
  }));
  
  setHandler(historyQuery, () => state.history);

  // ==================== 流程执行 ====================
  
  try {
    // 找到开始节点
    const startNode = state.processModel.nodes.find(n => n.type === 'START');
    if (!startNode) {
      throw new Error('流程缺少开始节点');
    }
    
    // 从开始节点开始执行
    let currentNodeId = startNode.id;
    
    while (!state.shouldTerminate) {
      const node = state.processModel.nodes.find(n => n.id === currentNodeId);
      if (!node) {
        throw new Error(`节点不存在: ${currentNodeId}`);
      }
      
      state.currentNodeId = currentNodeId;
      
      // 检查循环保护
      const nodeExecCount = (state.nodeExecutionCounts[currentNodeId] || 0) + 1;
      state.nodeExecutionCounts[currentNodeId] = nodeExecCount;
      state.totalNodeExecutions++;
      
      if (nodeExecCount > state.settings.maxNodeExecutions) {
        await acts.failProcess(state.instanceId, `节点 ${node.name} 执行次数超限`);
        state.status = 'FAILED';
        break;
      }
      
      if (state.totalNodeExecutions > state.settings.maxTotalNodeExecutions) {
        await acts.failProcess(state.instanceId, '流程总执行次数超限');
        state.status = 'FAILED';
        break;
      }
      
      // 执行节点
      const result = await executeNode(state, node, acts);
      
      // 记录历史
      state.history.push({
        nodeId: node.id,
        nodeName: node.name,
        nodeType: node.type,
        result,
        timestamp: new Date().toISOString(),
      });
      
      // 处理结果
      if (result.type === 'END') {
        state.status = result.endStatus || 'APPROVED';  // 默认为已通过
        break;
      } else if (result.type === 'NEXT') {
        currentNodeId = result.nextNodeId!;
      } else if (result.type === 'RETURN') {
        currentNodeId = result.returnNodeId!;
      } else if (result.type === 'TERMINATE') {
        state.status = 'TERMINATED';
        break;
      }
    }
    
    // 流程结束，触发回调
    await acts.onProcessCompleted({
      instanceId: state.instanceId,
      status: state.status,
      endReason: state.terminateReason,
      variables: state.variables,
    });
    
  } catch (error) {
    state.status = 'FAILED';
    await acts.failProcess(state.instanceId, error.message);
  }
  
  return {
    status: state.status,
    endReason: state.terminateReason,
    history: state.history,
  };
}
```

### 节点执行逻辑

```typescript
async function executeNode(
  state: WorkflowState,
  node: ProcessNode,
  acts: typeof activities
): Promise<NodeExecutionResult> {
  
  // 创建节点实例
  const nodeInstance = await acts.createNodeInstance({
    instanceId: state.instanceId,
    nodeId: node.id,
    nodeName: node.name,
    nodeType: node.type,
  });
  
  switch (node.type) {
    case 'START':
      // 开始节点，自动完成
      await acts.completeNodeInstance(nodeInstance.id, { success: true });
      return { type: 'NEXT', nextNodeId: findNextNode(state, node.id) };
      
    case 'END':
      // 结束节点：根据流程结束原因设置最终状态
      await acts.completeNodeInstance(nodeInstance.id, { success: true });
      // 如果是拒绝导致的结束，状态为REJECTED；否则为APPROVED
      const endStatus = state.terminateReason === 'REJECTED' ? 'REJECTED' : 
                        state.terminateReason === 'WITHDRAWN' ? 'WITHDRAWN' : 'APPROVED';
      return { type: 'END', endStatus };
      
    case 'USER_TASK':
      // 用户任务，需要等待审批
      return await executeUserTask(state, node, nodeInstance.id, acts);
      
    case 'SERVICE_TASK':
      // 服务任务，自动执行
      return await executeServiceTask(state, node, nodeInstance.id, acts);
      
    case 'EXCLUSIVE_GATEWAY':
      // 排他网关，条件路由
      return await executeExclusiveGateway(state, node, acts);
      
    case 'PARALLEL_GATEWAY':
      // 并行网关
      return await executeParallelGateway(state, node, acts);
      
    default:
      throw new Error(`不支持的节点类型: ${node.type}`);
  }
}
```

### 用户任务执行（含连续上级审批）

```typescript
async function executeUserTask(
  state: WorkflowState,
  node: ProcessNode,
  nodeInstanceId: string,
  acts: typeof activities
): Promise<NodeExecutionResult> {
  
  // 解析审批人
  let approvers: ApproverInfo[];
  
  if (node.assignee?.startsWith('initiator:managerChain')) {
    // 连续上级审批
    approvers = await acts.resolveManagerChain({
      initiatorId: state.initiatorId,
      assigneeExpression: node.assignee,
      chainConfig: node.chainConfig || {},
      variables: state.variables,
    });
  } else {
    // 普通审批人解析
    approvers = await acts.resolveApprovers({
      assigneeExpression: node.assignee,
      initiatorId: state.initiatorId,
      variables: state.variables,
    });
  }
  
  if (approvers.length === 0) {
    // 处理空审批人
    const emptyHandler = node.chainConfig?.emptyHandler || 'fail';
    if (emptyHandler === 'skip') {
      await acts.skipNodeInstance(nodeInstanceId, '无审批人，自动跳过');
      return { type: 'NEXT', nextNodeId: findNextNode(state, node.id) };
    } else if (emptyHandler.startsWith('fallback:user:')) {
      const fallbackUser = emptyHandler.substring(14);
      approvers = [{ userId: fallbackUser, level: 0 }];
    } else {
      throw new Error(`节点 ${node.name} 无法解析审批人`);
    }
  }
  
  // 根据审批模式创建任务
  const approvalMode = node.approvalMode || 'SINGLE';
  
  if (approvalMode === 'SEQUENTIAL' || node.assignee?.includes('managerChain')) {
    // 顺序审批（含连续上级）
    return await executeSequentialApproval(state, node, nodeInstanceId, approvers, acts);
  } else if (approvalMode === 'AND') {
    // 会签
    return await executeCountersignApproval(state, node, nodeInstanceId, approvers, acts);
  } else if (approvalMode === 'OR') {
    // 或签
    return await executeOrSignApproval(state, node, nodeInstanceId, approvers, acts);
  } else {
    // 单人审批
    return await executeSingleApproval(state, node, nodeInstanceId, approvers[0], acts);
  }
}
```

### 顺序审批（连续上级）

```typescript
async function executeSequentialApproval(
  state: WorkflowState,
  node: ProcessNode,
  nodeInstanceId: string,
  approvers: ApproverInfo[],
  acts: typeof activities
): Promise<NodeExecutionResult> {
  
  const chainConfig = node.chainConfig || {};
  const autoApproveMode = chainConfig.autoApprove || 'both';
  let lastApprover: string | null = null;
  
  for (let i = 0; i < approvers.length; i++) {
    const approver = approvers[i];
    
    // 检查是否需要自动通过
    let shouldAutoApprove = false;
    let autoApproveReason = '';
    
    if (autoApproveMode !== 'none') {
      // 检查是否是同一审批人
      if ((autoApproveMode === 'sameApprover' || autoApproveMode === 'both') 
          && lastApprover === approver.userId) {
        shouldAutoApprove = true;
        autoApproveReason = '同一审批人，自动通过';
      }
      
      // 检查是否是发起人
      if ((autoApproveMode === 'initiator' || autoApproveMode === 'both')
          && approver.userId === state.initiatorId) {
        shouldAutoApprove = true;
        autoApproveReason = '审批人为发起人，自动通过';
      }
    }
    
    if (shouldAutoApprove) {
      // 自动通过
      await acts.createAutoApprovedTask({
        instanceId: state.instanceId,
        nodeInstanceId,
        assignee: approver.userId,
        reason: autoApproveReason,
        comment: chainConfig.autoApproveComment || autoApproveReason,
      });
      
      state.history.push({
        nodeId: node.id,
        action: 'AUTO_APPROVE',
        operator: approver.userId,
        comment: autoApproveReason,
        timestamp: new Date().toISOString(),
      });
      
      lastApprover = approver.userId;
      continue;
    }
    
    // 创建审批任务
    const task = await acts.createApprovalTask({
      instanceId: state.instanceId,
      nodeInstanceId,
      name: `${node.name} - 第${i + 1}级审批`,
      assignee: approver.userId,
      type: 'SEQUENTIAL',
      timeout: node.timeout,
    });
    
    // 设置超时 Timer
    let timeoutTriggered = false;
    if (node.timeout?.hours) {
      const timeoutPromise = handleTimeout(state, node, task.id, acts);
      // 不 await，让它在后台运行
    }
    
    // 等待审批结果
    const result = await waitForApprovalResult(state, task.id);
    
    if (result.action === 'APPROVE') {
      lastApprover = approver.userId;
      // 继续下一级
    } else if (result.action === 'REJECT') {
      await acts.completeNodeInstance(nodeInstanceId, { 
        success: false, 
        action: 'REJECTED',
        comment: result.comment,
      });
      return { type: 'END', endStatus: 'TERMINATED' };
    } else if (result.action === 'RETURN') {
      await acts.completeNodeInstance(nodeInstanceId, { 
        success: false, 
        action: 'RETURNED',
      });
      return { type: 'RETURN', returnNodeId: result.targetNodeId };
    } else if (result.action === 'APPROVER_WITHDRAW') {
      // 审批人撤回，回到上一级
      i = Math.max(0, i - 2);  // 回退到上一级（因为 for 循环会 i++）
      continue;
    }
  }
  
  // 所有审批人都通过
  await acts.completeNodeInstance(nodeInstanceId, { success: true, action: 'APPROVED' });
  return { type: 'NEXT', nextNodeId: findNextNode(state, node.id) };
}
```

### 超时处理

```typescript
async function handleTimeout(
  state: WorkflowState,
  node: ProcessNode,
  taskId: string,
  acts: typeof activities
): Promise<void> {
  const timeout = node.timeout;
  if (!timeout) return;
  
  // 超时前提醒
  if (timeout.remindBeforeHours) {
    const remindDelay = (timeout.hours - timeout.remindBeforeHours) * 60 * 60 * 1000;
    await sleep(remindDelay);
    
    // 检查任务是否还在进行中
    const task = await acts.getTask(taskId);
    if (task.status === 'PENDING' || task.status === 'IN_PROGRESS') {
      await acts.sendReminder({
        taskId,
        type: 'BEFORE_TIMEOUT',
        hoursRemaining: timeout.remindBeforeHours,
      });
    }
  }
  
  // 等待超时
  const remainingDelay = (timeout.remindBeforeHours 
    ? timeout.remindBeforeHours 
    : timeout.hours) * 60 * 60 * 1000;
  await sleep(remainingDelay);
  
  // 检查任务是否还在进行中
  const task = await acts.getTask(taskId);
  if (task.status !== 'PENDING' && task.status !== 'IN_PROGRESS') {
    return;  // 任务已处理，不需要超时动作
  }
  
  // 执行超时动作
  for (const action of timeout.actions || []) {
    if (action.type === 'REMIND') {
      // 催办
      let reminderCount = 0;
      while (reminderCount < (action.maxCount || 3)) {
        const currentTask = await acts.getTask(taskId);
        if (currentTask.status !== 'PENDING' && currentTask.status !== 'IN_PROGRESS') {
          break;
        }
        
        await acts.sendReminder({ taskId, type: 'TIMEOUT' });
        reminderCount++;
        
        if (action.intervalHours) {
          await sleep(action.intervalHours * 60 * 60 * 1000);
        }
      }
    } else if (action.type === 'ESCALATE') {
      // 升级
      if (action.afterRemindCount && task.reminderCount >= action.afterRemindCount) {
        const escalateTo = await acts.resolveApprovers({
          assigneeExpression: action.escalateTo,
          initiatorId: state.initiatorId,
          variables: state.variables,
        });
        
        if (escalateTo.length > 0) {
          await acts.escalateTask({
            taskId,
            newAssignee: escalateTo[0].userId,
          });
        }
      }
    } else if (action.type === 'AUTO_APPROVE' || action.type === 'AUTO_REJECT') {
      // 自动处理
      if (action.afterHours) {
        await sleep(action.afterHours * 60 * 60 * 1000);
        
        const currentTask = await acts.getTask(taskId);
        if (currentTask.status === 'PENDING' || currentTask.status === 'IN_PROGRESS') {
          if (action.type === 'AUTO_APPROVE') {
            state.pendingSignals.push({
              type: 'approve',
              data: { taskId, comment: '超时自动通过', isSystem: true },
            });
          } else {
            state.pendingSignals.push({
              type: 'reject',
              data: { taskId, comment: '超时自动驳回', isSystem: true },
            });
          }
        }
      }
    }
  }
}
```

### 等待审批结果

```typescript
async function waitForApprovalResult(
  state: WorkflowState,
  taskId: string
): Promise<ApprovalResultInfo> {
  
  while (true) {
    // 等待有新的 Signal
    await condition(() => state.pendingSignals.length > 0 || state.shouldTerminate);
    
    if (state.shouldTerminate) {
      return { action: 'TERMINATE' };
    }
    
    // 处理 Signal
    const signal = state.pendingSignals.shift();
    if (!signal) continue;
    
    // 检查是否是当前任务的 Signal
    if (signal.data.taskId !== taskId) {
      // 不是当前任务的，放回队列（可能是其他任务的）
      // 或者根据业务逻辑处理
      continue;
    }
    
    switch (signal.type) {
      case 'approve':
        return { action: 'APPROVE', comment: signal.data.comment };
        
      case 'reject':
        return { action: 'REJECT', comment: signal.data.comment };
        
      case 'return':
        return { 
          action: 'RETURN', 
          targetNodeId: signal.data.targetNodeId,
          comment: signal.data.comment,
        };
        
      case 'forward':
        // 转发不改变等待状态，只是更新任务的 assignee
        // 继续等待
        continue;
        
      case 'approverWithdraw':
        return { action: 'APPROVER_WITHDRAW' };
        
      case 'addSign':
        // 加签逻辑
        // ...
        continue;
        
      default:
        continue;
    }
  }
}
```

---

## 🔧 审批人解析服务

当前审批人解析逻辑在 Temporal Activities 中实现，入口集中在：

- `backend/src/engines/approval/temporal/activities/approval.activities.ts`
  - `resolveApprovers(...)`
  - `resolveManagerChain(...)`

该实现负责解析审批人表达式与组织关系，并将结果用于节点任务分配。

---

## 🔐 幂等性与并发控制

当前实现重点在状态校验与流程一致性：

- `backend/src/engines/approval/approval.controller.ts` 接收 `X-Request-Id` 与 `X-Idempotency-Key` 并透传到 DTO
- `backend/src/engines/approval/approval.service.ts` 使用 `getTaskWithValidation` / `validateTaskForAction` 进行任务状态与权限校验
- `approval_tasks.version` 字段存在，但服务层未实现基于版本号的显式乐观锁更新

---

## 🔄 回调与补偿机制

当前回调与补偿逻辑集中在 Temporal Activities：

- `backend/src/engines/approval/temporal/activities/approval.activities.ts`
  - `callbackBusinessSystem(...)`：回调业务系统
  - `addToRetryQueue(...)`：写入 `callback_retry_queue` 进行重试
  - `syncBusinessDataChanges(...)`：同步业务数据用于条件分支
- `backend/src/engines/approval/business-type-registry.service.ts`
  - `notifyApprovalComplete(...)`：通知业务系统审批完成

工作流内触发位置：`backend/src/engines/approval/temporal/workflows/generic-approval.workflow.ts`

---

## 📈 数据库索引设计

### 完整索引定义

```sql
-- ==================== ApprovalDefinition ====================
CREATE UNIQUE INDEX idx_process_definition_key 
  ON approval_definitions(key);
  
CREATE INDEX idx_process_definition_status 
  ON approval_definitions(status);
  
CREATE INDEX idx_process_definition_category 
  ON approval_definitions(category);
  
CREATE INDEX idx_process_definition_org 
  ON approval_definitions(organization_id) 
  WHERE organization_id IS NOT NULL;

-- ==================== ApprovalVersion ====================
CREATE INDEX idx_process_version_definition 
  ON approval_versions(definition_id);
  
CREATE UNIQUE INDEX idx_process_version_unique 
  ON approval_versions(definition_id, version);
  
CREATE INDEX idx_process_version_default 
  ON approval_versions(definition_id, is_default) 
  WHERE is_default = true;

-- ==================== ApprovalInstance ====================
CREATE INDEX idx_instance_business 
  ON approval_instances(business_type, business_id);
  
CREATE INDEX idx_instance_business_key 
  ON approval_instances(business_key);
  
CREATE INDEX idx_instance_initiator_status 
  ON approval_instances(initiator_id, status);
  
CREATE INDEX idx_instance_status 
  ON approval_instances(status);
  
CREATE INDEX idx_instance_workflow 
  ON approval_instances(workflow_id);
  
CREATE INDEX idx_instance_start_time 
  ON approval_instances(start_time DESC);
  
-- 复合索引：我发起的（分页查询）
CREATE INDEX idx_instance_initiator_start 
  ON approval_instances(initiator_id, start_time DESC);

-- ==================== ApprovalNodeInstance ====================
CREATE INDEX idx_node_instance_instance 
  ON node_instances(instance_id);
  
CREATE INDEX idx_node_instance_status 
  ON node_instances(instance_id, status);

-- ==================== ApprovalTask ====================
-- 待办查询（最高频）
CREATE INDEX idx_task_assignee_status 
  ON approval_tasks(assignee, status) 
  WHERE status IN ('PENDING', 'IN_PROGRESS');
  
-- 候选人待办（GIN 索引）
CREATE INDEX idx_task_candidate_status 
  ON approval_tasks USING GIN(candidate_users) 
  WHERE status = 'PENDING';
  
CREATE INDEX idx_task_instance 
  ON approval_tasks(instance_id);
  
CREATE INDEX idx_task_node_instance 
  ON approval_tasks(node_instance_id);
  
CREATE INDEX idx_task_create_time 
  ON approval_tasks(create_time DESC);
  
-- 超时任务查询
CREATE INDEX idx_task_due_date 
  ON approval_tasks(due_date) 
  WHERE status IN ('PENDING', 'IN_PROGRESS') AND due_date IS NOT NULL;

-- ==================== ApprovalTaskLog ====================
CREATE INDEX idx_action_log_task 
  ON approval_task_logs(task_id);
  
CREATE INDEX idx_action_log_instance 
  ON approval_task_logs(instance_id);
  
CREATE INDEX idx_action_log_operator 
  ON approval_task_logs(operator_id, action_time DESC);
  
CREATE INDEX idx_action_log_time 
  ON approval_task_logs(action_time DESC);

-- ==================== CallbackRetryQueue ====================
CREATE INDEX idx_callback_retry_status_time 
  ON callback_retry_queue(status, next_retry_at) 
  WHERE status = 'PENDING';
```

---

## 🎯 关键设计决策

### 1. 选择 Temporal 作为工作流引擎

**决策**: 使用 Temporal.io 而非自研或其他方案（如 Flowable）。

**原因**:
- ✅ 代码即流程，TypeScript 原生支持，与现有技术栈一致
- ✅ 状态持久化，故障自动恢复，保证流程可靠执行
- ✅ 已有 Temporal 基础设施
- ✅ Signal/Query 机制完美匹配审批场景
- ✅ Timer 机制支持超时处理

**对比**:

| 方案 | 优势 | 劣势 |
|------|------|------|
| Temporal ✓ | 代码即流程、状态持久化、TypeScript 支持 | 学习曲线 |
| Flowable | 可视化设计、BPMN 标准 | Java 生态、与现有技术栈不一致 |
| 纯自研 | 完全可控 | 开发成本高、可靠性难保证 |

### 2. 6 表核心数据模型

**决策**: 使用 6 张核心表存储审批数据。

**原因**:
- ✅ 职责清晰，每张表单一职责
- ✅ 支持复杂查询和统计
- ✅ 便于扩展和维护
- ✅ 与 Temporal 状态分离，便于审计和查询

### 3. 松耦合业务集成

**决策**: 通过 `businessType` + `businessId` 松耦合关联业务，审批引擎不存储业务数据副本。

**原因**:
- ✅ 审批引擎不依赖具体业务
- ✅ 业务系统可独立演进
- ✅ 通过事件/回调保持同步
- ✅ 支持任意业务类型接入

**业务数据存储规范**:

```
┌─────────────────────────────────────────────────────────────────────────┐
│                        审批引擎（通用基础设施）                          │
├─────────────────────────────────────────────────────────────────────────┤
│  ApprovalInstance.variables                                             │
│  ├── 存储: 流程上下文元数据（businessKey, submitterId, priority 等）    │
│  └── ⚠️ 不存储业务数据副本（如 formData）                               │
│                                                                         │
│  业务数据获取: 通过 BusinessDataProvider 接口按需查询                    │
│  数据变更同步: 通过 ApprovalTaskLog.formDataChanges 记录，              │
│               业务系统负责同步更新自己的数据                             │
└─────────────────────────────────────────────────────────────────────────┘
                                    ▲
                    ┌───────────────┼───────────────┐
                    │               │               │
        ┌───────────┴───┐   ┌───────┴───────┐   ┌───┴───────────┐
        │ Form Engine   │   │ Work Record   │   │ 其他业务系统   │
        │ FormInstance  │   │ WorkRecord    │   │ CustomEntity  │
        │ .data         │   │ .hours/.date  │   │ .data         │
        └───────────────┘   └───────────────┘   └───────────────┘
        businessType:       businessType:       businessType:
        "FORM_INSTANCE"     "WORK_RECORD"       "CUSTOM"
```

**业务系统接入契约**:

```typescript
// 业务系统需要实现的接口
interface ApprovalBusinessAdapter {
  // 获取业务数据（用于条件评估、审批人查看）
  getBusinessData(businessId: string): Promise<Record<string, any>>;
  
  // 同步审批人的数据修改
  syncDataChanges(businessId: string, changes: DataChanges): Promise<void>;
  
  // 审批完成后更新业务状态
  onApprovalComplete(businessId: string, result: 'APPROVED' | 'REJECTED'): Promise<void>;
}
```

📖 **详细文档**: [Form Management ARCHITECTURE.md](../../form-management/docs/ARCHITECTURE.md#td-6-审批引擎与业务数据的解耦设计)

### 4. 版本隔离策略

**决策**: 运行中的流程实例继续按原版本执行，新版本只影响新实例。

**原因**:
- ✅ 保证行为一致性
- ✅ 避免运行中流程出现不可预期的行为
- ✅ 简化版本升级逻辑

### 5. 连续上级审批默认到部门负责人

**决策**: `initiator:managerChain` 默认终止条件为部门负责人。

**原因**:
- ✅ 符合大多数企业审批习惯
- ✅ 简化配置，减少出错
- ✅ 可通过配置修改终止条件

### 6. 乐观锁 + 幂等性

**决策**: 使用乐观锁处理并发，结合请求 ID 实现幂等性。

**原因**:
- ✅ 避免悲观锁带来的性能问题
- ✅ 正确处理并发审批场景
- ✅ 防止重复提交

---

## 📡 事件机制

### 事件类型

```typescript
enum ApprovalEventType {
  // 流程事件
  PROCESS_STARTED = 'approval.process.started',
  PROCESS_COMPLETED = 'approval.process.completed',
  PROCESS_TERMINATED = 'approval.process.terminated',
  PROCESS_FAILED = 'approval.process.failed',
  
  // 节点事件
  NODE_ACTIVATED = 'approval.node.activated',
  NODE_COMPLETED = 'approval.node.completed',
  NODE_SKIPPED = 'approval.node.skipped',
  
  // 任务事件
  TASK_CREATED = 'approval.task.created',
  TASK_ASSIGNED = 'approval.task.assigned',
  TASK_COMPLETED = 'approval.task.completed',
  TASK_CANCELLED = 'approval.task.cancelled',
  
  // 操作事件
  ACTION_APPROVED = 'approval.action.approved',
  ACTION_REJECTED = 'approval.action.rejected',
  ACTION_RETURNED = 'approval.action.returned',
  ACTION_FORWARDED = 'approval.action.forwarded',
  ACTION_WITHDRAWN = 'approval.action.withdrawn',
  
  // 超时事件
  TASK_TIMEOUT_WARNING = 'approval.task.timeout_warning',
  TASK_TIMEOUT = 'approval.task.timeout',
  TASK_ESCALATED = 'approval.task.escalated',
}
```

### 事件订阅示例

```typescript
// 业务系统订阅事件
@OnEvent('approval.process.completed')
async handleProcessCompleted(event: ProcessCompletedEvent) {
  const { instanceId, businessType, businessId, status, endReason } = event;
  
  // 根据业务类型更新状态
  switch (businessType) {
    case 'FORM_INSTANCE':
      await this.formService.updateApprovalStatus(businessId, 
        status === 'APPROVED' ? 'APPROVED' : 'REJECTED'
      );
      break;
      
    case 'OVERTIME':
      await this.overtimeService.updateStatus(businessId,
        status === 'APPROVED' ? 'APPROVED' : 'REJECTED'
      );
      break;
  }
}

// 通知服务订阅事件
@OnEvent('approval.task.created')
async handleTaskCreated(event: TaskCreatedEvent) {
  const { taskId, assignee, candidateUsers, instanceId } = event;
  
  // 发送待办通知
  const recipients = assignee ? [assignee] : candidateUsers;
  await this.notificationService.sendTaskNotification(recipients, {
    type: 'NEW_TASK',
    taskId,
    instanceId,
  });
}
```

---

## 📦 类型库设计

### 为什么需要独立类型库

流程模型的类型定义（`ProcessModel`、`ProcessNode`、`ProcessEdge` 等）需要在多个地方使用：

| 使用场景 | 说明 |
|----------|------|
| **前端流程设计器** | 拖拽节点、配置属性、生成 JSON |
| **前端流程预览器** | 解析并渲染流程图 |
| **后端流程加载器** | 解析 YAML/JSON 配置 |
| **后端工作流执行** | 运行时解析节点配置 |
| **后端 API 验证** | 校验前端提交的流程定义 |

如果各端各自定义类型，会导致：
- ❌ 类型不一致，前后端对不上
- ❌ 重复维护，修改需要同步多处
- ❌ 验证逻辑分散，难以保证一致性

### 类型库结构

```
packages/
└── approval-types/                    # @ffoa/approval-types
    ├── package.json
    ├── tsconfig.json
    ├── src/
    │   ├── index.ts                   # 统一导出
    │   │
    │   ├── models/                    # 核心模型
    │   │   ├── process-model.ts       # ProcessModel, ProcessNode, ProcessEdge
    │   │   ├── node-types.ts          # NodeType, ApprovalMode 等枚举
    │   │   └── index.ts
    │   │
    │   ├── configs/                   # 配置类型
    │   │   ├── timeout-config.ts      # TimeoutConfig
    │   │   ├── delegation-config.ts   # DelegationConfig
    │   │   ├── hook-config.ts         # NodeHook, HookType
    │   │   ├── validator-config.ts    # NodeValidator, ValidatorType
    │   │   ├── chain-config.ts        # ManagerChainConfig
    │   │   └── index.ts
    │   │
    │   ├── dto/                       # 数据传输对象
    │   │   ├── approval-action.dto.ts # 审批操作 DTO
    │   │   ├── process-instance.dto.ts
    │   │   └── index.ts
    │   │
    │   ├── schemas/                   # Zod 验证 Schema
    │   │   ├── process-model.schema.ts
    │   │   ├── node-config.schema.ts
    │   │   └── index.ts
    │   │
    │   └── utils/                     # 工具函数
    │       ├── expression-parser.ts   # 表达式解析
    │       ├── validators.ts          # 验证工具
    │       └── index.ts
    │
    └── dist/                          # 编译输出
```

### 使用示例

**前端设计器**:

```typescript
// frontend/src/features/approval/designer/ProcessDesigner.tsx
import { 
  ProcessModel, 
  ProcessNode, 
  NodeType,
  ApprovalMode,
  processModelSchema,
} from '@ffoa/approval-types';

const [processModel, setProcessModel] = useState<ProcessModel>({
  nodes: [],
  edges: [],
});

// 添加节点
const addNode = (type: NodeType) => {
  const newNode: ProcessNode = {
    id: generateId(),
    name: '新节点',
    type,
    position: { x: 100, y: 100 },
  };
  setProcessModel(prev => ({
    ...prev,
    nodes: [...prev.nodes, newNode],
  }));
};

// 保存时验证
const handleSave = () => {
  const result = processModelSchema.safeParse(processModel);
  if (!result.success) {
    showError(result.error.message);
    return;
  }
  // 提交到后端
};
```

**后端流程加载**:

```typescript
// backend/src/engines/approval/process-loader.service.ts
import { 
  ProcessModel, 
  ApprovalVersion,
  processModelSchema,
} from '@ffoa/approval-types';
import * as yaml from 'yaml';

@Injectable()
export class ProcessLoaderService {
  async loadFromYaml(filePath: string): Promise<ApprovalVersion> {
    const content = await fs.readFile(filePath, 'utf-8');
    const raw = yaml.parse(content);
    
    // 使用共享的 schema 验证
    const model = processModelSchema.parse(raw.processModel);
    
    return {
      // ...
      processModel: model,
    };
  }
}
```

**后端工作流执行**:

```typescript
// backend/src/engines/approval/temporal/workflows/generic-approval.workflow.ts
import { 
  ProcessModel, 
  ProcessNode, 
  NodeType,
  NodeHook,
  HookType,
} from '@ffoa/approval-types';

async function executeNode(state: WorkflowState, node: ProcessNode) {
  // 执行前置钩子
  if (node.beforeEnterHooks?.length) {
    await executeHooks(node.beforeEnterHooks, state);
  }
  
  // 执行节点...
  
  // 执行后置钩子
  if (node.afterCompleteHooks?.length) {
    await executeHooks(node.afterCompleteHooks, state);
  }
}
```

### Zod Schema 示例

```typescript
// packages/approval-types/src/schemas/process-model.schema.ts
import { z } from 'zod';

export const nodeTypeSchema = z.enum([
  'START', 'END', 'USER_TASK', 'SERVICE_TASK',
  'EXCLUSIVE_GATEWAY', 'PARALLEL_GATEWAY', 'INCLUSIVE_GATEWAY', 'SUB_PROCESS',
]);

export const approvalModeSchema = z.enum(['SINGLE', 'AND', 'OR', 'SEQUENTIAL']);

export const nodeHookSchema = z.object({
  id: z.string(),
  name: z.string(),
  type: z.enum([
    'SEND_EMAIL', 'SEND_DINGTALK', 'SEND_FEISHU', 'SEND_WEBHOOK',
    'CREATE_TASK', 'UPDATE_RECORD', 'CALL_API',
    'SET_VARIABLE', 'LOG', 'CUSTOM',
  ]),
  enabled: z.boolean(),
  condition: z.string().optional(),
  config: z.record(z.any()),
  onError: z.enum(['ignore', 'warn', 'fail']),
  retryConfig: z.object({
    maxAttempts: z.number(),
    intervalSeconds: z.number(),
  }).optional(),
});

export const nodeValidatorSchema = z.object({
  id: z.string(),
  name: z.string(),
  type: z.enum([
    'FIELD_REQUIRED', 'FIELD_MODIFIED', 'FIELD_VALUE', 'FIELD_PATTERN',
    'CONDITION', 'ATTACHMENT_REQUIRED', 'ATTACHMENT_COUNT',
    'COMMENT_REQUIRED', 'COMMENT_MIN_LENGTH', 'CUSTOM',
  ]),
  enabled: z.boolean(),
  triggerCondition: z.string().optional(),
  config: z.record(z.any()),
  errorMessage: z.string(),
  errorMessageI18n: z.string().optional(),
});

export const processNodeSchema = z.object({
  id: z.string(),
  name: z.string(),
  type: nodeTypeSchema,
  description: z.string().optional(),
  position: z.object({ x: z.number(), y: z.number() }).optional(),
  
  // USER_TASK 配置
  assignee: z.string().optional(),
  approvalMode: approvalModeSchema.optional(),
  chainConfig: z.record(z.any()).optional(),
  delegationConfig: z.record(z.any()).optional(),
  timeout: z.record(z.any()).optional(),
  withdrawConfig: z.record(z.any()).optional(),
  editableFields: z.array(z.string()).optional(),
  requiredFields: z.array(z.string()).optional(),
  validators: z.array(nodeValidatorSchema).optional(),
  allowedActions: z.array(z.string()).optional(),
  returnTargets: z.array(z.string()).optional(),
  
  // 钩子
  beforeEnterHooks: z.array(nodeHookSchema).optional(),
  afterCompleteHooks: z.array(nodeHookSchema).optional(),
  
  // GATEWAY 配置
  condition: z.string().optional(),
  
  // SERVICE_TASK 配置
  serviceConfig: z.record(z.any()).optional(),
});

export const processEdgeSchema = z.object({
  id: z.string(),
  source: z.string(),
  target: z.string(),
  condition: z.string().optional(),
  label: z.string().optional(),
  priority: z.number().optional(),
});

export const processModelSchema = z.object({
  nodes: z.array(processNodeSchema).min(2, '流程至少需要开始和结束节点'),
  edges: z.array(processEdgeSchema),
}).refine(
  (model) => model.nodes.some(n => n.type === 'START'),
  { message: '流程必须有一个开始节点' }
).refine(
  (model) => model.nodes.some(n => n.type === 'END'),
  { message: '流程必须有一个结束节点' }
);
```

### 发布与使用

```bash
# 发布到内部 npm 仓库
cd packages/approval-types
npm version patch
npm publish

# 前端安装
cd frontend
npm install @ffoa/approval-types

# 后端安装
cd backend
npm install @ffoa/approval-types
```

---

## 🪝 钩子执行引擎

### Activities 补充

在 [Activities 目录](#activities-目录) 中补充钩子和验证相关的 Activity：

| Activity | 说明 | 所属文件 |
|----------|------|----------|
| **钩子执行** | | |
| `executeHook` | 执行单个钩子 | `hook.activities.ts` |
| `executeHooks` | 批量执行钩子（按顺序） | `hook.activities.ts` |
| `sendEmailHook` | 发送邮件钩子 | `hook.activities.ts` |
| `sendWebhookHook` | 调用 Webhook 钩子 | `hook.activities.ts` |
| `createTaskHook` | 创建业务任务钩子 | `hook.activities.ts` |
| `updateRecordHook` | 更新记录钩子 | `hook.activities.ts` |
| `callApiHook` | 调用 API 钩子 | `hook.activities.ts` |
| **验证执行** | | |
| `validateNode` | 执行节点验证器 | `validator.activities.ts` |
| `validateField` | 字段验证 | `validator.activities.ts` |
| `validateCondition` | 条件验证 | `validator.activities.ts` |
| **委托处理** | | |
| `checkDelegation` | 检查是否需要委托 | `delegation.activities.ts` |
| `resolveDelegateUser` | 解析委托人 | `delegation.activities.ts` |
| `executeDelegation` | 执行委托转派 | `delegation.activities.ts` |
| `getUserDelegationSetting` | 获取用户委托设置 | `delegation.activities.ts` |

### 钩子执行流程

```typescript
// activities/hook.activities.ts

export async function executeHooks(
  hooks: NodeHook[],
  context: HookContext
): Promise<HookExecutionResult[]> {
  const results: HookExecutionResult[] = [];
  
  for (const hook of hooks) {
    if (!hook.enabled) continue;
    
    // 检查执行条件
    if (hook.condition) {
      const shouldExecute = evaluateExpression(hook.condition, context.variables);
      if (!shouldExecute) continue;
    }
    
    try {
      const result = await executeHook(hook, context);
      results.push({ hookId: hook.id, success: true, result });
    } catch (error) {
      const errorResult = { hookId: hook.id, success: false, error: error.message };
      results.push(errorResult);
      
      // 根据错误处理策略决定是否继续
      if (hook.onError === 'fail') {
        throw new HookExecutionError(hook.name, error.message);
      } else if (hook.onError === 'warn') {
        logger.warn(`Hook ${hook.name} failed: ${error.message}`);
      }
      // 'ignore' 则静默继续
    }
  }
  
  return results;
}

export async function executeHook(
  hook: NodeHook,
  context: HookContext
): Promise<any> {
  switch (hook.type) {
    case 'SEND_EMAIL':
      return await sendEmailHook(hook.config as EmailHookConfig, context);
      
    case 'SEND_WEBHOOK':
      return await sendWebhookHook(hook.config as WebhookHookConfig, context);
      
    case 'CREATE_TASK':
      return await createTaskHook(hook.config as CreateTaskHookConfig, context);
      
    case 'UPDATE_RECORD':
      return await updateRecordHook(hook.config as UpdateRecordHookConfig, context);
      
    case 'CALL_API':
      return await callApiHook(hook.config as CallApiHookConfig, context);
      
    case 'SET_VARIABLE':
      return await setVariableHook(hook.config as SetVariableHookConfig, context);
      
    case 'CUSTOM':
      return await executeCustomHook(hook.config as CustomHookConfig, context);
      
    default:
      throw new Error(`Unknown hook type: ${hook.type}`);
  }
}

async function sendEmailHook(config: EmailHookConfig, context: HookContext) {
  const recipients = config.recipients.map(r => 
    evaluateExpression(r, context.variables)
  );
  
  await emailService.sendTemplate({
    templateId: config.templateId,
    to: recipients,
    cc: config.cc?.map(c => evaluateExpression(c, context.variables)),
    variables: context.variables,
  });
}

async function createTaskHook(config: CreateTaskHookConfig, context: HookContext) {
  const assignee = evaluateExpression(config.assignee, context.variables);
  const title = interpolateTemplate(config.title, context.variables);
  
  await taskService.create({
    type: config.taskType,
    assignee,
    title,
    dueDate: config.dueDate ? evaluateExpression(config.dueDate, context.variables) : undefined,
    data: Object.fromEntries(
      Object.entries(config.data || {}).map(([k, v]) => 
        [k, evaluateExpression(v, context.variables)]
      )
    ),
    sourceInstanceId: context.instanceId,
    sourceNodeId: context.nodeId,
  });
}
```

### 验证器执行流程

```typescript
// activities/validator.activities.ts

export async function validateNode(
  validators: NodeValidator[],
  context: ValidationContext
): Promise<ValidationResult> {
  const errors: ValidationError[] = [];
  
  for (const validator of validators) {
    if (!validator.enabled) continue;
    
    // 检查触发条件
    if (validator.triggerCondition) {
      const shouldValidate = evaluateExpression(
        validator.triggerCondition, 
        { ...context.variables, action: context.action }
      );
      if (!shouldValidate) continue;
    }
    
    const isValid = await executeValidator(validator, context);
    
    if (!isValid) {
      errors.push({
        validatorId: validator.id,
        validatorName: validator.name,
        message: validator.errorMessage,
        messageI18n: validator.errorMessageI18n,
      });
    }
  }
  
  return {
    valid: errors.length === 0,
    errors,
  };
}

async function executeValidator(
  validator: NodeValidator,
  context: ValidationContext
): Promise<boolean> {
  switch (validator.type) {
    case 'FIELD_REQUIRED': {
      const config = validator.config as FieldRequiredConfig;
      // 检查条件
      if (config.condition) {
        const shouldCheck = evaluateExpression(config.condition, context.variables);
        if (!shouldCheck) return true;  // 条件不满足，跳过验证
      }
      const value = getValueByPath(context.formData, config.fieldPath);
      return value !== null && value !== undefined && value !== '';
    }
    
    case 'FIELD_MODIFIED': {
      const config = validator.config as FieldModifiedConfig;
      const originalValue = getValueByPath(context.originalFormData, config.fieldPath);
      const currentValue = getValueByPath(context.formData, config.fieldPath);
      const isModified = originalValue !== currentValue;
      
      if (config.byApprover) {
        // 检查是否由当前审批人修改
        return isModified && context.modifiedBy === context.currentUserId;
      }
      return isModified;
    }
    
    case 'CONDITION': {
      const config = validator.config as ConditionConfig;
      return evaluateExpression(config.expression, context.variables);
    }
    
    case 'COMMENT_REQUIRED': {
      return context.comment && context.comment.trim().length > 0;
    }
    
    case 'COMMENT_MIN_LENGTH': {
      const config = validator.config as CommentConfig;
      return context.comment && context.comment.length >= (config.minLength || 0);
    }
    
    case 'ATTACHMENT_REQUIRED': {
      return context.attachments && context.attachments.length > 0;
    }
    
    default:
      return true;
  }
}
```

### 委托执行流程

```typescript
// activities/delegation.activities.ts

export async function checkDelegation(
  assignee: string,
  delegationConfig: DelegationConfig,
  context: DelegationContext
): Promise<DelegationCheckResult> {
  if (!delegationConfig?.enabled) {
    return { shouldDelegate: false };
  }
  
  // 1. 检查用户级别的委托设置
  const userSetting = await getUserDelegationSetting(assignee, context.processKey);
  if (userSetting?.enabled) {
    const now = new Date();
    const isInRange = (!userSetting.startTime || now >= userSetting.startTime) &&
                      (!userSetting.endTime || now <= userSetting.endTime);
    if (isInRange) {
      return {
        shouldDelegate: true,
        delegateTo: userSetting.delegateUserId,
        reason: userSetting.reason || '用户设置了委托',
        ruleId: 'USER_SETTING',
      };
    }
  }
  
  // 2. 检查节点级别的委托规则
  const sortedRules = [...delegationConfig.rules]
    .filter(r => r.enabled)
    .sort((a, b) => a.priority - b.priority);
  
  for (const rule of sortedRules) {
    const triggered = await checkDelegationTrigger(rule.trigger, assignee, context);
    if (triggered) {
      const delegateTo = await resolveDelegateUser(rule.delegateTo, context);
      if (delegateTo) {
        return {
          shouldDelegate: true,
          delegateTo,
          reason: rule.name,
          ruleId: rule.id,
          behavior: rule.behavior,
        };
      }
    }
  }
  
  // 3. 默认行为
  if (delegationConfig.defaultBehavior === 'fallback' && delegationConfig.fallbackAssignee) {
    const fallback = await resolveDelegateUser(delegationConfig.fallbackAssignee, context);
    if (fallback) {
      return {
        shouldDelegate: true,
        delegateTo: fallback,
        reason: '降级到默认审批人',
        ruleId: 'FALLBACK',
      };
    }
  }
  
  return { shouldDelegate: false };
}

async function checkDelegationTrigger(
  trigger: DelegationTrigger,
  assignee: string,
  context: DelegationContext
): Promise<boolean> {
  switch (trigger.type) {
    case 'USER_ON_LEAVE': {
      const user = await userService.findById(assignee);
      return user?.status === 'ON_LEAVE';
    }
    
    case 'USER_INACTIVE': {
      const user = await userService.findById(assignee);
      return user?.status !== 'ACTIVE';
    }
    
    case 'TIME_RANGE': {
      const config = trigger.config as TimeRangeTriggerConfig;
      return isInTimeRange(config);
    }
    
    case 'WORKING_HOURS': {
      return !isWorkingHours();
    }
    
    case 'HOLIDAY': {
      return await isHoliday();
    }
    
    case 'TASK_TIMEOUT': {
      const config = trigger.config as TaskTimeoutTriggerConfig;
      const taskAge = Date.now() - context.taskCreatedAt.getTime();
      return taskAge > config.timeoutHours * 60 * 60 * 1000;
    }
    
    case 'CUSTOM': {
      const config = trigger.config as CustomTriggerConfig;
      return evaluateExpression(config.expression, context.variables);
    }
    
    default:
      return false;
  }
}
```

---

## 🔧 流程配置管理

### 可视化配置

流程配置通过**表单管理页面**完成，无需编辑配置文件：

1. **创建表单定义**：访问 `表单管理 > 表单定义 > 创建表单`
2. **设计表单字段**：在表单设计器中添加字段
3. **配置审批流程**：切换到「流程设计」标签，启用审批流程
4. **添加审批节点**：拖拽添加审批节点，配置审批人、审批模式
5. **提交审核**：保存后提交审核，审核通过后即可使用

### 流程配置要素

| 要素 | 说明 |
|------|------|
| 节点类型 | START、USER_TASK、CC、EXCLUSIVE_GATEWAY、PARALLEL_GATEWAY、END |
| 审批模式 | AND（会签）、OR（或签）|
| 审批人指定 | 固定用户、角色、发起人主管、表单字段 |
| 超时配置 | 支持设置超时时间和提醒 |
| 条件分支 | 支持基于表单字段的条件路由 |

### 版本管理策略

| 场景 | 行为 |
|------|------|
| 流程定义升级 | 新实例使用新版本，运行中实例继续用旧版本 |
| 流程定义回滚 | 同上，只影响新创建的实例 |
| 版本废弃 | 运行中实例继续执行，新实例无法使用该版本 |

> 💡 **说明**：流程配置已从 YAML 文件迁移至可视化页面，所有配置通过表单管理模块完成。

---

## 📈 性能优化

### 已实施优化

#### 1. 并行查询优化

```typescript
// 优化后（并行查询）
const [pendingMyApproval, myCreated, myProcessed] = await Promise.all([
  this.prisma.approvalTask.count(...),
  this.prisma.approvalInstance.count(...),
  this.prisma.approvalTask.count(...),
]);
// 总耗时 = max(T1, T2, T3)，性能提升约 2-3 倍
```

### 数据库索引建议

```sql
-- ApprovalTask 表索引
CREATE INDEX idx_task_assignee_status ON approval_tasks(assignee, status);
CREATE INDEX idx_task_candidate_status ON approval_tasks USING GIN(candidate_users) WHERE status = 'PENDING';

-- ApprovalInstance 表索引
CREATE INDEX idx_instance_initiator_status ON approval_instances(initiator_id, status);
CREATE INDEX idx_instance_start_time ON approval_instances(start_time DESC);
```

### Redis 缓存策略

```typescript
@Injectable()
export class ApprovalService {
  private readonly CACHE_TTL = 60; // 缓存 60 秒

  async getMyApprovalStats(userId: string) {
    const cacheKey = `approval:stats:${userId}`;
    
    // 1. 尝试从缓存读取
    const cached = await this.redis.get(cacheKey);
    if (cached) return JSON.parse(cached);

    // 2. 查询数据库
    const stats = await this.fetchStatsFromDB(userId);

    // 3. 写入缓存
    await this.redis.setex(cacheKey, this.CACHE_TTL, JSON.stringify(stats));
    return stats;
  }
}
```

### 性能对比

| 场景 | 数据量 | 原始方案 | 并行查询 | +索引 | +缓存 |
|------|--------|---------|---------|-------|-------|
| 小型 | 1K | 50ms | 20ms | 10ms | 2ms |
| 中型 | 10K | 200ms | 80ms | 30ms | 2ms |
| 大型 | 100K | 2s | 800ms | 100ms | 2ms |

---

## 🌐 国际化 (i18n)

### 配置方式

在表单管理页面中配置流程时，名称和描述字段支持多语言：

- 在设计器中填写的名称会自动存储为默认语言
- 可通过表单管理的多语言设置添加其他语言版本
- 前端会根据用户语言偏好自动显示对应语言内容

### 翻译文件

```typescript
// frontend/src/locales/zh.ts
export const zh = {
  approvals: {
    workRecord: {
      processName: '工作记录审批流程',
      nodes: {
        managerApproval: '主管审批',
      },
      fields: {
        comment: '审批意见',
      },
    },
  },
};
```

### 使用工具函数

```typescript
import { translateKey } from '@/utils/i18n-helper';

// 自动翻译 i18n key
<h3>{translateKey(task.name, locale)}</h3>

// 示例
translateKey('approvals.workRecord.processName', 'zh')  // → "工作记录审批流程"
translateKey('approvals.workRecord.processName', 'en')  // → "Work Record Approval"
```

---

## 📚 相关文档

- [PRD 需求文档](PRD.md)
- [API 接口文档](API.md)
- [开发待办](TODO.md)
- [变更记录](CHANGELOG.md)

---

**创建日期**: 2024-11-15  
**最后更新**: 2025-12-07  
**版本**: v3.1
