Files
CFDivePlatform/openspec/specs/booking-lifecycle/spec.md
T
a620906209 03f8caf3e9 feat:實作通知系統 — 站內通知、Email 通知、Polling 機制
後端
- 新增 6 個 Notification class(預約建立/確認/拒絕/取消/完成、收到評價),database + mail 雙 channel
- 新增 NotificationController(list / unread-count / markRead / markAllRead / destroy)
- 整合通知觸發至 MemberBookingController、ProviderBookingController、CompleteFinishedBookings、ReviewController
- 新增 notifications / jobs / failed_jobs migration
- Docker Compose 加入 queue-worker、mailpit service
- DivingOffer 補上 provider() 關聯

前端
- 新增 notificationStore(Polling 30s/60s 自適應 + Page Visibility API)
- 新增 NotificationBell(未讀 Badge)、NotificationDrawer(側邊通知中心)
- main.js:auth store init 前置於 router.use(),修正 beforeEach guard 時序問題
- notificationAxios:依路徑動態選擇 member/coach token
- NotificationDrawer:改用 new URL().pathname 提取 action_url 路徑

OpenSpec
- 歸檔 notification-system change
- 同步 notification-core / notification-email / notification-triggers specs 至主規格
- 更新 booking-lifecycle / review-lifecycle spec(補充通知觸發 requirement)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-17 22:26:14 +08:00

6.1 KiB
Raw Blame History

Requirement: Member 送出預約

Member SHALL 能選擇一個開放時段送出預約,系統記錄價格快照。pending 狀態不佔用時段名額。

Scenario: 成功建立預約

  • WHEN 已登入 Member 送出 POST /api/member/bookings,指定 schedule_idparticipants(≥1
  • THEN 系統建立 Bookingstatus 為 pendingtotal_price 快照為 diving_offer.price × participants,回傳 201

Scenario: 時段已滿無法預約

  • WHEN 指定時段 status 為 fullcancelled
  • THEN 系統回傳 422,告知時段不可用

Scenario: 超過剩餘名額(API 層快速驗證)

  • WHEN participants 大於時段當前剩餘名額(max_participants - current_participants),在進入 DB transaction 前
  • THEN 系統回傳 422,告知人數超過上限,不進入 lockForUpdate 流程

Scenario: 超過剩餘名額(DB 層二次驗證)

  • WHEN API 層通過但 lockForUpdate 後重新計算剩餘名額仍不足(race condition 情境)
  • THEN 系統 rollback transaction,回傳 422,告知名額不足

Scenario: 不可重複預約同一時段

  • WHEN Member 對同一 schedule_id 已有 pendingconfirmed 狀態的 Booking
  • THEN 系統回傳 422,告知已有預約(取消後可重新預約)

Requirement: 預約狀態機

系統 SHALL 維護七個合法狀態,且只允許以下轉換:

  • pendingconfirmedProvider 確認)
  • pendingrejectedProvider 拒絕)
  • pendingmember_cancelledMember 取消)
  • pendingexpiredScheduler 超時)
  • confirmedcompletedScheduler 課程後自動)
  • confirmedmember_cancelledMember 取消)
  • confirmedprovider_cancelledProvider 取消)

Scenario: 非法狀態轉換被拒絕

  • WHEN 任何角色嘗試執行上述以外的狀態轉換
  • THEN 系統回傳 422,說明當前狀態不允許此操作

Requirement: Provider 確認或拒絕預約

Provider SHALL 能對自己課程的 pending 預約執行確認或拒絕。確認時才真正佔用時段名額。

Scenario: 確認預約

  • WHEN Provider 送出 PUT /api/provider/bookings/{id}/confirm
  • THEN Booking status 改為 confirmed,時段 current_participants 增加對應人數

Scenario: 拒絕預約

  • WHEN Provider 送出 PUT /api/provider/bookings/{id}/reject
  • THEN Booking status 改為 rejectedcurrent_participants 不變(pending 未佔位)

