# 方案计划文档管理系统功能与接口规格

> 文档转换与 PDF 预览同步版本：1.6。实现以 `DMS_FUNCTION_CONTRACT.md` 1.4和 `DMS_API_CONTRACT.md` 1.4为最终依据。

## 1. 文档目的

本文档用于对齐方案计划文档管理系统第一阶段的功能边界、业务规则和后端接口颗粒度，作为前后端开发、联调和验收依据。

如本文档与界面模拟数据不一致，以本文档中的业务规则为准；如需要改变业务规则，应先修改本文档并确认，再修改代码。

## 2. 第一阶段范围

### 2.1 本阶段包含

- 用户登录、退出和当前用户信息。
- 组织机构和人员查询。
- 方案计划分类树管理。
- 主案管理。
- 子方案管理。
- 共享附件库管理。
- 主案与共享附件的多对多挂载。
- 主案和子方案权限配置。
- 文档文件上传、预览和下载。
- 文档逻辑删除和恢复。
- 操作日志和基础统计。
- MySQL结构化数据持久化。
- `backend/dms-storage/`文件持久化。

### 2.2 本阶段不包含

- 前端视觉风格重构。
- Milvus和文档向量化。
- AI模块改造。
- 电子签章。
- 审批工作流。
- 在线Office编辑。
- 国产化适配。
- 复杂文件历史版本管理。
- 匿名访问。
- 分类、用户或组织恢复。
- 批量恢复、物理删除和自动清理。
- 独立恢复ACL或挂载关系。
- 将逻辑删除文件移动到`recycle`目录。

## 3. 角色定义

| 角色 | 说明 | 默认功能 |
|---|---|---|
| 普通用户 `USER` | 浏览方案资料的已登录用户 | 浏览有权访问的主案、子方案及其已挂载附件 |
| 管理员 `ADMIN` | 系统、文档及审计管理人员 | 分类、主案、子方案、共享附件、权限、文件和审计管理 |

第一阶段角色固定，不提供动态角色配置页面。

## 4. 核心术语

| 术语 | 定义 |
|---|---|
| 主案 | 顶层方案文档，可以包含多个子方案并挂载多个共享附件 |
| 子方案 | 直接归属于一个主案的方案文档，第一阶段不允许继续嵌套 |
| 共享附件 | 独立于主案存在、对全部已登录用户共享的附件文档 |
| 挂载 | 建立主案与共享附件之间的引用关系，不复制文件 |
| 解除挂载 | 删除主案与附件的关系，不删除附件文件 |
| 方案分类 | 用于组织主案和子方案的树形分类 |
| 组织机构 | 用于用户归属和文档权限配置的组织树，不作为方案分类 |

## 5. 全局业务规则

### BR-001 登录范围

“所有人共享”是指所有已登录且状态正常的系统用户，不允许匿名访问。

### BR-002 附件独立性

共享附件是独立文档，不属于某一个主案：

- 附件自身只保存一份文件和一份元数据。
- 同一附件可以挂载到多个主案。
- 同一主案不能重复挂载同一附件。
- 解除挂载不能删除附件文件。
- 删除主案不能删除共享附件。

### BR-003 附件访问权限

共享附件不配置组织或人员ACL。所有已登录用户均可查看和下载共享附件。

共享附件的上传、编辑、删除和挂载管理只允许管理员操作。

### BR-004 附件删除

- 未挂载到任何主案的附件允许逻辑删除。
- 已挂载到一个或多个主案的附件，普通删除请求必须返回冲突错误。
- 第一阶段不提供“强制解除全部挂载并删除”接口。
- 管理员需要先处理全部挂载关系，再删除附件。

### BR-005 主案与子方案

- 一个子方案只能属于一个主案。
- 一个主案可以包含多个子方案。
- 第一阶段不允许子方案继续包含子方案。
- 删除存在有效子方案的主案时默认拒绝。

### BR-006 文件存储

