From 72daf9baa0535c79c725ccef6b8188bbb8964b67 Mon Sep 17 00:00:00 2001 From: Hank Date: Sun, 18 Oct 2026 01:45:26 +0800 Subject: [PATCH] =?UTF-8?q?chore(openspec):=20=E6=AD=B8=E6=AA=94=20fix-boo?= =?UTF-8?q?king-exception-handling=20=E4=B8=A6=E5=90=8C=E6=AD=A5=E4=B8=BB?= =?UTF-8?q?=E8=A6=8F=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - booking-lifecycle 主規格新增「預約交易錯誤分類」requirement - tasks 3.2 正式環境驗證完成(建立、確認預約正常,未觀察到 500) - 待辦文件標記 M7 完成 Co-Authored-By: Claude Opus 5.5 --- .../2026-10-14-project-review-todo.md | 2 +- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/booking-lifecycle/spec.md | 0 .../tasks.md | 3 ++- openspec/specs/booking-lifecycle/spec.md | 23 +++++++++++++++++++ 7 files changed, 26 insertions(+), 2 deletions(-) rename openspec/changes/{fix-booking-exception-handling => archive/2026-10-18-fix-booking-exception-handling}/.openspec.yaml (100%) rename openspec/changes/{fix-booking-exception-handling => archive/2026-10-18-fix-booking-exception-handling}/design.md (100%) rename openspec/changes/{fix-booking-exception-handling => archive/2026-10-18-fix-booking-exception-handling}/proposal.md (100%) rename openspec/changes/{fix-booking-exception-handling => archive/2026-10-18-fix-booking-exception-handling}/specs/booking-lifecycle/spec.md (100%) rename openspec/changes/{fix-booking-exception-handling => archive/2026-10-18-fix-booking-exception-handling}/tasks.md (96%) diff --git a/docs/analysis/2026-10-14-project-review-todo.md b/docs/analysis/2026-10-14-project-review-todo.md index e62e323..13a4bf2 100644 --- a/docs/analysis/2026-10-14-project-review-todo.md +++ b/docs/analysis/2026-10-14-project-review-todo.md @@ -21,7 +21,7 @@ | M4 | 🟡 中 | 前端 token 儲存不一致(admin 用 localStorage) | 是 | `unify-frontend-token-storage` | | M5 | 🟡 中 | CORS 在正式環境仍允許 localhost | 是(小) | `restrict-cors-by-env` | | M6 | 🟡 中 | CI 使用 SQLite,鎖定/超賣測試可能假綠 | 是 | `ci-mysql-test-job` | -| M7 | 🟡 中 | 預約流程 `catch (RuntimeException)` 把例外訊息直接回給使用者 | 是(小) | `fix-booking-exception-handling` | +| M7 | ✅ 已完成(PR #74、#75) | 預約流程 `catch (RuntimeException)` 把例外訊息直接回給使用者 | 是(小) | `fix-booking-exception-handling` | | M8 | 🟡 中(影響資料正確性,需並發才觸發) | 預約取消/確認的狀態在鎖之前讀取,並發下名額可能重複增減 | 是 | `fix-booking-state-race`(建議併入 M2) | 建議順序:H1 → H2 → M7 → M6 → M5 → M8 與 M2 合併處理 → M1 → M4 → M3(M3 已下修,可視驗證結果延後或關閉)。 diff --git a/openspec/changes/fix-booking-exception-handling/.openspec.yaml b/openspec/changes/archive/2026-10-18-fix-booking-exception-handling/.openspec.yaml similarity index 100% rename from openspec/changes/fix-booking-exception-handling/.openspec.yaml rename to openspec/changes/archive/2026-10-18-fix-booking-exception-handling/.openspec.yaml diff --git a/openspec/changes/fix-booking-exception-handling/design.md b/openspec/changes/archive/2026-10-18-fix-booking-exception-handling/design.md similarity index 100% rename from openspec/changes/fix-booking-exception-handling/design.md rename to openspec/changes/archive/2026-10-18-fix-booking-exception-handling/design.md diff --git a/openspec/changes/fix-booking-exception-handling/proposal.md b/openspec/changes/archive/2026-10-18-fix-booking-exception-handling/proposal.md similarity index 100% rename from openspec/changes/fix-booking-exception-handling/proposal.md rename to openspec/changes/archive/2026-10-18-fix-booking-exception-handling/proposal.md diff --git a/openspec/changes/fix-booking-exception-handling/specs/booking-lifecycle/spec.md b/openspec/changes/archive/2026-10-18-fix-booking-exception-handling/specs/booking-lifecycle/spec.md similarity index 100% rename from openspec/changes/fix-booking-exception-handling/specs/booking-lifecycle/spec.md rename to openspec/changes/archive/2026-10-18-fix-booking-exception-handling/specs/booking-lifecycle/spec.md diff --git a/openspec/changes/fix-booking-exception-handling/tasks.md b/openspec/changes/archive/2026-10-18-fix-booking-exception-handling/tasks.md similarity index 96% rename from openspec/changes/fix-booking-exception-handling/tasks.md rename to openspec/changes/archive/2026-10-18-fix-booking-exception-handling/tasks.md index 0f8555d..3b92a02 100644 --- a/openspec/changes/fix-booking-exception-handling/tasks.md +++ b/openspec/changes/archive/2026-10-18-fix-booking-exception-handling/tasks.md @@ -21,4 +21,5 @@ ## 3. 手動驗證 - [x] 3.1 [手動驗證] 變異驗證(只在本機執行,不提交):暫時把 1.2、1.3 的 catch 改回 `\RuntimeException`,確認 2.4、2.5 失敗後再還原,證明測試抓得到原本的問題 -- [ ] 3.2 [手動驗證] 部署後在正式環境建立一筆預約、確認一筆預約,確認業務流程正常(201/200);Sentry 是否收到預約端點的 500 只能等實際錯誤發生,若期間沒有錯誤則記錄為「未觀察到」,不視為失敗(自動化驗證以 2.4、2.5 為準) +- [x] 3.2 [手動驗證] 部署後在正式環境建立一筆預約、確認一筆預約,確認業務流程正常(201/200);Sentry 是否收到預約端點的 500 只能等實際錯誤發生,若期間沒有錯誤則記錄為「未觀察到」,不視為失敗(自動化驗證以 2.4、2.5 為準) + - 2026-10-18 結果:Hank 在正式環境建立、確認預約皆正常,未出現 500;Sentry 未觀察到預約端點的 500 diff --git a/openspec/specs/booking-lifecycle/spec.md b/openspec/specs/booking-lifecycle/spec.md index f3fd137..3c6bea5 100644 --- a/openspec/specs/booking-lifecycle/spec.md +++ b/openspec/specs/booking-lifecycle/spec.md @@ -133,3 +133,26 @@ Member SHALL 能查詢自己所有預約的列表及詳情,含課程連結與 - **WHEN** notify 呼叫拋出例外 - **THEN** 預約狀態已正確儲存,HTTP response 成功回傳,錯誤記錄至 Laravel log + +### Requirement: 預約交易錯誤分類 +建立預約(`POST /api/member/bookings`)與確認預約(`PUT /api/provider/bookings/{id}/confirm`)的交易中,系統 SHALL 區分業務規則失敗與伺服器端故障:業務規則失敗(名額不足、重複預約)回傳 422 與既定訊息;其他任何例外(含資料庫錯誤)MUST NOT 以 422 回傳,MUST 回傳 500,回應內容不得包含 SQL、資料表或欄位名稱,且該例外 MUST 交由例外回報機制記錄(正式環境會送至 Sentry)。兩種情況下交易都 MUST rollback,不留下部分寫入。 + +#### Scenario: 重複預約仍回 422 與既定訊息 +- **WHEN** 已登入 member 對同一時段已有 `pending` 預約,再次送出 `POST /api/member/bookings`(`schedule_id` 相同、`participants` 1) +- **THEN** 系統回傳 422,body 為 `{"status": false, "message": "您已預約此時段"}`,該 member 在此時段的預約筆數不變 + +#### Scenario: 交易內名額不足仍回 422 與既定訊息 +- **WHEN** 已登入 member 送出 `POST /api/member/bookings` 時通過交易前的名額檢查,但交易內鎖定時段後重新計算的剩餘名額小於 `participants` +- **THEN** 系統回傳 422,body 為 `{"status": false, "message": "名額不足,請重新選擇"}`,不建立預約 + +#### Scenario: 確認預約時名額不足仍回 422 與既定訊息 +- **WHEN** 已登入 provider 對自己課程的 `pending` 預約送出 `PUT /api/provider/bookings/{id}/confirm`,而該預約人數大於時段剩餘名額 +- **THEN** 系統回傳 422,body 為 `{"status": false, "message": "名額不足,無法確認此預約"}`,預約維持 `pending`,`current_participants` 不變 + +#### Scenario: 建立預約時資料庫錯誤回 500 且不洩漏細節 +- **WHEN** `APP_DEBUG` 為 false,已登入 member 送出合法的 `POST /api/member/bookings`,而交易內寫入預約之後發生資料庫例外 +- **THEN** 系統回傳 500,body 為通用錯誤訊息且不含 SQL 語句、`bookings` 等資料表名稱與欄位名稱,資料庫例外被回報,`bookings` 筆數不變 + +#### Scenario: 確認預約時資料庫錯誤回 500 且不洩漏細節 +- **WHEN** `APP_DEBUG` 為 false,已登入 provider 對自己課程的 `pending` 預約送出 `PUT /api/provider/bookings/{id}/confirm`(名額足夠),而交易內預約狀態已更新、更新時段名額時發生資料庫例外 +- **THEN** 系統回傳 500,body 為通用錯誤訊息且不含 SQL 語句、資料表名稱與欄位名稱,資料庫例外被回報,預約維持 `pending`,`current_participants` 不變