Files
CFDivePlatform/openspec/specs/review-lifecycle/spec.md
T
a620906209 81a9f84b26 feat:實作評價系統 — 匿名評價、有幫助投票、手動完成預約
後端:
- 新增 reviews / review_edits / review_votes migration(含索引)
- Review / ReviewEdit / ReviewVote Model
- ReviewController:評價 CRUD、資格驗證(completed booking)、rating 即時重算
- toggleHelpful:Member 限定、GREATEST 原子防負、DB transaction 同步
- AdminReviewController:全量列表、刪除(含重算)
- AdminBookingController:全量列表、手動標記 completed
- ProviderBookingController 新增 complete 方法(教練手動完成預約)
- DevelopmentSeeder:快速重建測試資料(admin/coach/member + offers + bookings)
- EnsureAdmin middleware 正式納入 bootstrap/app.php
- Nginx server_name 加入 cfdive.local

前端:
- 課程詳情頁加入評價區塊(星等分布、排序切換、撰寫/修改/刪除、有幫助 Toggle)
- Coach Portal 新增「課程評價」頁(只讀,依課程分組)
- Coach 預約管理加入「完成」按鈕
- Admin 新增「預約管理」頁(標記完成)、「評價管理」頁(刪除)
- Admin / Coach Navbar 新增對應連結

OpenSpec:
- review-system change 歸檔至 archive/2026-05-12-review-system
- 新增 specs/review-lifecycle 與 specs/review-voting 主規格
- review-voting spec 補充 Member 限定與 GREATEST 原子更新說明

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-12 02:46:54 +08:00

82 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### Requirement: Member 新增評價
已完成特定課程的 Member SHALL 能對該課程留下一次評價(星等 + 文字)。
#### Scenario: 成功新增評價
- **WHEN** 已登入 Member 送出 `POST /api/member/reviews`,包含 `diving_offer_id``rating`15 整數)、`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` 不在 15 之間
- **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`(平均星等、總數、15 星分布)與 `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`