# SSP 控制項現況批次匯入匯出 — 前端整合規格

## 功能概述

SSP 控制項現況說明（Control Implementation）的批次匯入匯出功能，讓使用者透過 Excel 一次維護所有控制項與檢查項目的實作狀態、現況描述、以及參考程序書關聯。

---

## 功能流程

```
Step 1: （前置）上傳程序書到 SSP 程序書池
  → 如果需要在 Excel 中關聯程序書，必須先到程序書管理頁面上傳

Step 2: 匯出 Excel
  GET /export → 下載 .xlsx（黃底=可編輯，灰底=唯讀，A 欄隱藏）

Step 3: 上傳 Excel
  POST /import (multipart/form-data) → 回傳驗證結果

Step 4: 驗證結果預覽
  → 有效行 ✓ / 錯誤行 ✗（標紅，顯示錯誤訊息）
  → 使用者可在表格中直接編輯修正

Step 5: 重新驗證（可重複多次）
  POST /import/validate (JSON) → 送修正後的 items → 回傳新的驗證結果

Step 6: 確認匯入
  POST /import/confirm (JSON) → 送有效 items → 批次寫入 → 刷新列表
```

---

## API 規格

### 1. 匯出 Excel

```
GET /api/1.0/ssp/<ssp_uid>/control-implementations/export
Authorization: Bearer <token>
```

**Response:** 直接下載 `.xlsx` 檔案

**前端處理：**
```javascript
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } })
const blob = await res.blob()
const a = document.createElement('a')
a.href = URL.createObjectURL(blob)
a.download = `ssp-control-implementations.xlsx`
a.click()
```

**Excel 結構（9 欄，A 欄隱藏）：**

| 欄 | 標題 | 底色 | 可見 | 說明 |
|----|------|------|------|------|
| A | statement_id | — | **隱藏** | AP task UUID，程式用 |
| B | 控制項識別碼 | 灰底（唯讀） | 可見 | 如 `AC.L1-3.1.1` |
| C | 控制項名稱 | 灰底（唯讀） | 可見 | 如 `Authorized Access Control` |
| D | 實作狀態 | 淡黃底（可編輯） | 可見 | 下拉選單 |
| E | 現況描述 | 淡黃底（可編輯） | 可見 | 自由文字 |
| F | 檢查項目名稱 | 灰底（唯讀） | 可見 | 如 `[a] authorized users are identified` |
| G | 檢查項目實作狀態 | 淡黃底（可編輯） | 可見 | 下拉選單 |
| H | 檢查項目現況描述 | 淡黃底（可編輯） | 可見 | 自由文字 |
| I | 參考程序書 | 淡黃底（可編輯） | 可見 | 逗號分隔程序書名稱 |

**實作狀態合法值：** `implemented`, `partial`, `not_applicable`, `not_implemented`, `inherited`, `unknown`

**行結構（黃底=可填，灰底=不可填）：**
- 控制項行：D, E, I 可填，其他灰底
- 檢查項目行：G, H, I 可填，其他灰底

**參考程序書欄位規則：**
- 逗號分隔程序書名稱，如：`資訊安全政策v3.0.pdf, 存取控制管理辦法.docx`
- 名稱必須存在於 SSP 程序書池中（透過 `GET /ssp/<uid>/document-pool` 查看池中文件）
- 留空 = 不關聯程序書（不會報錯）
- 匯入確認後會自動建立程序書與控制項/AO 的關聯

---

### 2. 匯入 Excel（上傳 + 驗證）

```
POST /api/1.0/ssp/<ssp_uid>/control-implementations/import
Authorization: Bearer <token>
Content-Type: multipart/form-data

file: <Excel 檔案，僅接受 .xlsx，上限 10MB>
```

**Response：**

```json
{
  "status": true,
  "data": {
    "valid_count": 36,
    "error_count": 40,
    "items": [
      {
        "row": 2,
        "control_identifier": "AC.L1-3.1.1",
        "control_name": "Authorized Access Control",
        "objective_id": null,
        "objective_name": null,
        "task_uid": null,
        "implementation_status": "implemented",
        "implementation_description": "組織已具備落實唯一身分識別與鑑",
        "reference_documents": "BT-I-B-13-存取控制管理程序書.docx",
        "errors": []
      },
      {
        "row": 3,
        "control_identifier": "AC.L1-3.1.1",
        "control_name": null,
        "objective_id": "e018c763-...",
        "objective_name": "[a] authorized users are identified",
        "task_uid": "e018c763-...",
        "obj_implementation_status": null,
        "obj_implementation_description": null,
        "reference_documents": "BT-I-B-13-存取控制管理程序書.docx, BT-I-B-09-人員安全與訓練管理程序書.docx",
        "errors": [
          {
            "field": "reference_documents",
            "message": "程序書不存在於池中：BT-I-B-13-存取控制管理程序書.docx, BT-I-B-09-人員安全與訓練管理程序書.docx"
          }
        ]
      }
    ]
  }
}
```

**欄位說明：**

