Yuruicamp

bookings

預約主檔,保存預約人、營區、住宿日期、金額與目前狀態。 * booking_selected_zones
預約所選營位明細與成交時的營位類型、平假日價格快照。 * booking_selected_rentals
預約加租裝備明細與商品、規格、價格及折扣快照。 * booking_status_history
預約狀態的異動歷程,可記錄操作後臺使用者與備註。

關聯與資料流

customers ─ 1:N ─ bookings ─ N:1 ─ campgrounds ├─ 1:N booking_selected_zones ─ N:1 ─ campground_zones ├─ 1:N booking_selected_rentals │ ├─ N:1 rental_listings │ └─ N:1 rental_sku_variants └─ 1:N booking_status_history ─ N:1 ─ admin_users(可為 NULL)

關聯

資料流程

預約成立時:

  1. 在 bookings 建立預約表頭、營區名稱/地區快照、日期、人數、金額與目前 status。
  2. 依選取營位,在 booking_selected_zones 建立一至多筆明細,保存營位類型與成交價格快照。
  3. 依加租裝備,在 booking_selected_rentals 建立零至多筆明細,保存 SKU、名稱、規格、價格與折扣快照。
  4. 在 booking_status_history 寫入初始狀態與發生時間。
  5. 後續確認、取消或完成時,更新 bookings.status,並新增一筆 booking_status_history。

欄位說明

bookings

booking_selected_zones

booking_selected_rentals

booking_status_history

運作模式

成交快照

營區名稱、地區、營位類型、租借 SKU/名稱/規格及價格都保存為快照。後續主檔修改名稱、規格或價格時,不應改寫既有預約的交易內容。

建立冪等

Booking Checkout 以會員與 checkout_idempotency_key 作為唯一範圍。資料庫唯一約束負責阻止同一會員產生重複 key;Service 還必須比較 checkout_request_hash,判斷應回放原結果或回傳 IDEMPOTENCY_CONFLICT。不同會員可以使用相同 key。

可用性查詢

POST /api/booking/check-availability 先依 booking_policies.id=1 驗證日期,再呼叫 get_zone_availability。API 的住宿區間是 [checkIn, checkOut),因此傳給資料庫函式的包含式結束日為 checkOut - 1 day。函式會扣除 zone_blocks 與政策表列出的 pending/confirmed 預約,公休日直接回傳 0;Service 再取每個 zone 在所有住宿晚上的最低剩餘量。這個查詢不新增 bookings,也不鎖位。

Booking Checkout 鎖位、租借與計價

POST /api/booking/checkout/sessions 先鎖定會員與營區,再依 zone_id 固定排序悲觀鎖定營位。Service 在同一交易內重查可用量,依 calendar_dates 與資料庫價格計算平假日晚數及金額。

有租借時會解析 campground_rental_locations,固定排序鎖定 rental_sku_variant_stocks,扣除日期重疊的 active 保留,再建立 booking_selected_rentals 快照與 rental_stock_reservations。listing 的 discount 依 Schema 為 0.00~0.30 比率。任何租借不足都會回滾整筆交易。

最後建立 pendingunpaid 表頭與初始歷程。checkout_expires_at 固定為建立時間加 15 分鐘。

取消與逾時釋放

E-6 的主動取消與每分鐘排程都會先對 Booking 執行悲觀鎖,再確認狀態仍為 pending + unpaid。符合條件時,同一交易會把 Booking 改為 cancelled、active 租借保留改為 released 並填入 released_at,以及新增 cancelled 歷程。已取消資料不會重複寫歷程;營位沒有額外釋放帳,因 cancelled 不在政策占用狀態中,可用性查詢會自然恢復數量。

排程只掃描 checkout_expires_at <= now 的候選資料,鎖定後仍會重查狀態。若付款通知先取得相同 Booking 的資料庫鎖並完成付款,排程取得鎖後會略過,避免把已付款預約取消。

會員預約讀取

E-5 提供會員列表、詳情與 Checkout 快照讀取。列表 SQL 依登入會員的 customer_id 分頁;單筆 SQL 同時限制 bookings.idcustomer_id。查不到與不屬於本人都回 404 NOT_FOUND,避免洩漏預約 ID 是否存在。

程式碼追蹤

可能的問題

後台使用 /api/admin/bookings 查詢預約;營位、租借與歷程只在詳情載入。hasRental 以 EXISTS 篩選,避免 N:M 明細造成錯誤分頁。

只有 paid pending 可確認,只有已到退房日的 paid confirmed 可完成。完成時 active 租借保留帳改為 fulfilled;Admin 不得將 unpaid 改為 paid,已付款取消與退款由線 D 負責。