Files
CFDivePlatform/openspec/specs/provider-verification/spec.md
T
a620906209 43720482c4
Run Tests / test (pull_request) Successful in 2m3s
feat(verification): 教練審核流程完整版——狀態機/證照送審/Admin 裁決/通知
實作 openspec change provider-verification-workflow(25/25 tasks):
- is_verified 升級為 verification_status 四狀態機(unsubmitted/pending/
  approved/rejected),migration 自動轉換既有資料,API 以 accessor 保留
  is_verified 相容輸出
- 教練端:證照上傳(≤3 張、複用 CompressesImages)、送審、駁回原因
  顯示與重送(/coach/verification 新頁面)
- Admin 端:審核佇列、通過/駁回(原因必填)、撤銷 approved;移除
  toggle-verified 端點(防繞過狀態機);裁決後 flush 課程快取
- 通知:審核通過/駁回(站內+Email,駁回含原因)
- 可見性與預約入口改判 approved(行為等價)
- 測試 +18(ProviderVerificationTest 10、AdminVerificationTest 8),
  既有 4 個測試檔 helper 遷移;Swagger 同步
- 規格同步:provider-verification 全面改寫、admin-user-management
  toggle 段落改為移除聲明

容器內全套件 173 passed / 439 assertions。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:26:12 +08:00

