# 專案受評範圍合併（Audit Scope Consolidation）— 設計文件

> **狀態**：設計確認中｜**範圍**：Frontend only｜**估時**：3-5 天
> **建立日期**：2026-04-25｜**作者**：Raymond + Claude

## 1. 需求說明

「資訊系統 (Information System, IS)」與「設備 (Device)」目前在兩個專案設定畫面裡分別佔據獨立的步驟 / Tab。從 user 視角來看，兩者都是「這個專案要稽核的範圍」之組成元件，分散在不同 step / tab 造成認知負擔。

本次需求：將兩者在 UI 層合併到單一區塊「**受評範圍**」，但保留現有資料模型與後端不動，作為將來 OSCAL SSP 匯出 / 跨模組引用的階段性踏腳石。

### 涉及的兩個畫面

| 路由 | 檔案 |
|------|------|
| `/project/projects/new` | `src/views/project/ProjectCreateView.vue` |
| `/project/projects/:projectUID/settings` | `src/views/project/ProjectSettingsView.vue` |

## 2. 目標 & 非目標

### ✅ 目標

- 把 IS 和 Device 的 picker 合併到一個 section「**受評範圍**」（Audit Scope）
- ProjectSettings：4 tabs → 3 tabs
- ProjectCreate：5 steps → 4 steps，順序調整為「基本資訊 → 參與者 → 受評範圍 → 確認」
- 兩個畫面共用一個新元件 `AuditScopeSection.vue`（包裝既有 SystemPicker + DevicePicker）
- 不影響任何既有功能 / API / DB 行為

### ❌ 非目標（明確排除）

- 不動 BE：`update_project` API、`PUT /api/1.0/grc-project/<id>` request shape、各 service 全部維持
- 不動 schema：`information_systems`、`devices`、`project_information_systems`、`project_device_mapping`、`job_execution_device_mapping`、`evidence` 全部不動
- 不動既有元件本身：`SystemPicker.vue` / `DevicePicker.vue` / `DeviceManage.vue` / `InformationSystemManage.vue`
- 不動 jedi-device 套件 / jedi_information_system 模組
- 不引入 ComponentService / ComponentPicker / FK 關聯
- 不做 OSCAL SSP exporter
- 不動 device 相關的高耦合區（job_execution_device_mapping、evidence.ref_id、device_view 聚合 SQL）

## 3. 設計選型過程

### 3.1 評估的方案

針對「合併」二字，討論過 5 個層次的方案：

| 方案 | 描述 | 估時 | 風險 |
|------|------|------|------|
| A | 大合併：JSONB 統一 components 表，廢掉 IS/Device | 4-6 週 | 高（影響 job、evidence、跑中流程） |
| B | 統一 component + 多表繼承（基底 + type 子表） | 3-4 週 | 中高 |
| C | UI 合併、資料分開、加 system-device-mapping 多對多 | 2 週 | 中 |
| D | 兩表保留 + 加 `device.information_system_id` FK + ComponentService 抽象 + 樹狀 ComponentPicker | 1.5-2 週 | 低 |
| **E** | **純 UI 整併，BE / schema 全不動** | **3-5 天** | **極低** |

### 3.2 為什麼選 E

關鍵發現（透過外部 reference 掃描）：

1. **Device 在 runtime 重度耦合**：
   - `job_execution_device_mapping` — job 完成判定要 device 全完成
   - `evidence.ref_id == device_uid` — 證據檔靠 device_uid 分類
   - 有獨立 `device_view` 複雜聚合 SQL（job × device × evidence × subtask × assignee）

2. **IS 相對輕量**：只在 project 啟動時做一次性 OSCAL SSP `system_characteristic` 快照，settings 改 IS 不會影響跑中的 job

3. **存檔流程超簡單**：`project_service.update_project` 對 audit_systems / devices 都是 M2M replace，沒有任何 cascade。UI 怎麼合都不會打壞 BE

4. **OSCAL SSP 匯出是 future**：用戶確認 Phase 1 不做匯出。匯出時可選 (a) 純讀現有表 (b) 加 mapping 小表 (c) 加 FK 到 device — 三條路在 Phase 1 都不需 lock-in