- 文件存放在`backend/dms-storage/`。
- 数据库只保存相对路径。
- 文件必须通过后端鉴权接口读取，不能暴露静态磁盘目录。
- 文件删除与业务记录删除分离，业务删除默认不立即物理删除文件。

### BR-007 逻辑删除

核心业务数据使用`is_deleted`和`deleted_at`逻辑删除。正常查询默认不返回已删除数据。

### BR-008 操作审计

登录、查看、下载、上传、编辑、删除、恢复成功、权限变更、挂载和解除挂载必须记录审计日志。恢复成功使用 `RESTORE_DOCUMENT`；最小范围内恢复失败不写业务审计，只写安全应用日志。

### BR-009 冗余字段

文档计数、附件挂载数、分类路径、人员名称等冗余字段由后端维护，前端不得将其作为可修改字段提交。

## 6. 功能描述

## 6.1 用户认证

### F-AUTH-001 用户登录

**功能说明**

用户通过用户名和密码登录系统。

**输入**

- 用户名。
- 密码。
- 是否保持登录状态。

**处理规则**

1. 用户名和密码不能为空。
2. 用户必须存在、未逻辑删除且状态为`ENABLED`。
3. 后端验证密码哈希。
4. 登录成功后返回访问凭证和当前用户概要。
5. 更新最近登录时间和IP。
6. 登录成功或失败均写入审计日志。

**输出**

- 登录凭证。
- 用户ID、姓名、组织、角色和安全等级。
- 可访问的顶部功能入口。

**异常**

- 用户名或密码错误。
- 用户被锁定或禁用。
- 系统内部错误。

### F-AUTH-002 用户退出

使当前登录凭证失效并记录退出日志。

### F-AUTH-003 当前用户

返回当前登录用户、所属组织和固定角色，用于恢复前端登录状态。

## 6.2 方案分类

### F-CAT-001 查询分类树

返回全部有效方案分类的树形结构，按`sort_no`排序。

### F-CAT-002 新增分类

管理员可以新增顶级分类或指定父分类下的子分类。

### F-CAT-003 编辑分类

管理员可以修改分类名称、类型和排序。修改后同步更新后代路径及关联文档的分类冗余字段。

### F-CAT-004 删除分类

存在有效子分类或有效文档时拒绝删除；否则进行逻辑删除。

## 6.3 主案管理

### F-MAIN-001 查询主案列表

**筛选条件**

- 分类ID。
- 关键词。
- 密级。
-可见范围。
- 状态。
- 更新时间。
- 页码、每页数量和排序。

**列表输出**

- 文档ID。
- 文档名称。
- 类型。
- 密级。
- 可见范围。
- 子方案数量。
- 已挂载附件数量。
- 更新时间。
- 当前用户允许的操作。

普通用户列表必须在后端完成权限过滤。

### F-MAIN-002 查询主案详情

返回文档元数据、文件信息、分类路径、标签、权限概要、子方案数量和附件数量。

### F-MAIN-003 上传主案或子案

单文件上传统一在一个弹框中选择方案类型。子案选择所属主案后自动继承主案分类。主案名称全局唯一，子案名称在所属主案内唯一；重名时必须经用户确认后覆盖原记录，不采用先删后传。

管理员上传一个文件并填写文档元数据。文件和数据库记录必须具有失败补偿。

### F-MAIN-004 编辑主案

管理员修改文档名称、概述、分类、密级、可见范围、标签和状态，不直接在线编辑文件内容。

### F-MAIN-005 删除与恢复主案

- 删除使用逻辑删除。
- 存在有效子方案时拒绝删除。
- 删除时只删除主案与附件的挂载关系，不删除共享附件。
- 恢复只使用 `/api/v1/recycle-bin/documents/{id}/restore`；旧 `/api/v1/documents/{id}/restore` 永久废止。
- 恢复时校验安全相对路径、原始文件、大小、哈希、格式和分类。
- 恢复主案时恢复同一删除批次中仍有效且不冲突的ACL和挂载关系；无效主体或已删除附件跳过并计数。
- `ORGANIZATION`恢复后必须有有效可查看ORG权限；`CUSTOM`允许空权限集合。

