03f8caf3e9
後端 - 新增 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>
5.1 KiB
5.1 KiB
Requirement: Member 新增評價
已完成特定課程的 Member SHALL 能對該課程留下一次評價(星等 + 文字)。
Scenario: 成功新增評價
- WHEN 已登入 Member 送出
POST /api/member/reviews,包含diving_offer_id、rating(1–5 整數)、comment(非空字串),且系統查詢bookings JOIN course_schedules找到至少一筆member_id = X AND diving_offer_id = Y AND status = 'completed' - THEN 系統建立 Review,回傳 201
Scenario: 未完成課程不可評價(資格驗證)
- WHEN Member 送出評價,但
bookings中不存在任何status = 'completed'且對應diving_offer_id的紀錄 - THEN 系統回傳 403,message:「須完成此課程後才能評價」
Scenario: 每門課只能評一次
- WHEN
reviews中已存在同一member_id+diving_offer_id的紀錄 - THEN 系統回傳 422(非 409),message:「已評價,如需修改請使用編輯功能」
Scenario: 星等範圍驗證
- WHEN
rating不在 1–5 之間 - THEN 系統回傳 422
Requirement: 評價後即時更新課程統計
Member 新增、修改或刪除評價時,系統 SHALL 在同一 DB transaction 內重算 diving_offers.rating 與 reviews。Provider 或 Admin 手動標記 booking 為 completed 亦同樣觸發評價資格。
Scenario: 新增評價後重算
- WHEN Review 建立成功
- THEN
diving_offers.rating = ROUND(AVG(rating), 1)、diving_offers.reviews = COUNT(*)即時更新
Scenario: 刪除評價後重算
- WHEN Review 被 Member 或 Admin 刪除
- THEN
rating與reviews在同一 transaction 內重算;若剩餘 0 筆評價,rating = 0、reviews = 0
Requirement: Member 修改評價
Member SHALL 能修改自己的評價,系統保留最近一次修改前的版本並標記已修改。
Scenario: 成功修改評價
- WHEN Member 送出
PUT /api/member/reviews/{id},包含新的rating或comment - THEN 系統將舊版
rating/comment寫入review_edits(若已存在則覆蓋);更新 Review 內容;將is_edited = true;重算課程統計
Scenario: 只能修改自己的評價
- WHEN Member 嘗試修改他人的評價
- THEN 系統回傳 403
Requirement: Member 刪除評價
Member SHALL 能刪除自己的評價,Admin SHALL 能刪除任何評價。
Scenario: Member 刪除自己的評價
- WHEN Member 送出
DELETE /api/member/reviews/{id} - THEN 系統刪除 Review 及對應的 review_edits / review_votes;重算課程統計
Scenario: Admin 刪除任意評價
- WHEN Admin 送出
DELETE /api/admin/reviews/{id} - THEN 系統刪除 Review 及關聯資料;重算課程統計
Scenario: 只能刪除自己的評價(非 Admin)
- WHEN 非 Admin Member 嘗試刪除他人評價
- THEN 系統回傳 403
Requirement: 評價公開顯示(匿名)
任何人(含未登入)SHALL 能查看課程評價列表,評價人統一顯示為「匿名潛水者」。Provider 在 Coach Portal 亦可查看自己課程的評價(只讀)。
Scenario: 取得評價列表(含 summary)
- WHEN 任何人送出
GET /api/diving-offers/{id}/reviews?sort=helpful|rating|newest - THEN 系統回傳
summary(平均星等、總數、1–5 星分布)與reviews列表;reviewer_name一律為「匿名潛水者」;已登入 Member 額外回傳is_mine;未登入has_voted固定為false、is_mine欄位省略
Scenario: 三種排序
- WHEN
sort=helpful(預設) - THEN 依
helpful_count DESC, created_at DESC排序 - WHEN
sort=rating - THEN 依
rating DESC, created_at DESC排序 - WHEN
sort=newest - THEN 依
created_at DESC排序
Requirement: 課程完成標記(評價資格觸發)
Provider 或 Admin SHALL 能手動將 confirmed 預約標記為 completed,讓 Member 可立即評價,不需等待排程。
Scenario: Provider 手動完成
- WHEN Provider 送出
PUT /api/provider/bookings/{id}/complete,Booking status 為confirmed - THEN Booking status 改為
completed,Member 即可對該課程送出評價
Scenario: Admin 手動完成
- WHEN Admin 送出
PUT /api/admin/bookings/{id}/complete,Booking status 為confirmed - THEN Booking status 改為
completed
Requirement: 評價建立後觸發 Provider 通知
評價系統 SHALL 在 Member 成功建立評價後,通知課程所屬 Provider(僅站內通知,不寄 Email)。ReviewService::create() MUST 在評價資料儲存成功後觸發通知,以 try/catch 包裹確保主業務不受影響。
Scenario: 評價成功送出
- WHEN
ReviewService::create()建立新評價,reviews資料表寫入成功 - THEN
$provider->notify(new ReviewReceivedNotification($review))被呼叫,Provider 站內通知新增一筆
Scenario: 通知失敗不影響評價建立
- WHEN notify 呼叫失敗(例:DB 寫入通知失敗)
- THEN 評價資料已正確儲存,HTTP response 成功回傳,錯誤記錄至 log