5. **E → 任一條 Phase 2 路線的 throwaway 小**：picker UI 元件約 3-5 天會被換掉，但 tab 結構、版面、route 都能保留

### 3.3 Phase 2 方向（不在本次計畫）

當未來決定要做 OSCAL SSP 匯出時，可選擇：

- **(a) 純讀既有表**：直接 query `information_systems` + `devices`，產出兩個獨立的 OSCAL component / inventory-item 區塊，不改 schema
- **(b) 加 mapping 小表**：新增 `device_system_mapping(device_id, system_id)`，只在匯出時用，不動既有表
- **(c) 加 FK 到 device + ComponentService（D 方案完整版）**：device.information_system_id + 樹狀 ComponentPicker + UI 表達層級

Phase 1 的設計對以上三條路徑都中立。

## 4. 系統 / 邏輯架構

```
ProjectCreateView.vue           ProjectSettingsView.vue
       │                                 │
       └────────────┬────────────────────┘
                    │
                    ▼
         AuditScopeSection.vue  ◄── 新元件（本次新增）
                    │
        ┌───────────┴───────────┐
        ▼                       ▼
SystemPicker.vue (既有)    DevicePicker.vue (既有)
        │                       │
        ▼                       ▼
   API.INFORMATION_       API.DEVICES /
   SYSTEMS_MENU           menuStore.deviceMenu
```

存檔仍走既有 API：

```
SystemPicker.@change → syncProjectSystems()
   → PUT /api/1.0/grc-project/<id>  body: { audit_systems: [...] }

DevicePicker.@change → syncProjectDevices()
   → PUT /api/1.0/grc-project/<id>  body: { devices: [...] }
```

兩支 sync function 維持獨立呼叫（不做合併呼叫）。

## 5. UI 變更明細

### 5.1 ProjectSettingsView：4 tabs → 3 tabs

| 變更前 | 變更後 |
|--------|--------|
| Tab 1: 基本資訊 | Tab 1: 基本資訊（不動） |
| Tab 2: 稽核系統 (n) | **🆕 Tab 2: 受評範圍 (n 系統 / m 設備)** |
| Tab 3: 參與者 (n) | Tab 3: 參與者（不動） |
| Tab 4: 設備 (n) | （刪除，內容搬到 Tab 2）|

**Tab header 計數**：分開顯示「3 系統 / 5 設備」格式（不用加總）。

### 5.2 ProjectCreateView：5 steps → 4 steps

| 變更前 | 變更後 |
|--------|--------|
| Step 0: 基本資訊 | Step 0: 基本資訊（不動） |
| Step 1: 稽核系統 | （移到 Step 2） |
| Step 2: 參與者 | **Step 1: 參與者**（位置往前） |
| Step 3: 設備 | （移到 Step 2） |
| Step 4: 確認 | **🆕 Step 2: 受評範圍** → Step 3: 確認 |

確認頁區段：原本 4 區段（基本 / 系統 / 參與者 / 設備）→ 3 區段（基本 / 參與者 / 受評範圍）。受評範圍區段內部展開兩個子區塊：「資訊系統」+「設備」。

### 5.3 新元件：`AuditScopeSection.vue`

**位置**：`src/components/grc/AuditScopeSection.vue`

**版面**（上下堆疊，stacked）：

```
┌─ 受評範圍 ───────────────────────────┐
├─────────────────────────────────────┤
│ 📦 資訊系統  [+ 新增] [搜尋...]        │
│   • 人事系統                         │
│   • 財務系統                         │
├─────────────────────────────────────┤
│ 💻 設備  [+ 新增] [搜尋...]           │
│   • ap-server-01                    │
│   • db-server-01                    │
└─────────────────────────────────────┘
```

**Props**：

```ts
interface Props {
  // System
  selectedSystemIds: string[]
  selectedSystemDetails: Record<string, any>
  infoSystemMenu: { id: string; name: string; abbreviation: string | null }[]
  // Device
  selectedDevices: string[]
  selectedDeviceDetails: Record<string, any>
  deviceMenu: any[]
  // Shared
  userMenu: any[]
  readonly?: boolean
  confirmBeforeRemove?: boolean
}
```

**Emits**：