## 6.4 子方案管理

### F-SUB-001 查询主案的子方案

根据主案ID返回其直接子方案列表。

### F-SUB-002 上传子方案

管理员在指定主案下上传子方案，创建时继承或复制主案权限。

### F-SUB-003 编辑、查看、下载和删除

行为与主案对应功能一致，但删除子方案不影响主案和共享附件。

## 6.5 共享附件库

### F-ATT-001 查询共享附件

**筛选条件**

- 关键词：名称、概述和标签。
- 附件类型。
- 文件格式。
- 更新时间。
- 页码、每页数量和排序。

**列表输出**

- 附件ID。
- 附件名称。
- 附件类型。
- 文件格式。
- 标签。
- 上传人。
- 更新时间。
- 已挂载主案数量。
- 当前用户允许的操作。

### F-ATT-002 上传附件

管理员将文件上传到共享附件库。上传成功后附件立即对全部已登录用户可见。

### F-ATT-003 批量导入附件

管理员一次选择多个文件；每个文件独立返回成功或失败结果，不因一个文件失败回滚全部成功文件。

### F-ATT-004 查看和下载附件

全部已登录用户均可调用。接口仍需验证登录状态并写入审计日志。

### F-ATT-005 编辑附件

管理员可以编辑名称、附件类型、概述和标签。不能通过编辑接口改变附件ID和挂载关系。

### F-ATT-006 删除附件

后端先计算有效挂载数量：

- 数量为0：逻辑删除。
- 数量大于0：返回`409 ATTACHMENT_IN_USE`及影响主案概要。

### F-ATT-007 查看附件挂载关系

返回附件当前挂载的全部有效主案，用于“挂载主案数”和挂载关系弹窗。

## 6.6 附件挂载

### F-BIND-001 查询主案已挂载附件

根据主案ID返回有效挂载附件，包含挂载顺序和附件当前挂载主案总数。

### F-BIND-002 批量挂载附件

管理员一次选择一个或多个共享附件挂载到当前主案。

**处理规则**

1. 主案必须存在且类型为`MAIN`。
2. 所有附件必须存在且类型为`ATTACHMENT`。
3. 已挂载附件不重复创建关系。
4. 一个请求内重复的附件ID自动去重。
5. 在一个数据库事务内创建挂载关系并更新主案附件计数。
6. 写入挂载审计日志。

**输出**

- 新增挂载数量。
- 已存在数量。
- 当前有效挂载总数。

### F-BIND-003 解除挂载

逻辑删除指定主案和附件的挂载关系，更新主案附件计数，附件本身保持不变。

### F-BIND-004 上传新附件并挂载

这是前端组合功能，不设计独立原子接口：

1. 调用“上传附件”。
2. 上传成功后调用“批量挂载附件”。
3. 如果挂载失败，新附件仍保留在共享附件库，前端提示用户可以稍后重新挂载。

## 6.7 文档权限

### F-PERM-001 查询权限

返回主案或子方案的组织权限和人员权限。共享附件不调用该功能。

### F-PERM-002 保存权限

管理员提交完整权限集合，后端在一个事务内完成新增、更新和逻辑删除。

权限维度：

- 查看。
- 下载。
- 编辑。
- 权限管理。
- 删除。

## 6.8 文件预览与下载

### F-FILE-001 预览文件

后端验证文档有效性和用户查看权限后返回文件。PDF 直接返回原文件；DOC、DOCX 先由 LibreOffice 离线转换为 PDF 再返回；XLS、XLSX 不支持在线预览。转换失败返回 `PREVIEW_CONVERTER_UNAVAILABLE`、`PREVIEW_CONVERSION_TIMEOUT` 或 `PREVIEW_CONVERSION_FAILED`。

### F-FILE-002 下载文件

后端验证下载权限、返回原始文件、更新下载计数并写入审计日志。

共享附件只检查登录状态；主案和子方案检查文档ACL。

