# 表单管理架构设计

> 架构设计文档 - 定义表单管理应用的系统架构、数据模型和技术决策

---

## 📐 系统架构

### 分层架构

```
┌─────────────────────────────────────────────────────────────────────────┐
│                            前端展示层                                    │
│  ┌───────────────────────────────┐ ┌───────────────────────────────┐   │
│  │ 表单管理 UI (/forms)          │ │ 审批中心 UI (/approvals)       │   │
│  │ [设计态 - 管理员/设计者]       │ │ [运行态 - 普通用户]            │   │
│  │ ├── 表单定义列表              │ │ ├── 发起申请                   │   │
│  │ ├── 一体化设计器              │ │ ├── 我的申请（实例列表）        │   │
│  │ │   └── 表单设计 + 流程设计   │ │ ├── 待我审批                   │   │
│  │ ├── 版本管理                  │ │ ├── 我已处理                   │   │
│  │ ├── 版本审核                  │ │ └── 抄送给我                   │   │
│  │ └── 模板库                    │ │                               │   │
│  └───────────────────────────────┘ └───────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                          业务应用层 (BFF)                                │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  FormManagementController                                        │   │
│  │  ├── 聚合 API（整合表单引擎 + 审批引擎）                           │   │
│  │  ├── 发布快照管理                                                 │   │
│  │  └── 跨引擎事务协调                                               │   │
│  └─────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                    ┌───────────────┴───────────────┐
                    ▼                               ▼
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│         表单引擎层               │ │         审批引擎层               │
│  ┌─────────────────────────┐   │ │  ┌─────────────────────────┐   │
│  │  FormDefinitionService  │   │ │  │  ProcessDefinitionService│   │
│  │  FormVersionService     │   │ │  │  ProcessVersionService   │   │
│  │  FormInstanceService    │   │ │  │  ProcessInstanceService  │   │
│  │  FormTemplateService    │   │ │  │  TaskService             │   │
│  └─────────────────────────┘   │ │  └─────────────────────────┘   │
└─────────────────────────────────┘ └─────────────────────────────────┘
                    │                               │
                    └───────────────┬───────────────┘
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                            数据存储层                                    │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  PostgreSQL                                                      │   │
│  │  ├── form_definitions, form_versions, form_instances            │   │
│  │  ├── process_definitions, process_versions, process_instances   │   │
│  │  └── release_snapshots (发布快照)                                │   │
│  └─────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘
```

### 模块职责

| 层级 | 模块 | 职责 |
|------|------|------|
| **前端展示层** | 表单管理 UI | 用户交互、设计器渲染 |
| **业务应用层** | FormManagementController | 聚合 API、发布快照、事务协调 |
| **引擎层** | 表单引擎 | 表单定义、版本、实例、模板 |
| **引擎层** | 审批引擎 | 流程定义、版本、实例、任务 |
| **数据层** | PostgreSQL | 数据持久化 |

### 🚨 API 调用规范

> **核心原则**: 前端只调用应用层 API，不直接调用引擎层 API

