# 表单管理 API

> API 接口文档 - 表单管理应用的聚合 API，协调表单引擎与审批引擎

---

## 📋 概述

| 属性 | 值 |
|------|-----|
| Base URL | `/api/v1/form-management` |
| 认证方式 | JWT Bearer Token |
| 接口数量 | 34 个（聚合 API） |
| 版本 | v1.0 |
| 响应格式 | 遵循 [API_STANDARDS.md](/docs/standards/API_STANDARDS.md) |

### 设计原则

表单管理 API 是**聚合层 API**，封装了对表单引擎和审批引擎的调用：

```
前端 ──> FormManagement API ──┬──> Form Engine API
                              └──> Approval Engine API
```

### 🚨 前端调用边界（v2.x 现状）

> **现状**：审批中心**没有**独立的 `/approval-center/*` 聚合层；审批运行时（"我发起的"/"待办"/"管理员"等）由前端直调 `approval-engine` 路由。详见 [approval-center/07-api.md](../approval-center/07-api.md) 的"实际调用前缀"段。

| 应用 | 职责 | 前端实际调用前缀 |
|------|------|------------------|
| **表单管理** | 配置 & 发布（设计态） | `/api/v1/form-management/*` |
| **表单运行时** | 实例创建 / 草稿 / 撤回 / 详情 | `/api/v1/form-management/instances/*` |
| **审批中心** | 用户态（发起/待办/已办/我的） | `/api/v1/approval/my/*`、`/api/v1/approval/start`、`/api/v1/approval/instances/:id/...`（**直调审批引擎**）|
| **审批中心 - 管理员** | 管理员数据中心、统计、明细 | `/api/v1/approval/admin/*`（直调审批引擎）|

**说明**：
- 表单管理（设计态）确实有聚合层，前端只调 `/form-management/*`。
- 审批中心是**前端模块名**，不是后端 API 前缀；不存在 `/approval-center/*` 这套接口。
- `/api/v1/forms/*`、`/api/v1/form-instances/*`（Form Engine）当前**仅供后端内部调用与回调**，前端走 `/form-management/*`。

---

## 🔐 权限要求

| 权限 | 说明 | 适用接口 |
|------|------|----------|
| `form:read` | 查看表单 | 列表、详情、快照查询、版本对比 |
| `form:design` | 设计表单 | 创建、编辑、保存设计 |
| `form:review` | 审核表单 | 审核通过/驳回 |
| `form:publish` | 发布表单 | 发布、归档、回滚 |
| `form:use` | 使用表单 | 创建实例、获取字段权限 |
| `form:admin` | 管理员 | 所有操作、Webhook 管理、跨区域操作 |

---

## 🌐 多区域支持（v2.0 架构）

> ⚠️ **架构变更**：v2.0 已彻底重构区域模型，事实源是
> [`backend/prisma/schema/FORM_ORGANIZATION_ARCHITECTURE.md`](../../../backend/prisma/schema/FORM_ORGANIZATION_ARCHITECTURE.md)。
> 核心原则："**组织决定范围，快照管理版本，实例记录环境**"。

### 区域模型现状

```
Organization
  ├── primaryRegionId        → 主区域（单一）
  └── organizationRegions[]  → OrganizationRegion 多对多 → Region (运营区域)

FormDefinition
  └── organizationId         → Organization
                             → 通过 organization.organizationRegions 间接确定可用区域
                             ❌ 没有 regionId 列（v2.0 已删除）

FormInstance / ApprovalInstance
  └── regionId               → 提交时由 user.regionId 写入（环境标记，仅做数据归属）

ReleaseSnapshot
  └── 全局唯一 ACTIVE 快照（一个 FormDefinition 一个版本，不分区域）
```

### 字段所有权

| 实体 | `organizationId` | `regionId` | 说明 |
|------|:----------------:|:----------:|------|
| FormDefinition | ✅ | ❌ | 通过 organization 间接获得运营区域 |
| ApprovalDefinition | ✅ | ❌ | 同上 |
| ReleaseSnapshot | — | ❌ | 全局版本管理，无区域差异 |
| FormInstance | — | ✅ | 记录提交时用户所在区域 |
| ApprovalInstance | — | ✅ | 同上 |
| FormWebhook | ✅ | ❌ | 区域信息从 payload 传递 |

### 表单可用性的过滤逻辑

平台级表单（`organizationId = null`）→ 所有用户可用；
组织专属表单（`organizationId != null`）→ 用户所在区域必须在
`organization.organizationRegions` 集合内。

后端服务层会基于 `user.regionId` 在查询时校验，**不需要前端传 `X-Region-Id`**
（历史 v1.x 的 `X-Region-Id` 请求头与 `regionId` 查询参数当前不再作为契约必填项；
保留兼容容忍但不依赖）。

### 模板的特殊性

模板（`/templates`）全局共享，与 organization / region 完全无关。

### 跨组织/跨区域操作

需 `form:admin` 权限；具体策略由后端在 service 层判断，详见
`form-management.service.ts` 中针对 `organization.organizationRegions` 的过滤实现。

---

## 📚 接口列表

### 1. 表单定义管理（聚合）

| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| GET | `/definitions` | 获取表单定义列表（分页） | `form:read` |
| POST | `/definitions` | 创建表单定义 | `form:design` |
| GET | `/definitions/:id` | 获取表单详情（含流程） | `form:read` |
| PATCH | `/definitions/:id` | 更新基本信息 | `form:design` |
| DELETE | `/definitions/:id` | 删除表单定义 | `form:admin` |
| POST | `/definitions/:id/archive` | 归档表单定义 | `form:publish` |
| POST | `/definitions/:id/disable` | 禁用表单定义 | `form:publish` |
| POST | `/definitions/:id/enable` | 启用表单定义 | `form:publish` |

### 2. 设计器（聚合）

| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| GET | `/definitions/:id/design` | 获取设计数据 | `form:design` |
| PUT | `/definitions/:id/form-design` | 保存表单设计 | `form:design` |
| PUT | `/definitions/:id/process-design` | 保存流程设计 | `form:design` |
| PUT | `/definitions/:id/design` | 一次性保存全部设计 | `form:design` |

### 3. 版本审核与发布（聚合）

| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| POST | `/definitions/:id/submit-review` | 提交审核 | `form:design` |
| GET | `/snapshots/pending` | 获取待审核列表 | `form:review` |
| POST | `/snapshots/:snapshotId/review` | 审核（通过/驳回） | `form:review` |
| POST | `/snapshots/:snapshotId/publish` | 发布 | `form:publish` |
| POST | `/snapshots/:snapshotId/rollback` | 回滚 | `form:publish` |

### 4. 快照与版本管理

| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| GET | `/definitions/:id/active-snapshot` | 获取当前激活快照 | `form:read` |
| GET | `/definitions/:id/snapshots` | 获取快照历史 | `form:read` |
| GET | `/snapshots/:snapshotId` | 获取快照详情 | `form:read` |
| GET | `/definitions/:id/versions/diff` | 对比两个版本的差异 | `form:read` |
| GET | `/snapshots/:id1/diff/:id2` | 对比两个快照的差异 | `form:read` |

### 5. 实例管理

> **前端调用说明**：实例管理 API 主要由**审批中心**前端调用（`/approvals` 页面），
> 用于"我的申请"列表、撤回操作等。表单管理前端（`/forms`）不直接展示用户实例。

| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| POST | `/instances` | 创建实例（草稿） | `form:use` |
| GET | `/instances/my` | 获取我的实例列表 | `form:use` |
| GET | `/instances/:id` | 获取实例详情 | `form:use` |
| PATCH | `/instances/:id` | 更新实例 | `form:use` |
| POST | `/instances/:id/submit` | 提交实例（发起审批） | `form:use` |
| POST | `/instances/:id/withdraw` | 撤回实例（发起人主动撤回） | `form:use` |
| DELETE | `/instances/:id` | 删除实例（软删除） | `form:use` |
| GET | `/instances/:id/field-access` | 获取当前节点字段权限 | `form:use` |

### 6. Webhook 管理

| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| POST | `/webhooks` | 创建 Webhook 订阅 | `form:admin` |
| GET | `/webhooks` | 获取 Webhook 列表 | `form:admin` |
| GET | `/webhooks/:id` | 获取 Webhook 详情 | `form:admin` |
| PATCH | `/webhooks/:id` | 更新 Webhook 配置 | `form:admin` |
| DELETE | `/webhooks/:id` | 删除 Webhook | `form:admin` |
| POST | `/webhooks/:id/test` | 发送测试事件 | `form:admin` |
| GET | `/webhooks/:id/logs` | 查看投递日志 | `form:admin` |

### 7. 统计分析

| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| GET | `/statistics` | 获取表单统计数据 | `form:read` |

---

## 📖 接口详情

### 0. 获取表单定义列表（分页）

获取当前用户可用的表单定义列表，支持分页和筛选。

**可用性过滤逻辑**（v2.0）：
- 平台级表单（`organizationId = null`）→ 所有用户可见
- 组织专属表单（`organizationId != null`）→ 仅当用户所在区域 ∈ `organization.organizationRegions` 时返回
- 由后端 service 基于 `user.regionId` + `organization` 关联自动过滤

**请求**

```http
GET /api/v1/form-management/definitions?page=1&limit=20&status=PUBLISHED&category=finance
Authorization: Bearer <token>
```

**查询参数**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `page` | number | 否 | 页码，默认 1 |
| `limit` | number | 否 | 每页数量，默认 20，最大 100 |
| `status` | string | 否 | 状态筛选：`DRAFT` / `PUBLISHED` / `DISABLED` / `ARCHIVED`（对应 `FormStatus` enum） |
| `category` | string | 否 | 分类筛选 |
| `keyword` | string | 否 | 关键字搜索（名称/标识） |

**响应**

```json
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "fd-001",
        "organizationId": "org-uuid-001",
        "key": "expense_claim",
        "name": "报销申请",
        "description": "员工报销申请表单",
        "category": "finance",
        "status": "PUBLISHED",
        "activeSnapshotId": "snap-001",
        "currentVersion": 2,
        "createdAt": "2025-12-01T10:00:00Z",
        "updatedAt": "2025-12-07T14:00:00Z",
        "createdBy": "user-001"
      }
    ],
    "total": 45,
    "page": 1,
    "limit": 20,
    "totalPages": 3,
    "hasNext": true,
    "hasPrev": false
  },
  "timestamp": "2025-12-07T10:00:00Z",
  "path": "/api/v1/form-management/definitions"
}
```

---

### 1. 创建表单定义

创建表单定义，同时创建关联的流程定义。支持两种模式：

| 模式 | 说明 |
|------|------|
| **空白创建** | 不传 `templateId`，创建空白表单定义 + 默认流程 |
| **从模板创建** | 传 `templateId`，从模板克隆 schema/uiSchema/基础流程 |

**请求**

```http
POST /api/v1/form-management/definitions
Content-Type: application/json
Authorization: Bearer <token>
```

**请求体（空白创建）**

```json
{
  "key": "expense_claim",
  "name": "报销申请",
  "description": "员工报销申请表单",
  "category": "finance",
  "requiresApproval": true
}
```

**请求体（从模板创建）**

```json
{
  "templateId": "tpl-001",
  "key": "expense_claim",
  "name": "报销申请",
  "description": "基于通用报销模板",
  "category": "finance"
}
```

**请求参数**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `templateId` | string | 否 | 模板 ID，若存在则从模板克隆 |
| `key` | string | 是 | 表单标识（唯一，仅字母/数字/下划线） |
| `name` | string | 是 | 表单名称 |
| `description` | string | 否 | 表单描述 |
| `category` | string | 否 | 表单分类 |
| `requiresApproval` | boolean | 否 | 是否需要审批流程，默认 true |

**创建规则**：
- 若 `templateId` 存在 → 从模板克隆 schema/uiSchema/基础流程
- 若 `templateId` 为空 → 创建空白表单定义 + 默认流程（开始→结束）
- `organizationId` 由当前用户上下文（user.organizationId）自动注入；若需创建平台级表单需 `form:admin` 权限

**响应**

```json
{
  "success": true,
  "data": {
    "id": "fd-001",
    "organizationId": "org-uuid-001",
    "key": "expense_claim",
    "name": "报销申请",
    "description": "员工报销申请表单",
    "category": "finance",
    "status": "DRAFT",
    "templateId": null,
    "formVersion": {
      "id": "fv-001",
      "version": 1,
      "versionName": null,
      "status": "DRAFT"
    },
    "processDefinition": {
      "id": "pd-001",
      "key": "expense_claim_approval",
      "processVersion": {
        "id": "pv-001",
        "version": 1,
        "versionName": null,
        "status": "DRAFT"
      }
    },
    "createdAt": "2025-12-07T10:00:00Z",
    "createdBy": "user-001"
  },
  "message": "表单定义创建成功",
  "timestamp": "2025-12-07T10:00:00Z",
  "path": "/api/v1/form-management/definitions"
}
```

**说明**：
- `status: "DRAFT"` 表示 FormDefinition 从未发布过
- `versionName` 初始为 null，可在设计器中设置
- `templateId` 返回创建时使用的模板 ID（如未使用模板则为 null）

**内部调用**:
1. 若有 `templateId`：`FormTemplateService.getTemplate()` - 获取模板数据
2. `FormEngine.createDefinition()` - 创建表单定义
3. `FormEngine.createVersion()` - 创建表单版本 v1（从模板克隆或空白）
4. `ApprovalEngine.createProcessDefinition()` - 创建流程定义
5. `ApprovalEngine.createProcessVersion()` - 创建流程版本 v1（从模板克隆或默认）

---

### 1.1 归档表单定义

将已发布的表单定义归档，不再接受新申请。

**请求**

```http
POST /api/v1/form-management/definitions/:id/archive
Authorization: Bearer <token>
```

**响应**

```json
{
  "success": true,
  "data": {
    "id": "fd-001",
    "status": "ARCHIVED",
    "archivedAt": "2025-12-19T10:00:00Z",
    "message": "表单已归档"
  },
  "timestamp": "2025-12-19T10:00:00Z"
}
```

**说明**：
- 只有 `PUBLISHED` 或 `DISABLED` 状态的表单可以归档
- 如果有审批中的实例，不允许归档
- 归档后表单不可恢复

---

### 1.2 禁用表单定义

暂停表单使用，不再接受新申请，但运行中的实例不受影响。

**请求**

```http
POST /api/v1/form-management/definitions/:id/disable
Authorization: Bearer <token>
```

**响应**

```json
{
  "success": true,
  "data": {
    "id": "fd-001",
    "status": "DISABLED",
    "disabledAt": "2025-12-19T10:00:00Z",
    "message": "表单已禁用"
  },
  "timestamp": "2025-12-19T10:00:00Z"
}
```

**说明**：
- 只有 `PUBLISHED` 状态的表单可以禁用
- 禁用后可以通过 `/enable` 接口恢复

---

### 1.3 启用表单定义

恢复已禁用的表单，允许新申请。

**请求**

```http
POST /api/v1/form-management/definitions/:id/enable
Authorization: Bearer <token>
```

**响应**

```json
{
  "success": true,
  "data": {
    "id": "fd-001",
    "status": "PUBLISHED",
    "enabledAt": "2025-12-19T10:00:00Z",
    "message": "表单已启用"
  },
  "timestamp": "2025-12-19T10:00:00Z"
}
```

**说明**：
- 只有 `DISABLED` 状态的表单可以启用
- 启用后状态恢复为 `PUBLISHED`

---

### 2. 获取设计数据

获取表单和流程的完整设计数据，用于设计器加载。

**请求**

```http
GET /api/v1/form-management/definitions/:id/design
Authorization: Bearer <token>
```

**响应**

```json
{
  "success": true,
  "data": {
    "formDefinition": {
      "id": "fd-001",
      "key": "expense_claim",
      "name": "报销申请"
    },
    "formVersion": {
      "id": "fv-001",
      "version": 1,
      "status": "DRAFT",
      "schema": {
        "type": "object",
        "properties": {
          "amount": { "type": "number", "title": "金额" }
        }
      },
      "uiSchema": {
        "ui:order": ["amount"]
      }
    },
    "processVersion": {
      "id": "pv-001",
      "version": 1,
      "status": "DRAFT",
      "model": {
        "nodes": [
          { "id": "start", "type": "START", "name": "开始" },
          { "id": "approve1", "type": "USER_TASK", "name": "部门审批", "config": {} },
          { "id": "end", "type": "END", "name": "结束" }
        ],
        "edges": [
          { "source": "start", "target": "approve1" },
          { "source": "approve1", "target": "end" }
        ]
      }
    }
  },
  "timestamp": "2025-12-07T10:00:00Z",
  "path": "/api/v1/form-management/definitions/fd-001/design"
}
```