## 6.9 审计日志与统计

### F-AUDIT-001 查询日志

支持按关键词、时间范围、用户、组织、操作类型、结果和目标对象分页查询。

### F-AUDIT-002 后台文档统计

返回：

- 有效文档总数。
- 主案数量。
- 子方案数量。
- 共享附件数量。
- 今日查看人数。

### F-AUDIT-003 审计图表

返回最近操作趋势、操作类型分布和人员活跃度。

## 6.10 回收站与恢复

### F-RECYCLE-001 查询文档回收站

仅ADMIN可以查询逻辑删除且 `deleted_at` 非空的MAIN、SUB_PLAN和ATTACHMENT。支持：

- keyword；
- documentType；
- categoryId；
- deletedFrom、deletedTo；
- page、pageSize；
- sortField、sortDirection。

删除时间使用带 `Z` 的UTC时间和 `[deletedFrom, deletedTo)`区间。默认按 `deletedAt desc, id desc`稳定排序。删除人从最近一次匹配的成功 `DELETE_DOCUMENT`审计批量获取；无可靠审计时返回null，不得用最后更新人冒充。

### F-RECYCLE-002 恢复文档

仅ADMIN可以单条恢复。请求必须携带删除记录当前 `rowVersion`。恢复采用事务前完整文件校验和事务内提交前文件状态复核；恢复、关系、冗余计数和 `RESTORE_DOCUMENT`审计原子提交。

- MAIN恢复同一删除批次的有效ACL和挂载关系；无效主体、已删除附件跳过，关系唯一冲突整体回滚。
- SUB_PLAN要求父主案和根主案指向同一有效MAIN，恢复后继承主案当前权限。
- ATTACHMENT保持PUBLIC和ALL_AUTHENTICATED，不恢复ACL或历史挂载。
- 恢复响应使用纯序列化，不增加查看次数或产生 `VIEW_DOCUMENT`审计。
- 不移动、复制或删除原始文件。

## 7. 接口通用约定

### 7.1 基础路径

```text
/api/v1
```

现有AI相关`/api`接口本阶段不调整。

### 7.2 认证

除登录接口外，所有接口都需要有效登录凭证。

推荐请求头：

```http
Authorization: Bearer <access-token>
X-Request-Id: <uuid>
```

### 7.3 成功响应

```json
{
  "code": "OK",
  "message": "success",
  "data": {},
  "requestId": "8ed3e305-73a8-4af7-8f39-1b86b9a2dad2",
  "timestamp": "2026-07-23T02:00:00.000Z"
}
```

### 7.4 分页响应

```json
{
  "code": "OK",
  "message": "success",
  "data": {
    "items": [],
    "page": 1,
    "pageSize": 20,
    "total": 0,
    "totalPages": 0
  },
  "requestId": "uuid",
  "timestamp": "2026-07-23T02:00:00.000Z"
}
```

### 7.5 错误响应

```json
{
  "code": "ATTACHMENT_IN_USE",
  "message": "附件已挂载到其他主案，不能删除",
  "details": {
    "attachmentId": 101,
    "mountedPlanCount": 3
  },
  "requestId": "uuid",
  "timestamp": "2026-07-23T02:00:00.000Z"
}
```

### 7.6 HTTP状态码

| 状态码 | 用途 |
|---:|---|
| 200 | 查询、编辑、删除成功 |
| 201 | 创建成功 |
| 400 | 参数格式错误 |
| 401 | 未登录或凭证失效 |
| 403 | 权限不足 |
| 404 | 数据不存在或已删除 |
| 409 | 数据冲突、附件正在使用、并发版本冲突 |
| 413 | 上传文件或请求体过大 |
| 415 | 不支持的文件格式 |
| 500 | 未处理的系统错误 |

### 7.7 并发更新

编辑和删除请求携带`rowVersion`。版本不一致返回：

```text
409 DATA_VERSION_CONFLICT
```

## 8. 接口目录

## 8.1 认证接口