```
┌─────────────────────────────────────────────────────────────────────────┐
│                           前端调用规范                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   表单管理 UI (/forms)                审批中心 UI (/approval-center)    │
│         │                                      │                        │
│         │ ✅ 只调用                             │ ✅ 只调用               │
│         ▼                                      ▼                        │
│   /api/v1/form-management/*            /api/v1/approval-center/*        │
│   (配置 & 发布)                         (运行时体验)                     │
│         │                                      │                        │
│         └──────────────┬───────────────────────┘                        │
│                        │ 后端内部调用                                    │
│                        ▼                                                │
│              Form Engine / Approval Engine API                          │
│              (前端 ❌ 禁止直接调用)                                       │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

| 场景 | 调用哪个 API |
|------|-------------|
| 创建/编辑/发布表单 | `/form-management/*` |
| 发起申请 | `/form-management/instances` (创建) → `/approval-center/*` (后续) |
| 处理审批任务 | `/approval-center/*` |
| 流程跟踪 | `/approval-center/*` |

---

## 📦 单一来源（Source of Truth）

> 明确各类数据的权威来源，避免职责混淆。

| 数据类型 | 权威来源 | 存储位置 |
|----------|----------|----------|
| **表单结构及字段** | Form Engine | `form_definitions` / `form_versions` |
| **流程结构及节点** | Approval Engine | `process_definitions` / `process_versions` |
| **表单+流程组合 & 对外发布版本** | Form Management | `release_snapshots` |
| **实例生命周期** | Approval Center + 引擎 | `form_instances` + `process_instances` |

### 职责边界

```
┌─────────────────────────────────────────────────────────────────────────┐
│  Form Engine                    │  Approval Engine                      │
│  "字段是什么"                    │  "流程怎么走"                          │
│  ─────────────────────────────  │  ─────────────────────────────────── │
│  • 字段类型、验证规则             │  • 节点类型、审批人配置                 │
│  • JSON Schema 结构              │  • 流程路由、条件分支                  │
│  • UI Schema 布局                │  • 字段权限（谁在什么时候能动哪些字段）   │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  Form Management                                                        │
│  "表单+流程的组合发布"                                                   │
│  ─────────────────────────────────────────────────────────────────────  │
│  • 发布快照管理（绑定表单版本 + 流程版本）                                 │
│  • 跨引擎事务协调                                                        │
│  • 版本审核流程                                                          │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  Approval Center                                                        │
│  "表单实例的运行时体验"                                                   │
│  ─────────────────────────────────────────────────────────────────────  │
│  • 发起申请、填写表单                                                     │
│  • 审批任务处理                                                          │
│  • 流程跟踪                                                              │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## 📊 数据模型

### ER 图

```
┌──────────────────┐       ┌──────────────────┐
│ FormDefinition   │       │ProcessDefinition │
├──────────────────┤       ├──────────────────┤
│ id               │       │ id               │
│ regionId ★       │       │ regionId ★       │  ← 多区域隔离
│ key              │       │ key              │
│ name             │       │ name             │
│ description      │       │ description      │
│ category         │       │ formDefinitionId │──┐
│ status ●         │       │ status           │  │  ← ● FormDefinition 有独立状态
│ createdBy        │       │ createdAt        │  │
│ createdAt        │       └────────┬─────────┘  │
└────────┬─────────┘                │            │
         │                          │            │
         │ 1:N                      │ 1:N        │ 1:1
         ▼                          ▼            │
┌──────────────────┐       ┌──────────────────┐  │
│ FormVersion      │       │ ProcessVersion   │  │
├──────────────────┤       ├──────────────────┤  │
│ id               │       │ id               │  │
│ regionId ★       │       │ regionId ★       │  ← 多区域隔离
│ formDefinitionId │       │ processDefId     │  │
│ version          │       │ version          │  │
│ versionName ◆    │       │ versionName ◆    │  ← ◆ 版本名称（可选）
│ status           │       │ status           │  │
│ schema           │       │ model (nodes)    │  │
│ uiSchema         │       │ createdBy        │  │
│ createdBy        │       │ createdAt        │  │
│ createdAt        │       └────────┬─────────┘  │
└────────┬─────────┘                │            │
         │                          │            │
         │                          │            │
         └──────────┬───────────────┘            │
                    │                            │
                    ▼ N:1                        │
         ┌──────────────────────┐                │
         │ ReleaseSnapshot      │◄───────────────┘
         ├──────────────────────┤
         │ id                   │
         │ regionId ★           │  ← 多区域隔离
         │ formDefinitionId     │
         │ formVersionId        │
         │ processDefinitionId  │  ← 冗余字段，便于查询
         │ processVersionId     │
         │ status               │  ← ACTIVE 唯一约束
         │ releaseNote ◇        │  ← ◇ 发布说明/备注
         │ publishedAt          │
         │ publishedBy          │
         │ reviewComment        │  ← 审核意见
         └────────┬─────────────┘
                  │
                  │ 1:N
                  ▼
         ┌──────────────────────┐
         │ FormInstance         │
         ├──────────────────────┤
         │ id                   │
         │ regionId ★           │  ← 多区域隔离
         │ snapshotId           │  ← 主权：绑定发布快照
         │ formDefinitionId     │  ← 冗余：便于按表单查询
         │ formVersionId        │  ← 冗余：便于按版本查询
         │ processInstanceId    │  ← 关联的流程实例
         │ formData             │
         │ status               │
         │ submittedBy          │
         │ submittedAt          │
         └──────────────────────┘

★ = 多区域隔离必填字段
● = FormDefinition 状态（DRAFT/ACTIVE/DISABLED/ARCHIVED）
◆ = 版本名称（可选，如 "v1.0 初版"）
◇ = 发布说明/备注（可选）
```

### 核心实体

#### FormDefinition（表单定义）

```typescript
interface FormDefinition {
  id: string;                      // 表单定义 ID
  
  // 区域配置（灵活设计）
  regionScope: 'ALL' | 'SPECIFIC'; // 区域范围
  allowedRegions?: string[];       // 允许的区域列表（regionScope=SPECIFIC时）
  
  key: string;                     // 表单唯一标识（如 expense_claim）
  slug: string;                    // URL slug（可修改，用于美化URL）
  name: string;                    // 表单名称
  description?: string;            // 表单描述
  category?: string;               // 分类（如 finance, hr, it）
  icon?: string;                   // 图标
  
  status: FormDefinitionStatus;    // 表单定义状态
  
  // 元数据
  createdBy: string;
  createdAt: Date;
  updatedAt: Date;
}

// 表单定义状态
type FormDefinitionStatus = 
  | 'DRAFT'      // 草稿（新建，从未发布）
  | 'PUBLISHED'  // 已发布（至少发布过一次）
  | 'DISABLED'   // 已禁用（暂停使用，但保留数据）
  | 'ARCHIVED';  // 已归档（不再使用）
```

**区域设计说明**：

实际实现采用了比文档更灵活的区域设计：
- `regionScope = 'ALL'`：表单对所有区域可见（全局表单）
- `regionScope = 'SPECIFIC' + allowedRegions`：表单仅对指定区域可见
- 这比简单的必填 `regionId` 更灵活，可以支持全局和区域两种模式

**FormDefinition 状态说明**：

| 状态 | 说明 | 允许的操作 |
|------|------|-----------|
| `DRAFT` | 新建的表单，从未发布过 | 编辑、删除、发布 |
| `ACTIVE` | 至少发布过一次，当前可用 | 编辑、禁用、归档 |
| `DISABLED` | 暂停使用，不再接受新申请 | 启用、归档 |
| `ARCHIVED` | 已归档，不再使用 | 仅查看 |

#### FormVersion（表单版本）

```typescript
interface FormVersion {
  id: string;                      // 版本 ID
  
  // 多区域隔离（必填）
  regionId: string;                // 区域 ID（继承自 FormDefinition）
  
  formDefinitionId: string;        // 所属表单定义
  version: number;                 // 版本号（1, 2, 3...）
  versionName?: string;            // 版本名称（如 "v1.0 初版", "v2.0 增加附件"）
  
  schema: object;                  // JSON Schema
  uiSchema: object;                // UI Schema
  
  status: VersionStatus;           // 版本状态
  
  // 元数据
  createdBy: string;
  createdAt: Date;
  updatedAt: Date;
}
```

#### ProcessVersion（流程版本）

```typescript
interface ProcessVersion {
  id: string;                      // 版本 ID
  
  // 多区域隔离（必填）
  regionId: string;                // 区域 ID（继承自 ProcessDefinition）
  
  processDefinitionId: string;     // 所属流程定义
  version: number;                 // 版本号（1, 2, 3...）
  versionName?: string;            // 版本名称（如 "v1.0 两级审批", "v2.0 增加财务节点"）
  
  model: ProcessModel;             // 流程模型（节点、边）
  
  status: VersionStatus;           // 版本状态
  
  // 元数据
  createdBy: string;
  createdAt: Date;
  updatedAt: Date;
}

// 版本状态（简化流程：审核通过 = 发布）
type VersionStatus = 
  | 'DRAFT'          // 草稿 - 设计中
  | 'PENDING_REVIEW' // 待审核 - 已提交，等待管理员审核
  | 'PUBLISHED'      // 已发布 - 审核通过，对外可用
  | 'REJECTED'       // 已驳回 - 审核未通过，需修改后重新提交
  | 'DEPRECATED';    // 已废弃 - 被新版本替代，历史实例仍可用
```

#### ReleaseSnapshot（发布快照）

```typescript
interface ReleaseSnapshot {
  id: string;                      // 快照 ID
  
  // 多区域隔离（必填）
  regionId: string;                // 区域 ID（CN / US / ME）
  
  formDefinitionId: string;        // 表单定义 ID
  formVersionId: string;           // 绑定的表单版本 ID
  processDefinitionId: string;     // 流程定义 ID（冗余，便于查询）
  processVersionId: string;        // 绑定的流程版本 ID
  
  status: SnapshotStatus;          // DRAFT | PENDING | ACTIVE | REJECTED | ARCHIVED
  
  // 发布信息
  releaseNote?: string;            // 发布说明/备注（如 "修复金额校验问题"）
  publishedAt?: Date;              // 发布时间
  publishedBy?: string;            // 发布人
  
  // 提交信息
  submittedBy?: string;            // 提交人
  submittedAt?: Date;              // 提交时间
  submitComment?: string;          // 提交备注
  
  // 审核信息
  reviewedAt?: Date;               // 审核时间
  reviewedBy?: string;             // 审核人
  reviewComment?: string;          // 审核意见
  
  // 元数据
  createdAt: Date;
  updatedAt: Date;
}

type SnapshotStatus = 
  | 'DRAFT'      // 草稿（暂未使用）
  | 'PENDING'    // 待审核
  | 'REJECTED'   // 已驳回
  | 'ACTIVE'     // 激活（审核通过后自动设置，当前对外版本）
  | 'ARCHIVED';  // 已归档

// 注意：简化流程，审核通过后直接设为 ACTIVE，无需单独的 APPROVED 状态
```

#### FormInstance（表单实例）

```typescript
interface FormInstance {
  id: string;
  
  // 多区域隔离（必填）
  regionId: string;                // 区域 ID（CN / US / ME）
  
  // 主权字段
  snapshotId: string;              // 绑定的发布快照（不可变）
  
  // 冗余字段（便于查询）
  formDefinitionId: string;        // 表单定义 ID
  formVersionId: string;           // 表单版本 ID
  
  // 关联流程
  processInstanceId: string;       // 关联的流程实例 ID
  
  // 数据
  formData: Record<string, any>;   // 表单数据
  status: InstanceStatus;
  
  // 提交信息
  submittedBy: string;
  submittedAt?: Date;
  
  createdAt: Date;
  updatedAt: Date;
}
```

#### 关键约束

```sql
-- 每个区域的每个表单定义最多只有一个 ACTIVE 快照
-- 使用 region_id 实现多区域隔离
CREATE UNIQUE INDEX idx_snapshot_active_region 
ON release_snapshots (region_id, form_definition_id) 
WHERE status = 'ACTIVE';

-- 快照必须关联有效的表单版本和流程版本
ALTER TABLE release_snapshots
ADD CONSTRAINT fk_form_version 
FOREIGN KEY (form_version_id) REFERENCES form_versions(id);

ALTER TABLE release_snapshots
ADD CONSTRAINT fk_process_version 
FOREIGN KEY (process_version_id) REFERENCES process_versions(id);

-- 快照状态唯一性约束
-- 每个区域的每个表单定义最多只有一个 DRAFT 快照
CREATE UNIQUE INDEX idx_snapshot_draft_region 
ON release_snapshots (region_id, form_definition_id) 
WHERE status = 'DRAFT';

-- 每个区域的每个表单定义最多只有一个 PENDING 快照
CREATE UNIQUE INDEX idx_snapshot_pending_region 
ON release_snapshots (region_id, form_definition_id) 
WHERE status = 'PENDING';

-- 区域索引（所有表都需要）
CREATE INDEX idx_form_definition_region ON form_definitions(region_id);
CREATE INDEX idx_form_version_region ON form_versions(region_id);
CREATE INDEX idx_snapshot_region ON release_snapshots(region_id);
CREATE INDEX idx_instance_region ON form_instances(region_id);

-- 实例查询优化索引（包含区域）
CREATE INDEX idx_instance_region_form_def ON form_instances(region_id, form_definition_id);
CREATE INDEX idx_instance_region_form_ver ON form_instances(region_id, form_version_id);
CREATE INDEX idx_instance_region_snapshot ON form_instances(region_id, snapshot_id);
```

---

## 🔄 核心流程

### 设计与发布流程

```
┌─────────────────────────────────────────────────────────────────────────┐
│                         设计与发布流程                                   │
└─────────────────────────────────────────────────────────────────────────┘

1. 创建表单定义
   └── POST /form-management/definitions
       ├── 调用 FormEngine: 创建 FormDefinition
       └── 调用 ApprovalEngine: 创建关联的 ProcessDefinition

2. 设计表单字段
   └── PATCH /form-management/definitions/:id/form-design
       └── 调用 FormEngine: 更新 FormVersion.schema

3. 设计审批流程
   └── PATCH /form-management/definitions/:id/process-design
       └── 调用 ApprovalEngine: 更新 ProcessVersion.model

4. 提交审核
   └── POST /form-management/definitions/:id/submit-review
       ├── 调用 FormEngine: FormVersion.status → PENDING
       ├── 调用 ApprovalEngine: ProcessVersion.status → PENDING
       └── 创建 ReleaseSnapshot (status: PENDING)

5. 审核
   └── POST /form-management/snapshots/:snapshotId/review
       ├── 通过: FormVersion/ProcessVersion.status → APPROVED
       └── 驳回: FormVersion/ProcessVersion.status → REJECTED

6. 发布
   └── POST /form-management/snapshots/:snapshotId/publish
       ├── 旧 ACTIVE 快照 → ARCHIVED
       ├── 当前快照 → ACTIVE
       ├── FormVersion.status → PUBLISHED
       └── ProcessVersion.status → PUBLISHED
```

#### 审核发布时序图

```
┌─────────┐          ┌─────────────────┐          ┌─────────────┐          ┌────────────────┐
│ 前端     │          │ Form Management │          │ Form Engine │          │ Approval Engine│
└────┬────┘          └────────┬────────┘          └──────┬──────┘          └───────┬────────┘
     │                        │                          │                         │
     │  1. 保存设计            │                          │                         │
     │ ──────────────────────>│                          │                         │
     │                        │  updateVersion(schema)   │                         │
     │                        │ ─────────────────────────>                         │
     │                        │                          │                         │
     │                        │  updateProcessVersion(model)                       │
     │                        │ ───────────────────────────────────────────────────>
     │                        │                          │                         │
     │  2. 提交审核            │                          │                         │
     │ ──────────────────────>│                          │                         │
     │                        │  setStatus(PENDING)      │                         │
     │                        │ ─────────────────────────>                         │
     │                        │                          │                         │
     │                        │  setStatus(PENDING)      │                         │
     │                        │ ───────────────────────────────────────────────────>
     │                        │                          │                         │
     │                        │  createSnapshot(PENDING) │                         │
     │                        │ ────────────┐            │                         │
     │                        │             │            │                         │
     │                        │ <───────────┘            │                         │
     │                        │                          │                         │
     │  3. 审核通过            │                          │                         │
     │ ──────────────────────>│                          │                         │
     │                        │  setStatus(APPROVED)     │                         │
     │                        │ ─────────────────────────>                         │
     │                        │                          │                         │
     │                        │  setStatus(APPROVED)     │                         │
     │                        │ ───────────────────────────────────────────────────>
     │                        │                          │                         │
     │  4. 发布                │                          │                         │
     │ ──────────────────────>│                          │                         │
     │                        │  archiveOldSnapshot()    │                         │
     │                        │ ────────────┐            │                         │
     │                        │             │            │                         │
     │                        │ <───────────┘            │                         │
     │                        │                          │                         │
     │                        │  setStatus(PUBLISHED)    │                         │
     │                        │ ─────────────────────────>                         │
     │                        │                          │                         │
     │                        │  setStatus(PUBLISHED)    │                         │
     │                        │ ───────────────────────────────────────────────────>
     │                        │                          │                         │
     │                        │  activateSnapshot()      │                         │
     │                        │ ────────────┐            │                         │
     │                        │             │            │                         │
     │                        │ <───────────┘            │                         │
     │                        │                          │                         │
     │  <─────────────────────│                          │                         │
     │        发布成功         │                          │                         │
┌────┴────┐          ┌────────┴────────┐          ┌──────┴──────┐          ┌───────┴────────┐
│ 前端     │          │ Form Management │          │ Form Engine │          │ Approval Engine│
└─────────┘          └─────────────────┘          └─────────────┘          └────────────────┘
```

**关键点**: 前端**永远只调** `/form-management/...` 这层，不直接调用 `form-engine` / `approval-engine` API。

### 用户发起申请流程

```
用户发起申请
     │
     ▼
┌─────────────────────────────────────────┐
│ 1. 获取当前 ACTIVE 快照                  │
│    GET /form-management/definitions/:id │
│    /active-snapshot                     │
└─────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────┐
│ 2. 根据快照获取表单结构                   │
│    - FormVersion.schema                 │
│    - FormVersion.uiSchema               │
└─────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────┐
│ 3. 用户填写表单                          │
│    - 前端根据 schema 渲染                │
│    - 实时验证                           │
└─────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────┐
│ 4. 保存草稿（可选）                       │
│    POST /form-management/instances      │
│    - 创建 FormInstance (DRAFT)          │
│    - 绑定 formVersionId                 │
│    - 可多次编辑保存                       │
└─────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────┐
│ 5. 提交审批                              │
│    POST /instances/:id/submit           │
│    - 更新状态为 PENDING_APPROVAL         │
│    - 启动审批流程                        │
│    - 创建 ApprovalInstance              │
└─────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────┐
│ 6. 流程执行（审批引擎）                   │
│    - 根据 ProcessVersion.model 执行      │
│    - 整个生命周期使用同一版本             │
└─────────────────────────────────────────┘
```

---

## 🔧 技术决策

### TD-1: 发布快照设计

**问题**: 如何确保运行中的实例不受表单/流程更新影响？

**方案**: 引入 ReleaseSnapshot 实体

```
FormDefinition 1 ──┬── N FormVersion
                   │
                   └── N ProcessVersion
                   │
                   └── N ReleaseSnapshot (绑定特定版本组合)
                             │
                             └── N FormInstance (绑定特定快照)
```

**理由**:
- 表单版本和流程版本可以独立演进
- 发布时明确绑定版本组合，形成不可变快照
- 实例始终引用快照，确保一致性

### TD-2: 聚合 API vs 直接调用引擎 API

**问题**: 前端应该直接调用两个引擎的 API，还是通过聚合层？

**方案**: 通过 FormManagementController 聚合

**理由**:
- **事务一致性**: 跨引擎操作需要事务协调
- **业务逻辑集中**: 发布快照逻辑在应用层
- **前端简化**: 前端只需调用一个 API 完成跨引擎操作
- **权限统一**: 在应用层统一处理权限校验

### TD-3: 版本号管理策略

**问题**: 表单版本和流程版本如何编号？

**方案**: 独立递增

```
FormDefinition: fd-001
├── FormVersion: v1, v2, v3, ...
└── ProcessDefinition: pd-001
    └── ProcessVersion: v1, v2, v3, ...

ReleaseSnapshot: snap-001
├── formVersionId: fv-003 (对应 FormVersion v3)
└── processVersionId: pv-002 (对应 ProcessVersion v2)
```

**理由**:
- 表单和流程可以独立迭代
- 发布快照明确记录版本组合
- 便于追溯历史

### TD-4: 字段权限存储与生效链路

**问题**: 节点的字段权限配置存在哪里？如何生效？

**方案**: 存储在 ProcessVersion.model 的节点配置中

```typescript
// ProcessVersion.model.nodes[i].config
{
  editableFields: ['field1', 'field2'],
  requiredFields: ['field1'],
  hiddenFields: ['field3']
}
```

**生效链路**:

```
1. 当前任务 → 找到当前 nodeInstance 对应的 ProcessNode.config

2. 从节点配置中读取 editableFields / requiredFields / hiddenFields

3. FormRenderer 接收一个 fieldAccessMap：
   fieldAccess['amount'] = { editable: false, required: false, hidden: false }

4. 前端渲染逻辑：
   • hidden = true → 不渲染
   • editable = false → 只读字段
   • required = true → 加星号 + 校验规则
```

**职责划分**:
- **表单引擎**: 只管"字段是什么"（类型、验证规则、默认值）
- **流程引擎**: 决定"谁在什么时候能动哪些字段"（节点级字段权限）

> 这样以后不会有人在表单引擎那边再想搞一个"字段权限表"，职责清晰。

**⚠️ 字段 Key 稳定性要求**:

> 字段权限依赖字段 key 的稳定性，字段 key 在设计器中应被视为 **ID**，而不是可随意修改的显示名。

| 规则 | 说明 |
|------|------|
| key 不可随意修改 | 设计器应禁止直接修改已有字段的 key |
| 修改需警告 | 如果必须修改，需提示"会影响历史流程配置中的字段权限" |
| label 可自由修改 | 用户可以修改字段的显示名（label），不影响 key |

**反面案例**:
```
表单设计者把 amount 改名成 invoiceAmount 
→ 流程配置里还写着 editableFields: ['amount'] 
→ 字段权限失效但没人发现
→ 审批人该编辑的字段编辑不了，没人知道为什么
```

### TD-5: FormInstance + ProcessInstance 创建模型（两阶段提交）

**问题**: 如何确保表单实例和流程实例的一致性？同时支持用户分步骤填写表单？

**方案**: 两阶段提交模式

```
阶段1: 创建草稿              阶段2: 提交审批
┌─────────────────┐         ┌─────────────────┐
│ POST /instances │         │ POST /:id/submit│
├─────────────────┤         ├─────────────────┤
│ 创建 FormInstance│         │ 更新状态        │
│ status: DRAFT   │ ──────> │ status: PENDING │
│ 可多次编辑       │         │ 启动审批流程     │
│ 无流程实例       │         │ 创建流程实例     │
└─────────────────┘         └─────────────────┘
```

**阶段1: 创建草稿 (POST /instances)**

```typescript
async create(dto: CreateInstanceDto): Promise<FormInstance> {
  return this.prisma.$transaction(async (tx) => {
    // 1. 获取 ACTIVE 快照
    const formVersion = await tx.formVersion.findFirst({
      where: { definitionId: dto.formDefinitionId, isDefault: true, status: 'PUBLISHED' }
    });
    
    // 2. 创建 FormInstance（草稿状态）
    const formInstance = await tx.formInstance.create({
      data: {
        formDefinitionId: dto.formDefinitionId,
        formVersionId: formVersion.id,
        formData: dto.formData,
        status: 'DRAFT',  // 草稿状态
        createdBy: dto.userId,
      }
    });
    
    // 注意：此时不创建 ProcessInstance
    
    return formInstance;
  });
}
```

**阶段2: 提交审批 (POST /instances/:id/submit)**

```typescript
async submitInstance(instanceId: string, userId: string): Promise<void> {
  // 1. 更新实例状态
  await this.prisma.formInstance.update({
    where: { id: instanceId },
    data: {
      status: 'PENDING_APPROVAL',
      submittedAt: new Date(),
      submittedBy: userId,
    }
  });
  
  // 2. 启动审批流程（调用审批引擎）
  const approvalInstance = await this.approvalService.startApproval({
    businessType: 'FORM_INSTANCE',
    businessId: instanceId,
    // ...
  });
  
  // 3. 关联审批实例
  await this.prisma.formInstance.update({
    where: { id: instanceId },
    data: { approvalInstanceId: approvalInstance.id }
  });
}
```

**设计理由**:
- **用户体验**: 允许用户分多次填写表单，避免一次性提交造成的数据丢失
- **草稿功能**: 用户可以保存草稿，稍后继续编辑
- **明确分离**: 创建和提交是两个独立的操作，符合用户直觉
- **失败隔离**: 如果审批流程启动失败，不影响表单草稿数据

**数据写入职责**:

| 表 | 写入方 | 说明 |
|---|--------|------|
| `form_instances` | Form Management | 创建草稿、更新状态 |
| `approval_instances` | Approval Engine | 提交时创建，由 Form Management 调用 |

### TD-6: 审批引擎与业务数据的解耦设计

**问题**: 审批引擎是否需要依赖表单引擎？业务数据应该存储在哪里？

**设计原则**: **审批引擎是通用基础设施，不强制依赖表单引擎**

```
┌─────────────────────────────────────────────────────────────────────────┐
│                        审批引擎（通用基础设施）                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ApprovalInstance                                                      │
│   ├── businessType: string   // 业务类型标识                            │
│   ├── businessId: string     // 业务单据 ID（指向业务系统的数据）         │
│   ├── businessKey: string    // 业务单号                                │
│   └── variables: Json        // 流程上下文（轻量级元数据）               │
│                                                                         │
│   ⚠️ 审批引擎职责边界：                                                  │
│   ✅ 管理流程实例生命周期（启动、暂停、终止）                             │
│   ✅ 管理审批任务分配与执行                                              │
│   ✅ 执行条件分支逻辑                                                    │
│   ✅ 记录审批操作日志                                                    │
│   ❌ 不存储业务数据副本                                                  │
│   ❌ 不关心业务数据的具体结构                                            │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
                                    ▲
                                    │ 通过 businessType + businessId 关联
                    ┌───────────────┼───────────────┐
                    │               │               │
        ┌───────────┴───┐   ┌───────┴───────┐   ┌───┴───────────┐
        │ Form Engine   │   │ Work Record   │   │ 其他业务系统   │
        │               │   │               │   │               │
        │ FormInstance  │   │ WorkRecord    │   │ CustomEntity  │
        │ .data (JSON)  │   │ .hours        │   │ .data         │
        └───────────────┘   └───────────────┘   └───────────────┘
        businessType:       businessType:       businessType:
        "FORM_INSTANCE"     "WORK_RECORD"       "CUSTOM"
```

---

#### 6.1 业务系统接入审批引擎的两种模式

**模式 A：通过表单引擎（推荐用于通用表单场景）**

```
用户填写表单 → FormInstance → Form Management → Approval Engine
                  ↑
                  └── 数据存储在 FormInstance.data
```

适用场景：
- 报销申请、请假申请等标准化表单
- 需要动态配置字段的业务
- 需要版本管理和快照的场景

**模式 B：业务系统直接对接（推荐用于专业业务场景）**

```
业务操作 → 业务实体（如 WorkRecord）→ 业务系统 Service → Approval Engine
                  ↑
                  └── 数据存储在业务系统自己的表
```

适用场景：
- 工时审批（WorkRecord 有专属数据模型）
- 采购审批（PurchaseOrder 有复杂业务逻辑）
- 已有业务系统需要接入审批流程

---

#### 6.2 业务数据存储规范（单一数据源原则）

**核心规范**: 业务数据只存储在业务系统，审批引擎只存储流程上下文

| 存储位置 | 存储内容 | 示例 |
|----------|----------|------|
| 业务系统（Source of Truth） | 业务数据 | `FormInstance.data`, `WorkRecord.*` |
| `ApprovalInstance.variables` | 流程元数据（只读） | `{ businessKey, submitterId, priority }` |
| `ApprovalTaskLog.formDataChanges` | 数据变更审计 | `{ amount: { old: 5000, new: 6000 } }` |

---

#### 6.3 Form Management 场景下的数据一致性

当使用表单引擎时，`FormInstance.data` 是唯一数据源：

```
┌─────────────────────────────────────────────────────────────────────────┐
│  Form Management 数据流                                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. 创建/提交实例                                                        │
│     FormInstance.data = 用户填写的数据                                   │
│     ApprovalInstance.variables = {                                      │
│       formKey: "expense-form",                                          │
│       formDefinitionId: "xxx",                                          │
│       submitterId: "user-id",                                           │
│       // ⚠️ 不存储 formData 副本                                         │
│     }                                                                   │
│                                                                         │
│  2. 审批人修改字段                                                       │
│     ├── ApprovalTaskLog.formDataChanges = { amount: { old: 5000, new: 6000 } }
│     └── FormInstance.data.amount = 6000  // 同步更新                     │
│                                                                         │
│  3. 查询表单数据                                                         │
│     └── 始终从 FormInstance.data 读取                                    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

**审批人修改数据时的同步实现**:

```typescript
// Form Management 提供的回调接口
// 审批引擎在审批人修改数据后调用此接口
async syncBusinessData(
  businessType: string,
  businessId: string,
  changes: Record<string, { old: any; new: any }>,
): Promise<void> {
  if (businessType === 'FORM_INSTANCE') {
    const instance = await this.prisma.formInstance.findUnique({
      where: { id: businessId },
    });
    
    // 应用变更
    const newData = { ...instance.data };
    for (const [field, change] of Object.entries(changes)) {
      newData[field] = change.new;
    }
    
    await this.prisma.formInstance.update({
      where: { id: businessId },
      data: { data: newData, updatedAt: new Date() },
    });
  }
}
```

---

#### 6.4 审批引擎条件评估（通用方案）

审批引擎需要获取业务数据进行条件判断时，通过回调机制获取：

```typescript
// 审批引擎定义的接口
interface BusinessDataProvider {
  getBusinessData(businessType: string, businessId: string): Promise<Record<string, any>>;
}

// Form Management 的实现
class FormBusinessDataProvider implements BusinessDataProvider {
  async getBusinessData(businessType: string, businessId: string) {
    if (businessType === 'FORM_INSTANCE') {
      const instance = await this.prisma.formInstance.findUnique({
        where: { id: businessId },
      });
      return instance.data;
    }
    throw new Error(`Unknown businessType: ${businessType}`);
  }
}

// 工时系统的实现
class WorkRecordBusinessDataProvider implements BusinessDataProvider {
  async getBusinessData(businessType: string, businessId: string) {
    if (businessType === 'WORK_RECORD') {
      const record = await this.prisma.workRecord.findUnique({
        where: { id: businessId },
      });
      return { hours: record.hours, date: record.date, ... };
    }
    throw new Error(`Unknown businessType: ${businessType}`);
  }
}

// 审批引擎条件评估（通用实现）
async evaluateCondition(expression: string, instanceId: string): Promise<boolean> {
  const instance = await this.prisma.approvalInstance.findUnique({
    where: { id: instanceId },
  });
  
  // 通过 Provider 获取业务数据（而非直接依赖 FormInstance）
  const provider = this.getProvider(instance.businessType);
  const businessData = await provider.getBusinessData(
    instance.businessType,
    instance.businessId,
  );
  
  // 合并上下文
  const context = {
    ...instance.variables,  // 流程元数据
    ...businessData,        // 业务数据
  };
  
  return evaluate(expression, context);
}
```

---

#### 6.5 业务系统接入审批引擎的契约

业务系统接入审批引擎需要实现以下契约：

| 契约 | 职责 | 说明 |
|------|------|------|
| `BusinessDataProvider` | 提供业务数据 | 审批引擎条件评估时调用 |
| `BusinessDataSyncer` | 同步数据变更 | 审批人修改数据后回调 |
| `BusinessStatusUpdater` | 更新业务状态 | 审批通过/拒绝时回调 |

```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>;
}
```

---

#### 6.6 设计决策总结

| 决策 | 说明 | 状态 |
|------|------|------|
| 审批引擎独立 | 不强制依赖表单引擎，可被任何业务系统使用 | ✅ 已实现 |
| 单一数据源 | 业务数据只存储在业务系统，审批引擎只存储流程上下文 | ✅ 已实现 |
| 契约驱动 | 业务系统通过 `BusinessTypeMetadata` 接口接入 | ✅ 已实现 |
| 变更审计 | 数据变更记录在 `ApprovalTaskLog.formDataChanges` | ✅ 已实现 |
| 条件评估获取业务数据 | 网关节点通过 Activity 实时获取业务数据 | ✅ 已实现 |
| 审批人修改同步 | 通过 `syncBusinessDataChanges` Activity 同步 | ✅ 已实现 |

#### 6.7 实现详情

**核心文件**:

| 文件 | 职责 |
|------|------|
| `approval/business-type-registry.service.ts` | 业务类型注册表，定义接口和同步方法 |
| `form-management/form-business-type.provider.ts` | 表单引擎的 Provider 实现 |
| `approval/temporal/activities/approval.activities.ts` | Activity 函数，包含数据同步 |
| `approval/temporal/workflows/generic-approval.workflow.ts` | Workflow 调用 Activity |
| `temporal/worker.ts` | Worker 初始化时注入 BusinessTypeRegistry |

**数据流**:

```
1. 条件评估（网关节点）:
   Workflow → getBusinessDataForCondition Activity 
           → BusinessTypeRegistry.getBusinessDataForCondition()
           → FormBusinessTypeProvider.getBusinessData()
           → FormInstance.data

2. 审批人修改数据:
   Workflow (handleApprove) → syncBusinessDataChanges Activity
                           → BusinessTypeRegistry.syncDataChanges()
                           → FormBusinessTypeProvider.syncDataChanges()
                           → UPDATE FormInstance.data

3. 审批完成:
   Workflow (onProcessCompleted) → BusinessTypeRegistry.notifyApprovalComplete()
                                → FormBusinessTypeProvider.onApprovalComplete()
                                → UPDATE FormInstance.status
```

---

### TD-7: 冗余字段设计（查询优化）

**问题**: 实际查询时经常需要按表单定义或版本筛选实例，但主权字段只有 snapshotId。

**方案**: 在关键表中添加冗余字段

| 表 | 冗余字段 | 用途 |
|---|---------|------|
| `release_snapshots` | `processDefinitionId` | 按流程维度统计、版本对比 |
| `form_instances` | `formDefinitionId`, `formVersionId` | 跨版本查询、版本统计 |

**原则**:
- **主权不变**: snapshotId 仍是 FormInstance 的主权字段
- **冗余只读**: 冗余字段在创建时写入，后续不修改
- **查询优化**: 避免 JOIN 快照表的性能开销

### TD-8: FormDefinition 与 ProcessDefinition 的绑定关系

**问题**: 一个表单是否可以对应多个流程？

**当前方案**: 1:1 绑定

```
FormDefinition ──1:1──> ProcessDefinition
```

**理由**:
- 简化设计，一个表单对应一个审批流程
- 发布快照时明确绑定关系
- 避免用户选择流程时的困惑

**未来扩展（预留）**: 1:N 支持

> 未来场景可能需要：一个表单支持多个流程（如请假表单：国内流程、国外流程）。

```
未来可能的架构：
FormDefinition
    ├── ProcessDefinition (国内流程)
    └── ProcessDefinition (国外流程)

ReleaseSnapshot 需要增加：
    - processDefinitionId（指定使用哪个流程）
    - 或 routingRule（根据表单数据动态选择流程）
```

**扩展时机**: 当业务明确需要"同一表单、多流程选择"时再实现。当前保持 1:1 简单模型。

### TD-9: ReleaseSnapshot 的 DRAFT 状态唯一性

**问题**: 一个 FormDefinition 是否允许同时存在多个 DRAFT 快照？

```
场景：
- v2 已发布（ACTIVE）
- v3 正在写（DRAFT）
- v4 想并行准备（DRAFT？）
```

**方案**: 一次只允许一个 DRAFT 快照

```sql
-- 每个表单定义在每个区域最多只有一个 DRAFT 快照
CREATE UNIQUE INDEX idx_snapshot_draft_region 
ON release_snapshots (region_id, form_definition_id) 
WHERE status = 'DRAFT';
```

**理由**:
- 明确发布目标，避免"发布哪个版本"的困惑
- 简化 UI 设计，设计器始终编辑唯一的草稿
- 符合大多数业务场景（顺序迭代）

**工作流程**:

```
当前状态：v2 ACTIVE, v3 DRAFT

想要准备 v4？
  ↓
必须先处理 v3：
  - 发布 v3 → v3 变为 ACTIVE，v2 归档
  - 或丢弃 v3 → 删除 v3 快照
  ↓
然后才能创建 v4 DRAFT
```

**约束总结**:

| 状态 | 每个 (regionId, formDefinitionId) 的数量限制 |
|------|---------------------------------------------|
| `DRAFT` | 最多 1 个 |
| `PENDING` | 最多 1 个（提交审核时从 DRAFT 转换） |
| `ACTIVE` | 最多 1 个 |
| `ARCHIVED` | 无限制 |
| `SUSPENDED` | 无限制 |

---

## 🔗 字段权限生效链路（详细）

```
┌─────────────────────────────────────────────────────────────────────────┐
│                        字段权限生效链路                                  │
└─────────────────────────────────────────────────────────────────────────┘

                    流程引擎                           表单引擎
                       │                                  │
1. 获取当前任务        │                                  │
   ────────────────────┼──────────────────────────────────┼────────────
                       ▼                                  │
   ┌───────────────────────────────────────┐              │
   │ TaskService.getCurrentTask(instanceId) │              │
   │ → 返回当前 nodeInstanceId              │              │
   └───────────────────────────────────────┘              │
                       │                                  │
2. 获取节点配置        │                                  │
   ────────────────────┼──────────────────────────────────┼────────────
                       ▼                                  │
   ┌───────────────────────────────────────┐              │
   │ ProcessVersion.model.nodes[nodeId]    │              │
   │ → node.config.editableFields          │              │
   │ → node.config.requiredFields          │              │
   │ → node.config.hiddenFields            │              │
   └───────────────────────────────────────┘              │
                       │                                  │
3. 构建字段权限映射    │                                  │
   ────────────────────┼──────────────────────────────────┼────────────
                       ▼                                  │
   ┌───────────────────────────────────────┐              │
   │ fieldAccessMap = {                    │              │
   │   'amount': {                         │              │
   │     editable: false,                  │              │
   │     required: false,                  │              │
   │     hidden: false                     │              │
   │   },                                  │              │
   │   'approvalComment': {                │              │
   │     editable: true,                   │              │
   │     required: true,                   │              │
   │     hidden: false                     │              │
   │   }                                   │              │
   │ }                                     │              │
   └───────────────────────────────────────┘              │
                       │                                  │
4. 渲染表单            │                                  │
   ────────────────────┼──────────────────────────────────┼────────────
                       │                                  ▼
                       │              ┌───────────────────────────────────────┐
                       │              │ FormRenderer(                         │
                       └─────────────>│   schema,                             │
                                      │   uiSchema,                           │
                                      │   formData,                           │
                                      │   fieldAccessMap  ← 传入字段权限       │
                                      │ )                                     │
                                      │                                       │
                                      │ 渲染逻辑:                              │
                                      │ • hidden=true  → 不渲染该字段          │
                                      │ • editable=false → 渲染为只读          │
                                      │ • required=true → 加星号+校验          │
                                      └───────────────────────────────────────┘
```

---

## 📁 代码结构

### 后端结构

```
backend/src/
├── form-management/                    # 表单管理应用模块
│   ├── form-management.module.ts       # 模块定义
│   ├── form-management.controller.ts   # 聚合 API 控制器
│   ├── form-management.service.ts      # 业务逻辑
│   ├── dto/
│   │   ├── create-form.dto.ts
│   │   ├── update-form-design.dto.ts
│   │   ├── update-process-design.dto.ts
│   │   ├── submit-review.dto.ts
│   │   ├── review.dto.ts
│   │   └── publish.dto.ts
│   └── entities/
│       └── release-snapshot.entity.ts  # 发布快照实体
│
├── form-engine/                        # 表单引擎（已有）
│   ├── definition/
│   ├── version/
│   ├── instance/
│   └── template/
│
└── approval-engine/                    # 审批引擎（已有）
    ├── definition/
    ├── version/
    ├── instance/
    └── task/
```

### 前端结构

```
frontend/src/
├── app/forms/                          # 表单管理页面
│   ├── layout.tsx                      # 模块布局
│   ├── page.tsx                        # 概览页
│   ├── definitions/
│   │   ├── page.tsx                    # 列表页
│   │   ├── new/page.tsx                # 创建页
│   │   └── [id]/
│   │       ├── page.tsx                # 详情页
│   │       ├── design/page.tsx         # 一体化设计器
│   │       └── versions/page.tsx       # 版本管理
│   ├── templates/page.tsx
│   └── ...
│
├── components/forms/                   # 表单引擎组件
│   ├── FormRenderer.tsx
│   └── designer/
│       ├── DesignCanvas.tsx
│       ├── FieldPalette.tsx
│       └── PropertyPanel.tsx
│
├── components/approval/designer/       # 流程设计器组件
│   ├── DingProcessDesigner.tsx
│   └── DingStylePropertyPanel.tsx
│
└── services/api/
    └── form-management.ts              # 聚合 API 客户端
```

---

## 🔒 安全设计

### 权限校验流程

```
请求 → 认证中间件 → 权限守卫 → 控制器 → 服务 → 响应
         │              │
         │              └── 检查 form:design / form:review / form:publish
         │
         └── 验证 JWT Token
```

### 数据隔离

| 场景 | 规则 |
|------|------|
| 列表查询 | 普通用户只看已发布；设计者看自己的+已发布；管理员看全部 |
| 编辑操作 | 仅创建者或管理员 |
| 审核操作 | 仅审核员或管理员 |
| 发布操作 | 仅管理员 |

### 审计日志

所有关键操作记录审计日志：

| 操作 | 日志级别 | 记录内容 |
|------|----------|----------|
| 创建表单 | INFO | 操作人、表单ID、时间 |
| 提交审核 | INFO | 操作人、版本ID、时间 |
| 审核通过/驳回 | WARN | 操作人、版本ID、审核意见、时间 |
| 发布 | WARN | 操作人、快照ID、时间 |
| 回滚 | ERROR | 操作人、快照ID、原因、时间 |

---

## 📈 性能考虑

### 缓存策略

| 数据 | 缓存策略 | TTL |
|------|----------|-----|
| ACTIVE 快照 | Redis 缓存 | 5 分钟 |
| 表单 Schema | CDN + 本地缓存 | 1 小时 |
| 模板列表 | Redis 缓存 | 10 分钟 |

### 数据库优化

```sql
-- 常用查询索引
CREATE INDEX idx_form_definition_status ON form_definitions(status);
CREATE INDEX idx_form_version_status ON form_versions(form_definition_id, status);
CREATE INDEX idx_snapshot_active ON release_snapshots(form_definition_id) WHERE status = 'ACTIVE';
CREATE INDEX idx_snapshot_process_def ON release_snapshots(process_definition_id);
CREATE INDEX idx_instance_snapshot ON form_instances(snapshot_id);
CREATE INDEX idx_instance_form_def ON form_instances(form_definition_id);
CREATE INDEX idx_instance_form_ver ON form_instances(form_version_id);
```

---

## 🔗 与其他模块集成

### 依赖的引擎文档

| 引擎 | 文档 |
|------|------|
| 表单引擎 | [form-engine/README.md](../form-engine/README.md) |
| 审批引擎 | [approval-engine/README.md](../approval-engine/README.md) |
| 审批中心 | [approval-engine/PRD.md](../approval-engine/PRD.md)（负责表单实例的运行时体验） |

### 与审批中心

```
表单管理                              审批中心
┌───────────────────┐               ┌───────────────────┐
│ 发布快照           │   引用        │ 发起申请          │
│ - formVersionId   │ ─────────>   │ - 获取 ACTIVE 快照 │
│ - processVersionId│               │ - 渲染表单         │
└───────────────────┘               │ - 启动流程         │
                                    │ - 字段权限控制     │
                                    └───────────────────┘
```

### 与用户中心

- 获取审批人列表（指定成员）
- 获取角色成员（指定角色）
- 获取部门主管（组织架构）

### 与通知服务

- 审核通过/驳回通知设计者
- 发布成功通知相关人员

---

## 🌐 多区域设计

> 表单管理模块从设计之初即支持多区域（大区级）部署，所有核心实体都包含 `regionId` 字段。

### 区域隔离模型

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                           多区域架构（大区级）                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌───────────────┐    ┌───────────────┐    ┌───────────────┐              │
│   │  CN (中国)     │    │  US (美国)     │    │  ME (中东)     │             │
│   ├───────────────┤    ├───────────────┤    ├───────────────┤              │
│   │ FormDefinition│    │ FormDefinition│    │ FormDefinition│              │
│   │ FormVersion   │    │ FormVersion   │    │ FormVersion   │              │
│   │ ReleaseSnapshot│   │ ReleaseSnapshot│   │ ReleaseSnapshot│             │
│   │ FormInstance  │    │ FormInstance  │    │ FormInstance  │              │
│   │ ProcessDef... │    │ ProcessDef... │    │ ProcessDef... │              │
│   │ ProcessInst...│    │ ProcessInst...│    │ ProcessInst...│              │
│   └───────────────┘    └───────────────┘    └───────────────┘              │
│          │                    │                    │                        │
│          │       完全隔离，互不影响                  │                        │
│          └────────────────────┴────────────────────┘                        │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
```

### 区域字段规范

| 实体 | 字段 | 类型 | 说明 |
|------|------|------|------|
| `FormDefinition` | `regionId` | `string` (required) | 表单定义隔离 |
| `FormVersion` | `regionId` | `string` (required) | 表单版本隔离 |
| `ReleaseSnapshot` | `regionId` | `string` (required) | 发布快照隔离 |
| `FormInstance` | `regionId` | `string` (required) | 表单实例隔离 |
| `ProcessDefinition` | `regionId` | `string` (required) | 流程定义隔离 |
| `ProcessVersion` | `regionId` | `string` (required) | 流程版本隔离 |
| `ProcessInstance` | `regionId` | `string` (required) | 流程实例隔离 |
| `FormTemplate` | - | **无** | ⚠️ 全局共享，不区分区域 |

### 模板的特殊处理

> **模板是全局共享的，不区分 region**，用于设计参考。

```
模板库（全局，无 regionId）
    │
    │ 选择模板
    ▼
从模板创建表单 → 必须指定 regionId（CN / US / ME）
    │
    ▼
FormDefinition（属于特定区域）
```

- 模板 API 不需要 `X-Region-Id` Header
- 从模板创建 `FormDefinition` 时，必须在请求体中指定 `regionId`
- 同一模板可以在不同区域分别创建表单定义

### 区域 ID 格式（大区级）

| 区域 | regionId | 说明 |
|------|----------|------|
| 中国 | `CN` | 中国区数据中心 |
| 美国 | `US` | 美国区数据中心 |
| 中东 | `ME` | 中东区数据中心 |

### regionId 继承规则

> 子实体的 `regionId` 必须与父实体一致，确保数据完整性。

```
FormDefinition (regionId: CN)
    │
    ├── FormVersion (regionId: CN)          ← 必须等于 FormDefinition.regionId
    │
    └── ProcessDefinition (regionId: CN)    ← 必须等于 FormDefinition.regionId
            │
            └── ProcessVersion (regionId: CN)  ← 必须等于 ProcessDefinition.regionId

ReleaseSnapshot (regionId: CN)
    │
    ├── formVersionId → FormVersion.regionId 必须 == CN
    └── processVersionId → ProcessVersion.regionId 必须 == CN

FormInstance (regionId: CN)
    │
    └── snapshotId → ReleaseSnapshot.regionId 必须 == CN
```

#### 继承规则表

| 子实体 | 父实体 | 规则 |
|--------|--------|------|
| `FormVersion` | `FormDefinition` | `FormVersion.regionId == FormDefinition.regionId` |
| `ProcessDefinition` | `FormDefinition` | `ProcessDefinition.regionId == FormDefinition.regionId` |
| `ProcessVersion` | `ProcessDefinition` | `ProcessVersion.regionId == ProcessDefinition.regionId` |
| `ReleaseSnapshot` | `FormVersion` + `ProcessVersion` | 三者 regionId 必须一致 |
| `FormInstance` | `ReleaseSnapshot` | `FormInstance.regionId == ReleaseSnapshot.regionId` |
| `ProcessInstance` | `FormInstance` | `ProcessInstance.regionId == FormInstance.regionId` |

#### 校验规则

> 所有带有资源 ID 的 API 必须校验请求的 `regionId` 与该资源记录中的 `regionId` 一致。

```typescript
// 示例：获取表单版本时校验 regionId
async getFormVersion(ctx: RequestContext, formVersionId: string) {
  const formVersion = await this.prisma.formVersion.findUnique({
    where: { id: formVersionId }
  });
  
  if (!formVersion) {
    throw new NotFoundException('FORM_VERSION_NOT_FOUND');
  }
  
  // 校验 regionId 一致性
  if (formVersion.regionId !== ctx.regionId) {
    throw new ForbiddenException('REGION_MISMATCH');
  }
  
  return formVersion;
}

// 示例：创建表单版本时继承 regionId
async createFormVersion(ctx: RequestContext, dto: CreateFormVersionDto) {
  const formDefinition = await this.prisma.formDefinition.findUnique({
    where: { id: dto.formDefinitionId }
  });
  
  // 校验父实体的 regionId
  if (formDefinition.regionId !== ctx.regionId) {
    throw new ForbiddenException('REGION_MISMATCH');
  }
  
  // 创建时继承父实体的 regionId
  return this.prisma.formVersion.create({
    data: {
      formDefinitionId: dto.formDefinitionId,
      regionId: formDefinition.regionId,  // 继承，不使用 ctx.regionId
      schema: dto.schema,
      // ...
    }
  });
}
```

#### 错误码

| 错误码 | HTTP 状态码 | 说明 |
|--------|-------------|------|
| `REGION_MISMATCH` | 403 | 请求的 regionId 与资源记录中的 regionId 不一致 |

### 区域隔离实现

```typescript
// 所有查询都必须包含 regionId
const snapshots = await prisma.releaseSnapshot.findMany({
  where: {
    regionId: ctx.regionId,      // 必填：区域隔离（CN / US / ME）
    formDefinitionId: dto.formDefinitionId,
    status: 'ACTIVE'
  }
});

// 创建实体时自动注入 regionId
const formDefinition = await prisma.formDefinition.create({
  data: {
    regionId: ctx.regionId,      // 从请求上下文获取
    key: dto.key,
    name: dto.name,
    // ...
  }
});
```

### regionId 从哪里来

> 用户**不需要手动选择区域**，由系统根据用户归属自动确定。

```
用户登录 → 获取 user.regionId (如 CN)
         ↓
前端调用 API 时始终带上区域标识:
  - HTTP Header: X-Region-Id: CN
         ↓
后端中间件校验 regionId，注入到请求上下文
         ↓
Service 层使用 ctx.regionId 进行数据隔离
```

**说明**：
- 用户的 `regionId` 由管理员在用户管理中配置
- 前端从登录态获取 `user.regionId`，调用 API 时通过 `X-Region-Id` Header 传递
- 跨区域操作（如模板同步）需要 `form:admin` 权限

### 区域上下文获取（后端实现）

```typescript
// 中间件从请求中提取 regionId
@Injectable()
export class RegionMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // 方式1：从 Header 获取（推荐）
    const regionId = req.headers['x-region-id'];  // CN / US / ME
    
    // 方式2：从用户信息获取（备选，用户绑定到特定区域）
    // const regionId = req.user?.regionId;
    
    if (!regionId) {
      throw new BadRequestException('REGION_REQUIRED');
    }
    
    if (!['CN', 'US', 'ME'].includes(regionId)) {
      throw new BadRequestException('INVALID_REGION');
    }
    
    req.regionContext = { regionId };
    next();
  }
}
```

### 跨区域数据同步（可选）

> 某些场景可能需要跨区域同步表单模板等数据。

| 同步类型 | 说明 |
|----------|------|
| 模板同步 | 系统模板可同步到所有区域 |
| 配置同步 | 全局配置可跨区域复制 |
| 实例隔离 | 表单实例和流程实例**不**跨区域同步 |

---

**最后更新**: 2025-12-11