---

### 3. 保存全部设计

一次性保存表单设计和流程设计。

**💡 使用建议**：
- **日常使用推荐** `PUT /:id/design` 一次性保存，以减少前后端状态不一致问题
- `/form-design` 和 `/process-design` 主要用于特殊场景（如自动化脚本或逐步迁移）

**请求**

```http
PUT /api/v1/form-management/definitions/:id/design
Content-Type: application/json
Authorization: Bearer <token>
```

```json
{
  "formDesign": {
    "schema": {
      "type": "object",
      "properties": {
        "amount": { "type": "number", "title": "金额", "x-required": true },
        "reason": { "type": "string", "title": "事由" }
      }
    },
    "uiSchema": {
      "ui:order": ["amount", "reason"]
    }
  },
  "processDesign": {
    "nodes": [
      { "id": "start", "type": "START", "name": "开始" },
      {
        "id": "approve1",
        "type": "USER_TASK",
        "name": "部门审批",
        "config": {
          "approverType": "DEPARTMENT",
          "approvalMode": "OR",
          "editableFields": ["approvalComment"],
          "requiredFields": ["approvalComment"],
          "hiddenFields": []
        }
      },
      { "id": "end", "type": "END", "name": "结束" }
    ],
    "edges": [
      { "source": "start", "target": "approve1" },
      { "source": "approve1", "target": "end" }
    ]
  }
}
```

**响应**

```json
{
  "success": true,
  "data": {
    "formVersionId": "fv-001",
    "processVersionId": "pv-001",
    "savedAt": "2025-12-07T10:30:00Z"
  },
  "message": "设计保存成功",
  "timestamp": "2025-12-07T10:30:00Z",
  "path": "/api/v1/form-management/definitions/fd-001/design"
}
```

**内部调用**:
1. `FormEngine.updateVersion()` - 更新表单版本
2. `ApprovalEngine.updateProcessVersion()` - 更新流程版本

---

### 4. 提交审核

将当前草稿版本提交审核，创建发布快照。

**请求**

```http
POST /api/v1/form-management/definitions/:id/submit-review
Content-Type: application/json
Authorization: Bearer <token>
```

```json
{
  "comment": "第一版表单，请审核"
}
```

**响应**

```json
{
  "success": true,
  "data": {
    "snapshotId": "snap-001",
    "formVersionId": "fv-001",
    "processVersionId": "pv-001",
    "status": "PENDING",
    "submittedAt": "2025-12-07T11:00:00Z"
  },
  "message": "已提交审核",
  "timestamp": "2025-12-07T11:00:00Z",
  "path": "/api/v1/form-management/definitions/fd-001/submit-review"
}
```

**内部逻辑**:
1. 验证 FormVersion 和 ProcessVersion 都是 DRAFT 状态
2. 检查是否已有 PENDING 快照 → 若有，返回 `PENDING_ALREADY_EXISTS`
3. 检查是否已有其他 DRAFT 快照 → 若有，返回 `DRAFT_ALREADY_EXISTS`
4. 更新 FormVersion.status → PENDING
5. 更新 ProcessVersion.status → PENDING
6. 创建 ReleaseSnapshot (status: PENDING)

**可能的错误**:
| 错误码 | 说明 |
|--------|------|
| `PENDING_ALREADY_EXISTS` | 该表单已有待审核版本，请先处理 |
| `DRAFT_ALREADY_EXISTS` | 该表单已有草稿版本 |
| `FORM_NOT_FOUND` | 表单定义不存在 |

---

### 5. 审核（审核通过 = 发布）

审核快照（通过或驳回）。

> **简化流程**：审核通过后，版本自动变为 `PUBLISHED` 并设为默认版本，无需额外的发布步骤。

**请求**

```http
POST /api/v1/form-management/snapshots/:snapshotId/review
Content-Type: application/json
Authorization: Bearer <token>
```

```json
{
  "action": "APPROVE",
  "comment": "审核通过，符合规范"
}
```

或驳回：

```json
{
  "action": "REJECT",
  "comment": "字段校验规则需要完善"
}
```

**响应（通过）**

审核通过后，版本状态变为 `PUBLISHED`，并自动设为默认版本。

```json
{
  "success": true,
  "data": {
    "snapshotId": "snap-001",
    "status": "PUBLISHED",
    "isDefault": true,
    "reviewedAt": "2025-12-07T12:00:00Z",
    "reviewedBy": "admin-001",
    "publishedAt": "2025-12-07T12:00:00Z"
  },
  "message": "审核通过，已发布",
  "timestamp": "2025-12-07T12:00:00Z",
  "path": "/api/v1/form-management/snapshots/snap-001/review"
}
```

**响应（驳回）**

```json
{
  "success": true,
  "data": {
    "snapshotId": "snap-001",
    "status": "REJECTED",
    "reviewedAt": "2025-12-07T12:00:00Z",
    "reviewedBy": "admin-001",
    "reviewComment": "字段校验规则需要完善"
  },
  "message": "审核已驳回",
  "timestamp": "2025-12-07T12:00:00Z",
  "path": "/api/v1/form-management/snapshots/snap-001/review"
}
```

---

### 6. 发布（可选 - 用于回滚/激活历史版本）

> **注意**：在简化流程下，审核通过即发布。此接口主要用于：
> - 回滚到历史已发布版本
> - 手动激活指定版本为默认版本

将已发布的版本设为当前激活版本。

**请求**

```http
POST /api/v1/form-management/snapshots/:snapshotId/publish
Content-Type: application/json
Authorization: Bearer <token>
```

```json
{
  "releaseNote": "首次发布，包含基础报销功能"
}
```

**响应**

```json
{
  "success": true,
  "data": {
    "snapshotId": "snap-001",
    "status": "ACTIVE",
    "releaseNote": "首次发布，包含基础报销功能",
    "publishedAt": "2025-12-07T14:00:00Z",
    "publishedBy": "admin-001",
    "previousSnapshotId": null,
    "formDefinitionStatus": "PUBLISHED"
  },
  "message": "发布成功",
  "timestamp": "2025-12-07T14:00:00Z",
  "path": "/api/v1/form-management/snapshots/snap-001/publish"
}
```

**说明**：
- `releaseNote`：发布说明/备注，便于追溯版本变更
- `formDefinitionStatus`：首次发布后，FormDefinition.status 从 DRAFT 变为 PUBLISHED

**内部逻辑**:
1. 验证快照状态是 APPROVED
2. 将当前 ACTIVE 快照（如有）归档为 ARCHIVED（注：ACTIVE 是 ReleaseSnapshotStatus 枚举值，区别于 FormDefinition.status）
3. 更新当前快照为 ACTIVE，保存 releaseNote
4. 更新 FormVersion.status → PUBLISHED
5. 更新 ProcessVersion.status → PUBLISHED
6. 如果是首次发布，更新 FormDefinition.status → PUBLISHED

**可能的错误**:
| 错误码 | 说明 |
|--------|------|
| `INVALID_STATUS_TRANSITION` | 快照不是 APPROVED 状态，无法发布 |
| `FORM_DEFINITION_ARCHIVED` | 表单已归档，不允许再发布新版本 |
| `SNAPSHOT_NOT_FOUND` | 快照不存在 |

---

### 7. 快照与版本管理

#### 7.1 获取当前激活快照

获取表单的当前对外发布版本。

**请求**

```http
GET /api/v1/form-management/definitions/:id/active-snapshot
Authorization: Bearer <token>
```

**响应**

