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:
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\ScheduleStatus;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CourseSchedule extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'diving_offer_id',
|
||||
'provider_id',
|
||||
'scheduled_date',
|
||||
'start_time',
|
||||
'max_participants',
|
||||
'current_participants',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'scheduled_date' => 'date',
|
||||
'max_participants' => 'integer',
|
||||
'current_participants' => 'integer',
|
||||
'status' => ScheduleStatus::class,
|
||||
];
|
||||
|
||||
public function divingOffer()
|
||||
{
|
||||
return $this->belongsTo(DivingOffer::class);
|
||||
}
|
||||
|
||||
public function provider()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'provider_id');
|
||||
}
|
||||
|
||||
public function bookings()
|
||||
{
|
||||
return $this->hasMany(Booking::class, 'schedule_id');
|
||||
}
|
||||
|
||||
public function remainingSpots(): int
|
||||
{
|
||||
return max(0, $this->max_participants - $this->current_participants);
|
||||
}
|
||||
|
||||
protected function startTime(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn($value) => $value ? substr($value, 0, 5) : $value,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user