Scenario: 只能操作自己課程的預約

  • WHEN Provider 嘗試操作不屬於自己課程的 Booking
  • THEN 系統回傳 403 Forbidden

Requirement: Provider 取消已確認預約

Provider SHALL 能取消 confirmed 狀態的預約(例如天氣因素)。

Scenario: Provider 取消確認中預約

  • WHEN Provider 送出 PUT /api/provider/bookings/{id}/cancel
  • THEN Booking status 改為 provider_cancelled,時段名額釋放

Requirement: Member 取消預約

Member SHALL 能取消自己的 pendingconfirmed 預約,但須在課程開始前 24 小時之前提出。

Scenario: 取消 pending 預約(期限內)

  • WHEN Member 送出 DELETE /api/member/bookings/{id}Booking status 為 pending,且當前時間早於 scheduled_date + start_time - 24h
  • THEN Booking status 改為 member_cancelledcurrent_participants 不變

Scenario: 取消 confirmed 預約(期限內)

  • WHEN Member 送出 DELETE /api/member/bookings/{id}Booking status 為 confirmed,且當前時間早於 scheduled_date + start_time - 24h
  • THEN Booking status 改為 member_cancelled,時段名額釋放

Scenario: 課程開始前 24h 內不可取消

  • WHEN Member 送出 DELETE /api/member/bookings/{id},但當前時間距 scheduled_date + start_time 不足 24 小時
  • THEN 系統回傳 422,告知「距課程開始不足 24 小時,無法取消,請聯繫教練」;Booking 狀態不變

Scenario: 不可取消已終態預約

  • WHEN Booking status 為 completedrejectedexpiredprovider_cancelled
  • THEN 系統回傳 422,告知無法取消

Requirement: 系統自動過期 pending 預約

Scheduler SHALL 每小時掃描 pending 超過 48 小時的 Booking 並標記為 expired

Scenario: 過期觸發

  • WHEN Booking status 為 pendingcreated_at 早於 48 小時前
  • THEN Scheduler 將 status 改為 expiredcurrent_participants 不變(pending 未佔位)

Requirement: 系統自動完成 confirmed 預約

Scheduler SHALL 每日掃描課程日期已過的 confirmed Booking 並標記為 completed

Scenario: 自動完成

  • WHEN Booking status 為 confirmed,對應 course_schedule.scheduled_date 早於今天
  • THEN Scheduler 將 status 改為 completed

Requirement: Member 查看自己的預約列表

Member SHALL 能查詢自己所有預約的列表及詳情,含課程連結與完整課程資訊。

Scenario: 取得預約列表

  • WHEN 已登入 Member 送出 GET /api/member/bookings
  • THEN 系統回傳該 Member 所有 Booking,含 offer_id、課程名稱、地點、時段日期、狀態、金額

Scenario: 取得單一預約詳情

  • WHEN 已登入 Member 送出 GET /api/member/bookings/{id}
  • THEN 系統回傳該 Booking 詳情;若非本人預約則回傳 403

Requirement: 預約狀態轉換觸發通知

預約七狀態機(pending / confirmed / completed / rejected / expired / member_cancelled / provider_cancelled)的每個轉換點,系統 SHALL 在狀態成功更新後觸發對應通知(詳見 notification-triggers spec)。通知觸發 MUST 在主業務 transaction commit 之後執行,且以 try/catch 包裹,不影響主業務結果。

Scenario: 狀態轉換後通知觸發

  • WHEN BookingService 中任一狀態轉換方法成功執行
  • THEN 對應的 Notification class 被觸發,不論通知是否成功主業務均正常回傳

Scenario: 通知失敗不影響主業務

  • WHEN notify 呼叫拋出例外
  • THEN 預約狀態已正確儲存,HTTP response 成功回傳,錯誤記錄至 Laravel log