```ts
defineEmits<{
  // System
  'update:selectedSystemIds': [string[]]
  'update:selectedSystemDetails': [Record<string, any>]
  'update:infoSystemMenu': [{...}[]]
  'change-systems': []
  // Device
  'update:selectedDevices': [string[]]
  'update:selectedDeviceDetails': [Record<string, any>]
  'change-devices': []
  'device-created': []
  'device-detail-saved': []
}>()
```

**內部結構**：使用 v-model 雙向綁定既有 SystemPicker / DevicePicker 的所有 props，emit 透傳父層。元件本身不做 API 呼叫、不存狀態，純粹是 layout wrapper。

**不顯示**：「未來支援層級關聯」hint（user 確認不要）。

> **後續調整（v0.2）**：實作後 user 試用發現「合併 tab 沒有 Save 按鈕」與其他有 Save 的 tab 並列產生違和感（沿用原本即時 sync 設計）。後加上：
> - Settings 模式下加 ⓘ 小字 hint「本區塊變更會即時自動儲存」（i18n key `section_audit_scope_autosave_hint`）
> - `syncProjectSystems` / `syncProjectDevices` 成功後 toast「已自動儲存」（life 1500ms，i18n key `toast_autosaved`）
> - Create 模式（wizard）不顯示此 hint，因為 wizard 是 deferred save 不是即時 sync

## 6. i18n 變更

### 6.1 新增 key（zh-tw + en 同步）

| key | zh-tw | en | 用途 |
|-----|-------|----|----|
| `project_settings.tab_audit_scope` | `受評範圍 ({systems} 系統 / {devices} 設備)` | `Audit Scope ({systems} systems / {devices} devices)` | Settings tab header |
| `project_settings.section_audit_scope_desc` | `本專案要稽核的資訊系統與設備` | `Information systems and devices in scope for this audit` | Settings 模式下區塊描述 |
| `project_settings.section_audit_scope_autosave_hint` | `本區塊變更會即時自動儲存，無需按存檔` | `Changes in this section are saved automatically — no save button needed` | Settings 模式下 ⓘ 小字提示（v0.2 加） |
| `project_settings.toast_autosaved` | `已自動儲存` | `Auto-saved` | sync 成功後 toast（v0.2 加） |
| `project_create.step_audit_scope` | `受評範圍` | `Audit Scope` | Create wizard step header |
| `project_create.audit_scope_title` | `受評範圍` | `Audit Scope` | Create step 內標題 |
| `project_create.audit_scope_subtitle` | `選擇本專案要稽核的系統與設備` | `Select systems and devices for this audit` | Create step 內副標題 |
| `project_create.review_audit_scope` | `受評範圍` | `Audit Scope` | Confirm 頁區段標題 |
| `project_create.review_audit_scope_systems` | `資訊系統` | `Information Systems` | Confirm 頁子區塊 |
| `project_create.review_audit_scope_devices` | `設備` | `Devices` | Confirm 頁子區塊 |

> 註：`project_settings.section_audit_scope`（純標題、不含 desc）原本也有定義但未被消費，留待 cleanup PR 移除。

### 6.2 保留 key

`section_audit_systems` / `section_devices` / `tab_audit_systems` / `tab_devices` 仍由內部 SystemPicker / DevicePicker 使用，**保留不刪**。PR review 時再決定要不要清理舊的 tab key。

### 6.3 檔案路徑

依專案既有 i18n 切分慣例（待 implementer 在 implementation 階段確認實際 file 結構）：
- `src/config/locales/i18n/zh-tw/project-settings.json`（或對應檔）
- `src/config/locales/i18n/en/project-settings.json`

## 7. 後端不變說明

`PUT /api/1.0/grc-project/<id>` 已支援以下三種 request body：

```json
// 只更新系統
{ "audit_systems": [{"information_system_uid": "..."}, ...] }

// 只更新設備
{ "devices": [{"device_uid": "..."}, ...] }

// 同時更新（也支援，但 Phase 1 不使用此模式）
{ "audit_systems": [...], "devices": [...] }
```

Phase 1 維持目前 FE 的「即時 sync 各自呼叫」模式：使用者勾選系統 → 立刻 PUT `audit_systems`；勾選設備 → 立刻 PUT `devices`。不做合併呼叫。