| 欄位 | 說明 | 前端用途 |
|------|------|---------|
| `row` | Excel 行號 | 顯示錯誤位置 |
| `control_identifier` | 控制項識別碼 | 表格顯示 |
| `control_name` | 控制項名稱 | 表格顯示（控制項行有值） |
| `objective_id` | 檢查項目 statement_id | **不要顯示**，confirm/validate 時原封帶回 |
| `objective_name` | 檢查項目名稱 | 表格顯示（檢查項目行有值） |
| `task_uid` | AP task UUID | **不要顯示**，confirm/validate 時原封帶回 |
| `implementation_status` | 控制項實作狀態 | 控制項行才有值，可編輯 |
| `implementation_description` | 控制項現況描述 | 控制項行才有值，可編輯 |
| `obj_implementation_status` | 檢查項目實作狀態 | 檢查項目行才有值，可編輯 |
| `obj_implementation_description` | 檢查項目現況描述 | 檢查項目行才有值，可編輯 |
| `reference_documents` | 參考程序書 | **需顯示**，逗號分隔名稱，可編輯 |
| `errors` | 驗證錯誤清單 | 空陣列=有效，有值=該行有錯 |

**判斷行類型：**
- `objective_id != null` → 檢查項目行，顯示 `objective_name`
- `objective_id == null` → 控制項行，顯示 `control_name`

---

### 3. 重新驗證（JSON）

```
POST /api/1.0/ssp/<ssp_uid>/control-implementations/import/validate
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body：** 與 `/import/confirm` 相同格式，將所有 items 原封送回

```json
{
  "items": [
    {
      "row": 2,
      "control_identifier": "AC.L1-3.1.1",
      "control_name": "Authorized Access Control",
      "objective_id": null,
      "objective_name": null,
      "task_uid": null,
      "implementation_status": "implemented",
      "implementation_description": "已修正的描述...",
      "reference_documents": ""
    },
    {
      "row": 3,
      "control_identifier": "AC.L1-3.1.1",
      "control_name": null,
      "objective_id": "e018c763-...",
      "objective_name": "[a] authorized users are identified",
      "task_uid": "e018c763-...",
      "obj_implementation_status": "implemented",
      "obj_implementation_description": "修正後的描述...",
      "reference_documents": ""
    }
  ]
}
```

**Response：** 與 `POST /import`（上傳 Excel）完全相同格式

---

### 4. 確認匯入（批次寫入）

```
POST /api/1.0/ssp/<ssp_uid>/control-implementations/import/confirm
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body：** 將驗證結果中 **errors 為空的項目** 原封送回。

**Response：**

```json
{
  "status": true,
  "data": {
    "updated_controls": 15,
    "updated_objectives": 30,
    "total": 45,
    "skipped_count": 0
  }
}
```

---

## 前端 UI 建議

### 按鈕位置
放在 SSP 控制項現況列表頁面的工具列：
- 「匯出 Excel」→ 點擊直接下載
- 「匯入 Excel」→ 點擊開啟 Dialog

### 匯入 Dialog — 驗證結果表格

**表格欄位：**

| 行號 | 類型 | 名稱 | 實作狀態 | 現況描述 | 參考程序書 | 狀態 |
|------|------|------|---------|---------|-----------|------|
| 2 | 控制項 | AC.L1-3.1.1 Authorized Access Control | implemented | 組織已具備... | 資訊安全政策.pdf | ✓ |
| 3 | 檢查項目 | [a] authorized users are identified | | | ~~BT-I-B-13-...~~ | ✗ 程序書不存在 |
| 4 | 檢查項目 | [b] processes acting on behalf... | | | | ✓ |

**可編輯欄位：**

| 行類型 | 可編輯欄位 |
|--------|-----------|
| 控制項行 | `implementation_status`（下拉）、`implementation_description`（文字）、`reference_documents`（文字） |
| 檢查項目行 | `obj_implementation_status`（下拉）、`obj_implementation_description`（文字）、`reference_documents`（文字） |
| 不可編輯 | `control_identifier`、`control_name`、`objective_id`、`objective_name`、`task_uid` |

**參考程序書欄位處理：**
- 顯示逗號分隔的程序書名稱
- 錯誤時標紅，hover 顯示「程序書不存在於池中：xxx」
- 使用者可清空此欄位（不關聯程序書）或修正名稱後重新驗證
- 提示：「程序書名稱需與程序書池中的檔案名稱或描述完全一致」

### 錯誤處理

| HTTP Status | 錯誤碼 | 情境 | 前端處理 |
|-------------|--------|------|---------|
| 400 | `GRC_400001` | 檔案格式無效（非 .xlsx 或 header 不符） | 提示「請使用匯出的 Excel 範本」 |
| 400 | `GRC_400002` | 檔案超過 10MB | 提示「檔案大小超過限制」 |
| 404 | `GRC_404012` | SSP 不存在 | 提示錯誤 |

---

## API 總覽

| Method | URL | 說明 |
|--------|-----|------|
| GET | `/api/1.0/ssp/<ssp_uid>/control-implementations/export` | 匯出 Excel |
| POST | `/api/1.0/ssp/<ssp_uid>/control-implementations/import` | 上傳 Excel + 驗證 |
| POST | `/api/1.0/ssp/<ssp_uid>/control-implementations/import/validate` | JSON 重新驗證 |
| POST | `/api/1.0/ssp/<ssp_uid>/control-implementations/import/confirm` | 確認匯入 |

---

## 注意事項

### 參考程序書的使用前提

匯入 Excel 中填寫參考程序書名稱前，必須先將程序書上傳到 SSP 程序書池：

1. 進入 SSP 程序書管理頁面（`GET /ssp/<uid>/document-pool`）
2. 上傳程序書（`POST /ssp/<uid>/document-pool`）
3. 上傳完成後，在 Excel 的 I 欄填入程序書的**檔案名稱**或**描述**
4. 匯入時系統會從池中比對名稱，自動建立關聯

如果不需要關聯程序書，I 欄留空即可。