| 方法 | 路径 | 功能 |
|---|---|---|
| POST | `/api/v1/auth/login` | 登录 |
| POST | `/api/v1/auth/logout` | 退出 |
| GET | `/api/v1/users/me` | 当前用户 |

## 8.2 组织和人员

| 方法 | 路径 | 功能 |
|---|---|---|
| GET | `/api/v1/organizations/tree` | 组织树 |
| GET | `/api/v1/users` | 人员查询 |

## 8.3 分类

| 方法 | 路径 | 功能 |
|---|---|---|
| GET | `/api/v1/categories/tree` | 分类树 |
| POST | `/api/v1/categories` | 新增分类 |
| PUT | `/api/v1/categories/{id}` | 编辑分类 |
| DELETE | `/api/v1/categories/{id}` | 逻辑删除分类 |

## 8.4 主案和子方案

| 方法 | 路径 | 功能 |
|---|---|---|
| GET | `/api/v1/documents` | 文档分页查询 |
| POST | `/api/v1/documents` | 上传主案或子方案 |
| GET | `/api/v1/documents/{id}` | 文档详情 |
| PUT | `/api/v1/documents/{id}` | 编辑文档 |
| DELETE | `/api/v1/documents/{id}` | 逻辑删除文档 |
| GET | `/api/v1/main-plans/{id}/sub-plans` | 查询主案子方案 |

旧 `/api/v1/documents/{id}/restore` 永久废止，不得重定向或兼容调用。

## 8.5 文档回收站

| 方法 | 路径 | 功能 |
|---|---|---|
| GET | `/api/v1/recycle-bin/documents` | 查询回收站文档 |
| POST | `/api/v1/recycle-bin/documents/{id}/restore` | 单条恢复文档 |

## 8.6 共享附件

| 方法 | 路径 | 功能 |
|---|---|---|
| GET | `/api/v1/attachments` | 附件分页查询 |
| POST | `/api/v1/attachments` | 上传附件 |
| POST | `/api/v1/attachments/batch-import` | 批量导入附件 |
| GET | `/api/v1/attachments/{id}` | 附件详情 |
| PUT | `/api/v1/attachments/{id}` | 编辑附件 |
| DELETE | `/api/v1/attachments/{id}` | 删除未挂载附件 |
| GET | `/api/v1/attachments/{id}/main-plans` | 查询附件挂载的主案 |

## 8.7 附件挂载

| 方法 | 路径 | 功能 |
|---|---|---|
| GET | `/api/v1/main-plans/{id}/attachments` | 查询主案已挂载附件 |
| POST | `/api/v1/main-plans/{id}/attachments/bind` | 批量挂载附件 |
| DELETE | `/api/v1/main-plans/{id}/attachments/{attachmentId}` | 解除挂载 |

## 8.8 权限

| 方法 | 路径 | 功能 |
|---|---|---|
| GET | `/api/v1/documents/{id}/permissions` | 查询文档权限 |
| PUT | `/api/v1/documents/{id}/permissions` | 保存文档权限 |

## 8.9 文件

| 方法 | 路径 | 功能 |
|---|---|---|
| GET | `/api/v1/documents/{id}/preview` | 预览文档 |
| GET | `/api/v1/documents/{id}/download` | 下载文档 |

共享附件同样使用统一的 `/api/v1/documents/{id}/preview` 和
`/api/v1/documents/{id}/download` 路径；不提供附件专用的重复文件读取路径。

## 8.10 审计和统计

| 方法 | 路径 | 功能 |
|---|---|---|
| GET | `/api/v1/audit/logs` | 日志查询 |
| GET | `/api/v1/statistics/documents` | 后台统计卡片 |
| GET | `/api/v1/audit/statistics/trend` | 操作趋势 |
| GET | `/api/v1/audit/statistics/actions` | 操作类型分布 |
| GET | `/api/v1/audit/statistics/users` | 人员活跃度 |

## 9. 关键接口详细描述

### 9.1 上传主案或子方案

