feat:實作預約系統 — 時段管理、預約生命週期與前端整合

後端:
- 新增 course_schedules / bookings migration(含索引)
- BookingStatus / ScheduleStatus PHP BackedEnum
- CourseSchedule / Booking Model(七狀態機 VALID_TRANSITIONS)
- ScheduleController、ProviderBookingController、MemberBookingController
- 雙層名額驗證(API 層快速失敗 + DB lockForUpdate 防超賣)
- 24h 取消截止、pending 不佔位設計
- ExpirePendingBookings(每小時)/ CompleteFinishedBookings(每日)Scheduler
- Docker cron 配置、CACHE_STORE 改為 file 修正 502

前端:
- 課程詳情頁加入時段選擇與預約流程
- 我的預約頁(展開式卡片、狀態說明、連結課程詳情)
- Coach 時段管理(上午/下午時間選擇器、新課程引導)
- Coach 預約管理(依課程分組、待確認徽章)
- Navbar 新增「我的預約」與「時段/預約管理」入口
- 密碼格式提示與即時比對

OpenSpec:
- booking-system change 歸檔至 archive/2026-05-12-booking-system
- 新增 specs/course-scheduling 與 specs/booking-lifecycle 主規格

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 00:24:51 +08:00
parent ad2c05779d
commit 975b56ca54
40 changed files with 2202 additions and 18 deletions
+19 -3
View File
@@ -4,6 +4,9 @@ use Illuminate\Support\Facades\Route;
use App\Http\Controllers\API\AuthController;
use App\Http\Controllers\API\DivingOfferController;
use App\Http\Controllers\API\ProviderOfferController;
use App\Http\Controllers\API\ScheduleController;
use App\Http\Controllers\API\ProviderBookingController;
use App\Http\Controllers\API\MemberBookingController;
use App\Http\Controllers\API\AdminStatsController;
use App\Http\Controllers\API\AdminUserController;
use App\Http\Controllers\API\AdminOfferController;
@@ -16,6 +19,7 @@ Route::get('/ping', function () {
// 潛水課程(公開)
Route::get('/diving-offers', [DivingOfferController::class, 'index']);
Route::get('/diving-offers/{id}', [DivingOfferController::class, 'show']);
Route::get('/diving-offers/{id}/schedules', [ScheduleController::class, 'publicList']);
// 你可以在這裡繼續新增 API 路由
Route::post('/testpost', function () {
@@ -43,9 +47,11 @@ Route::middleware(['auth:sanctum'])->prefix('member')->group(function () {
Route::put('/profile', [AuthController::class, 'updateMemberProfile']);
// 修改密碼
Route::put('/change-password', [AuthController::class, 'changeMemberPassword']);
// 你可以再加上訂單、收藏、通知等API
// Route::get('/orders', [OrderController::class, 'memberOrders']);
// Route::get('/favorites', [FavoriteController::class, 'memberFavorites']);
// 預約
Route::get('/bookings', [MemberBookingController::class, 'index']);
Route::post('/bookings', [MemberBookingController::class, 'store']);
Route::get('/bookings/{id}', [MemberBookingController::class, 'show']);
Route::delete('/bookings/{id}', [MemberBookingController::class, 'destroy']);
});
// 服務提供者註冊/登入
@@ -68,6 +74,16 @@ Route::middleware(['auth:sanctum'])->prefix('provider')->group(function () {
Route::get('/offers/{id}', [ProviderOfferController::class, 'show']);
Route::put('/offers/{id}', [ProviderOfferController::class, 'update']);
Route::delete('/offers/{id}', [ProviderOfferController::class, 'destroy']);
// 時段管理
Route::get('/schedules', [ScheduleController::class, 'index']);
Route::post('/schedules', [ScheduleController::class, 'store']);
Route::put('/schedules/{id}', [ScheduleController::class, 'update']);
Route::delete('/schedules/{id}', [ScheduleController::class, 'destroy']);
// 預約管理
Route::get('/bookings', [ProviderBookingController::class, 'index']);
Route::put('/bookings/{id}/confirm', [ProviderBookingController::class, 'confirm']);
Route::put('/bookings/{id}/reject', [ProviderBookingController::class, 'reject']);
Route::put('/bookings/{id}/cancel', [ProviderBookingController::class, 'cancel']);
});
// 管理員註冊/登入