SSP 程序書池 + 多對多關聯 — 前端整合規格

§1

功能概述

將 SSP 程序書改為「池 + 關聯」架構:

  • 程序書池:SSP 級別,上傳一次即可
  • 關聯:同一份程序書可關聯到多個控制項和 AO

§2

概念說明

之前(一對一,重複上傳):
  控制項 A → 上傳 程序書1.pdf
  控制項 B → 再上傳一次 程序書1.pdf(重複)
  AO [a]   → 又上傳一次 程序書1.pdf(第三次)

現在(池 + 多對多):
  SSP 程序書池:
    📄 程序書1.pdf(上傳一次)
    📄 程序書2.pdf
    📄 程序書3.pdf

  控制項 A → 關聯 程序書1, 程序書2
  控制項 B → 關聯 程序書1, 程序書3
  AO [a]   → 關聯 程序書1
  (同一份文件,不同地方使用,不需重複上傳)

§3

使用流程

Step 1: 管理者進入 SSP → 「程序書管理」頁面
  → 拖拽批次上傳所有程序書
  → 每份可填寫描述(如「資訊安全政策 v3.0」)

Step 2: 在控制項詳情頁
  → 點「關聯程序書」→ Dialog 顯示池中所有程序書
  → 多選 → 確認

Step 3: 在 AO 詳情頁
  → 同上操作

§4

API 規格

一、程序書池 CRUD

列出池

GET /api/1.0/ssp/<ssp_uid>/document-pool
Authorization: Bearer <token>

Response:

{
  "status": true,
  "data": [
    {
      "uid": "doc-uuid-1",
      "file_uid": "file-uuid-1",
      "file_name": "資訊安全政策v3.0.pdf",
      "file_size": 1024000,
      "description": "資訊安全政策 v3.0",
      "mapping_count": 5,
      "created_at": "2026-03-29T10:00:00"
    },
    {
      "uid": "doc-uuid-2",
      "file_uid": "file-uuid-2",
      "file_name": "存取控制管理辦法.docx",
      "file_size": 512000,
      "description": "存取控制管理辦法 v2.1",
      "mapping_count": 3,
      "created_at": "2026-03-29T10:05:00"
    }
  ]
}
欄位 說明
uid 程序書 UID(用於關聯和刪除)
file_uid 檔案 UID(用於下載)
file_name 檔案名稱
file_size 檔案大小(bytes)
description 程序書描述
mapping_count 被關聯的次數(控制項 + AO 的總數)
created_at 上傳時間

上傳到池

POST /api/1.0/ssp/<ssp_uid>/document-pool
Authorization: Bearer <token>
Content-Type: application/json

{
  "documents": [
    {
      "file_uid": "已上傳檔案的 UID(透過 /file/upload 取得)",
      "description": "資訊安全政策 v3.0"
    },
    {
      "file_uid": "另一個 file_uid",
      "description": "存取控制管理辦法"
    }
  ]
}

流程: 先用 POST /file/upload 上傳實體檔案取得 file_uid,再呼叫此 API 加入池。

從池刪除

DELETE /api/1.0/ssp/<ssp_uid>/document-pool/<doc_uid>

注意: 刪除會 cascade 刪除所有關聯。建議前端在刪除前:

  • 檢查 mapping_count > 0 時顯示警告:「此程序書已關聯 N 個控制項/檢查項目,刪除後關聯也會被移除」

二、控制項關聯

列出控制項的關聯程序書

GET /api/1.0/ssp/<ssp_uid>/control-implementation/<control_identifier>/document-mappings

Response:

{
  "status": true,
  "data": [
    {
      "uid": "doc-uuid-1",
      "file_uid": "file-uuid-1",
      "file_name": "資訊安全政策v3.0.pdf",
      "file_size": 1024000,
      "description": "資訊安全政策 v3.0"
    }
  ]
}

批次建立控制項關聯

POST /api/1.0/ssp/<ssp_uid>/control-implementation/<control_identifier>/document-mappings
Content-Type: application/json

{
  "document_uids": ["doc-uuid-1", "doc-uuid-2", "doc-uuid-3"]
}

Response:

{
  "status": true,
  "data": {
    "added_count": 3
  }
}

注意: 已存在的關聯會自動跳過,不會重複建立。

取消控制項關聯

DELETE /api/1.0/ssp/<ssp_uid>/control-implementation/<control_identifier>/document-mapping/<doc_uid>

三、AO 關聯

API 格式與控制項關聯完全相同,只是 URL 多了 /objective/<statement_identifier> 層級。

列出 AO 關聯

GET /api/1.0/ssp/<ssp_uid>/control-implementation/<ctrl_id>/objective/<stmt_id>/document-mappings

建立 AO 關聯