```http
POST /api/v1/documents
Content-Type: multipart/form-data
```

表单字段：

| 字段 | 必填 | 说明 |
|---|---|---|
| `file` | 是 | 原始文件 |
| `documentName` | 是 | 文档名称 |
| `documentType` | 是 | `MAIN`或`SUB_PLAN` |
| `parentDocumentId` | 子方案必填 | 所属主案 |
| `categoryId` | 是 | 方案分类 |
| `summary` | 否 | 文档概述 |
| `securityLevel` | 是 | 密级 |
| `visibilityType` | 是 | 可见范围类型 |
| `tags` | 否 | JSON数组字符串 |
| `versionNo` | 否 | 默认`V1` |

成功响应`data`至少包含：

```json
{
  "id": 125,
  "documentName": "综合应急预案",
  "documentType": "MAIN",
  "fileOriginalName": "综合应急预案.docx",
  "fileSize": 102400,
  "fileHash": "sha256",
  "createdAt": "2026-07-23T02:00:00.000Z",
  "rowVersion": 0
}
```

### 9.2 查询共享附件

```http
GET /api/v1/attachments?page=1&pageSize=20&keyword=应急&attachmentType=POLICY&fileFormat=PDF
```

附件项：

```json
{
  "id": 101,
  "attachmentName": "中华人民共和国突发事件应对法",
  "attachmentType": "POLICY",
  "attachmentTypeName": "政策法规",
  "fileFormat": "DOCX",
  "tags": ["应急", "法规"],
  "uploaderId": 1,
  "uploaderName": "系统管理员",
  "updatedAt": "2026-07-20T08:00:00.000Z",
  "mountedPlanCount": 6,
  "allowedActions": ["VIEW", "DOWNLOAD", "EDIT", "DELETE"]
}
```

普通用户的`allowedActions`只返回`VIEW`和`DOWNLOAD`。

### 9.3 批量挂载共享附件

```http
POST /api/v1/main-plans/1/attachments/bind
Content-Type: application/json
```

请求：

```json
{
  "attachmentIds": [101, 102, 108],
  "rowVersion": 3
}
```

响应：

```json
{
  "code": "OK",
  "message": "挂载完成",
  "data": {
    "mainPlanId": 1,
    "createdCount": 2,
    "alreadyMountedCount": 1,
    "totalMountedCount": 5,
    "rowVersion": 4
  }
}
```

### 9.4 解除附件挂载

```http
DELETE /api/v1/main-plans/1/attachments/108
Content-Type: application/json
```

请求：

```json
{
  "rowVersion": 4
}
```

响应需要明确附件未被删除：

```json
{
  "code": "OK",
  "message": "已解除挂载，共享附件仍保留在附件库",
  "data": {
    "mainPlanId": 1,
    "attachmentId": 108,
    "totalMountedCount": 4,
    "rowVersion": 5
  }
}
```

### 9.5 删除共享附件

```http
DELETE /api/v1/attachments/101
Content-Type: application/json
```

请求：

```json
{
  "rowVersion": 2
}
```

如果附件仍被使用：

```json
{
  "code": "ATTACHMENT_IN_USE",
  "message": "附件已挂载到6个主案，请先解除挂载关系",
  "details": {
    "attachmentId": 101,
    "mountedPlanCount": 6,
    "mainPlans": [
      {"id": 1, "name": "2024年度综合应急预案"},
      {"id": 2, "name": "战备物资管理规程"}
    ]
  }
}
```

### 9.6 保存文档权限

```http
PUT /api/v1/documents/1/permissions
Content-Type: application/json
```

请求：

```json
{
  "rowVersion": 2,
  "permissions": [
    {
      "subjectType": "ORG",
      "subjectId": 11,
      "canView": true,
      "canDownload": true,
      "canEdit": false,
      "canManagePermission": false,
      "canDelete": false
    },
    {
      "subjectType": "USER",
      "subjectId": 1001,
      "canView": true,
      "canDownload": true,
      "canEdit": true,
      "canManagePermission": false,
      "canDelete": false
    }
  ]
}
```

