# FR-052 實作計畫 — 雲端整合 Tab 顯示 Gate

> 依 `docs/features/FR-052-2607-cloud-tab-gate/design.md`。
> 跨 repo：FE 為主（compliance-manager-fe）；BE 視 Step 0 選項（零或極小）。
> **只規劃不實作**——user approve 後才進 Phase 5。

---

## CLAUDE.md 規範檢查

- [x] **DDD 層級**：選項 B 新 route 不查 DB，委派 app service；app service reuse `get_status`。
- [x] **@transaction**：`get_status` 既有處理；`is_connected` 只讀，掛既有 service。
- [x] **權限**：`jwt_required()`，tenant-scoped（既有）。不新增角色檢查（唯讀 gate）。
- [x] **Error code**：不新增。
- [x] **不重複造輪子**：優先 reuse 既有 `GET /integrations/google-drive`（§Step 0 決策）。
- [x] **URL kebab-case**：`/integrations/google-drive/connected`（若走 B）。
- [x] **顯式 git add，禁 -am**；不切 branch；push 等 user 明示。

---

## Step 0 — A/B 二選一（動手前必拍板，design §2.2）

**先讓 user 二選一**（design §2.2 表）：

| 選項 | BE 改動 | 說明 |
|------|---------|------|
| **A（建議）** | 零 | FE 直接打既有 `GET /integrations/google-drive`，判 `status==='CONNECTED'` |
| B（user Phase 0 原選）| 新一支 `/connected` route | 語意最窄但與既有 GET 重疊 |

> user Phase 0 選 B，但當時未知既有 GET 已非 admin gated。**開工前一句話確認**——若改選 A，跳過所有 BE 任務。

**FE 前置 grep**：`CloudIntegrationService`（`src/service/`）是否已有取 tenant drive status 的方法？有 → 直接用，FE 也不重造。

**輸出**：Step 0 結論寫本檔末，再開工。

---

## BE 任務（僅選項 B；選 A 則全跳過）

### Task B1 — is_connected app method〔0.1d〕
- `app/cloud_integration/service/google_drive_integration_service.py`：加
  ```python
  @transaction
  def is_connected(self, tenant_id: int) -> bool:
      return self.get_status(tenant_id).status == "CONNECTED"
  ```

### Task B2 — connected route〔0.15d〕
- `api/cloud_integration/routes/google_drive_integration_route.py`：新 `GoogleDriveConnectedRoute(MethodResource)`，`GET`，`jwt_required()`，回 `return_response(True, {"connected": service.is_connected(user.tenant_id)})`。
- `api/cloud_integration/__init__.py`：`add_resource(GoogleDriveConnectedRoute, "/integrations/google-drive/connected")`。
- **驗**：pytest — CONNECTED tenant 回 true；DISCONNECTED/EXPIRED/REVOKED 回 false。

---

## FE 任務（compliance-manager-fe，主體）

### Task F1 — service 取狀態方法〔選 A：0；選 B：0.15d〕
- **選項 A（review 已證零改動）**：`CloudIntegrationService.getGoogleDriveStatus()` **已存在**（`src/service/CloudIntegrationService.js:14-17`，打 `GET /integrations/google-drive`，已含 BaseService 返回值歸一化）→ **F1 零改動，直接用**。
- 選項 B：新增 `getDriveConnected()` → `GET /integrations/google-drive/connected` + `src/config/api/api.js` 補常數。

### Task F2 — ProjectPlanningView tab gate〔0.3d〕
- `src/views/project/ProjectPlanningView.vue`：
  - `const driveConnected = ref(false)`；`onMounted` 內 `loadDriveStatus()`（A: `s.status==='CONNECTED'`；B: `s.connected===true`；catch → false，fail-closed）。
  - 雲端整合 `<TabPanel>`（:2110）加 `v-if="driveConnected"`。
- **⚠️ 深連結 clamp 必做（review 風險升級：從「保險」改「必做」）**：`ProjectPlanningView.vue:289-296` `TAB_INDEX_BY_QUERY` 含 `cloud:5`，`activeTabIndex` 在 setup 同步初始化、**早於** `loadDriveStatus()` resolve。未連接 tenant 帶 `?tab=cloud` 進來 → `activeIndex=5` 指向不存在的 tab。**F2 必做 clamp**：`loadDriveStatus` resolve 後 `if (!connected && activeTabIndex.value >= 5) activeTabIndex.value = 0`。
  - review 全 FE grep 確認無其他寫死 `tab=cloud` 來源，但 query 是 user 可自由帶的 → clamp 不可省。
- **驗（FR-047 frontend-overview §3.6 雷區）**：雲端 tab 是末 tab（index 5），`v-if` 移除末 tab 不影響前 5 index。連 / 未連 / `?tab=cloud` 深連結三情境驗。

### Task F3 — i18n〔0〕
- 無新文案（決策 2 = 直接隱藏，不加引導）。既有 `tab_cloud_integrations` 譯名不動。

---

## 執行順序 / 依賴

```
Step 0（A/B 拍板 + FE grep 既有 service）
   ↓
[若 B] B1 → B2（BE pytest 綠）
   ↓
F1（service）→ F2（tab v-if）→ 驗 TabView index
   ↓
manual smoke（連 / 未連兩 tenant）→ Phase 4 test-plan → E2E
```

- 選 A：無 BE 任務，直接 F1→F2。
- 回滾：F2 一 commit（移 `v-if` 即恢復恆顯）；B1/B2 各一 commit。

---

## 完成標準
- [ ] CONNECTED tenant：規劃頁看得到雲端整合 tab、可用。
- [ ] 未連接 tenant（DISCONNECTED/EXPIRED/REVOKED）：tab 不出現，前 5 tab 正常、index 無錯位。
- [ ] 狀態查詢失敗：tab 隱藏（fail-closed），頁面不報錯。
- [ ]〔若 B〕BE pytest：is_connected 四狀態正確。

---

## Step 0 查證結果

> 2026-07-21 首腦 review 已做完 FE grep + BE route 複核。A/B 最終仍等 user 一句話拍板（勿自行定案），但證據強烈指向 A。

**✅ FE grep（已做完）**：
- `CloudIntegrationService.getGoogleDriveStatus()` **已存在**（`src/service/CloudIntegrationService.js:14-17`，打 `GET /integrations/google-drive`，已含 BaseService 返回值歸一化）→ **選 A 連 F1 都零改動，FE 只剩 F2 一個 task**。

**✅ BE route 複核（已做完）**：
- `GET /integrations/google-drive` 只有 `jwt_required()`，`DELETE` 才 `viewer_is_super_admin`（`google_drive_integration_route.py:22-50`）→ 選 A 的 reuse 端點對全登入者開放，可行。

**判定**：選 B 的唯一理由「不外洩 email/folder」**不成立**——既有 GET 本來就對全 JWT 開放，B 收不回任何暴露。**選 A 則整個 FR-052 = `ProjectPlanningView.vue` 單檔改動**（F1 零改、只做 F2）。首腦強烈建議 A。

**✅ 決策：走 A（reuse 既有 GET，零 BE 改動）**（2026-07-21 首腦「往下做」授權下定案；B 唯一理由已證不成立）。→ **BE 任務全跳過，FR-052 = `ProjectPlanningView.vue` 單檔 F2**（F1 用既有 `getGoogleDriveStatus()`）。

**✅ 深連結風險已證實（見 F2）**：`?tab=cloud` 殘留 index=5 為真（`ProjectPlanningView.vue:289-296`），F2 clamp 已從「保險」升為「必做」。