POST .../objective/<stmt_id>/document-mappings
Body: { "document_uids": ["doc-uid-1", "doc-uid-2"] }

取消 AO 關聯

DELETE .../objective/<stmt_id>/document-mapping/<doc_uid>

§5

前端 UI 設計建議

1. 程序書管理頁面(SSP 級別)

入口: SSP 詳情頁面新增「程序書管理」tab 或左側選單項目

┌──────────────────────────────────────────────────────────────┐
│  SSP 程序書管理                              [上傳程序書]      │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────┬──────────┬───────────┬──────────┬──────────┐  │
│  │ 檔案名稱  │ 描述     │ 檔案大小   │ 關聯數量  │ 操作     │  │
│  ├──────────┼──────────┼───────────┼──────────┼──────────┤  │
│  │ 資訊安全  │ v3.0     │ 1.0 MB    │ 5        │ 🔗 🗑   │  │
│  │ 政策.pdf  │          │           │          │          │  │
│  ├──────────┼──────────┼───────────┼──────────┼──────────┤  │
│  │ 存取控制  │ v2.1     │ 512 KB    │ 3        │ 🔗 🗑   │  │
│  │ 管理辦法  │          │           │          │          │  │
│  └──────────┴──────────┴───────────┴──────────┴──────────┘  │
│                                                              │
│  拖拽檔案到此處上傳,或點擊「上傳程序書」按鈕                   │
└──────────────────────────────────────────────────────────────┘

操作:

  • 「上傳程序書」→ 支援多檔選擇或拖拽,上傳後填寫描述
  • 🔗 → 查看此程序書關聯了哪些控制項/AO
  • 🗑 → 刪除(mapping_count > 0 時警告)

2. 控制項/AO 詳情頁的關聯操作

在控制項和 AO 詳情頁面,「參考程序書」區塊改為:

┌────────────────────────────────────────────┐
│ 參考程序書                    [關聯程序書]   │
├────────────────────────────────────────────┤
│ 📄 資訊安全政策v3.0.pdf        [取消關聯]   │
│ 📄 存取控制管理辦法.docx       [取消關聯]   │
│                                            │
│ (尚未關聯程序書時顯示空狀態提示)           │
└────────────────────────────────────────────┘

3. 關聯程序書 Dialog

點擊「關聯程序書」按鈕,彈出從池中選擇的 Dialog:

┌────────────────────────────────────────────┐
│ 關聯程序書                     [搜尋...]    │
├────────────────────────────────────────────┤
│ ☑ 資訊安全政策v3.0.pdf     (已關聯)       │
│ ☐ 存取控制管理辦法.docx                     │
│ ☐ 實體安全管理規範.pdf                      │
│ ☑ 事件應變計畫.pdf         (已關聯)       │
├────────────────────────────────────────────┤
│              [取消]  [確認關聯]              │
└────────────────────────────────────────────┘

邏輯:

  • 從池 API 取得所有程序書 GET /document-pool
  • 從 mapping API 取得已關聯的 GET /document-mappings
  • 已關聯的預設勾選
  • 使用者勾選/取消勾選後,計算差異
  • 確認後:新增的打 POST /document-mappings,取消的打 DELETE /document-mapping/<uid>

4. 上傳流程

// Step 1: 上傳實體檔案
const fileRes = await uploadFile(file)  // POST /file/upload
const fileUid = fileRes.data.uid

// Step 2: 加入程序書池
await api.post(`/ssp/${sspUid}/document-pool`, {
  documents: [{ file_uid: fileUid, description: '資訊安全政策 v3.0' }]
})

// Step 3: 刷新池清單
refreshPool()

§6

API 總覽

Method URL 說明
GET /ssp/<uid>/document-pool 列出程序書池
POST /ssp/<uid>/document-pool 上傳到池
DELETE /ssp/<uid>/document-pool/<doc_uid> 從池刪除(cascade)
GET .../control-implementation/<ctrl>/document-mappings 控制項的關聯清單
POST .../control-implementation/<ctrl>/document-mappings 建立控制項關聯
DELETE .../control-implementation/<ctrl>/document-mapping/<doc_uid> 取消控制項關聯
GET .../objective/<stmt>/document-mappings AO 的關聯清單
POST .../objective/<stmt>/document-mappings 建立 AO 關聯
DELETE .../objective/<stmt>/document-mapping/<doc_uid> 取消 AO 關聯

§7

與舊 API 的關係

舊 API 狀態 說明
POST .../reference-documents 保留但建議逐步遷移 舊的直接上傳到控制項/AO
DELETE .../reference-document/<uid> 保留 舊的刪除
document-pool 系列 新增 池 + 關聯模式

建議前端先實作新的池 + 關聯 UI,舊的 API 暫時保留相容,後續視遷移進度決定是否廢棄。