線稿索引所有 spec最後更新 2026-07-01
specs/trips.md

行程與梯次

一句話:trips行程產品定義(目錄/行銷/MDX 內容),departures 是該行程的可賣梯次(日期區間 + 席次容量 = 旅遊團的售賣單位)。本檔是行程實體的單一入口。欄位以 code 為準(packages/core/src/schema/business.tspackages/core/src/catalog/*),畫面以 wireframe 為準(§6)。

定位:domain/core spec。功能切片(定價 / 內容 / 團號 / 配套住宿 / 分房 / 登山難度)在各自 spec,§5 索引串接。訂房不走此模型(旅宿走房型每晚庫存,見 lodging-inventory)。狀態見 roadmap

1. 實體模型

  • trips(行程定義)slug(unique)、titlesummarycontent_mdxcover_image_urlprice_from_twd(起價,展示用)、duration_daysdestinationstatusdraft…)、模組旗標 is_climbing / is_overseas(行程層 gate,見 modules)、三條硬規則 config(見 §3.1)、created_at/updated_at
  • trip_imagestrip_id(cascade)、urlcaptiondisplay_order
  • departures(可賣梯次 = 售賣單位)trip_id(cascade)、departure_datereturn_dateprice_twd(梯次價)、capacitybooked_countstatusselling…)、notes。CHECK:capacity > 0booked_count <= capacity

與訂單的接點order_linestour_booking_lines.departure_iddepartures.id。一張 tour 訂單的 party_size 由 tour line 承載(見 orders)。

2. 狀態機

  • trips.statusdraft → 上架(published)等。
  • departures.statusselling → 關閉/額滿/取消等(updateDepartureStatus)。

    確切值集以 code 為準(catalog/trips-repo.ts);本檔描述語意,不鎖列舉。

2.1 三條硬規則 config(行程層可設定,D5 / item 5)

GS 國內高山團的三條硬規則扶正為 trips 上的可設定欄(已落地 packages/core/src/schema/business.ts)。本批僅落「設定欄」;實際 gating / 抽籤 / 候補遞補的 enforcement 邏輯隨 lottery-integration / climbing-screening epic。

欄位型別語意
confirm_group_multipleinteger nullable成團倍數(GS 國內高山團 = 7 的倍數)。NULL = 不限倍數。成團 / 派車 / 人力(每 8 人一組)算法的基準。
restricted_quota_enabledboolean default false人頭使用次數限制開關(奇萊南華 / 嘉明湖 / 北大武等以身分證計次的國家公園 permit 配額)。
quota_per_person_limitinteger nullable每人(每身分證)使用次數上限。NULL = 不限。
quota_windowtext nullable計次窗口(如 'annual')。NULL = 不限窗口。
waitlist_enabledboolean default false候補開關(玉山搶候補 / 候補遞補)。本批僅旗標,遞補邏輯隨 lottery epic。

三者皆 nullable 或有預設,不破既有列。人頭使用次數限制屬登山專屬規則(permit 配額),enforcement 檢查層會比對旅客身分證的歷史使用次數(見 lottery-integration 的人頭限制檢查層註記)。

3. 不變式

  • race-safe 席次(核心):訂位用 conditional UPDATE departures SET booked_count = booked_count + N WHERE booked_count + N <= capacity + DB CHECK booked_count <= capacity不靠讀後寫catalog/seat-reservation.ts,由 orders createOrderWithReservation 呼叫)。
  • 兩層模組 gate:登山/海外功能 = 平台層授權control_db.tenant.module_* 行程層旗標(trips.is_climbing / is_overseas)皆開才生效(modules)。
  • 價格解析單一來源:有效售價走唯一的 resolveDeparturePricecatalog/pricing.ts,fallback 鏈 promo→梯次價→行程起價),歸 trip-pricing不另寫第二份
  • slug 唯一、cascade(trip 刪→images/departures 連帶)。
  • 訂房不適用:旅宿不建 departures,走每晚共用庫存(lodging-inventory)。

4. 存取層契約

packages/core/src/catalog/trips-repo.ts(first arg db: ScopedDb):

  • 前台listPublishedTrips(目錄卡)、getTripBySlug(行程頁)。
  • 後台listAdminTripsgetTripByIdcreateTripupdateTripcreateDepartureupdateDepartureStatus
  • 席次原語catalog/seat-reservation.ts價格catalog/pricing.tsresolveDeparturePricetrip-pricing)。

5. 擴充行程的功能 spec(索引)

Spec擴充什麼
trip-pricing直客/同業價欄、訂金/尾款、resolveDeparturePrice、包團(charter)
content-authoringMDX 行程內容 / 圖片 / journal 撰寫
group-code梯次團號(region/airline code + 產生/搜尋)
climbing-screening行程難度欄(grade_score 等)+ is_climbing,下單資格審核
lodging-tour-packagedeparture_lodging 配套住宿(扣旅宿庫存、派生 departure_rooms)
room-assignmentdeparture_rooms 分房配置
modulesis_climbing / is_overseas 兩層 gate
departure-prep出團 deadline 警戒 / 許可證文件
lottery-integration成團倍數 / 人頭使用次數限制 / 候補(§2.1 config 的 enforcement)

6. 畫面(UI = wireframe)

  • 後台行程列表 / 編輯(含梯次/價格/配套)../apps/wireframes/app/admin/trips/

Changelog

版本日期變更Commit
12026-06-01初版:行程/梯次實體核心 spec(模型/狀態/race-safe 席次/兩層 gate/契約/擴充索引/wireframe)
22026-06-23§2.1 補三條硬規則 trip 層 config(已落地 schema):confirm_group_multiple(成團倍數)/ restricted_quota_enabled + quota_per_person_limit + quota_window(人頭使用次數限制)/ waitlist_enabled(候補);本批僅設定欄,enforcement 隨 lottery / climbing epic(§5 索引加 lottery-integration)。
32026-07-01§6 wireframe 連結由過時 legacy .html 更新為現行 apps/wireframes/app/admin/trips/(Next.js static export React app)。