共享附件不能调用此接口，调用时返回：

```text
400 SHARED_ATTACHMENT_PERMISSION_NOT_CONFIGURABLE
```

## 10. 前端按钮与接口映射

| 页面按钮 | 功能编号 | 接口 |
|---|---|---|
| 登录 | F-AUTH-001 | `POST /auth/login` |
| 添加顶级分类 | F-CAT-002 | `POST /categories` |
| 编辑分类 | F-CAT-003 | `PUT /categories/{id}` |
| 删除分类 | F-CAT-004 | `DELETE /categories/{id}` |
| 上传主案 | F-MAIN-003 | `POST /documents` |
| 查看主案 | F-MAIN-002 | `GET /documents/{id}`、`GET /documents/{id}/preview` |
| 编辑主案 | F-MAIN-004 | `PUT /documents/{id}` |
| 主案权限 | F-PERM-001/002 | `GET/PUT /documents/{id}/permissions` |
| 删除主案 | F-MAIN-005 | `DELETE /documents/{id}` |
| 查询回收站 | F-RECYCLE-001 | `GET /recycle-bin/documents` |
| 恢复文档 | F-RECYCLE-002 | `POST /recycle-bin/documents/{id}/restore` |
| 上传子方案 | F-SUB-002 | `POST /documents` |
| 共享附件库 | F-ATT-001 | `GET /attachments` |
| 上传附件 | F-ATT-002 | `POST /attachments` |
| 批量导入附件 | F-ATT-003 | `POST /attachments/batch-import` |
| 从共享附件库挂载 | F-BIND-002 | `POST /main-plans/{id}/attachments/bind` |
| 解除挂载 | F-BIND-003 | `DELETE /main-plans/{id}/attachments/{attachmentId}` |
| 查看挂载主案数 | F-ATT-007 | `GET /attachments/{id}/main-plans` |
| 删除共享附件 | F-ATT-006 | `DELETE /attachments/{id}` |
| 日志筛选 | F-AUDIT-001 | `GET /audit/logs` |

## 11. 第一阶段验收场景

### AC-001 同一附件挂载多个主案

上传一个共享附件，将其分别挂载到两个主案。附件库只存在一条附件记录，挂载主案数为2。

### AC-002 重复挂载

对同一主案重复挂载同一附件，不产生重复关系，接口返回已存在数量。

### AC-003 解除挂载

从一个主案解除附件后：

- 该主案的附件数量减少；
- 其他主案挂载不受影响；
- 附件库仍可查看和下载附件。

### AC-004 删除正在使用的附件

删除已挂载附件时返回`409 ATTACHMENT_IN_USE`，附件和全部挂载关系保持不变。

### AC-005 全员共享

任意状态正常的普通用户登录后，可以查询、预览和下载共享附件，但不能上传、编辑、删除或挂载附件。

### AC-006 主案权限不影响共享附件

用户无权查看某一主案，但仍可以从共享附件库访问该主案所挂载的共享附件。

### AC-007 文件和数据库一致性

上传文件失败时不产生有效文档记录；数据库事务失败时不留下正式目录孤儿文件。

### AC-008 逻辑删除

逻辑删除后的主案、子方案或附件不出现在正常列表，也不能继续预览和下载。

## 12. 待确认但不阻塞当前设计的事项

以下事项后续可以单独确认，不影响当前功能和接口颗粒度：

1. 共享附件是否允许用户收藏。
2. 附件类型是否固定为政策法规、工作规范、表格资料、图示资料和其他资料。
3. 主案删除时是否自动解除附件挂载，当前定义为自动解除但不删除附件。
4. 回收站页面采用独立资源路径，不启用已废止的文档恢复旧路径。
5. 批量导入是否升级为持久化异步任务，当前第一阶段定义为受限的同步逐文件结果。

## 13. B8恢复数据和迁移补充