## 8. 實作步驟（建議順序）

1. **建 `AuditScopeSection.vue`**：先獨立元件，定義 props/emits 介面
2. **i18n 加 key**：兩語系同步加（zh-tw + en），先不刪舊 key
3. **改 `ProjectSettingsView.vue`**：tab 合併（4 → 3），用 AuditScopeSection 取代原 tab 2 + tab 4
4. **改 `ProjectCreateView.vue`**：step 重排（5 → 4，順序 (2)），確認頁區段更新
5. **i18n key audit**：grep 確認沒有 dead key
6. **建立 changelog**：`docs/changelog/2026-04-25-audit-scope-merge.md`

## 9. 測試計畫

### 9.1 ProjectSettings 手測

- [ ] 既有專案開啟 → 「受評範圍」tab 顯示，原選的系統 / 設備正確帶出
- [ ] 系統區塊：搜尋、選 / 取消、展開詳情、新增系統 → 即時 sync（network tab 看 PUT request body 含 `audit_systems`）
- [ ] 設備區塊：搜尋、選 / 取消、展開詳情、新增設備 → 即時 sync（PUT body 含 `devices`）
- [ ] 切到別的 tab 再切回，狀態保留
- [ ] readonly user（非 manager）→ 兩區塊都 readonly
- [ ] tab header 計數正確顯示「3 系統 / 5 設備」格式
- [ ] 系統 / 設備數量為 0 時也正確顯示

### 9.2 ProjectCreate 手測

- [ ] 4 step 流程順走：基本資訊 → 參與者 → 受評範圍 → 確認 → 建立成功
- [ ] 受評範圍 step：兩區塊都能新增 / 移除
- [ ] 確認頁：受評範圍區段內列出系統 + 設備兩個子清單
- [ ] 從確認頁回上一步編輯，回來資料還在
- [ ] 跳過受評範圍（兩區塊都空）→ 建立成功（既有 BE 允許 audit_systems / devices 為空 list）

### 9.3 Regression（重點顧及，**必跑**）

- [ ] ProjectAuditorOverview 不受影響（顯示 systems / devices 正確）
- [ ] JobExecutionDrawer 不受影響（device 列表正常）
- [ ] DeviceManage（獨立管理頁）不受影響
- [ ] InformationSystemManage（獨立管理頁）不受影響
- [ ] BE log 確認 PUT request body 結構不變（重點：仍是 `audit_systems` / `devices` 兩個 key 各自獨立 sync）
- [ ] OSCAL Project start 流程（POST `/oscal-project/start`）不受影響

### 9.4 i18n 測試

- [ ] zh-tw 語系：所有新加 key 顯示正確、無 raw key 漏翻
- [ ] en 語系：同上
- [ ] 切換語系時 tab header / step header 即時更新

## 10. 風險評估

| 風險 | 機率 | 影響 | mitigation |
|------|------|------|----------|
| i18n key rename / 漏翻 | 中 | 低（顯示 raw key） | 兩語系同步加 + grep audit |
| user 找不到原本的 tab | 低 | 低（位置變但功能在） | tab header 計數明確顯示「N 系統 / M 設備」 |
| 確認頁區段重排造成 user 慣性錯亂 | 低 | 極低 | 只是視覺合併，資料不變 |
| AuditScopeSection 透傳 props 漏接 | 中 | 中（功能殘缺） | 開發階段對照 SystemPicker / DevicePicker 完整 props 列表 |
| Step 順序調整造成既有教學文件 / 截圖過時 | 低 | 低 | 改完 PR 描述列出文件需更新清單 |

## 11. 變更紀錄要求

依 CLAUDE.md 規範，PR 落地時必須建立：
- `docs/changelog/2026-04-25-audit-scope-merge.md`

內容包含：需求說明 / 變更檔案清單 / 行為差異（無 API 變更）/ 參考連結到本 design doc。

## 12. 後續引用

- 實作計畫文件（之後寫）：`docs/features/FR-017-2604-audit-scope-merge/implementation-plan.md`
- 變更紀錄：`docs/changelog/2026-04-25-audit-scope-merge.md`（PR 落地時建立）