```json
{
  "success": true,
  "data": {
    "snapshotId": "snap-001",
    "formDefinitionId": "fd-001",
    "formVersion": {
      "id": "fv-001",
      "version": 1,
      "versionName": "v1.0 初版",
      "schema": { "..." : "..." },
      "uiSchema": { "..." : "..." }
    },
    "processVersion": {
      "id": "pv-001",
      "version": 1,
      "versionName": "v1.0 基础审批流程",
      "model": { "..." : "..." }
    },
    "publishedAt": "2025-12-07T14:00:00Z",
    "releaseNote": "首次发布，包含基础报销功能"
  },
  "timestamp": "2025-12-07T15:00:00Z",
  "path": "/api/v1/form-management/definitions/fd-001/active-snapshot"
}
```

---

#### 7.2 版本差异对比（Diff）

对比两个版本或快照之间的差异，用于版本回顾和变更审计。

**请求（对比表单版本）**

```http
GET /api/v1/form-management/definitions/:id/versions/diff?from=1&to=2
Authorization: Bearer <token>
```

**请求（对比快照）**

```http
GET /api/v1/form-management/snapshots/:snapshotId1/diff/:snapshotId2
Authorization: Bearer <token>
```

**查询参数（版本对比）**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `from` | number | 是 | 源版本号（较旧版本） |
| `to` | number | 是 | 目标版本号（较新版本） |

**响应**

```json
{
  "success": true,
  "data": {
    "formDefinitionId": "fd-001",
    "comparison": {
      "from": {
        "version": 1,
        "versionName": "v1.0 初版",
        "snapshotId": "snap-001"
      },
      "to": {
        "version": 2,
        "versionName": "v2.0 增加附件字段",
        "snapshotId": "snap-002"
      }
    },
    "formDiff": {
      "schema": {
        "added": [
          { "path": "$.properties.attachment", "value": { "type": "array", "title": "附件" } }
        ],
        "removed": [],
        "modified": [
          {
            "path": "$.properties.amount.x-required",
            "oldValue": false,
            "newValue": true
          }
        ]
      },
      "uiSchema": {
        "added": [],
        "removed": [],
        "modified": [
          {
            "path": "$.ui:order",
            "oldValue": ["amount", "reason"],
            "newValue": ["amount", "reason", "attachment"]
          }
        ]
      }
    },
    "processDiff": {
      "nodes": {
        "added": [
          { "id": "approve2", "type": "USER_TASK", "name": "财务审批" }
        ],
        "removed": [],
        "modified": []
      },
      "edges": {
        "added": [
          { "source": "approve1", "target": "approve2" },
          { "source": "approve2", "target": "end" }
        ],
        "removed": [
          { "source": "approve1", "target": "end" }
        ],
        "modified": []
      }
    },
    "summary": {
      "formFieldsAdded": 1,
      "formFieldsRemoved": 0,
      "formFieldsModified": 1,
      "processNodesAdded": 1,
      "processNodesRemoved": 0,
      "processEdgesChanged": 3
    }
  },
  "timestamp": "2025-12-07T15:00:00Z",
  "path": "/api/v1/form-management/definitions/fd-001/versions/diff"
}
```

**Diff 结构说明**：
| 字段 | 说明 |
|------|------|
| `added` | 新增的字段/节点/边 |
| `removed` | 删除的字段/节点/边 |
| `modified` | 修改的字段（包含 oldValue 和 newValue） |
| `summary` | 变更统计摘要 |

**对比粒度规范**：

| 对象 | 粒度 | 说明 |
|------|------|------|
| **表单字段** | 字段级（Field-level） | 以 `$.properties.<fieldKey>` 为最小单位，字段内部属性变化归为 `modified` |
| **流程节点** | 节点级（Node-level） | 以 `nodeId` 为最小单位，节点配置变化归为 `modified` |
| **流程边** | 边级（Edge-level） | 以 `source + target` 为最小单位 |

**JSONPath 规范**：

