specs/lodging-direct-booking.md

訂房散客直訂

一句話:訂房散客直訂是 lodging 模組的成交 flow,前台或後台選旅宿 / 房型 / 日期區間,扣每晚庫存,建立 lodging order line,並接同一套金流分類帳。

1. 成交 Flow

1.1 前台自助

  1. 使用者進 /booking,只看 lodging 模組啟用且可直訂的旅宿。
  2. 選日期、房型、間數與入住人數。
  3. 系統查 room_night_inventory,計算逐晚價格與可售數。
  4. checkout 建單:
    • 建 orders(kind='lodging');
    • 建 order_lines(line_kind='lodging');
    • 建 lodging_booking_lines;
    • 建 lodging_nightly_snapshots;
    • 每晚寫 order_charge_lines(type='lodging_night');
    • 若有折扣碼,寫負的 discount charge line;
    • 建 payment_schedules 與首個 payment_intents。
  5. redirect 到付款 provider;付款成功後金流 helper 重算狀態。

1.2 後台代訂

後台 /admin/lodging/book 可替客戶建 lodging order。代訂可選立即手動入帳、ATM / credit card redirect,或先建立待付款單;無論入口如何,資料模型與前台自助一致。

2. 庫存與 race-safe

訂房不走 trips/departures 席次模型;庫存以 property + room_type + date 為單位。

  • reserveNightsInTx 在同一交易中 conditional update 每晚 booked_count;
  • 任一晚不足即整筆失敗,不建立訂單;
  • 取消 lodging order 時釋回所有 nights,但不自動退款;
  • direct booking 與 lodging-tour-package 共用同一份 room_night_inventory。

3. 訂單 Line 模型

Lodging order 不在 orders 外殼保存房型或金額。資料責任:

表職責
orders營運外殼與三軸狀態快取。
order_lineslodging 商品 line;保存顯示 snapshot。
lodging_booking_lines房型、入住退房區間、間數、入住人數、訂房人快照。
lodging_nightly_snapshots每晚售價與庫存快照,供稽核與重新顯示。
order_charge_lines每晚應收、折扣、費用等財務真相。

會員中心與訂單詳情必須 kind-aware 呈現 lodging 訂單;tour-only 管理頁面不得靜默把 lodging order 當 tour 處理。

4. 付款

  • lodging 自助預設全額收款;若未來支援訂金,也應透過 payment_schedules 表達。
  • ECPay / test provider 都是 adapter;付款事實只在 payment_transactions。
  • 已付款後更改住宿內容,應 append charge line 並產生 adjustment schedule / intent,不直接改原始 nightly snapshot。

5. 取消與退款

取消 lodging order 一律走 cancelLodgingBooking(lodging/booking-repo.ts):

  1. 鎖訂單;冪等 early-return 只認 booking_state='cancelled'——refund_state='refunded' 但仍 confirmed 的單照樣取消釋庫存(先退款後取消不可漏放房晚);
  2. 釋回 nightly inventory(同 tx 標 room_night_holds.released_at,見 lodging-inventory §3.3.1);
  3. 將 booking_state 轉為 cancelled;
  4. 統一沖零(與 tour 取消同一語意,見 order-money-model §4):gross ≠ 0 時 append 負 reversal 沖掉應收 → reconcileSchedulesToReceivable 收斂 schedules → 重算三軸 + 斷言不變量;同時 expire 開放中的 attempts / intents(現為 inline 實作,待整併 import 共用 sweepOrderReceivableOnCancel);
  5. 寫 audit。

通用 cancelOrder(tour 取消入口)對 lodging 訂單 fail-closed 回 invalid_kind——server action 與 /api/v1 直呼皆被擋,不再有「翻狀態不釋庫存」的漏放路徑。退款需另走 refunds 工作流;取消本身不代表自動退款(取消後 gross=0,退款走 overpayment_return)。

6. 權限與 Gate

  • lodging 模組未啟用時,前台 /booking 與後台 lodging routes fail closed。
  • 後台代訂需 staff 權限;前台自助需 customer session。
  • 所有 DB 仍是 tenant DB;不得在 lodging flow 引入 tenant id column。