- 文档名称、原文件名、分类下名称、同一主案下子方案名称和文件哈希允许重复。
- 恢复冲突仅处理有效ACL、有效挂载及主键或结构一致性冲突。
- B8实现创建 `0002_add_restore_audit_action`，扩展审计动作CHECK并在 `doc_document`增加 `(is_deleted, deleted_at, id)`索引。
- 不修改 `0001_initial_schema`，不新增表，不修改历史审计。
- B8实现同步将OpenAPI `info.version`升级为 `1.1.0`。
- 恢复错误码统一为：

```text
DOCUMENT_NOT_DELETED
FILE_INTEGRITY_MISMATCH
FILE_PATH_INVALID
RESTORE_FILE_CHANGED
RESTORE_CATEGORY_INVALID
RESTORE_PARENT_INVALID
RESTORE_PERMISSION_INVALID
RESTORE_RELATION_CONFLICT
```

HTTP状态、message和details结构以 `DMS_API_CONTRACT.md` 1.1为准，响应不得泄露绝对路径、哈希、SQL、数据库连接信息或内部异常堆栈。

## 14. Q1-B统一UI字典补充

- 新增只读接口 `GET /api/v1/config/ui-dictionaries`，所有有效登录用户均可访问。
- API和数据库继续使用稳定英文枚举代码；中文标签仅用于展示，由后端配置文件统一提供。
- 默认配置文件为 `backend/dms/resources/ui-dictionaries.zh-CN.json`，可通过 `DMS_UI_DICTIONARY_CONFIG_PATH`覆盖。外部路径仅供服务端使用，不得进入响应。
- 配置完整覆盖14组正式界面枚举，并在启动时与Python枚举代码集合进行严格一致性校验。
- 配置加载为不可变内存对象，请求不重复读取磁盘；修改配置后重启生效，本阶段不支持热更新。
- 字典查询不产生业务审计，字典读取本身不访问数据库；现有认证基础设施继续执行令牌和用户状态校验。
- 不新增数据库表、字段或迁移，不提供配置写接口，不修改既有业务语义。
- Q1-B实现同步将OpenAPI `info.version`升级为 `1.2.0`。

## 15. Q2-B更新时间范围检索补充

- `GET /api/v1/documents`、`GET /api/v1/main-plans/{id}/sub-plans`、`GET /api/v1/main-plans/{id}/attachments`统一接受可选 `updatedFrom`和 `updatedTo`。
- 两个边界分别表示文档自身 `updated_at >= updatedFrom`和 `updated_at <= updatedTo`，同时提供时为闭区间。
- 子方案使用子方案自身更新时间；挂载附件使用附件文档自身更新时间，不使用挂载关系时间或排序号。
- 时间必须是包含明确时区的ISO 8601格式，前端统一发送UTC `Z`；空字符串、无时区、非法格式和反向区间返回 `400 INVALID_ARGUMENT`。
- 时间条件在数据库查询和分页前执行，且不改变权限、密级、状态、排序、DTO、计数和审计语义。
- Q2-B不新增数据库、字段、索引或迁移；当时OpenAPI升级为 `1.3.0`。

## 22. Q3-B 1.4同步说明

Q3-B将正式角色收敛为ADMIN和USER，审计能力归入ADMIN；分类仅允许叶子节点直接存放MAIN，分类树计数按当前用户在完整子树中的可见MAIN计算；文档列表支持`includeDescendants`。MAIN/SUB_PLAN创建和编辑不再接收status、visibilityType，SUB_PLAN分类继承主案。独立附件库仅ADMIN可枚举，USER按已挂载主案权限访问附件。统一preview路径原样返回PDF或DOCX，DOC/XLS/XLSX返回415，不调用Office类软件。`0003_q3_business_alignment_and_content_search`增加正文提取字段并将search_text扩展为LONGTEXT，keyword组合检索元数据与正文；当前参数化LIKE适用于现阶段规模，但数据量增长后存在全表/大文本扫描风险，后续索引或搜索引擎方案必须另行评审。OpenAPI为1.4.0。