> 所有路径均使用 [JSONPath 标准语法](https://goessner.net/articles/JsonPath/)

| 路径前缀 | 适用场景 | 示例 |
|----------|----------|------|
| `$.properties.<key>` | 表单字段定义 | `$.properties.amount` |
| `$.properties.<key>.<attr>` | 字段属性 | `$.properties.amount.x-required` |
| `$.ui:order` | UI Schema 排序 | `$.ui:order` |
| `$.ui:<key>` | UI Schema 字段配置 | `$.ui:amount.ui:widget` |

**modified 详细结构**：

```json
{
  "path": "$.properties.amount.x-required",
  "fieldKey": "amount",
  "attribute": "x-required",
  "oldValue": false,
  "newValue": true,
  "changeType": "VALUE_CHANGED"
}
```

| changeType | 说明 |
|------------|------|
| `VALUE_CHANGED` | 值变更 |
| `TYPE_CHANGED` | 类型变更（如 string → number） |
| `VALIDATION_CHANGED` | 校验规则变更 |
| `CONFIG_CHANGED` | 节点配置变更（流程） |

**可能的错误**：
| 错误码 | 说明 |
|--------|------|
| `VERSION_NOT_FOUND` | 指定的版本号不存在 |
| `SNAPSHOT_NOT_FOUND` | 指定的快照不存在 |
| `INVALID_VERSION_RANGE` | 版本范围无效（from >= to） |

---

### 8. 实例管理

> **设计说明**：实例管理采用"两阶段提交"模式：
> 1. **创建（DRAFT）**：用户调用 `POST /instances` 创建草稿，可多次编辑保存
> 2. **提交（PENDING_APPROVAL）**：用户调用 `POST /instances/:id/submit` 正式提交，启动审批流程
>
> 这样设计允许用户分多次填写表单，避免一次性提交造成的数据丢失。

#### 8.1 创建实例（草稿）

创建表单实例（草稿状态），此时**不会启动审批流程**。

**⚠️ 重要规则**：
- **请求体不允许传 `snapshotId`**，由服务端在当前区域自动查询 ACTIVE 快照
- 创建后实例状态为 `DRAFT`，需调用 `/submit` 才会启动审批
- 这是为了防止绕过版本系统，确保所有实例都基于正式发布的快照创建

**请求**

```http
POST /api/v1/form-management/instances
Content-Type: application/json
Authorization: Bearer <token>
```

```json
{
  "formDefinitionId": "fd-001",
  "formData": {
    "amount": 5000,
    "reason": "出差报销"
  },
  "departmentId": "dept-001"
}
```

**请求参数**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `formDefinitionId` | string | 是 | 表单定义 ID |
| `formData` | object | 是 | 表单数据 |
| `departmentId` | string | 否 | 发起人所属部门 |
| ~~`snapshotId`~~ | - | ❌ 禁止 | 不允许客户端指定，服务端强制使用 ACTIVE 快照 |

**响应**

```json
{
  "success": true,
  "data": {
    "formInstance": {
      "id": "fi-001",
      "snapshotId": "snap-001",
      "formDefinitionId": "fd-001",
      "formVersionId": "fv-001",
      "regionId": "CN",
      "status": "DRAFT",
      "formData": {
        "amount": 5000,
        "reason": "出差报销"
      }
    },
    "processInstance": {
      "id": "",
      "processDefinitionId": "",
      "processVersionId": "",
      "status": "NOT_STARTED",
      "currentNode": ""
    },
    "createdAt": "2025-12-07T16:00:00Z"
  },
  "message": "草稿创建成功",
  "timestamp": "2025-12-07T16:00:00Z",
  "path": "/api/v1/form-management/instances"
}
```

**说明**：
- `status: "DRAFT"` 表示草稿状态
- `processInstance.status: "NOT_STARTED"` 表示审批流程尚未启动
- 用户可以继续编辑草稿，直到调用 `POST /instances/:id/submit` 提交

**内部逻辑（事务）**:
1. **客户端不可指定 snapshotId**，服务端强制使用当前区域的 ACTIVE 快照
2. 若无 ACTIVE 快照 → 返回 `NO_ACTIVE_SNAPSHOT`
3. 验证 FormDefinition.status 不是 DISABLED 或 ARCHIVED
4. **执行实例初始化钩子**（见下方说明）
5. 创建 FormInstance（绑定 snapshotId，继承 regionId，状态为 DRAFT）
6. **不创建 ProcessInstance**（审批流程在 submit 时启动）
7. 提交事务

**实例初始化钩子（Pre-processing）**：

> 在创建实例前，服务端会自动执行以下初始化操作：

| 钩子类型 | 说明 | 示例 |
|----------|------|------|
| **默认字段填充** | 自动填充系统字段 | `applicant`（发起人）、`applicantDept`（所属部门）、`applyTime`（申请时间） |
| **计算字段** | 根据表单配置执行公式计算 | `totalAmount = quantity * unitPrice` |
| **联动字段** | 根据其他字段值自动填充 | 选择部门后自动填充部门负责人 |
| **默认值注入** | 从表单 schema 中读取 default 值 | 日期字段默认为今天 |
| **上下文注入** | 注入运行时上下文 | `regionId`、`tenantId`（如有） |

**钩子执行顺序**：
```
客户端 formData
     │
     ▼
1. 默认值注入（schema.default）
     │
     ▼
2. 系统字段填充（applicant, applyTime, ...）
     │
     ▼
3. 联动字段计算（基于依赖关系）
     │
     ▼
4. 计算字段执行（公式计算）
     │
     ▼
5. 数据验证（JSON Schema 校验）
     │
     ▼
最终 formData → 写入 FormInstance
```

**前端注意事项**：
- 发起人、部门、时间等系统字段**无需前端传递**，由服务端自动填充
- 计算字段的结果由服务端确定，前端显示的预览值可能与最终值略有差异
- 如需在前端预览计算结果，可调用 `POST /instances/preview`（可选实现）

**可能的错误**:
| 错误码 | 说明 |
|--------|------|
| `NO_ACTIVE_SNAPSHOT` | 该表单尚无已发布的 ACTIVE 快照 |
| `FORM_DEFINITION_DISABLED` | 表单已禁用，不允许新发起 |
| `FORM_DEFINITION_ARCHIVED` | 表单已归档，不允许任何操作 |
| `FORM_DATA_INVALID` | 表单数据验证失败 |

---

#### 8.2 获取我的实例列表

获取当前用户创建的实例列表，支持分页和筛选。

**请求**

```http
GET /api/v1/form-management/instances/my?page=1&limit=20&status=DRAFT
Authorization: Bearer <token>
```

**查询参数**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `page` | number | 否 | 页码，默认 1 |
| `limit` | number | 否 | 每页数量，默认 20 |
| `formDefinitionId` | string | 否 | 按表单定义筛选 |
| `status` | string | 否 | 状态筛选：DRAFT / PENDING_APPROVAL / APPROVED / REJECTED / WITHDRAWN / CANCELLED |

**响应**

```json
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "fi-001",
        "formDefinitionId": "fd-001",
        "formVersionId": "fv-001",
        "businessKey": "EXP-2025120001",
        "status": "DRAFT",
        "form": {
          "id": "fd-001",
          "key": "expense_claim",
          "name": "报销申请",
          "category": "finance"
        },
        "version": 1,
        "data": { "amount": 5000, "reason": "出差报销" },
        "createdAt": "2025-12-07T16:00:00Z",
        "updatedAt": "2025-12-07T16:00:00Z",
        "submittedAt": null
      }
    ],
    "total": 15,
    "page": 1,
    "limit": 20,
    "totalPages": 1
  },
  "timestamp": "2025-12-07T16:30:00Z",
  "path": "/api/v1/form-management/instances/my"
}
```

---

#### 8.3 获取实例详情

获取单个实例的详细信息。

**请求**

```http
GET /api/v1/form-management/instances/:id
Authorization: Bearer <token>
```

**响应**

```json
{
  "success": true,
  "data": {
    "id": "fi-001",
    "formDefinitionId": "fd-001",
    "formVersionId": "fv-001",
    "businessKey": "EXP-2025120001",
    "regionId": "CN",
    "status": "PENDING_APPROVAL",
    "approvalStatus": "RUNNING",
    "data": {
      "amount": 5000,
      "reason": "出差报销"
    },
    "definition": {
      "id": "fd-001",
      "key": "expense_claim",
      "name": "报销申请"
    },
    "version": {
      "id": "fv-001",
      "version": 1
    },
    "createdBy": {
      "id": "user-001",
      "username": "zhangsan",
      "displayName": "张三"
    },
    "submittedBy": {
      "id": "user-001",
      "username": "zhangsan",
      "displayName": "张三"
    },
    "createdAt": "2025-12-07T16:00:00Z",
    "updatedAt": "2025-12-07T16:30:00Z",
    "submittedAt": "2025-12-07T16:30:00Z",
    "approvalStartTime": "2025-12-07T16:30:00Z",
    "approvalEndTime": null
  },
  "timestamp": "2025-12-07T17:00:00Z",
  "path": "/api/v1/form-management/instances/fi-001"
}
```

**可能的错误**:
| 错误码 | 说明 |
|--------|------|
| `INSTANCE_NOT_FOUND` | 实例不存在（在当前区域） |

---

#### 8.4 更新实例

更新草稿或被驳回的实例数据。

**请求**

```http
PATCH /api/v1/form-management/instances/:id
Content-Type: application/json
Authorization: Bearer <token>
```

```json
{
  "formData": {
    "amount": 6000,
    "reason": "出差报销-已修改"
  }
}
```

**限制条件**：
- 只能更新 `DRAFT` 或 `REJECTED` 状态的实例
- 只有创建者可以更新

**响应**

```json
{
  "success": true,
  "data": {
    "id": "fi-001",
    "status": "DRAFT",
    "data": {
      "amount": 6000,
      "reason": "出差报销-已修改"
    },
    "updatedAt": "2025-12-07T17:00:00Z"
  },
  "message": "更新成功",
  "timestamp": "2025-12-07T17:00:00Z",
  "path": "/api/v1/form-management/instances/fi-001"
}
```

**可能的错误**:
| 错误码 | 说明 |
|--------|------|
| `INSTANCE_NOT_FOUND` | 实例不存在 |
| `FORBIDDEN` | 无权修改此实例（非创建者） |
| `INVALID_STATUS` | 当前状态不允许修改 |
| `FORM_DATA_INVALID` | 表单数据验证失败 |

---

#### 8.5 提交实例（发起审批）

将草稿实例提交审批，启动审批流程。

**请求**

```http
POST /api/v1/form-management/instances/:id/submit
Content-Type: application/json
Authorization: Bearer <token>
```

```json
{
  "comment": "请审批",
  "attachments": ["file-001", "file-002"]
}
```

**请求参数**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `comment` | string | 否 | 提交备注 |
| `attachments` | string[] | 否 | 附件 ID 列表 |

**响应**

```json
{
  "success": true,
  "data": {
    "id": "fi-001",
    "status": "PENDING_APPROVAL",
    "submittedAt": "2025-12-07T17:00:00Z",
    "approvalProcessId": "ai-001",
    "message": "提交成功，审批流程已启动"
  },
  "timestamp": "2025-12-07T17:00:00Z",
  "path": "/api/v1/form-management/instances/fi-001/submit"
}
```

**内部逻辑**:
1. 验证实例状态为 `DRAFT` 或 `REJECTED`
2. 验证表单数据完整性
3. 计算 `needsApproval = Boolean(formDefinition.requiresApproval && formDefinition.approvalProcessKey)`
4. 若 `needsApproval` 为 true：
   - 调用审批引擎启动流程
   - 关联 `approvalInstanceId`，实例状态置为 `PENDING_APPROVAL`，`approvalStatus = 'RUNNING'`
5. 若 `needsApproval` 为 false（`requiresApproval=false` 或 `approvalProcessKey` 为空）：
   - **跳过审批启动**，实例状态直接置为 `SUBMITTED`（无审批流程的表单）
   - 不会抛错；这是表单"自动完成"的设计

> 💡 `approvalProcessKey` 为空但 `requiresApproval=true` 的情况会被同样处理为"跳过审批"，
> 不再视为配置错误。如需强制要求配置，请在表单设计器审核阶段拦截。

**可能的错误**:
| 错误码 | 说明 |
|--------|------|
| `INSTANCE_NOT_FOUND` | 实例不存在 |
| `FORBIDDEN` | 无权提交此实例 |
| `ALREADY_SUBMITTED` | 实例已提交或已审批完成 |
| `FORM_DATA_INVALID` | 表单数据验证失败 |

---

#### 8.6 撤回实例

发起人主动撤回审批中的实例。

**⚠️ WITHDRAWN vs CANCELLED**：
| 状态 | 行为主体 | 说明 |
|------|----------|------|
| `WITHDRAWN` | 发起人 | 用户主动撤回，可基于此重新提交 |
| `CANCELLED` | 管理员/系统 | 被动终止（违规、超时等） |

**请求**

```http
POST /api/v1/form-management/instances/:id/withdraw
Content-Type: application/json
Authorization: Bearer <token>
```

```json
{
  "reason": "信息填写有误，需要修改"
}
```

**请求参数**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `reason` | string | 否 | 撤回原因 |

**限制条件**：
- 只能撤回 `PENDING_APPROVAL` 状态的实例
- 只有发起人可以撤回

**响应**

```json
{
  "success": true,
  "data": {
    "id": "fi-001",
    "status": "WITHDRAWN",
    "withdrawnAt": "2025-12-07T17:00:00Z",
    "withdrawnBy": "user-001",
    "reason": "信息填写有误，需要修改",
    "message": "实例已撤回"
  },
  "timestamp": "2025-12-07T17:00:00Z",
  "path": "/api/v1/form-management/instances/fi-001/withdraw"
}
```

**可能的错误**:
| 错误码 | 说明 |
|--------|------|
| `INSTANCE_NOT_FOUND` | 实例不存在 |
| `FORBIDDEN` | 无权撤回此实例（非发起人） |
| `INVALID_STATUS` | 当前状态不允许撤回（非 PENDING_APPROVAL） |

---

#### 8.7 删除实例（软删除）

删除草稿、被驳回或已撤回的实例。

**请求**

```http
DELETE /api/v1/form-management/instances/:id
Authorization: Bearer <token>
```

**限制条件**：
- 只能删除 `DRAFT`、`REJECTED` 或 `WITHDRAWN` 状态的实例
- 只有创建者可以删除

**响应**

```json
{
  "success": true,
  "data": {
    "id": "fi-001",
    "message": "删除成功"
  },
  "timestamp": "2025-12-07T17:00:00Z",
  "path": "/api/v1/form-management/instances/fi-001"
}
```

**可能的错误**:
| 错误码 | 说明 |
|--------|------|
| `INSTANCE_NOT_FOUND` | 实例不存在 |
| `FORBIDDEN` | 无权删除此实例 |
| `INVALID_STATUS` | 当前状态不允许删除 |

---

#### 8.7 获取当前节点字段权限

获取当前审批节点的字段权限配置。

**用途**：对当前实例 + 当前任务节点 → 计算一个 `fieldAccessMap` 给前端渲染使用。

**💡 审批中心集成说明**：
> 审批中心在渲染审批表单时，应**优先调用此接口**获取字段访问控制，再结合 Approval Engine 返回的任务信息一起渲染。

**请求**

```http
GET /api/v1/form-management/instances/:id/field-access
Authorization: Bearer <token>
```

**响应**

```json
{
  "success": true,
  "data": {
    "instanceId": "fi-001",
    "currentNodeId": "approve1",
    "currentNodeName": "部门审批",
    "fieldAccess": {
      "amount": {
        "editable": false,
        "required": false,
        "hidden": false
      },
      "reason": {
        "editable": false,
        "required": false,
        "hidden": false
      },
      "approvalComment": {
        "editable": true,
        "required": true,
        "hidden": false
      }
    }
  },
  "timestamp": "2025-12-07T16:30:00Z",
  "path": "/api/v1/form-management/instances/fi-001/field-access"
}
```

**字段访问规则**：
| 属性 | 说明 |
|------|------|
| `editable: false` | 只读字段，不可修改 |
| `editable: true` | 可编辑字段 |
| `required: true` | 必填字段（提交时校验） |
| `hidden: true` | 隐藏字段，不渲染 |

---

## 🔴 错误码

> 遵循项目统一 API 规范 ([API_STANDARDS.md](/docs/standards/API_STANDARDS.md))

### 标准错误码

| 错误码 | HTTP 状态码 | 说明 |
|--------|-------------|------|
| `VALIDATION_ERROR` | 400 | 请求参数验证失败 |
| `BAD_REQUEST` | 400 | 请求格式错误 |
| `UNAUTHORIZED` | 401 | 未授权（未登录或 Token 无效） |
| `FORBIDDEN` | 403 | 无权限执行此操作 |
| `NOT_FOUND` | 404 | 资源不存在 |
| `CONFLICT` | 409 | 资源冲突 |
| `UNPROCESSABLE_ENTITY` | 422 | 无法处理的实体 |
| `INTERNAL_SERVER_ERROR` | 500 | 服务器内部错误 |

### 业务错误码

| 错误码 | HTTP 状态码 | 说明 | 常见触发接口 |
|--------|-------------|------|-------------|
| `CROSS_ORGANIZATION_FORBIDDEN` | 403 | 表单归属组织在用户所在区域外（`user.regionId` ∉ `organization.organizationRegions`） | GET/PATCH/DELETE 资源 |
| `FORM_NOT_FOUND` | 404 | 表单定义不存在 | GET/PATCH/DELETE 表单 |
| `VERSION_NOT_FOUND` | 404 | 指定的版本号不存在 | 版本对比 |
| `SNAPSHOT_NOT_FOUND` | 404 | 快照不存在 | 审核、发布、查询快照、版本对比 |
| `INSTANCE_NOT_FOUND` | 404 | 实例不存在 | 查询实例、字段权限 |
| `NO_ACTIVE_SNAPSHOT` | 404 | 表单没有激活的发布版本 | 创建实例 |
| `INVALID_VERSION_RANGE` | 400 | 版本范围无效（from >= to） | 版本对比 |
| `DRAFT_ALREADY_EXISTS` | 409 | 该表单已有草稿版本，请先处理现有草稿 | 提交审核 |
| `PENDING_ALREADY_EXISTS` | 409 | 该表单已有待审核版本 | 提交审核 |
| `INVALID_STATUS_TRANSITION` | 400 | 状态转换不合法（如 PENDING→ACTIVE） | 发布、审核 |
| `ALREADY_PUBLISHED` | 409 | 快照已发布 | 发布 |
| `REVIEW_REQUIRED` | 400 | 需要先通过审核才能发布 | 发布 |
| `FORM_DATA_INVALID` | 422 | 表单数据验证失败 | 创建实例 |
| `FORM_DEFINITION_DISABLED` | 403 | 表单已禁用，不允许新发起 | 创建实例 |
| `FORM_DEFINITION_ARCHIVED` | 403 | 表单已归档，不允许任何操作 | 发布、创建实例 |
| `TRANSACTION_FAILED` | 500 | 事务执行失败 | 创建实例 |

**跨组织/跨区域规则**（v2.0）：
> 普通用户：组织专属表单仅在 `user.regionId ∈ organization.organizationRegions` 时可访问。
> 平台级表单（`organizationId = null`）所有用户可见。
> `form:admin` 权限可跨组织/跨区域操作。
> 历史的 `REGION_REQUIRED` / `INVALID_REGION` / `REGION_MISMATCH` / `CROSS_REGION_FORBIDDEN`
> 在 v2.0 中已不再作为契约错误码使用（保留兼容容忍但不主动抛出）。

### 错误响应格式

> 完整格式遵循 [API_STANDARDS.md](/docs/standards/API_STANDARDS.md) 中的 `ApiError` 接口

```json
{
  "success": false,
  "error": {
    "code": "NO_ACTIVE_SNAPSHOT",
    "message": "该表单没有已发布的版本",
    "details": {
      "formDefinitionId": "fd-001",
      "regionId": "CN"
    }
  },
  "timestamp": "2025-12-07T10:00:00Z",
  "path": "/api/v1/form-management/definitions/fd-001/active-snapshot",
  "method": "GET",
  "statusCode": 404
}
```

### 验证错误响应示例

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数验证失败",
    "errors": [
      {
        "field": "name",
        "message": "表单名称不能为空",
        "constraint": "isNotEmpty"
      },
      {
        "field": "key",
        "message": "表单标识只能包含字母、数字和下划线",
        "value": "invalid-key!",
        "constraint": "matches"
      }
    ]
  },
  "timestamp": "2025-12-07T10:00:00Z",
  "path": "/api/v1/form-management/definitions",
  "method": "POST",
  "statusCode": 400
}
```

---

## 📝 与引擎 API 的关系

### ⚠️ 重要说明

> 以下"直接使用引擎 API"的场景，指的是**后端服务之间的调用**，不是前端直接调用。
> 
> 前端仍应通过 `/form-management/*` 或 `/approval-center/*` 调用，由后端聚合层决定是否直接透传给引擎。

### 后端可直接调用引擎 API 的场景

以下操作在后端实现时可直接调用对应引擎的 Service：

| 场景 | 后端调用 |
|------|----------|
| 获取表单列表 | `FormDefinitionService.findAll()` |
| 获取模板列表 | `FormTemplateService.findAll()` |
| 管理翻译 | `FormVersionService.updateTranslations()` |
| 审批任务操作 | `TaskService.approve()` |
| 流程跟踪 | `ProcessInstanceService.getTimeline()` |

### 必须通过聚合层的场景

| 场景 | 原因 |
|------|------|
| 创建表单定义 | 需同时创建流程定义 |
| 保存设计 | 需同时更新表单版本和流程版本 |
| 提交审核 | 需创建发布快照 |
| 审核/发布 | 需操作快照和更新两边版本状态 |
| 创建实例 | 需在事务中创建表单实例和流程实例 |
| 获取字段权限 | 需结合当前任务和流程配置 |

### 错误码映射策略

> 聚合层负责统一收敛底层引擎的错误，对外提供一致的错误码体验。

**映射原则**：

| 原则 | 说明 |
|------|------|
| **语义收敛** | 底层引擎的细粒度错误码映射为聚合层的业务错误码 |
| **详情透传** | 原始错误信息放入 `error.details` 供调试 |
| **状态码保持** | HTTP 状态码尽量与底层一致（4xx/5xx） |

**Form Engine 错误码映射**：

| Form Engine 错误码 | 聚合层错误码 | 说明 |
|-------------------|--------------|------|
| `SCHEMA_VALIDATION_FAILED` | `FORM_DATA_INVALID` | 表单数据不符合 JSON Schema |
| `FIELD_REQUIRED` | `FORM_DATA_INVALID` | 必填字段缺失 |
| `FIELD_TYPE_MISMATCH` | `FORM_DATA_INVALID` | 字段类型不匹配 |
| `FIELD_PATTERN_MISMATCH` | `FORM_DATA_INVALID` | 字段格式不符合正则 |
| `FORM_DEFINITION_NOT_FOUND` | `FORM_NOT_FOUND` | 表单定义不存在 |
| `FORM_VERSION_NOT_FOUND` | `VERSION_NOT_FOUND` | 表单版本不存在 |

**Approval Engine 错误码映射**：

| Approval Engine 错误码 | 聚合层错误码 | 说明 |
|-----------------------|--------------|------|
| `PROCESS_DEFINITION_NOT_FOUND` | `FORM_NOT_FOUND` | 流程定义不存在（关联表单） |
| `PROCESS_VERSION_NOT_FOUND` | `VERSION_NOT_FOUND` | 流程版本不存在 |
| `INVALID_PROCESS_MODEL` | `VALIDATION_ERROR` | 流程模型校验失败 |
| `TASK_NOT_FOUND` | `INSTANCE_NOT_FOUND` | 任务不存在 |
| `TASK_ALREADY_COMPLETED` | `INVALID_STATUS_TRANSITION` | 任务已完成 |

**错误响应示例（带 details）**：

```json
{
  "success": false,
  "error": {
    "code": "FORM_DATA_INVALID",
    "message": "表单数据验证失败",
    "details": {
      "engine": "form-engine",
      "originalCode": "SCHEMA_VALIDATION_FAILED",
      "validationErrors": [
        {
          "field": "amount",
          "rule": "minimum",
          "message": "金额不能小于 0",
          "value": -100,
          "constraint": { "minimum": 0 }
        },
        {
          "field": "email",
          "rule": "format",
          "message": "邮箱格式不正确",
          "value": "invalid-email",
          "constraint": { "format": "email" }
        }
      ]
    }
  },
  "timestamp": "2025-12-07T10:00:00Z",
  "path": "/api/v1/form-management/instances",
  "method": "POST",
  "statusCode": 422
}
```

**聚合层错误处理代码示例**：

```typescript
// form-management.service.ts
async createInstance(dto: CreateInstanceDto): Promise<FormInstance> {
  try {
    // 调用 Form Engine 验证数据
    await this.formEngine.validateFormData(dto.formData, schema);
    
    // 调用 Approval Engine 创建流程
    const processInstance = await this.approvalEngine.createProcessInstance(...);
    
    // ...
  } catch (error) {
    // 映射 Form Engine 错误
    if (error instanceof FormEngineException) {
      throw new BusinessException(
        '表单数据验证失败',
        'FORM_DATA_INVALID',
        HttpStatus.UNPROCESSABLE_ENTITY,
        {
          engine: 'form-engine',
          originalCode: error.code,
          validationErrors: error.details,
        }
      );
    }
    
    // 映射 Approval Engine 错误
    if (error instanceof ApprovalEngineException) {
      throw this.mapApprovalEngineError(error);
    }
    
    throw error;
  }
}
```

**前端错误处理建议**：

```typescript
// 前端统一错误处理
if (error.code === 'FORM_DATA_INVALID') {
  const fieldErrors = error.details?.validationErrors || [];
  
  // 按字段显示错误
  fieldErrors.forEach(({ field, message }) => {
    form.setFieldError(field, message);
  });
  
  // 或显示汇总提示
  toast.error(`表单验证失败：${fieldErrors.length} 个字段有问题`);
}
```

---

## 🏗️ 前后端调用架构

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                                  前端                                       │
│  ┌─────────────────────────┐       ┌─────────────────────────┐              │
│  │  表单管理 (/forms)       │       │ 审批中心 (/approval-center)│             │
│  └───────────┬─────────────┘       └───────────┬─────────────┘              │
│              │                                  │                           │
│              │ ✅ 只调用                         │ ✅ 只调用                  │
│              ▼                                  ▼                           │
└─────────────────────────────────────────────────────────────────────────────┘
               │                                  │
               ▼                                  ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                             后端应用层 (BFF)                                 │
│  ┌─────────────────────────┐       ┌─────────────────────────┐              │
│  │ FormManagementController│       │ApprovalCenterController │              │
│  │ /api/v1/form-management │       │/api/v1/approval-center  │              │
│  └───────────┬─────────────┘       └───────────┬─────────────┘              │
│              │                                  │                           │
│              │ 内部调用                          │ 内部调用                   │
│              ▼                                  ▼                           │
│  ┌─────────────────────────────────────────────────────────────┐            │
│  │                        引擎层 (内部)                          │            │
│  │  ┌─────────────────────┐     ┌─────────────────────┐        │            │
│  │  │    Form Engine      │     │   Approval Engine   │        │            │
│  │  │  FormDefService     │     │  ProcessDefService  │        │            │
│  │  │  FormVersionService │     │  TaskService        │        │            │
│  │  └─────────────────────┘     └─────────────────────┘        │            │
│  └─────────────────────────────────────────────────────────────┘            │
└─────────────────────────────────────────────────────────────────────────────┘
```

**职责分工**:

| 模块 | 职责 |
|------|------|
| **表单管理** | 配置 & 发布（设计态） |
| **审批中心** | 运行时体验（发起申请 / 待办处理 / 流程跟踪） |

---

## 🔔 Webhook 事件通知

> 表单管理模块在关键业务节点会触发事件通知，支持外部系统集成。

### 事件类型

| 事件类型 | 触发时机 | 说明 |
|----------|----------|------|
| `form.definition.created` | 创建表单定义 | 新表单定义创建成功 |
| `form.snapshot.submitted` | 提交审核 | 版本提交审核，等待审批 |
| `form.snapshot.approved` | 审核通过 | 版本审核通过，可以发布 |
| `form.snapshot.rejected` | 审核驳回 | 版本审核被驳回 |
| `form.snapshot.published` | 发布成功 | 新版本发布上线 |
| `form.instance.created` | 实例创建 | 用户发起新申请 |
| `form.instance.completed` | 实例完成 | 申请流程结束（通过/拒绝） |
| `form.instance.cancelled` | 实例取消 | 申请被撤回或取消 |

### 事件载荷格式

```json
{
  "eventId": "evt-20251207-001",
  "eventType": "form.snapshot.published",
  "timestamp": "2025-12-07T14:00:00Z",
  "regionId": "CN",
  "payload": {
    "formDefinitionId": "fd-001",
    "formDefinitionName": "报销申请",
    "snapshotId": "snap-001",
    "formVersionId": "fv-002",
    "processVersionId": "pv-002",
    "version": 2,
    "versionName": "v2.0 增加附件字段",
    "releaseNote": "支持上传发票附件",
    "publishedBy": "admin-001",
    "publishedAt": "2025-12-07T14:00:00Z"
  },
  "metadata": {
    "source": "form-management",
    "traceId": "trace-abc-123"
  }
}
```

### 各事件载荷示例

**`form.instance.created` 载荷**：

```json
{
  "eventType": "form.instance.created",
  "payload": {
    "formInstanceId": "fi-001",
    "formDefinitionId": "fd-001",
    "formDefinitionName": "报销申请",
    "snapshotId": "snap-001",
    "applicant": {
      "id": "user-001",
      "name": "张三",
      "department": "技术部"
    },
    "formData": {
      "amount": 5000,
      "reason": "出差报销"
    },
    "processInstanceId": "pi-001",
    "createdAt": "2025-12-07T16:00:00Z"
  }
}
```

**`form.instance.completed` 载荷**：

```json
{
  "eventType": "form.instance.completed",
  "payload": {
    "formInstanceId": "fi-001",
    "formDefinitionId": "fd-001",
    "status": "APPROVED",
    "processInstanceId": "pi-001",
    "completedAt": "2025-12-08T10:00:00Z",
    "duration": 64800000,
    "approvalChain": [
      { "nodeId": "approve1", "nodeName": "部门审批", "approver": "李四", "action": "APPROVE", "time": "2025-12-07T18:00:00Z" },
      { "nodeId": "approve2", "nodeName": "财务审批", "approver": "王五", "action": "APPROVE", "time": "2025-12-08T10:00:00Z" }
    ]
  }
}
```

### Webhook 订阅配置

**订阅接口（管理员）**：

```http
POST /api/v1/form-management/webhooks
Content-Type: application/json
Authorization: Bearer <token>
```

```json
{
  "name": "ERP 系统集成",
  "url": "https://erp.example.com/webhooks/form-events",
  "events": [
    "form.snapshot.published",
    "form.instance.created",
    "form.instance.completed"
  ],
  "secret": "webhook-secret-key",
  "organizationId": "org-uuid-001",
  "enabled": true
}
```

**响应**：

```json
{
  "success": true,
  "data": {
    "id": "wh-001",
    "name": "ERP 系统集成",
    "url": "https://erp.example.com/webhooks/form-events",
    "events": ["form.snapshot.published", "form.instance.created", "form.instance.completed"],
    "organizationId": "org-uuid-001",
    "enabled": true,
    "createdAt": "2025-12-07T10:00:00Z"
  },
  "message": "Webhook 创建成功",
  "timestamp": "2025-12-07T10:00:00Z"
}
```

### Webhook 管理接口

| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| POST | `/webhooks` | 创建 Webhook 订阅 | `form:admin` |
| GET | `/webhooks` | 获取 Webhook 列表 | `form:admin` |
| GET | `/webhooks/:id` | 获取 Webhook 详情 | `form:admin` |
| PATCH | `/webhooks/:id` | 更新 Webhook 配置 | `form:admin` |
| DELETE | `/webhooks/:id` | 删除 Webhook | `form:admin` |
| POST | `/webhooks/:id/test` | 发送测试事件 | `form:admin` |
| GET | `/webhooks/:id/logs` | 查看投递日志 | `form:admin` |

### 安全性

| 机制 | 说明 |
|------|------|
| **签名验证** | 使用 HMAC-SHA256 签名，接收方需验证 `X-Signature` 头 |
| **重试机制** | 投递失败自动重试 3 次（间隔 1min, 5min, 30min） |
| **超时设置** | 单次请求超时 10 秒 |
| **日志记录** | 保留 30 天投递日志，支持查询和重发 |

**签名验证示例（接收方）**：

```typescript
import crypto from 'crypto';

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(`sha256=${expectedSignature}`)
  );
}
```

---

## 📊 统计分析

### GET /statistics - 获取表单统计数据

获取表单使用统计、热门表单排行、提交趋势等分析数据。

**权限**: `form:read`

**请求**:

```http
GET /api/v1/form-management/statistics?timeRange=30d
Authorization: Bearer <token>
```

**查询参数**:

| 参数 | 类型 | 必填 | 说明 | 示例 |
|------|------|------|------|------|
| `timeRange` | string | 否 | 时间范围: `7d`, `30d`, `90d`, `1y` | `30d` |
| `startDate` | string | 否 | 开始时间 (ISO 8601)，优先级高于 timeRange | `2024-01-01T00:00:00Z` |
| `endDate` | string | 否 | 结束时间 (ISO 8601) | `2024-12-31T23:59:59Z` |
| `category` | string | 否 | 表单分类筛选 | `finance` |

**响应** (200):

```json
{
  "success": true,
  "data": {
    "stats": {
      "totalSubmissions": 1245,
      "totalForms": 25,
      "activeForms": 20,
      "draftForms": 5,
      "avgProcessingTime": "2.5 小时",
      "avgProcessingTimeMs": 9000000,
      "approvalRate": 92,
      "activeUsers": 156
    },
    "topForms": [
      {
        "formDefinitionId": "fd-001",
        "formName": "报销申请",
        "slug": "expense_claim",
        "submissions": 342,
        "approvals": 315,
        "rejections": 27,
        "approvalRate": 92,
        "avgProcessingTime": 7200000
      }
    ],
    "trends": [
      {
        "date": "2024-12-01",
        "submissions": 42,
        "approvals": 38,
        "rejections": 4
      }
    ],
    "categoryDistribution": [
      {
        "category": "财务",
        "count": 15,
        "percentage": 35
      }
    ],
    "timeRange": {
      "from": "2024-11-01T00:00:00.000Z",
      "to": "2024-12-01T23:59:59.999Z"
    }
  }
}
```

**字段说明**:

| 对象 | 字段 | 类型 | 说明 |
|------|------|------|------|
| stats | `totalSubmissions` | number | 总提交数 |
| stats | `totalForms` | number | 总表单数 |
| stats | `activeForms` | number | 已发布表单数 |
| stats | `draftForms` | number | 草稿表单数 |
| stats | `avgProcessingTime` | string | 平均处理时间（格式化） |
| stats | `avgProcessingTimeMs` | number | 平均处理时间（毫秒） |
| stats | `approvalRate` | number | 审批通过率 (0-100) |
| stats | `activeUsers` | number | 活跃用户数 |
| topForms[] | `formDefinitionId` | string | 表单定义 ID |
| topForms[] | `formName` | string | 表单名称 |
| topForms[] | `slug` | string | 表单标识 |
| topForms[] | `submissions` | number | 提交次数 |
| topForms[] | `approvals` / `rejections` | number | 通过 / 驳回次数 |
| topForms[] | `approvalRate` | number | 通过率 (0-100) |
| topForms[] | `avgProcessingTime` | number | 平均处理时间（毫秒） |
| trends[] | `date` | string | 日期 (YYYY-MM-DD) |
| trends[] | `submissions` / `approvals` / `rejections` | number | 提交 / 通过 / 驳回数 |
| categoryDistribution[] | `category` | string | 分类名称 |
| categoryDistribution[] | `count` / `percentage` | number | 数量 / 百分比 (0-100) |

**统计口径**:

- 默认按 `user.regionId` 聚合（即用户当前区域的 FormInstance 统计）
- `form:admin` 可指定 `regionId` 查询参数获取其他区域统计
- 表单定义设置了归属组织时，统计口径改为该组织的主区域（`organization.primaryRegionId`）
- 未设置归属组织（平台级）时，统计口径为所有区域数据的聚合结果
- 审批通过率 = 通过数 / (通过数 + 驳回数) × 100，仅统计已完成审批实例
- 活跃用户数 = 指定时间范围内提交过表单的唯一用户数
- topForms 默认返回前 10 条

**错误响应**:

| 错误码 | HTTP | 说明 |
|--------|------|------|
| `VALIDATION_ERROR` | 400 | timeRange 必须是 7d, 30d, 90d, 1y 之一 |

---

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