feat(verification): 教練審核流程完整版——狀態機/證照送審/Admin 裁決/通知
Run Tests / test (pull_request) Successful in 2m3s

實作 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>
This commit is contained in:
2026-06-12 00:26:12 +08:00
parent 37140554d3
commit 43720482c4
36 changed files with 1622 additions and 274 deletions
@@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('provider_profiles', function (Blueprint $table) {
$table->string('verification_status', 20)->default('unsubmitted')->after('is_verified')
->comment('教練驗證狀態:unsubmitted / pending / approved / rejected');
$table->text('rejection_reason')->nullable()->after('verification_status');
});
// 既有資料轉換:已驗證視為審核通過,未驗證回到未送審
DB::table('provider_profiles')->where('is_verified', true)->update(['verification_status' => 'approved']);
DB::table('provider_profiles')->where('is_verified', false)->update(['verification_status' => 'unsubmitted']);
Schema::table('provider_profiles', function (Blueprint $table) {
$table->dropColumn('is_verified');
});
}
public function down(): void
{
Schema::table('provider_profiles', function (Blueprint $table) {
$table->boolean('is_verified')->default(false);
});
DB::table('provider_profiles')->where('verification_status', 'approved')->update(['is_verified' => true]);
Schema::table('provider_profiles', function (Blueprint $table) {
$table->dropColumn(['verification_status', 'rejection_reason']);
});
}
};
@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('provider_certifications', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete()->comment('教練 User id');
$table->string('image_path')->comment('證照圖片相對路徑(public disk');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('provider_certifications');
}
};