121 lines
6.7 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.
# provider-verification Specification
## Purpose
定義教練資質審核的完整生命週期:教練上傳證照送審、Admin 審核裁決(通過/駁回含原因)、結果通知,以及「未通過審核(approved)之教練的課程不對公開端點曝光、不可接受新預約」的可見性約束。
## ADDED Requirements
### Requirement: 教練驗證狀態機
`provider_profiles.verification_status` SHALL 為四狀態字串:`unsubmitted`(註冊預設)、`pending``approved``rejected`。合法轉移僅限:`unsubmitted→pending`(送審)、`pending→approved`(通過)、`pending→rejected`(駁回)、`rejected→pending`(重新送審,同時清空 `rejection_reason`)、`approved→rejected`(撤銷,原因必填)。原 boolean `is_verified` 欄位移除,API 輸出以 accessor 保留 `is_verified`= status 為 approved)。
#### Scenario: 新教練註冊預設未送審
- **WHEN** 教練完成註冊
- **THEN** `verification_status = unsubmitted`,不出現在 Admin 待審佇列
#### Scenario: 非法轉移被拒絕
- **WHEN** 嘗試對 `unsubmitted` 教練執行 approve,或對 `approved` 教練執行 approve
- **THEN** 回傳 HTTP 422,狀態不變
---
### Requirement: 教練上傳證照與送審
教練 SHALL 能於後台上傳 1~3 張證照圖片(jpeg/png/webp、≤10MB,伺服器端壓縮為 `.jpg`,存 `providers/{user_id}/certifications/`)並送出審核。證照僅於 `unsubmitted` / `rejected` 狀態可增刪;`pending` / `approved` 狀態鎖定(審核依據不可變動)。送審需至少 1 張證照。
#### Scenario: 上傳證照
- **WHEN** `unsubmitted` 教練 `POST /api/provider/verification/certifications` 上傳合法圖片且現有 < 3 張
- **THEN** 壓縮儲存並建立 `provider_certifications` 紀錄,回傳圖片資訊
#### Scenario: 超過 3 張上限
- **WHEN** 教練已有 3 張證照再上傳
- **THEN** 回傳 HTTP 422
#### Scenario: 送審成功
- **WHEN** 教練至少有 1 張證照,`POST /api/provider/verification/submit`
- **THEN** 狀態轉為 `pending``rejection_reason` 清空
#### Scenario: 無證照不可送審
- **WHEN** 教練無任何證照即送審
- **THEN** 回傳 HTTP 422,狀態不變
#### Scenario: pending 期間證照鎖定
- **WHEN** `pending``approved` 教練嘗試上傳或刪除證照
- **THEN** 回傳 HTTP 422
#### Scenario: 查詢自身驗證狀態
- **WHEN** 教練 `GET /api/provider/verification`
- **THEN** 回傳 `status``rejection_reason``certifications[]`id、url
---
### Requirement: Admin 審核佇列與裁決
Admin SHALL 能查詢審核佇列(`GET /api/admin/verifications?status=pending`,預設 pending,含教練資料與證照 URL),並對 `pending` 教練執行通過(`PUT /api/admin/verifications/{userId}/approve`)或駁回(`PUT /api/admin/verifications/{userId}/reject``reason` 必填、max 500)。Admin SHALL 能駁回 `approved` 教練(撤銷驗證,原因必填)。裁決後 SHALL flush `diving_offers` 快取(可見性立即生效)。原 `PUT /api/admin/providers/{id}/toggle-verified` 端點已移除。
#### Scenario: 通過審核
- **WHEN** Admin 對 pending 教練執行 approve
- **THEN** 狀態轉 `approved`,教練課程立即恢復公開可見性(不受 180 秒快取影響)
#### Scenario: 駁回需附原因
- **WHEN** Admin 執行 reject 未帶 `reason`
- **THEN** 回傳 HTTP 422,狀態不變
#### Scenario: 撤銷已通過教練
- **WHEN** Admin 對 approved 教練執行 reject 並附原因
- **THEN** 狀態轉 `rejected`,課程立即從公開列表消失;既有預約照常
#### Scenario: 舊 toggle 端點已移除
- **WHEN** 任何人請求 `PUT /api/admin/providers/{id}/toggle-verified`
- **THEN** 回傳 HTTP 404
---
### Requirement: 審核結果通知
系統 SHALL 於 approve / reject 後通知該教練(站內 + Email):通過通知告知課程已可公開曝光;駁回通知包含駁回原因。通知失敗不阻斷審核主流程。
#### Scenario: 通過通知
- **WHEN** Admin approve
- **THEN** 教練收到站內通知與 Email,內容含審核通過訊息
#### Scenario: 駁回通知含原因
- **WHEN** Admin reject 並附原因
- **THEN** 教練收到的通知內容包含該原因
---
### Requirement: 未通過審核教練的課程不對公開端點曝光
公開課程端點(`GET /api/diving-offers` 列表、`GET /api/diving-offers/{id}` 詳情、`/{id}/schedules``/{id}/reviews`)SHALL 僅回傳符合以下任一條件的課程:(a) `provider_id` 為 null(平台自有資料);(b) 課程所屬教練 `verification_status = 'approved'`。其餘狀態(unsubmitted / pending / rejected,或無 ProviderProfile)的課程在列表中排除、詳情與子端點回傳 404。
#### Scenario: approved 教練的課程正常曝光
- **WHEN** 匿名使用者請求公開課程端點
- **THEN** approved 教練的課程出現在結果中,詳情/時段/評價端點照常回傳
#### Scenario: 非 approved 教練的課程不曝光
- **WHEN** 課程教練狀態為 unsubmitted / pending / rejected
- **THEN** 列表排除該課程,詳情/時段/評價端點回傳 404
#### Scenario: provider_id 為 null 的課程不受限
- **WHEN** 匿名使用者請求公開課程端點,課程的 `provider_id` 為 null
- **THEN** 課程正常曝光
---
### Requirement: 未通過審核教練的課程不可建立新預約
`POST /api/member/bookings` SHALL 在建立預約前驗證 schedule 所屬課程可預約(`provider_id` 為 null 或教練 `verification_status = 'approved'`),不符時回傳 HTTP 422,不建立預約。既有預約(pending / confirmed / completedSHALL 不受教練驗證狀態變動影響:教練仍可處理 pending、confirmed 的聊天與完課流程照常、completed 可正常評價。
#### Scenario: 未通過審核教練課程的新預約被拒絕
- **WHEN** 會員以非 approved 教練課程的 schedule_id 送出預約
- **THEN** 回傳 HTTP 422`{ status: false, message: "此課程目前不開放預約" }`,不建立 Booking
#### Scenario: 教練被撤銷驗證後既有預約照常
- **WHEN** 教練在預約 confirmed 之後被撤銷驗證(approved→rejected
- **THEN** 該預約的聊天、完課、評價流程照常運作;僅新預約被擋
---
### Requirement: 教練自有管理端點不受可見性限制
Provider 對自己課程的管理端點(`/api/provider/offers*`)與 Admin 管理端點(`/api/admin/offers*`)SHALL 不受公開可見性過濾影響,未通過審核的教練仍可登入、編輯與管理自己的課程、上傳證照與送審。
#### Scenario: 未通過審核教練仍可管理自己的課程
- **WHEN** 未通過審核教練以有效 token 請求 `GET /api/provider/offers`
- **THEN** 回傳該教練的全部課程