Aquaturbo V2 行為規格文檔

基於 V2 三份需求文檔(系統端 / 客戶端 / 報名+訂單+付款流程)撰寫, 供需求方驗收使用。
每個 scenario = 一個可驗收的行為。 黃色框 ❓ 為文檔未明確、需要確認的點。

V2.1 修訂版 — 全 17 features 完成(2026-05-08 會議拍板後修訂)。

📌 V2.1 重大架構變更摘要(會議拍板):
雙層餘額模型:付款先進餘額(不可消費 / 自由消費),再從餘額扣費(正典 E + F05)。
廢除點名:課程開始時系統自動扣費(F08 重寫)。
請假 12hr → 24hr:24hr 前移除 enrollment 不扣費,24hr 內仍扣費(F09 + F10)。
補課同等級「及以上」:僅季度課,每季 2 次,價差走前台(F10)。
Waitlist 綁 session:排課表上點某課的「加入候補」(F13 重寫)。
標籤互斥:5 互斥(其一)+ CONTACTED 手動 + WAITLIST 自動,最多 3 標籤(F12)。
換課拍板版:前台 only,自動生成補差價訂單(F16)。
取消註冊碼:開放註冊(F01)。
新增任意金額付款:客服後台代家長充值,進自由餘額(F07)。
退款表單簡化:金額 / 路徑 / 原因,其他自動算(F11)。

每個受影響 feature 頂部有黃色 ⚠️ V2.1 變更 banner。 黃色 ❓ 框是仍需確認的細節(71 個)。建議先看 banner 確認方向,再過 ❓。

🧭 建議 review 順序:先看「正典定義」確認術語與角色 → F01 → F02 → F03 → F09。 每個 ❓ 黃框都是需要你拍板的點,按 Enter 編號可在會議上逐條過。

📖 正典定義(Canonical Definitions)

用途: 所有 features 共用的術語、角色、數據常量、狀態機。任何 scenario 內出現的概念都應以此為準。 需求方確認此區塊後,後續 features 不再對這些定義做局部變更。

A. 角色與人物

  • Given 王女士(Wang) — 家長 / Parent,名下有學員小明、小華
  • And 小明 — 王女士的兒子,當前 Level L3,Burnaby 校區
  • And 小華 — 王女士的女兒,當前 Level L1,Burnaby 校區
  • And 陳先生 — 另一聯絡人(用於跨家長對照場景)
  • And Lisa — 客服 / Admin,Burnaby 校區
  • And Tom — 客服 / Admin,Coquitlam 校區(用於跨校區場景)
  • And Mark — 主管 / Super Admin,跨校區
  • And Coach Luke — 教練,Level 4,可帶 L3-L4 課程
  • And Coach Eric — 教練,Level 2,可帶 L1-L2 課程

B. 場域與資源

  • Given 校區:Burnaby(主校區)、Coquitlam、Surrey(範例)
  • And 泳池:Pool A(25m)、Pool B(50m)
  • And 泳道:Lane 1、Lane 2、Lane 3(每條泳道為獨立排課單位)
  • And 季度(Term/Season):2026 Spring(3-6月)、2026 Summer(7-9月)、2026 Fall(10-12月)、2026 Winter(1-3月)
  • And 課程類型:1on1、1on2、Group、長訓(Long-Training)
  • And Level:L1(初學)、L2、L3、L4、L5(進階)
  • And 典型上課時段:週二 17:00-18:00(用於範例)

C. 金錢與費率

  • Given 貨幣單位:CAD(加幣)
  • And GST 稅率:5%(聯邦)
  • And 註冊費:30 CAD per 學員,一次性,不退款(用於泳帽、袋子、證書、人工成本)
  • And Trial 折扣:正式價格的 80%(即 8 折),折價部分以「優惠」名義呈現於 Invoice
  • And 金額計算:所有金額用 BigDecimal,HALF_UP 保留 2 位小數
  • And 價格快照:訂單建立時鎖定當時價格表,後續價格表變動「不」回溯
待確認 QC.1:註冊費 30 CAD 是「per 學員」一次性(一個學員生命週期一次),還是「per 訂單」?我預設 per 學員一次性(首次報名時收,之後同學員所有報名都不再收)。請確認。
待確認 QC.2:學員退款後再回來,註冊費是否重收?我預設 不重收(既然標榜「一次性、不退款」就保持不重收)。

D. 訂單狀態機(V2 核心)

  • Given 訂單共 6 種狀態,狀態機如下:
  • And 待付款 — 客服已生成訂單,課程名額暫時保留,等待客戶付款
  • And 待確認 — 客戶已宣告完成付款,等待客服 / 金流系統確認
  • And 已完成 — 付款已確認,學員正式加入課表,開始扣費
  • And 已取消 — 超時未付款 / 客戶取消(capacity 立即釋放)
  • And 退款中 — 已申請退款,等待財務 / 金流處理
  • And 已退款 — 退款完成並確認
  • And 合法 transitions
  •   待付款 → 待確認(客戶宣告付款)
  •   待付款 → 已取消(超時 / 客戶取消)
  •   待確認 → 已完成(客服 / 金流確認)
  •   已完成 → 退款中(SUPER_ADMIN 發起退款)
  •   退款中 → 已退款(金流系統確認退款完成)
待確認 QD.1:「待確認」是否能轉「已取消」?例如客戶宣告付款但實際沒付,48 小時後客服查無入帳,是否能取消?預設 ,加 transition 待確認 → 已取消。
待確認 QD.2:退款是否支持「部分退款」?訂單中只退一部分課程的金額。預設 支持(V1 schema 已有 PARTIAL_REFUNDED),請確認 V2 是否保留此機制。

E. 餘額模型 — 雙層餘額(V2.1 拍板版,2026-05-08 會議)

  • Given 每位學員有「雙層餘額」:
  •   1. 不可自由消費餘額(Locked Balance) — 來自訂單付款,綁定訂單,必須用於該訂單對應的課程
  •   2. 可自由消費餘額(Free Balance) — 來自任意金額付款 / 退款找零 / 訂單對應課程全部結束後剩餘的轉入
  • And 兩種餘額在客戶端分別展示,合計 = 總餘額
  • And 付款全部先進餘額,再從餘額扣費(不直接扣訂單剩餘)
  • And 付款進入餘額的規則:
  •   • 訂單付款 → 「不可消費餘額」(綁該訂單 ID)
  •   • 任意金額付款(客服在後台代操作) → 「自由消費餘額」
  •   • 退款找零 → 「自由消費餘額」(原訂單已退無法綁)
  • And 扣費規則(系統自動,無人工點名)
  •   • 課程開始時系統觸發自動扣費(cron 每分鐘掃)
  •   • 該堂課對應訂單的「不可消費餘額」優先扣(單堂價格快照)
  •   • 若不足(補課差價、Trial 折扣等情境)→ 從「自由消費餘額」補
  •   • 若都不足 → 系統 alert 客服 + 該堂課狀態標記為「待處理(缺費)」
  • And 不消費餘額轉自由的時機
  •   • 該訂單對應的所有 sessions 全部結束(已扣完 / 取消 / 過期)
  •   • 系統 cron 自動轉換(每天 00:00 掃)
  •   • 例:12 堂季度課,10 堂已扣 + 2 堂提前請假無補上 → 訂單對應 sessions 全結束 → 剩 0;若有 1 堂換到便宜課剩 30 → 季度結束後 30 → 自由
  • And 請假與扣費關係
  •   • 24 小時前請假 → 該堂課從 enrollment 直接移除,不扣費(產生補課額度,僅季度課)
  •   • 24 小時內請假 → 不可補課,仍照常扣費(系統當日自動扣)
  • And 客戶端展示
  •   • Dashboard:「不可消費餘額 $X / 自由消費餘額 $Y / 總計 $X+Y」
  •   • 詳情頁:每筆訂單剩餘 + 自由餘額明細 + 完整流水
  •   • 文案強調:「不可消費 = 您為某組課程預付,按堂扣完即止;自由 = 您可用於任意課程」
已拍板 E.1:採用雙層餘額(不可消費 + 自由消費)。先進餘額再扣費。
已拍板 E.2:任意金額付款由客服後台代操作,進入自由消費餘額。
已拍板 E.3:不消費餘額轉自由的時機 = 訂單對應所有 sessions 全部結束。
已拍板 E.4:退款找零 → 自由消費餘額(不再綁訂單)。
待確認 QE.5:餘額不足時,alert 客服的具體做法 — 預設「該堂課狀態為『待處理(缺費)』,客服收到通知後手動處理(要求家長充值或聯繫退課)」。請確認此 fallback 行為。

F. 時間與時區

  • Given 後端儲存時間統一用 UTC(LocalDateTime 存 UTC)
  • And 前端顯示與輸入用 Vancouver Local Time(PST/PDT)
  • And 文檔中的時間描述(如「17:00」、「2026-04-07」)均指 Vancouver Local
  • And 時區轉換在 Controller 邊界處理

G. 通用業務規則

  • Given 軟刪除:所有業務表用 deleted_at 軟刪;刪除後資料保留但不顯示於日常查詢
  • And 歷史紀錄保留:請假/補課/扣款/退款/Audit 紀錄永久保留,即使學員或訂單軟刪
  • And Audit Log:刪除、退款、Credit 手動調整、跨季補課特批 → 必須留下 audit log
  • And 不可變表:t_credit_log / t_audit_log / t_refund 只允許 INSERT,禁止 UPDATE/DELETE
  • And API 錯誤訊息:英文(前端按錯誤碼映射為雙語顯示)
  • And UI 介面語言:繁體中文 / 英文,可切換,登入後延續

01. 登入 / 註冊 / 忘記密碼(V2.1:開放註冊)已完成 家長端 + 客服端

⚠️ V2.1 重大變更(2026-05-08 拍板): 取消「註冊碼」機制,改為開放註冊(任何人都可在家長端註冊)。
因此 Scenarios 4-6(註冊碼相關)和 Scenario 12(綁定碼衝突)不再適用, 請忽略或視為過時。實作時 Scenario 7(6 步註冊向導)變成「無需註冊碼即可進入」。
其他流程(登入、忘記密碼、員工帳號、教練)保持不變。
主要角色:家長(王女士)、客服(Lisa)、主管(Mark)、教練(Coach Luke)、系統

Background 共用前置條件

  • Given 系統已預先建立 SUPER_ADMIN「Mark」、ADMIN「Lisa」(綁定 Burnaby)、Coach「Luke」三個內部帳號
  • And 系統的家長端網址為 https://parent.aquaturboswim.ca
  • And 系統的客服端網址為 https://admin.aquaturboswim.ca
  • And 客服可在後台「聯絡人管理」頁面派發註冊碼

Scenario 1 家長以 Email + 密碼登入家長端

  • Given 王女士已完成註冊,Email 為 wang@example.com,密碼為 Wang2026!
  • When 她在家長端登入頁輸入 Email + 密碼,點擊「登入」
  • Then 系統驗證通過
  • And 跳轉到家長端 Dashboard
  • And 顯示「歡迎回來,王女士」
  • And 介面語言沿用她上次選擇的語言(繁中或英文)

Scenario 2 密碼錯誤 — 統一錯誤訊息

  • Given 王女士的帳號為 wang@example.com
  • When 她輸入正確 Email 但錯誤密碼
  • Then 系統返回「Email 或密碼錯誤」
  • And 洩露是 Email 不存在還是密碼不正確(防帳號探測)
  • And 該失敗事件被記錄到登入失敗日誌
待確認 Q01.1:是否啟用「連續 5 次錯誤鎖定 15 分鐘」防爆破機制?文檔未提,我預設 啟用。請確認。

Scenario 3 忘記密碼 — Email 驗證碼重設

  • Given 王女士在登入頁點擊「忘記密碼」
  • When 她輸入 wang@example.com
  • Then 系統發送 6 位數字驗證碼至該 Email
  • And 顯示提示「驗證碼已發送,10 分鐘內有效」
  • And 該驗證碼僅可使用一次
  • When 她在驗證頁輸入正確的 6 位數碼 + 新密碼 Wang2027New!
  • Then 系統重置密碼並自動登入
  • And 通知中心新增「密碼已重設」通知
待確認 Q01.2:驗證碼長度(6 位)+ 有效期(10 分鐘)+ 一次使用 — 預設這樣,請確認規格。
待確認 Q01.3:密碼複雜度要求?預設 ≥ 8 字元、至少 1 字母 + 1 數字。是否要求大小寫 / 特殊符號?

Scenario 4 註冊 — 持有客服派發的註冊碼

  • Given 客服 Lisa 在後台為新客戶王女士派發註冊碼 AQUA-X8K9-2026
  • And 該註冊碼未被使用、未過期(有效期 30 天)
  • When 王女士在家長端註冊頁輸入該註冊碼
  • Then 系統驗證通過
  • And 進入註冊向導 Step 1(關係選擇)
待確認 Q01.4:註冊碼有效期?我預設 30 天。是否每組註冊碼也只能用 1 次?預設 。請確認。
待確認 Q01.5:註冊碼派發方式?我預設 客服在後台手動產生 + 透過 WeChat / Email 告知客戶。是否要批次產生?是否要 QR Code?

Scenario 5 註冊 — 註冊碼無效

  • Given 王女士輸入無效的註冊碼(已使用 / 已過期 / 不存在)
  • When 點擊「下一步」
  • Then 系統顯示「註冊碼無效或已使用」
  • And 顯示「請聯繫客服取得新註冊碼」
  • And 顯示 4 種聯繫方式:WeChat / WhatsApp / Email / Phone

Scenario 6 註冊 — 沒有註冊碼,顯示客服聯繫方式

  • Given 王女士第一次接觸系統,沒有註冊碼
  • When 她在註冊頁點擊「我沒有註冊碼」
  • Then 系統顯示說明:「為避免無效帳號,所有家長需透過客服取得註冊碼」
  • And 顯示 4 種聯繫方式(WeChat / WhatsApp / Email / Phone)含 QR Code 或 deep link
  • And 不允許在沒有註冊碼的情況下進入註冊流程

Scenario 7 註冊向導 6 步驟

  • Given 王女士已通過註冊碼驗證
  • When 她依序填寫註冊向導 6 步:
  •  Step 1 關係:父母 / 家人 / 其他 → 選「父母」
  •  Step 2 聯絡人資料:姓名「王女士」、Email「wang@example.com」、電話「604-xxx-xxxx」、密碼「Wang2026!」
  •  Step 3 註冊對象:自己 / 孩子 → 選「孩子」
  •  Step 4 主校區:Burnaby
  •  Step 5 學員資料(可多位):小明(生日 2018-05-01、初始 Level L2)+ 小華(生日 2019-08-12、L1)
  •  Step 6 備註:「小明對氯敏感,建議戴護目鏡」
  • Then 系統建立聯絡人「王女士」
  • And 在王女士名下建立 2 位學員「小明」「小華」
  • And 註冊碼 AQUA-X8K9-2026 標記為已使用
  • And 自動登入到 Dashboard
  • And 通知中心新增「歡迎使用 Aquaturbo,註冊完成」通知
  • And 兩位學員的初始 Level(L2 / L1)進入 Level 調整歷史
待確認 Q01.6:Level 由家長自選 — 文檔說「系統提供 Level 說明供參考」。是否每個 Level 都有圖文說明(如「L1 = 不會浮、不會閉氣」)?這份說明文案誰來提供?

Scenario 8 註冊 — Email 已被註冊

  • Given Email wang@example.com 已存在於系統
  • When 新註冊嘗試使用同一 Email
  • Then 系統提示「此 Email 已註冊,如忘記密碼請使用『忘記密碼』功能」
  • And 不消耗註冊碼(讓使用者改用其他 Email 或聯繫客服)

Scenario 9 員工登入 — 角色決定跳轉與權限

  • Given 系統有以下三個員工帳號
  • When 各自登入客服端
  • Then 跳轉與可見資料如下表所示

Examples:

角色帳號登入後跳轉可見資料
SUPER_ADMINmark@aquaturbo.com後台 Dashboard所有校區
ADMINlisa@aquaturbo.com後台 Dashboard僅本校區(Burnaby)
COACHluke@aquaturbo.com個人課表頁僅自己負責的 sessions

Scenario 10 教練登入 — 唯讀視圖

  • Given Coach Luke 已建立教練帳號
  • When Luke 登入客服端
  • Then 跳轉到「我的課表」頁,僅顯示自己負責的 sessions
  • And 介面隱藏所有「新增課程」「編輯」「刪除」「報名學員」按鈕
  • And 介面隱藏所有財務 / 退款 / 報表 / 帳號管理選單
  • And 介面顯示「點名」按鈕(可逐生點名 + 批量點名)
待確認 Q01.7:系統端文檔說「教練不可修改任何資料」,但點名是修改 attendance 紀錄。教練可以點名嗎?我預設 可以點名(含逐生與批量),但不可修改其他資料。請確認。

Scenario 11 客服手動建立家長帳號 — 預設密碼 123456

  • Given 客服 Lisa 在後台「聯絡人管理」頁
  • When Lisa 為陳先生建立聯絡人:姓名、Email「chen@example.com」、電話、不設定密碼
  • Then 系統自動建立帳號,密碼為 123456
  • And Lisa 看到提示「請告知客戶首次登入密碼為 123456」
  • When 陳先生首次以該 Email + 123456 登入
  • Then 系統強制要求修改密碼後才能進入 Dashboard

Scenario 12 客服已建檔 + 家長持註冊碼自助註冊 — 衝突處理

  • Given 客服 Lisa 已手動建立王女士的聯絡人記錄(Email wang@example.com,密碼 123456)
  • And 同時 Lisa 也派發了一組註冊碼給王女士
  • When 王女士不知客服已建檔,自己拿註冊碼上來「註冊」
  • And 在 Step 2 填入相同 Email wang@example.com
  • Then 系統檢測到 Email 已存在
  • And 顯示提示「此 Email 已有帳號,請以該 Email + 預設密碼 123456 登入;或聯繫客服協助」
  • And 註冊碼標記為已使用(保留供綁定用)
  • And 不重複建立聯絡人
待確認 Q01.8:「綁定碼」與「註冊碼」是否同一機制?我預設 同一個碼:客服建檔時派發一個碼,家長拿這碼來:(a) 若帳號不存在 → 完整註冊;(b) 若帳號已存在 → 跳到登入提示。請確認。

Scenario 13 系統清理過期密碼重設碼 內部行為

  • Given 系統存在多筆已超過 10 分鐘有效期的密碼重設碼
  • When 系統於每天 02:00 執行清理任務
  • Then 所有過期重設碼從 DB 中刪除
  • And 清理動作記錄到 audit log

Scenario 14 系統清理過期未使用註冊碼 內部行為

  • Given 一組註冊碼派發後 30 天未被使用
  • When 系統於每天 02:00 執行清理任務
  • Then 該註冊碼被標記為 EXPIRED
  • And 客服在「註冊碼管理」頁可看到過期紀錄
  • And 後台支援批次重新派發已過期的註冊碼給同一客戶

02. 帳號 / 學員管理 已完成 家長端

業務價值: 一個帳號管理多位學員(家庭場景);家長可自助修改聯絡人 / 學員的基本資料, 但 Level 由教練評估後由客服統一調整,確保專業判斷不被家長主觀干擾。
主要角色:家長(王女士)、客服(Lisa)、系統

Background 共用前置條件

  • Given 王女士帳號下有兩位學員:「小明」(L3)、「小華」(L1)
  • And 王女士已登入家長端
  • And 當前語言設定為「繁體中文」

Scenario 1 在學員之間切換

  • Given 王女士當前正在查看「小明」的 Dashboard
  • When 她點擊頂部學員切換器,選擇「小華」
  • Then Dashboard 立即切換為「小華」的資料
  • And 顯示小華的下一堂課、Credit、待補課數、剩餘請假額度
  • And 後續所有頁面(課表 / 帳單 / 通知)均以小華為視角
  • And 切換器在所有頁面右上角始終可見

Scenario 2 修改聯絡人姓名與電話

  • Given 王女士當前的電話為 604-xxx-1111
  • When 她在「我的資料」頁修改電話為 778-xxx-2222 並點擊「保存」
  • Then 系統更新聯絡人電話
  • And 顯示 Toast「資料已更新」
  • And 客服後台的聯絡人記錄即時更新
  • And 不需要重新登入

Scenario 3 修改 Email — 需驗證新 Email

  • Given 王女士當前 Email 為 wang@example.com
  • When 她在「我的資料」頁修改為 wang.new@example.com 並提交
  • Then 系統發送驗證碼到新 Email
  • And 在驗證前舊 Email 仍可登入、新 Email 不可登入
  • When 王女士在新 Email 收到驗證碼,回到設定頁輸入並確認
  • Then 系統切換為新 Email
  • And 通知中心新增「Email 已變更」通知
待確認 Q02.1:修改 Email 是否需要驗證新 Email?我預設 需要(防誤輸 + 防被冒用)。是否同時要求舊 Email 確認?

Scenario 4 修改密碼 — 需要舊密碼

  • Given 王女士在「修改密碼」頁
  • When 她輸入舊密碼 Wang2026! + 新密碼 Wang2027New!
  • Then 系統驗證舊密碼正確
  • And 更新為新密碼
  • And 顯示提示「密碼已更新,請重新登入」
  • And 自動登出,跳轉到登入頁
  • And 通知中心新增「密碼已修改」通知

Scenario 5 修改密碼 — 舊密碼錯誤

  • Given 王女士在「修改密碼」頁
  • When 她輸入錯誤的舊密碼 + 任意新密碼
  • Then 系統拒絕並顯示「舊密碼錯誤」
  • And 不更新任何資料
  • And 不發通知

Scenario 6 修改學員基本資料

  • Given 王女士當前查看「小明」的學員資料頁
  • When 她修改備註:「對氯敏感,需戴護目鏡」並保存
  • Then 系統更新學員備註
  • And 顯示成功提示
  • And 客服後台查看小明資料時可見此備註
待確認 Q02.2:家長可修改的學員欄位有哪些?預設:姓名、生日、緊急聯絡人、過敏資訊、備註。不可改:Level、所屬聯絡人、主校區。請確認。

Scenario 7 學員 Level 唯讀 — 家長端不可修改

  • Given 王女士在「小明」(L3)的學員資料頁
  • When 她查看 Level 欄位
  • Then Level 顯示為 L3(唯讀)
  • And 旁邊顯示說明:「Level 由教練評估後由客服調整」
  • And 不存在編輯按鈕
  • And 直接在 URL 或 API 嘗試提交 Level 變更會被後端拒絕

Scenario 8 查看 Level 調整歷史

  • Given「小明」初始註冊 Level 為 L1,後續客服 Lisa 進行了 2 次調整:
日期操作人原因
2026-04-10L1L2Lisa教練 Eric 期中評估
2026-05-15L2L3Lisa學期末綜合評估
  • When 王女士進入「小明」的 Level 紀錄頁
  • Then 顯示 2 筆紀錄(按日期倒序)
  • And 每筆顯示日期、變化、操作人、原因
  • And 初始 L1 顯示在紀錄中(符合需求文檔「初始 Level 不納入紀錄」)

Scenario 9 客服調整 Level — 自動寫入歷史 + 通知家長

  • Given Coach Luke 評估「小明」可升至 L4
  • And 客服 Lisa 在後台將「小明」Level 從 L3 改為 L4,原因:「教練評估提升」
  • When Lisa 點擊保存
  • Then 系統建立 1 筆 t_student_level_log,含時間 / from / to / 操作人 / 原因
  • And 王女士的家長端鈴鐺新增「小明 Level 已調整為 L4」通知
  • And 學員資料頁的 Level 欄位即時更新
  • And 後續報名時,「小明」可選 L3-L5 區間的課程(即使原本不能)

Scenario 10 家長端不可新增學員

  • Given 王女士想為新出生的女兒建立第三位學員
  • When 她在「學員管理」頁尋找「新增學員」按鈕
  • Then 介面顯示新增按鈕
  • And 顯示提示:「如需新增學員,請聯繫客服」並附 4 種聯繫方式
待確認 Q02.3:家長註冊時可填多位學員,但註冊後是否還能新增?V2 文檔未明說。我預設 不可,需聯繫客服(符合 V2「客服統一管理」精神)。請確認。

Scenario 11 家長端不可刪除學員

  • Given 王女士想停用「小華」(學業繁忙暫不上課)
  • When 她在學員管理頁尋找「刪除」按鈕
  • Then 介面不顯示刪除按鈕
  • And 提示「如需暫停或刪除,請聯繫客服」

Scenario 12 客服軟刪學員 — 歷史保留

  • Given 客服 Lisa 在後台軟刪「小華」
  • When Lisa 提交刪除
  • Then「小華」的 deleted_at 被標記
  • And 王女士的家長端「小華」從學員切換器消失
  • And 但「小華」的歷史 attendance / payment / level_log 全部保留
  • And 系統建立 1 筆 audit log,記錄刪除操作

Scenario 13 Level 變更時系統發出通知 內部行為

  • Given 任意客服在後台調整某學員的 Level
  • When 系統建立 t_student_level_log 紀錄
  • Then 系統觸發 1 筆通知到該學員所屬聯絡人的家長端
  • And 通知標題:「[學員名] Level 已調整為 [新 Level]」
  • And 通知內含教練評估原因(若有填寫)
  • And 通知狀態:未讀

03. 後台設定(校區/泳池/泳道/價格/帳號)已完成 客服端 / SUPER_ADMIN

業務價值: 後台設定是系統運營的基礎:校區 / 泳池 / 泳道 / 價格表 / 員工帳號。 這些資料為 SUPER_ADMIN 唯一管轄;ADMIN 可讀但不可寫,避免分校區客服誤改全局配置。
主要角色:主管(Mark)、客服(Lisa)、教練(Coach Luke)、系統

Background 共用前置條件

  • Given SUPER_ADMIN「Mark」已登入後台
  • And 系統已有預設校區「Burnaby」「Coquitlam」
  • And Mark 的角色可看 / 可寫所有設定

Scenario 1 創建新校區

  • Given Mark 在「校區管理」頁
  • When 他點擊「新增校區」並填入名稱「Surrey」、地址、電話、營業時間
  • And 點擊保存
  • Then 系統建立 t_branch 紀錄
  • And 校區列表新增 Surrey
  • And 報名流程的校區下拉選單立即可選 Surrey

Scenario 2 在校區下創建泳池與泳道

  • Given Mark 進入 Burnaby 校區詳情頁
  • When 他新增泳池:名稱「Pool A」、長度 25m、深度 1.2-1.8m、最大同時開課數 6
  • And 進入 Pool A 詳情,批量新增 6 條泳道:Lane 1 ~ Lane 6
  • Then 系統建立 1 筆 t_pool + 6 筆 t_lane
  • And 每條泳道為獨立排課單位
  • And 泳道編輯頁可獨立啟用 / 停用某條 lane(例如維修中)

Scenario 3 ADMIN 嘗試新增校區 — 被拒絕

  • Given 客服 Lisa(ADMIN,Burnaby)登入後台
  • When 她嘗試進入「校區管理」頁
  • Then 介面隱藏該選單
  • And 直接訪問該 URL 跳到「無權限」頁
  • And API 拒絕並返回 403

Scenario 4 嘗試刪除有關聯課程的校區 — 阻擋

  • Given Surrey 校區下有 1 個 session 已建立
  • When Mark 嘗試刪除 Surrey 校區
  • Then 系統阻擋
  • And 顯示提示「該校區下有 1 個課程,請先移除課程或使用『停用』」
  • And 不變更任何資料
待確認 Q03.1:校區是否真有「刪除」需求?預設 軟刪除(停用)為主,硬刪除僅限完全無關聯資料的新建校區。請確認。

Scenario 5 停用校區 — 既有課程不受影響

  • Given Surrey 校區下有進行中的課程
  • When Mark 將 Surrey 設為「停用」
  • Then Surrey 不再出現在報名流程的校區選擇
  • And 既有課程繼續運行直到自然結束
  • And 既有家長 / 學員不受影響
  • And 報表中該校區仍可被選為篩選條件(看歷史數據)

Scenario 6 建立價格表(教練等級 × 課程類型)

  • Given Mark 在「價格表」頁
  • When 他建立以下價格行(皆為單堂未稅 CAD):
課程類型教練等級單堂價格
1on1L4-L580
1on1L1-L360
1on2L4-L550
1on2L1-L340
Group任意30
長訓L4-L535
  • Then 系統儲存 6 條價格
  • And 之後新建課程時,根據 課程類型 × 該節課指派的教練等級 自動帶出單堂價格
  • And 報名 Step 5 費用明細顯示此價格作為基準價

Scenario 7 修改既有價格 — 已報名訂單不受影響(價格快照)

  • Given「1on1 + L4-L5」當前單堂價格 80 CAD
  • And 王女士已為小明以此價格報名 12 堂課(訂單 #123,總額已鎖定)
  • When Mark 將該價格調整為 90 CAD,於 2026-05-08 生效
  • Then 訂單 #123 的單堂價格快照仍為 80 CAD(不影響既有訂單的扣費 / 退款金額)
  • And 之後(含 2026-05-08 起)新建的「1on1 + L4-L5」報名,單堂為 90 CAD
  • And 系統建立 1 筆價格變更 audit log

Scenario 8 建立 ADMIN 員工帳號 — 必須綁校區

  • Given Mark 在「員工帳號」頁
  • When 他新增員工:姓名「Tom」、Email「tom@aquaturbo.com」、角色「ADMIN」、不選校區
  • And 點擊保存
  • Then 系統阻擋
  • And 顯示「ADMIN 角色必須指定所屬校區」

Scenario 9 建立 COACH 員工帳號 — 必須關聯一名教練實體

  • Given 系統已有教練 Coach Luke 的 t_coach 紀錄
  • When Mark 新增員工:姓名「Luke」、角色 COACH、關聯教練:Coach Luke
  • Then 系統建立 sys_user 紀錄並綁定 coach_id
  • And Luke 登入後僅能看到自己的課表
  • When Mark 嘗試建立 COACH 帳號但不關聯任何教練
  • Then 系統阻擋並提示「COACH 角色必須關聯一名教練」

Scenario 10 建立 SUPER_ADMIN 員工帳號 — 跨校區

  • Given Mark 新增員工:姓名「Alice」、角色「SUPER_ADMIN」
  • When 他不指定校區,直接保存
  • Then 系統建立成功
  • And Alice 登入後可看 / 可操作所有校區
  • And Alice 可發起退款、修改已結束的點名、查所有財務

Scenario 11 修改員工角色 — 觸發權限重評

  • Given Tom 當前是 ADMIN(綁定 Burnaby)
  • When Mark 將 Tom 的角色改為 SUPER_ADMIN
  • Then 系統清除 Tom 的 branch_id 綁定
  • And Tom 下次登入即為 SUPER_ADMIN,可看所有校區
  • And Tom 在當前已登入的 session 會被強制登出(因 JWT 中的角色變了)
  • And 系統建立 1 筆角色變更 audit log

Scenario 12 停用員工帳號 — 既有紀錄保留

  • Given 員工 Tom 即將離職
  • When Mark 將 Tom 的帳號設為「停用」
  • Then Tom 無法登入(包含當前 session 強制失效)
  • And Tom 過往的所有操作紀錄保留
  • And Tom 經手的訂單 / 退款 / 點名紀錄不受影響
  • And 報表查詢中可仍以「Tom」為操作人篩選歷史

Scenario 13 教練等級設定與排課限制

  • Given Mark 在「教練管理」頁建立教練「Coach Henry」
  • And 設定 Coach Henry 的 Level 為 L3
  • When Mark 嘗試將 Coach Henry 排到一節 L4-L5 的 1on1 課程
  • Then 系統顯示警告「教練等級(L3)低於課程 Level 區間(L4-L5)」
  • And 預設允許強制排課(彈性需求),但記錄到 audit log
Q03.2:教練等級限制 — 「警告但允許強制」 vs 「完全禁止」?我預設 警告但允許(教學現場可能臨時換人)。請確認。

Scenario 14 國定假日清單管理

  • Given Mark 在「國定假日」頁,2026 年清單預設包含:
  •  • 2026-01-01 New Year
  •  • 2026-04-03 Good Friday
  •  • 2026-05-18 Victoria Day
  •  • 2026-07-01 Canada Day
  •  • 2026-09-07 Labour Day
  •  • 2026-10-12 Thanksgiving
  •  • 2026-12-25 Christmas Day
  • When Mark 將 2026-04-06 也加入「館內活動日(休課)」
  • Then 該日期被加入校區的 closed_dates
  • And 報名 Step 4 上課日期選擇時,該日期顯示為「國定日(休)」且不可選
Q03.3:國定假日是全系統一份還是每校區一份?我預設 全系統一份(加拿大 BC 省假日表),各校區可額外加自己的休館日。

09. 請假(V2.1:24 小時前 / 內 規則)已完成 家長端

⚠️ V2.1 重大變更(2026-05-08 拍板): 請假分界從 12 小時 → 24 小時
≥ 24hr 請假:學員從 enrollment 直接移除、不扣費、僅季度課產生補課額度。
< 24hr 請假:仍在 enrollment、照常扣費、無補課(懲罰晚請假)。
「No-show」概念已廢除(由 F08 自動扣費取代;24hr 內請假等同 No-show 扣費)。
業務價值: 作為家長,我希望能為孩子在課程列表中提交請假, 以便管理孩子無法到場的時段,並在符合條件時自動取得補課機會, 同時讓客服掌握出席狀況。
主要角色:家長(Parent)、客服(Admin)、系統(System)

Background 共用前置條件

  • Given 學員「小明」是聯絡人「王女士」名下的學員
  • And「小明」在 Burnaby 校區已報名 2026 Spring 季度週二 17:00 的 1on2 課程,共 12 堂
  • And「小明」當前的請假額度為 4 次/季度
  • And「小明」目前尚未使用任何請假額度
  • And 王女士已登入家長端
待確認 Q09.1:請假額度 4 次/季度 — 這個數字是固定全系統一致,還是 per 學員/per 等級可配置?文檔未明說。

Scenario 1 24 小時前請假(≥ 24hr)— 從 enrollment 移除 + 不扣費 + 補課額度(僅季度課)

  • Given 距離下堂課(2026-04-07 17:00)還有 25 小時
  • And 該堂課屬於季度課程
  • When 王女士在課程列表點擊該堂課的「請假」並確認
  • Then 該堂課狀態 → 「正常請假」
  • And 系統立即將小明從該 session 的 enrollment 移除(capacity 釋放)
  • And 系統對該堂課扣費(4/7 17:00 自動扣費 cron 觸發時,小明已不在 enrollment)
  • And 系統為小明建立 1 筆「待補課」紀錄
  • And 補課額度 -1(從 2 變 1,僅季度課有額度)
  • And 請假額度 -1(從 4 變 3)
  • And Toast 提示「請假成功,可申請補課(同等級及以上空檔)」
  • And 通知中心新增「請假成功」

Scenario 2 24 小時內請假(< 24hr)— 仍 enrollment、照常扣費、無補課

  • Given 距離下堂課還有 8 小時(< 24hr)
  • When 王女士提交請假
  • Then 介面先彈確認框:「距離上課不足 24 小時,本次請假將不可補課,且該堂課照常扣費。確定提交?」
  • When 王女士確認
  • Then 該堂課狀態 → 「晚請假(不可補課)」
  • And 小明仍在該 session 的 enrollment 中
  • And 系統建立補課紀錄
  • And 補課額度不變、請假額度不變(晚請假無實際權益,不消耗)
  • And 課程開始時系統照常扣 80 CAD(依 F08 自動扣費)
待確認 Q09.2:晚請假是否消耗請假額度?我預設 不消耗(沒拿到任何權益不應再扣額度)。請確認。

Scenario Outline 請假距離 vs 後果對照表(V2.1:24 小時為界)

  • Given 距離下堂課還有 <小時> 小時
  • When 王女士提交請假
  • Then 請假類型 = <類型>
  • And 從 enrollment 移除 = <移除>
  • And 是否扣費 = <扣費>
  • And 補課額度變動 = <補課>(僅季度課)
  • And 請假額度變動 = <請假>

Examples:

小時類型移除扣費補課請假
72正常請假−1−1
24.01正常請假−1−1
24.00正常請假−1−1
23.99晚請假00
1晚請假00
0晚請假00
待確認 Q09.4:「24 小時前」邊界是否含 24:00:00 整點?我預設 (≥ 24hr → 正常請假)。請確認。

Scenario 3 請假額度已用完,自助請假被阻擋

  • Given「小明」當前剩餘請假額度為 0
  • When 王女士嘗試對該堂課提交請假
  • Then 系統阻擋此次請假
  • And 顯示提示「請假額度已用完,如需請假請聯繫客服」
  • And 該堂課狀態維持為「已報名」
  • And 不產生任何請假紀錄、不扣額度、不通知

Scenario 4 撤回請假(補課名額未被佔用)— 成功

  • Given 王女士已對 2026-04-07 的課提交「正常請假」
  • And 該堂課對應的「待補課」名額尚未被任何學員選走
  • When 王女士點擊「撤回請假」
  • Then 該堂課狀態恢復為「已報名」
  • And「小明」剩餘請假額度回到請假前的值(恢復為 4 次)
  • And 對應的「待補課」紀錄被移除
  • And 通知中心新增「撤回請假成功」通知

Scenario 5 撤回請假(名額已被他人補走)— 阻擋

  • Given 王女士已對該堂課提交請假
  • And 學員「小華」已選擇該名額補課並完成
  • When 王女士點擊「撤回請假」
  • Then 系統阻擋撤回
  • And 顯示提示「該名額已被他人補走,無法撤回。如需協助請聯繫客服」
  • And 該堂課狀態維持為「正常請假」

Scenario 6 客服特批超額請假

  • Given「小明」剩餘請假額度為 0
  • And 客服「Lisa」已登入後台
  • When Lisa 在學員詳細頁為「小明」對某堂課手動添加「特批請假」
  • Then 該堂課狀態顯示為「超額請假(特批)」
  • And「小明」剩餘請假額度仍為 0(特批不扣額度)
  • And 系統建立 1 筆 audit log,記錄操作人 Lisa、時間、原因
  • And 王女士的家長端收到「客服已為您安排請假」通知
待確認 Q09.5:特批請假是否同時提供補課資格?我預設「」(特批的意義在於恢復家長權益)。請確認。

Scenario 7 24hr 內請假與 F08 自動扣費的整合 內部行為

  • Given 小明對 2026-04-07 17:00 提交「晚請假」(< 24hr)
  • And 小明仍在該 session enrollment 中
  • When 17:00 系統觸發 F08 自動扣費 cron
  • Then 系統檢測到小明仍在 enrollment(晚請假不移除)
  • And 從訂單對應餘額扣 80(單堂價)
  • And t_credit_log change_type = LATE_LEAVE_DEDUCT(區別於正常 SESSION_AUTO_DEDUCT)
  • And 不建立補課紀錄
  • And session.status → PROCESSED

Scenario 8 24hr 前請假時 enrollment 立即移除 內部行為

  • Given 王女士提交 24hr 前請假
  • When 系統處理請假請求(單一事務)
  • Then 系統執行:
  •  • DELETE FROM t_enrollment WHERE student_id = X AND session_id = Y
  •  • INSERT t_leave_record(紀錄請假歷史,永久保留)
  •  • 季度課:INSERT t_makeup_quota(補課額度)
  •  • UPDATE t_student.leave_quota -= 1(季度範圍)
  •  • 通知家長
  • And 之後 F08 自動扣費 cron 觸發時,該 student 不在 enrollment 列表中,跳過扣費

04. 課表查看(多視角) 已完成 家長端 + 客服端 + 教練

⚠️ V2.1 變更: 家長端課表規則調整為「只顯示有空位可填 Waitlist 的課程 + 自家學員已報名的課程」。 結合 F13 V2.1(Waitlist 綁 session),家長從課表上看到滿員課可點「加入 Waitlist」。 客服 / 教練端不變。
主要角色:家長(王女士)、客服 ADMIN(Lisa, Burnaby)、客服 SUPER_ADMIN(Mark)、教練(Coach Luke)、系統

Background 共用前置條件

  • Given 2026 Spring 季度(2026-03-01 ~ 2026-06-15)已建立
  • And Burnaby 校區的 Pool A 已建立(含 Lane 1, 2, 3)
  • And Burnaby 課表包含以下課程:
  •  • 1on2 週二 17:00-18:00 Pool A Lane 1 by Coach Luke (L4),Level 區間 L3-L4,2 left
  •  • 1on1 週四 18:00-19:00 Pool A Lane 2 by Coach Luke,Level 區間 L4-L5,1 left
  •  • Group 週六 10:00-11:00 Pool A Lane 3 by Coach Eric,Level 區間 L1-L2,4 left
  • And 小明(L3)已報名週二 17:00 那節 1on2
  • And 王女士已登入家長端
  • And Lisa(ADMIN, Burnaby)已登入後台
  • And Coach Luke 已登入

Scenario 1 家長查看自家學員的課表(當前季度)

  • Given 王女士當前查看「小明」的視角
  • And 當前是 2026-04-15(在 2026 Spring 季度內)
  • When 她進入家長端「課表」頁
  • Then 預設顯示週視圖(本週週一到週日)
  • And 課表只顯示 2026 Spring 季度的 sessions
  • And 看到 3 種狀態的課程:
  •  • 已報名(藍色框):週二 17:00 1on2 by Coach Luke
  •  • 可補(橙色框):之前請假符合補課條件的課(若無則不顯示)
  •  • Available(淡灰色虛線):尚未設定類型的空堂
  • And 顯示其他學員(如小華)的課程
  • And 顯示其他校區(如 Coquitlam)的課程

Scenario 2 家長端視圖切換 — 日 / 週 + 月份切換器

  • Given 王女士在課表頁
  • When 她點擊頂部「日」/「週」切換器
  • Then 切換對應視圖
  • And 月份切換器在頂部,可前後翻月
  • And 但只能在 2026 Spring 季度範圍內切換(2026-03 至 2026-06)
  • And 嘗試切到季度外的月份時提示「下一季度尚未開放查看」
待確認 Q04.1:家長端是否提供「月視圖」(grid 顯示一整月)?文檔說「日 / 週兩種視圖,並可切換月份」 — 我解讀為「日 + 週兩種視圖,月份是切換器」(沒有真正的月 grid view)。請確認。

Scenario 3 家長僅看符合學員 Level 的課(+ 所有 Available 空堂)

  • Given 小明 Level = L3
  • And Burnaby 課表有以下課程(與 Background 列表)
  • When 王女士查看小明的課表
  • Then 看到 1on2 L3-L4(符合 Level)
  • And 看到 1on1 L4-L5(Level 不符)
  • And 看到 Group L1-L2(Level 不符)
  • And 所有 Available 空堂仍顯示(不論 Level — 因為空堂沒有預設 Level)

Scenario 4 家長無法查看歷史 / 未來季度

  • Given 當前是 2026 Spring 季度進行中
  • And 2025 Fall(已結束)和 2026 Summer(未開始)的課表也都存在
  • When 王女士嘗試切換月份到 2026-07 或 2026-01
  • Then 系統提示「僅可查看當前季度(2026 Spring)」
  • And 不顯示這些月份的課程
待確認 Q04.2:家長端是否能查看「已結束的歷史季度」?我預設 不可(在課表頁僅當前季度),但「學習紀錄」頁可以看歷史已上完課程。請確認。

Scenario 5 家長點擊 Available 空堂 — 不能直接報名

  • Given 課表存在 Available 空堂(週三 17:00 Pool A Lane 1)
  • When 王女士點擊該空堂
  • Then 開啟說明卡片:「此時段尚未開課,如有興趣請聯繫客服」
  • And 卡片提供 2 個入口:「聯繫客服」+「加入 Waitlist」
  • And 完全沒有「立即報名」按鈕(V2 規定家長不可自助報名)

Scenario 6 家長點擊已報名課程 — 進入詳情面板

  • Given 小明已報名週二 17:00 課
  • When 王女士點擊該課程卡片
  • Then 開啟詳情面板,顯示:
  •  • 課程資訊(時間、教練、Level、泳道、課程類型)
  •  • 該日課程狀態(待點名 / 出席 / 請假 / No-show)
  •  •「請假」按鈕(依 12 小時規則決定行為,詳見 F09)
  •  •「查看課程說明」入口(教練介紹、課程目標等)
  • And 不顯示其他學員的個人資訊(即使同節 1on2 還有另一位學員)

Scenario 7 家長點擊「可補」標記 — 進入補課流程

  • Given 小明前一週請假了 1 堂 1on2 課(正常請假,產生補課額度)
  • And 課表上有另一堂同類型同教練等級同價格的 1on2 課程,未滿員
  • When 王女士點擊該「可補」標記的課程
  • Then 進入補課確認流程(詳見 F10 補課)

Scenario 8 ADMIN 僅看本校區的課表

  • Given Lisa 是 ADMIN,綁定 Burnaby 校區
  • When 她進入後台課表
  • Then 校區下拉選擇器只顯示「Burnaby」(唯一可選)
  • And 直接調整 URL 或 API 嘗試切到 Coquitlam → 返回 403
  • And 後端會強制注入 branchId 過濾條件,無法繞過

Scenario 9 SUPER_ADMIN 跨校區查看

  • Given Mark 是 SUPER_ADMIN,無校區綁定
  • When 他進入後台課表
  • Then 校區下拉可選 Burnaby / Coquitlam / Surrey 等
  • And 切換校區後課表立即更新
  • And 可選「全部校區」做跨校區比較(如報表用途)

Scenario 10 客服 11 維度篩選器

  • Given Lisa 在後台課表頁
  • When 她展開頂部固定篩選列
  • Then 看到以下 11 個篩選欄位:
#欄位取值
1課程種類(Kind)正課 / 補課 / 單次(One-Time)/ Trial
2課程類型(Type)1on1 / 1on2 / Group / 長訓
3教練下拉選一個或多個
4日期單日 / 範圍
5季度2026 Spring / Summer / Fall / Winter
6泳道Lane 1 / Lane 2 / ...
7泳池Pool A / Pool B
8學員標籤WAITLIST / ACTIVE_CREDIT / NEW / ...(詳見 F12)
9等級(Level 區間)L1 / L2 / L3 / L4 / L5
10時間段例:14:00-18:00
11搜尋欄關鍵字模糊搜尋(教練名 / 學員名 / 備註)
待確認 Q04.3:「課程種類」與「課程類型」是否為兩個不同欄位?我解讀為:「種類」= 報名性質(正課 / 補課 / 一次性 / Trial),「類型」= 教學形式(1on1 / 1on2 / Group / 長訓)。請確認。

Scenario 11 客服建立新課程 — 設定教練 / 泳道 / 時間 / Level / 人數

  • Given Lisa 在週四 17:00 的空白時段
  • When 她點擊「新增課程」並填寫:
  •  • 課程類型: 1on2
  •  • 教練: Coach Luke
  •  • 泳道: Pool A Lane 2
  •  • 時間: 週四 17:00-18:00
  •  • Level 區間: L3-L4(自定義)
  •  • 人數上下限(1on2 自動鎖 = 2/2)
  • And 點擊保存
  • Then 系統建立 1 個 RecurrenceGroup(季度週期性紀錄)
  • And 系統自動展開該季度所有上課日期的 sessions(扣除國定假日)
  • And 課表立即顯示新增課程

Scenario 12 衝突檢測 — 泳道時間衝突(V2 必須阻擋)

  • Given 週二 17:00-18:00 Pool A Lane 1 已被 Coach Luke 的 1on2 課程使用
  • When Lisa 嘗試在同時段同泳道安排另一節課
  • Then 系統阻擋
  • And 顯示「Pool A Lane 1 在週二 17:00-18:00 已被 Coach Luke 的 1on2 課程使用」
  • And 不建立新課程
待確認 Q04.4:泳道衝突在 V1 是「未阻擋」,文檔明確要求 V2 阻擋。但實際使用是否需要「強制覆蓋」逃生門?例如同時間同泳道可拆兩 lane(如 Lane 1A / Lane 1B)?預設 嚴格阻擋,無逃生門

Scenario 13 衝突檢測 — 教練時間衝突

  • Given Coach Luke 在週二 17:00-18:00 已有 1on2 課程(Pool A Lane 1)
  • When Lisa 嘗試在同時段安排 Coach Luke 的另一節 1on1 課(在 Pool A Lane 3)
  • Then 系統阻擋
  • And 顯示「Coach Luke 在週二 17:00-18:00 已有課程」

Scenario 14 衝突檢測 — 學員時間衝突(報名階段)

  • Given 小明已報名週二 17:00 的 1on2 課程
  • When Lisa 嘗試將小明加入週二同時段的另一節課
  • Then 系統阻擋
  • And 顯示「學員小明在週二 17:00 已有課程」
  • And 報名流程的學員選擇步驟即時提示衝突,不必等到 Step 7

Scenario 15 1on1 / 1on2 為固定人數

  • Given Lisa 建立課程,課程類型: 1on2
  • When 系統初始化人數設定
  • Then 系統自動鎖定人數上限為 2
  • And 不允許客服修改該數字(欄位變灰)
  • And 同理 1on1 鎖定為 1

Scenario 16 Group / 長訓 自定義人數上下限

  • Given Lisa 建立課程,課程類型: Group
  • When 系統提示設定人數
  • Then 允許輸入下限(warning_capacity,例 4)和上限(hard_capacity,例 10)
  • And 報名超過 hard_capacity 會被阻擋
  • And 報名數量低於 warning_capacity 時,課表顯示「人數不足」黃色標記
待確認 Q04.5:「人數不足」是否影響開課?例如下限 4 但只報 3 人,是否自動取消開課?預設 不自動取消,僅提示客服,由客服人工決定是否取消或鼓勵 Waitlist。

Scenario 17 ADMIN 不可跨校區點名

  • Given Lisa 是 ADMIN,綁定 Burnaby
  • And 系統設定允許 ADMIN 看到全部校區的課表(純查看)
  • When Lisa 嘗試對 Coquitlam 校區的某節課點名
  • Then 系統阻擋
  • And 顯示「您僅能對 Burnaby 校區的學員點名」
  • And 但仍可查看 Coquitlam 課表(如需 Burnaby 學員到 Coquitlam 上課的情境)
待確認 Q04.6:ADMIN 是否能「看」其他校區的課表?文檔說「ADMIN 可操作所有校區的課表與學員安排」+「不可跨校區進行點名」。看來是 可看全部、僅本校區可點名。請確認。

Scenario 18 教練只看自己的課表

  • Given Coach Luke 已登入
  • When 他進入課表頁
  • Then 只看到自己負責的 sessions
  • And 完全看不到其他教練(如 Coach Eric)的課程
  • And 跨校區(如他同時在 Burnaby 和 Coquitlam 教課)的課全部一起顯示,並標註校區

Scenario 19 教練篩選器(校區 / 等級 / 週幾)

  • Given Coach Luke 同時在 Burnaby 和 Coquitlam 帶 4 個 sessions
  • When Luke 點擊頂部篩選器
  • Then 提供 3 個篩選維度:
  •  • 校區: Burnaby / Coquitlam(限他帶過課的校區)
  •  • 等級: L3 / L4(限他帶過的 sessions Level 區間)
  •  • 週幾: 一 ~ 日
  • And 不提供其他維度(如教練、泳道、學員標籤)— 教練視角是課程而非學員

Scenario 20 教練不可修改任何資料(除點名)

  • Given Coach Luke 在某節 session 詳情頁
  • When 他查看介面
  • Then 介面隱藏「編輯課程」「刪除」「報名學員」「取消報名」等按鈕
  • But「點名」按鈕保留可用(含逐生點名 + 批量點名)
  • And 即使 API 直接呼叫修改 / 刪除類接口會返回 403

Scenario 21 系統自動展開季度週期性課程 內部行為

  • Given Lisa 建立週二 17:00 1on2 課程,季度為 2026 Spring(2026-03-01 ~ 2026-06-15)
  • And 季度區間共有 15 個週二
  • And 其中 2026-04-07(Good Friday)為國定假日
  • When 系統建立 RecurrenceGroup 並執行展開
  • Then 系統建立 14 個 t_session 紀錄(15 - 1 國定假日)
  • And 每個 session 對應一個具體日期
  • And 國定假日不建立 session
  • And 報名 Step 4 顯示這 14 個日期 + 國定假日(標記「國定日(休)」不可選)

Scenario 22 季度跨月份切換時的 session 展示 內部行為

  • Given 課表查詢請求要看 2026-04 月的課程
  • When 後端收到請求
  • Then 從 t_session 中查詢 session_date 在 2026-04-01 ~ 2026-04-30 範圍內的紀錄
  • And 應用該角色的篩選(家長: 自己學員; ADMIN: 校區; COACH: 自己)
  • And 應用該角色的可見性(家長: Level 過濾; 客服 / 教練: 全部可見)
  • And 返回 sessions + 每節的當前報名數 + 每節的學員列表

05. 餘額 / 訂單模型(V2.1:雙層餘額)已完成 內部行為 + 客服端 + 家長端

⚠️ V2.1 重大變更(2026-05-08 拍板): 架構從「訂單剩餘額度直接扣」 → 「付款先進餘額,再從餘額扣」。
餘額分兩層:(1) 不可消費餘額(訂單付款,綁訂單);(2) 自由消費餘額(任意金額付款 / 退款找零 / 訂單對應課程結束後剩餘)。
扣費先扣訂單對應的不可消費餘額,不足從自由消費補;都不足 alert 客服。
訂單對應 sessions 全部結束後,剩餘自動轉自由消費餘額(cron 每天)。
詳見正典定義 E。Scenarios 中的「訂單剩餘額度」應理解為「訂單對應的不可消費餘額」(語義等價)。
新增功能:任意金額付款(客服在後台代家長充值,進自由消費餘額),FIFO 抵扣概念由「訂單建立時間」改為「整個餘額池中先扣不可消費(按訂單時間)→ 再扣自由消費」。
主要角色:系統、客服(Lisa)、SUPER_ADMIN(Mark)、家長(王女士)

Background 共用前置條件

  • Given 王女士名下有學員小明(L3)
  • And 小明當前無任何訂單,Credit Pool 為空
  • And 系統已配置:1on2 + L3-L4 教練 = 80 CAD/堂(未稅)
  • And GST = 5%
  • And 註冊費 = 30 CAD(一次性,已收於小明首次報名)

Scenario 1 訂單完整生命週期:待付款 → 待確認 → 已完成(Online)

  • Given Lisa 為小明生成訂單 #001:12 堂 1on2,總額 1008 CAD(80×12×1.05)
  • When Lisa 點擊「生成訂單」
  • Then 訂單 #001 狀態 = 待付款
  • And 12 個 sessions 的 capacity 暫時保留(小明被視為「待付款佔位」,不算正式報名)
  • And 系統設定 60 分鐘倒數計時
  • And 王女士的家長端立即看到該待付款訂單
  • When 30 分鐘後王女士在家長端完成 Online 付款
  • Then 訂單狀態 → 待確認
  • And Online 倒數計時停止
  • When 金流系統 webhook 確認 Stripe 收款成功(或 Lisa 在後台手動標記確認)
  • Then 訂單狀態 → 已完成
  • And 小明正式加入 12 個 sessions 的學員列表
  • And 訂單剩餘額度 = 12 堂 / 960 CAD(未稅課程價值 = 1008 - 48 GST)
  • And 通知中心:「訂單 #001 已完成,學員已加入課表」
待確認 Q05.1:訂單剩餘額度的單位 — 是「堂數」還是「金額」?我預設兩個都記錄(堂數 = 主鍵;金額 = 單堂價格快照 × 堂數)。每節課單價可能因「教練等級」「課程類型」而異,所以堂數+單價快照組合最準確。請確認。
待確認 Q05.2:Credit Pool 計入的金額是「未稅」還是「含稅」?我預設 未稅(GST 屬政府不該流入學員餘額池)。退款時 GST 部分按比例退回原付款方式。請確認。

Scenario 2 訂單超時自動取消(Online 60 分鐘)

  • Given 訂單 #002 在 14:00 生成,待付款狀態
  • And 12 個 sessions 的 capacity 暫時保留
  • When 60 分鐘後(15:00)王女士仍未完成 Online 付款
  • Then 系統自動將訂單 #002 狀態改為 已取消
  • And capacity 立即釋放(其他學員可報名同 session)
  • And 王女士的家長端待付款訂單從列表消失
  • And 通知中心新增「訂單 #002 已自動取消(超時未付款)」
  • And 該訂單不可再被使用 / 復活

Scenario 3 Cash 訂單超時自動取消(48 小時)

  • Given 訂單 #003 為 Cash 付款方式,2026-04-01 14:00 生成
  • When 48 小時後(2026-04-03 14:00)小明 / 王女士都未到客服處交現金
  • Then 系統自動將訂單 #003 改為已取消
  • And capacity 釋放
  • And 家長端通知「Cash 訂單 #003 已逾時取消,如仍需報名請聯繫客服重新生成訂單」

Scenario 4 已完成訂單退款流程(已完成 → 退款中 → 已退款)

  • Given 訂單 #001 已完成,已上 4 堂課
  • And 訂單剩餘 = 8 堂 / 640 CAD(未稅)
  • And 王女士因小明受傷申請退款
  • When SUPER_ADMIN Mark 在學員詳情頁發起退款
  • And 填寫退款明細:未上 8 堂 = 640;扣手續費 10;可退 = 630(不退註冊費,因為註冊費已隱含在歷史訂單中)
  • Then 訂單 #001 狀態 → 退款中
  • And 訂單剩餘額度凍結(不可再扣費,從 Credit Pool 移除)
  • And 8 個未來 sessions 中的小明被移除(capacity 釋放)
  • When 金流系統確認已退款 630 CAD 到原付款方式
  • And Mark 在後台手動標記「金流已確認」
  • Then 訂單狀態 → 已退款
  • And 通知中心:「退款 630 CAD 已完成」
  • And 學員標籤更新為 REFUNDED(詳見 F12)

Scenario 5 不允許的訂單狀態轉換 — 防呆

  • Given 系統定義合法 transitions:
  •  • 待付款 → 待確認 / 已取消
  •  • 待確認 → 已完成 / 已取消
  •  • 已完成 → 退款中
  •  • 退款中 → 已退款
  • When 任何 service / 直接 SQL 嘗試非法 transition
  • Then 系統拒絕並丟出錯誤碼 ORDER_INVALID_TRANSITION

非法 transitions 對照表:

合法?原因
待付款已完成必須經過待確認
已取消任何取消後不可復活
已退款任何終態
已完成已取消已完成只能走退款
待確認已取消是 (Q)客服查無入帳可取消

Scenario 6 已完成訂單形成 Credit Pool

  • Given 訂單 #001 已完成,總價值 960 CAD(12 堂 × 80 未稅)
  • And 已上 4 堂 → 扣 320 CAD
  • When 系統查詢小明的 Credit Pool 餘額
  • Then Pool = 960 - 320 = 640 CAD(剩 8 堂)
  • And 來源明細顯示:「訂單 #001:剩 8 堂 1on2 / 640 CAD」
  • And 該餘額可用於該訂單的後續 8 堂課扣費
  • And 該餘額可用於 FIFO 抵扣下一筆同學員的新訂單

Scenario 7 退款找零回 Credit Pool

  • Given 訂單 #001 申請退款
  • And 應退 = 540 CAD(已扣 4 堂 + 註冊費不退 + 手續費 + 一些奇數調整)
  • And 但金流系統因規則限制只能整數退 530 CAD
  • When Mark 在退款表單裡分配「退到金流 530,找零 10 進 Credit Pool」
  • Then 訂單 #001 狀態 → 已退款
  • And Credit Pool 增加 10 CAD(來源:退款找零)
  • And Credit 詳情頁新增一行:「退款找零 +10 CAD(來自訂單 #001 退款)」
  • And 該 10 CAD 可用於下次報名 FIFO 抵扣
待確認 Q05.3:找零的決定權 — 是「金流退不到自動入池」還是「Mark 手動分配」?我預設 Mark 手動分配(更靈活,可以選擇全退金流 / 部分入池)。請確認。

Scenario 8 點名觸發扣款 — 從訂單剩餘扣(綁定 order_id)

  • Given 小明訂單 #001 剩 8 堂 / 640 CAD
  • And 4 月 14 日的課程屬於該訂單
  • When Lisa 對 4/14 課程點名小明為「出席」
  • Then 系統從訂單 #001 扣除 1 堂(80 CAD 未稅)
  • And 訂單 #001 剩餘 = 7 堂 / 560 CAD
  • And t_credit_log 新增 1 筆紀錄:
  •  • student_id = 小明
  •  • amount = -80
  •  • change_type = SESSION_DEDUCT
  •  • session_id = 4/14 課程的 session
  •  • order_id = #001 ← 綁定到具體訂單
  •  • balance_after = 560
  • And 訂單仍為「已完成」狀態

Scenario 9 No-show 自動扣款(與出席同等)

  • Given 小明訂單 #001 剩 7 堂額度
  • And 4/21 課程結束後沒人點名小明
  • When 系統於課程結束(19:00)自動結算
  • Then 小明標記為 No-show
  • And 從訂單 #001 扣除 1 堂(與正常出席相同)
  • And t_credit_log 新增 change_type = NO_SHOW_DEDUCT

Scenario 10 訂單耗盡時的處理

  • Given 小明訂單 #001 剩 1 堂 / 80 CAD
  • When 點名最後一堂為出席
  • Then 訂單 #001 剩餘 = 0 堂 / 0 CAD
  • And 訂單狀態仍為「已完成」(不變)
  • And 訂單在「Credit Pool 來源明細」中標記為「已耗盡」
  • And 不再出現在「可抵扣下次報名」的可用列表中

Scenario 11 Dashboard 顯示總 Credit 餘額(多訂單合計)

  • Given 小明的 Credit 來源:
  •  • 訂單 #001:剩 7 堂 1on2 / 560 CAD
  •  • 訂單 #002:剩 3 堂 1on1 / 240 CAD(不同類型)
  •  • 退款找零:10 CAD
  • When 王女士在家長端 Dashboard 查看小明
  • Then 顯示主要數字:「Credit 餘額:810 CAD」
  • And 下方備註:「來自 2 筆訂單 + 退款找零」
  • And 點擊「詳情」進入逐筆明細頁

Scenario 12 詳情頁逐筆訂單明細(強調「對應課程」)

  • Given 王女士進入 Credit 詳情頁
  • Then 顯示 3 筆來源(按 FIFO 順序):
來源內容對應課程
訂單 #001剩 7 堂 1on2 / 560 CAD連結 → 看 7 個未來 sessions
訂單 #002剩 3 堂 1on1 / 240 CAD連結 → 看 3 個未來 sessions
退款找零10 CAD(任意類型可抵扣)來源訂單 #003 退款
  • And 訊息語氣強調「對應課程」而非「自由餘額」
  • And 介面顯示說明文字:「此 Credit 對應您已付款的課程,可用於後續扣費或抵扣新訂單」

Scenario 13 Credit 流水紀錄(每筆綁訂單)

  • Given 小明已上完 5 堂 1on2 課
  • When 王女士查看 Credit 流水頁
  • Then 顯示流水紀錄(按時間倒序):
日期動作金額關聯訂單扣後餘額
2026-04-21出席扣費-80#001560
2026-04-14出席扣費-80#001640
2026-04-07出席扣費-80#001720
2026-03-31出席扣費-80#001800
2026-03-24出席扣費-80#001880
2026-03-17訂單建立+960#001960
  • And 每筆顯示日期、動作(出席 / 退款 / 找零 / 抵扣)、金額、訂單關聯、扣後餘額

Scenario 14 FIFO 抵扣下次報名

  • Given 小明 Credit Pool 來源:
  •  • 訂單 #001(已完成 2026-03-17):剩 200 CAD
  •  • 訂單 #002(已完成 2026-04-25):剩 100 CAD
  •  • 退款找零(產生 2026-05-15):50 CAD
  • And Lisa 為小明生成新訂單 #004:總額 250 CAD(含稅)
  • When Lisa 在 Step 6 輸入「使用 250 Credit 抵扣」
  • Then 系統按 FIFO 順序扣:
  •  • 從訂單 #001 扣 200(耗盡)
  •  • 從訂單 #002 扣 50(剩 50)
  • And 退款找零未被動到
  • And 訂單 #004 待付款金額 = 0 CAD(純 Credit 抵扣)
  • And Credit 流水新增 2 筆「抵扣訂單 #004」紀錄
待確認 Q05.4:FIFO 是按「訂單完成時間」還是「訂單建立時間」?我預設 訂單完成時間(更貼近「先收到的錢先用」的會計邏輯)。請確認。
待確認 Q05.5:退款找零是否與訂單剩餘額度走同一個 Pool?或是優先 / 最後扣?我預設 同 Pool 但 FIFO 在最後(找零產生時間通常較晚)。請確認。

Scenario 15 抵扣超過 Credit Pool 餘額 — 阻擋

  • Given 小明 Credit Pool 總計 350 CAD
  • When Lisa 在 Step 6 嘗試輸入「抵扣 400 CAD」
  • Then 系統阻擋
  • And 顯示「Credit 餘額不足,最多可使用 350 CAD」
  • And 自動將輸入框值修正為 350

Scenario 16 抵扣範例(文檔指定範例)

  • Given 訂單總額 621.18 CAD(含稅)
  • And 小明 Credit Pool = 220 CAD
  • When Lisa 在 Step 6 輸入「使用 220 Credit」
  • Then 系統即時計算「待付款 = 401.18 CAD」(621.18 - 220)
  • And 點擊生成訂單後,220 CAD 從 Pool 扣除
  • And 待付款訂單顯示應付 401.18

Scenario 17 Credit 不可跨學員共用(兄弟姐妹獨立池)

  • Given 王女士有 2 個學員:小明(Credit 200)、小華(Credit 0)
  • When Lisa 為小華生成訂單時嘗試使用「家庭合計 Credit」
  • Then 系統不提供此選項
  • And Credit 抵扣輸入欄位顯示「小華 Credit Pool: 0 CAD」
  • And 小明的 Credit 不可用於小華的訂單
待確認 Q05.6:同家庭兄弟姐妹之間 Credit 是否可共享?我預設 不共享(每學員獨立池,避免會計混亂)。如果需要共享,可由 SUPER_ADMIN 手動發起「Credit 轉移」(特批操作 + audit log)。請確認。

Scenario 18 跨季度延續(Credit 不過期)

  • Given 2026 Spring 結束時小明 Credit Pool 還有 200 CAD
  • And 進入 2026 Summer 季度
  • When Lisa 為小明報名 2026 Summer 課程
  • Then 上季剩餘 200 CAD 仍可用於本次報名(FIFO 抵扣)
  • And Credit Pool 永不過期
待確認 Q05.7:Credit Pool 真的永不過期?實務上有些學校會設「12 個月內未使用即過期,作為公司收入」。我預設 永不過期(V2 文檔未提,且更符合用戶權益)。請確認。

Scenario 19 部分退款的處理

  • Given 訂單 #001 已完成,剩 8 堂未上 / 640 CAD
  • And 王女士想退 4 堂(保留 4 堂繼續上)
  • When Mark 發起部分退款,退 4 堂 = 320 CAD
  • Then 訂單 #001 狀態仍為「已完成」(不變)
  • But 在訂單詳情標註「部分退款 320 CAD(2026-05-08)」
  • And 訂單剩餘額度從 8 堂 / 640 變為 4 堂 / 320
  • And 對應的 4 個未來 sessions 中小明被移除
  • And t_refund 新增 1 筆部分退款紀錄(refund_type = PARTIAL)
  • And t_credit_log 新增 1 筆 PARTIAL_REFUND 流水
待確認 Q05.8:部分退款後訂單狀態保持「已完成」是否合理?或要新增「部分退款」狀態?我預設 保持已完成 + 詳情標註(避免狀態機過度複雜)。請確認。

Scenario 20 t_credit_log / t_audit_log / t_refund 不可修改 內部行為

  • Given 系統有 100 筆 credit_log 紀錄
  • When 任何 Service / Mapper 嘗試 UPDATE 或 DELETE 一筆 credit_log
  • Then 操作被 Mapper 層阻擋(編譯期不存在這兩個方法)
  • And 唯一允許的操作是 INSERT 新紀錄
  • And 同樣規則適用於 t_audit_log 與 t_refund(refund 不可 DELETE)
  • And 即使 SUPER_ADMIN 也無法繞過 — 想修正錯誤紀錄只能 INSERT 一筆「沖正」紀錄

Scenario 21 訂單超時自動取消任務(系統定時) 內部行為

  • Given 系統有大量待付款訂單
  • When 系統每 1 分鐘執行一次「訂單超時掃描」
  • Then 找出所有 created_at 超過 60 分鐘的 Online 待付款訂單,標記為已取消
  • And 找出所有 created_at 超過 48 小時的 Cash 待付款訂單,標記為已取消
  • And 釋放對應的 capacity
  • And 對受影響的家長發出通知
  • And 系統 audit log 紀錄取消批次

06. 報名 7 步流程 已完成 客服端

⚠️ V2.1 變更: (1) 季度課必須先排好課(建立 RecurrenceGroup + 展開 sessions)才能生成訂單(流程不變,但概念明確)。 (2) 新增「任意金額付款」捷徑(不走 7 步,直接客服在後台「為學員充值」彈窗,進自由消費餘額)— 詳見 F07 新增 scenario。 (3) Step 6 Credit 抵扣的「Credit Pool」改為「自由消費餘額」(不可消費餘額不可作為抵扣下次報名的工具,仍綁原訂單)。
主要角色:客服(Lisa)、SUPER_ADMIN(Mark)、家長(王女士)、學員(小明 L3 / 小華 L1)、系統

Background 共用前置條件

  • Given Lisa(ADMIN, Burnaby)已登入後台
  • And 2026 Spring 季度(2026-03-01 ~ 2026-06-15)已建立
  • And Burnaby 課表包含:1on2 週二 17:00 by Coach Luke (L3-L4)、1on1 週四 18:00 by Coach Luke (L4-L5)、Group 週六 10:00 by Coach Eric (L1-L2)
  • And 價格表:1on2 + L3-L4 = 80 CAD/堂;1on1 + L4-L5 = 80 CAD/堂;Group 任意 = 30 CAD/堂
  • And 註冊費 = 30 CAD(per 學員生命週期一次)
  • And GST = 5%
  • And 小明(L3)尚無任何訂單;小華(L1)首次接觸系統

Scenario 1 入口 1:從課表的某個課程進入「學員報名」

  • Given 課表上有「週二 17:00 1on2 by Coach Luke」課程,1 left
  • When Lisa 在課表點擊該課程的「學員報名」按鈕
  • Then 進入報名嚮導 Step 1
  • And Step 1 自動帶入:校區 = Burnaby
  • And Step 2 預設選「季度(Seasonal)」+ 季度 = 2026 Spring
  • And Step 3 自動帶入:課程類型 = 1on2、週幾 = 週二、時間 = 17:00、教練 = Coach Luke、泳道 = Lane 1、Level 區間 = L3-L4
  • And Lisa 只需 Step 1 選學員、Step 4 確認日期、後續走完即可

Scenario 2 入口 2:從 Waitlist 進入「報名」

  • Given Waitlist 上有小華的候補需求:Burnaby、2026 Spring、週六、10:00、Group
  • When Lisa 在 Waitlist 列表點擊小華那筆紀錄的「報名」按鈕
  • Then 進入 Step 1
  • And 自動帶入:學員 = 小華、聯絡人 = 王女士、Level = L1、校區 = Burnaby
  • And Step 2 預設選「季度」+ 季度 = 2026 Spring(依 Waitlist 偏好)
  • And Step 3 預設課程類型 = Group、週幾 = 週六、時間提示 ≈ 10:00(仍由 Lisa 確認)

Scenario 3 入口 3:從學員管理頁進入「報名」

  • Given Lisa 在學員列表點擊小明那行的「報名」按鈕
  • When 進入報名流程
  • Then Step 1 自動帶入:學員 = 小明、聯絡人 = 王女士、Level = L3
  • And 校區欄位顯示小明主校區 = Burnaby(但可改)
  • And Step 2-7 全部需 Lisa 手動選擇

Scenario 4 Step 1:校區可重新選擇(學員可能跨校區上課)

  • Given Lisa 在 Step 1,預設校區 = 小明主校區 Burnaby
  • When Lisa 將校區改為 Coquitlam
  • Then 系統允許切換
  • And 後續 Step 3 課程列表將顯示 Coquitlam 校區的課程
  • And 訂單最終建立在 Coquitlam 校區下(branch_id = Coquitlam)
  • And 小明的「主校區」欄位不變(仍為 Burnaby)

Scenario 5 Step 2:選擇 Seasonal — 需指定具體季度

  • Given Lisa 在 Step 2
  • When 她選「季度(Seasonal)」
  • Then 顯示季度下拉:2026 Spring(進行中)/ 2026 Summer / 2026 Fall
  • And 預設選擇進行中或下一個將開放的季度
  • And 已結束的季度(如 2025 Fall)在選項中

Scenario 6 Step 2:選擇 Trial — 自動套用 8 折

  • Given 小明從未上過 Coach Luke 的正式課
  • And 小明從未對 Coach Luke 申請過 Trial
  • When Lisa 在 Step 2 選「Trial」
  • Then 系統允許繼續
  • And Step 5 費用明細的單堂計算將自動套用 80% × 80 = 64 CAD
  • And 折價 16 CAD 顯示在 Invoice 的「折扣」欄位(呈現原價 + 折扣)

Scenario 7 Step 2:Trial 規則 — 每教練名下僅一次

  • Given 小明已對 Coach Luke 申請過 Trial 並已上完
  • When Lisa 在 Step 2 為小明選「Trial」並在 Step 3 選 Coach Luke 的課
  • Then 系統阻擋
  • And 顯示「該學員已對 Coach Luke 使用過 Trial,不可再 Trial」
  • But Lisa 可改選 Coach Eric 或其他教練的 Trial(前提是該學員從未對該教練 Trial 或上正式課)

Scenario 8 Step 2:Trial 規則 — 已上過正式課則不可

  • Given 小明已上過 Coach Luke 的 1on2 季度課(已 attendance)
  • When Lisa 嘗試為小明選 Trial + Coach Luke 的課
  • Then 系統阻擋
  • And 顯示「學員已上過 Coach Luke 的正式課程,無法使用 Trial」
待確認 Q06.1:「上過正式課」的判定 — 是「曾被任意點名為出席」,還是「曾完成過該教練名下的訂單付款」?我預設 曾被點名為出席(attendance status = ATTENDED)。請確認。

Scenario 9 Step 3:列表只顯示符合學員 Level 的課程

  • Given 小明 Level = L3
  • And Burnaby 課程:1on2 L3-L4、1on1 L4-L5、Group L1-L2
  • When Lisa 在 Step 3 選「課程類型 = 1on2」+「週幾 = 週二」
  • Then 列表顯示「17:00-18:00 Lane 1 L3-L4 Coach Luke 1 left」
  • And 不顯示 1on1 L4-L5(Level 不符)
  • And 不顯示 Group(course type 不符)

Scenario 10 Step 3:滿員的課程不顯示

  • Given 「週二 17:00 1on2」已滿員(2/2)
  • When Lisa 在 Step 3 選 1on2 + 週二
  • Then 該時段在列表中
  • And 列表只顯示其他週二的 1on2(未滿員)
  • And 若無任何符合課程,顯示「無可報名課程,請考慮其他週幾或加入 Waitlist」

Scenario 11 Step 3:空堂(Available)不在報名列表中

  • Given 課表有「週三 19:00-20:00 Lane 1」空堂(尚未設定課程類型)
  • When Lisa 在 Step 3 選擇任何課程類型
  • Then 空堂不在列表中
  • And 顯示提示:「如需開新課程,請先於課表建立後再報名」

Scenario 12 Step 4:季度課自動展開所有日期 + 國定假日標記

  • Given Lisa 在 Step 4,季度課 = 2026 Spring,週二 17:00
  • And 季度區間共 14 個週二
  • And 其中 2026-04-07(Good Friday)為國定假日
  • When 系統載入日期列表
  • Then 顯示 14 個日期
  • And 2026-04-07 標記「國定日(休)」且 checkbox 不可勾選
  • And 預設其他 13 個日期全部勾選
  • And Lisa 可逐個取消其他日期(例如已知小明 4/14 將出國)

Scenario 13 Step 4:全選 / 重置功能

  • Given Lisa 已逐個取消了一些日期
  • When 她點擊「重置」
  • Then 所有日期被取消勾選(除已標記國定假日)
  • When 她點擊「全選」
  • Then 所有非假日日期被勾選

Scenario 14 Step 4:單次課僅選單一日期

  • Given Lisa 在 Step 2 選了「One-Time」
  • When 進到 Step 4
  • Then 介面顯示日期挑選器(single date picker)
  • And 一旦選了某日期,「下一步」按鈕可用
  • And 國定假日不可選

Scenario 15 Step 5:費用明細完整展示(含註冊費首次收)

  • Given Lisa 在 Step 5
  • And 季度報名 12 堂 1on2(80 CAD/堂未稅)
  • And 小明首次報名(尚未繳註冊費)
  • When 系統載入費用明細
  • Then 顯示課程資訊區:
  •  • 學員:小明 / 校區:Burnaby / 種類:Seasonal / 季度:2026 Spring
  •  • 課程類型:1on2 / 週幾:週二 / 時間:17:00 / 課堂數量:12 堂
  •  • 所有上課日期列表(12 個日期,國定假日已排除)
  • And 顯示費用明細:
項目金額(CAD)備註
一次性註冊費30.00首次收,不退款
課程費用960.0012 × 80
折扣0.00無折扣
小計(未稅)990.00
GST (5%)49.50對小計徵稅
分期手續費0.00預留欄位
總計1039.50含稅

Scenario 16 Step 5:註冊費 — 已收過則不重複收

  • Given 小明歷史紀錄中已有 1 筆「已完成」訂單,包含註冊費
  • When Lisa 為小明再次報名(任何類型:Seasonal / One-Time / Trial)
  • Then Step 5 費用明細「註冊費」項顯示為 0 CAD
  • And 旁邊標註「已收(2026-03-17 訂單 #001)」
待確認 Q06.2:註冊費判定 — 我預設「per 學員生命週期一次」(同一個 student_id 只收一次)。是否應改為「per 聯絡人」(家庭只交一次)?我預設 per 學員(資料隔離更清楚)。請確認。

Scenario 17 Step 5:註冊費 — 退款後再回來不重收

  • Given 小明的歷史訂單 #001 已退款
  • And 註冊費 30 CAD 在退款流程中明確「不退」
  • When Lisa 為小明再次報名
  • Then 註冊費仍為 0(歷史已收,不重收)
  • And 旁邊標註「已收(含於已退款訂單 #001)」

Scenario 18 Step 5:Trial 折扣呈現為「優惠」

  • Given Lisa 在 Step 5,類型 = Trial
  • And 1on2 單堂正式價 80 CAD
  • When 系統計算 Trial 費用
  • Then 費用明細顯示:
項目金額(CAD)
註冊費(首次)30.00
課程費用(原價 80)80.00
折扣(Trial 8 折)-16.00
小計(未稅)94.00
GST (5%)4.70
總計98.70

Scenario 19 Step 5:優惠碼 — 4 種類型

  • Given 系統有以下優惠碼:
類型範例代碼行為
固定金額折扣WELCOME50課程費用 - 50 CAD
百分比折扣SAVE10課程費用 × 10% 折扣
免註冊費WAIVE-REG註冊費歸 0
特定課程優惠GROUP-PROMO僅 Group 類型可用,特殊折扣
  • When Lisa 在 Step 5 輸入優惠碼「WELCOME50」
  • Then 系統驗證優惠碼有效
  • And 「折扣」欄顯示 -50 CAD
  • And 總計即時更新
待確認 Q06.3:優惠碼是否第一版就要實作?V2 文檔說「需支持未來優惠碼功能」 — 預設 第一版只實作後台「優惠碼管理」CRUD + Step 5 的代碼輸入欄位(驗證 + 套用),但管理後台先不上線,等業務確定具體優惠策略再啟用。請確認。

Scenario 20 Step 6:Online + Credit 抵扣 — 即時計算

  • Given Step 5 計算總計 = 1039.50 CAD
  • And 小明 Credit Pool = 220 CAD
  • When Lisa 在 Step 6 選「Online」付款方式
  • And 在「使用 Credit 金額」欄輸入 220
  • Then 系統即時計算「待付款金額 = 819.50 CAD」(1039.50 - 220)
  • And Invoice Detail 顯示完整明細:總額、Credit 抵扣、待付款
  • And Lisa 可在 Step 6 看到「點生成訂單後家長將在 60 分鐘內完成付款」提示

Scenario 21 Step 6:Cash + Credit 抵扣

  • Given Step 5 計算總計 = 1039.50 CAD
  • And 小明 Credit Pool = 220 CAD
  • When Lisa 在 Step 6 選「Cash」+ 輸入 Credit 220
  • Then 系統計算「Cash 應付 = 819.50 CAD」
  • And 提示「點生成訂單後家長將在 48 小時內到場交現金」

Scenario 22 Step 6:Credit 抵扣超過總額 — 阻擋

  • Given Step 5 總計 = 100 CAD
  • And 小明 Credit Pool = 200 CAD
  • When Lisa 嘗試輸入 Credit 抵扣 = 150
  • Then 系統阻擋
  • And 顯示「Credit 抵扣金額不可超過訂單總額(最多 100 CAD)」
  • And 自動修正輸入為 100

Scenario 23 Step 6:純 Credit 抵扣(待付款 = 0)

  • Given 訂單總額 100 CAD
  • And Credit Pool = 200 CAD
  • When Lisa 輸入 Credit 抵扣 = 100(剛好抵完)
  • Then 待付款 = 0 CAD
  • And Step 7 生成訂單時,訂單建立後直接跳到「待確認」狀態(跳過付款步驟)
  • And Lisa 可在後台直接「確認」(因為沒有現金 / Online 流入)
待確認 Q06.4:純 Credit 抵扣的訂單流程 — 我預設 跳過待付款,直接進待確認,由 Lisa 一鍵確認 → 已完成。是否要更簡化(直接已完成)?預設保留待確認步驟以維持流程一致性。

Scenario 24 Step 7:Online 訂單生成 — capacity 暫時保留 + 60 分鐘倒數

  • Given Lisa 完成 Step 6(Online 付款方式)
  • When 她點擊「生成訂單」
  • Then 系統建立訂單 #001(狀態:待付款)
  • And 12 個 sessions 中小明被加入為「待付款佔位」狀態(capacity 已扣 1)
  • And 設定 60 分鐘倒數(自訂單建立時刻起算)
  • And 王女士的家長端立即收到「待付款訂單 #001」通知
  • And 訂單卡片顯示倒數計時:「請於 60 分鐘內完成付款,逾時自動取消」

Scenario 25 Step 7:Cash 訂單生成 — 48 小時倒數

  • Given Lisa 完成 Step 6(Cash 付款方式)
  • When 她點擊「生成訂單」
  • Then 系統建立訂單(狀態:待付款 - Cash)
  • And capacity 同樣保留
  • And 48 小時倒數啟動
  • And 客戶端訂單卡片顯示「請於 48 小時內到客服處交現金」

Scenario 26 未解決邏輯:單次課插入季度課程的名額計算

  • Given 季度課「週二 17:00 1on2」共 12 個 sessions
  • And 第 5 週某日(2026-04-14)有人以「One-Time」單次報名加入
  • When Lisa 為新學員嘗試季度報名同一節課
  • Then ❓ 第 5 週的容量是否應視為「已用 = 季度學員數 + One-Time 加入數」?
待確認 Q06.5:此為文檔明確標示的「尚未解決邏輯」。我預設方案:「per session 動態計算 capacity」 — 每個 session 的剩餘 = hard_capacity - 該 session 當前的所有 enrollments(含季度 + 單次 + 補課)。Lisa 看到剩餘為 0 就無法把新季度學員塞進去(即使其他週仍有空)。請確認此邏輯。

Scenario 27 Step 7 內部:capacity 暫時保留的數據結構 內部行為

  • Given Lisa 點擊「生成訂單」
  • When 系統建立訂單與佔位
  • Then 為訂單裡每個 session 建立 1 筆 t_enrollment 紀錄
  • And enrollment.status = PENDING_PAYMENT(區別於 ACTIVE)
  • And 計算 session capacity 時,PENDING_PAYMENT enrollments 也要計入「已用」
  • And 訂單超時取消時,所有 PENDING_PAYMENT enrollments 改為 CANCELLED 並釋放 capacity
  • And 訂單付款確認時,所有 PENDING_PAYMENT enrollments 改為 ACTIVE

Scenario 28 「不可付款 Lisa 開錯訂單」的撤回機制

  • Given Lisa 剛生成的訂單 #001(待付款)
  • And 她發現選錯課程或學員
  • When Lisa 在訂單列表點擊「取消訂單」(針對待付款狀態)
  • Then 訂單狀態 → 已取消
  • And capacity 立即釋放
  • And 家長端待付款訂單消失
  • And Lisa 可重新走 7 步流程
待確認 Q06.6:客服主動取消「待付款」訂單是否需要原因記錄?我預設 需要(audit log + 簡短備註欄)。請確認。

07. 付款(Online / Cash + 任意金額付款)已完成 家長端 + 客服端

⚠️ V2.1 新增:「任意金額付款」流程 — 客服在後台「學員詳細頁 → 充值」彈窗發起, 選擇付款方式(Online / Cash),金額自由填,建立 order_type = TOPUP 的訂單。 完成後直接進入自由消費餘額(不綁特定課程)。
其他內容(訂單付款 60min/48h 倒數、Stripe webhook、純 Credit 抵扣)保持不變。
主要角色:家長(王女士)、客服(Lisa)、SUPER_ADMIN(Mark)、金流系統(Stripe)、系統

Background 共用前置條件

  • Given Lisa 已透過 F06 為小明生成訂單 #001(Online,總額 1039.50 CAD,待付款)
  • And 訂單建立時刻 = 2026-05-08 14:00
  • And 12 個 sessions 中小明為 PENDING_PAYMENT 佔位
  • And 王女士已收到「待付款訂單 #001」通知
  • And 王女士已登入家長端

Scenario 1 Online 付款成功 — 完整流程

  • Given 王女士在家長端「待付款訂單」列表點擊訂單 #001
  • When 她查看訂單詳情
  • Then 顯示完整 Invoice:學員、課程資訊、費用明細、待付款 1039.50 CAD
  • And 顯示倒數計時器(如剩 47:23)
  • When 她點擊「立即付款」
  • Then 跳轉到 Stripe 付款頁面
  • When 她輸入信用卡資訊並完成付款
  • Then Stripe 返回成功
  • And 訂單 #001 狀態 → 待確認
  • And 倒數計時停止(已不需再倒數)
  • And 王女士看到「付款成功,等待客服確認」
  • When Stripe webhook 回調系統,確認 charge.succeeded
  • Then 訂單 #001 狀態 → 已完成
  • And 12 個 sessions 中小明從 PENDING_PAYMENT 改為 ACTIVE
  • And Credit Pool 增加:訂單 #001 剩 12 堂 / 960 CAD(未稅)
  • And 王女士收到「訂單 #001 已完成,學員已加入課表」通知
待確認 Q07.1:金流選擇 — 預設 Stripe(北美主流,支援 SCA、退款、webhook)。是否要支援 Square / Moneris 等加拿大本地金流?
待確認 Q07.2:「待確認」狀態的觸發 — 我預設 客戶完成付款後 Stripe 還沒 webhook 回來時為「待確認」中間態。Webhook 回來後變「已完成」。如果 webhook 30 秒內沒回來,是否要 polling Stripe API 主動確認?

Scenario 2 Online 付款失敗(信用卡被拒)

  • Given 王女士點擊「立即付款」並進入 Stripe 頁
  • When 她輸入信用卡,但 Stripe 回應「銀行拒絕」
  • Then 訂單狀態仍為「待付款」(不變)
  • And 倒數計時繼續(不重置)
  • And 王女士看到錯誤提示「付款失敗:銀行拒絕,請更換卡片或聯繫銀行」
  • And 她可在倒數結束前再次嘗試付款

Scenario 3 Online 倒數計時器 — 視覺提示

  • Given 訂單 #001 待付款,建立 14:00
  • When 王女士查看訂單卡片
  • Then 顯示倒數時鐘(mm:ss 格式)
  • And 倒數 ≥ 30 分鐘 → 綠色
  • And 倒數 30 ~ 10 分鐘 → 黃色
  • And 倒數 ≤ 10 分鐘 → 紅色 + 閃爍
  • And 倒數 = 0 自動消失,訂單卡片變為「已取消」狀態

Scenario 4 Online 超時自動取消(60 分鐘)

  • Given 訂單 #001 建立於 14:00,待付款
  • When 60 分鐘後(15:00)王女士仍未完成付款
  • Then 系統自動將訂單狀態 → 已取消
  • And 12 個 PENDING_PAYMENT enrollments 改為 CANCELLED
  • And capacity 立即釋放
  • And 王女士收到通知「訂單 #001 已自動取消(超時未付款)」
  • And 訂單卡片從家長端待付款區消失,移到「歷史訂單」
  • And 該訂單不可再被付款 / 復活
  • And 若仍需報名,需 Lisa 重新走 F06 流程

Scenario 5 王女士主動取消待付款訂單

  • Given 訂單 #001 待付款,王女士改變主意不報了
  • When 她在訂單卡片點擊「取消訂單」
  • And 確認彈窗
  • Then 訂單狀態 → 已取消
  • And capacity 釋放
  • And 不收任何違約金
  • And 通知中心新增「訂單 #001 已取消」

Scenario 6 Cash 付款流程 — 王女士到客服處交現金

  • Given 訂單 #002 為 Cash 方式,建立 2026-05-08 14:00(待付款)
  • And 48 小時倒數中
  • When 王女士於 2026-05-09 11:00 到 Burnaby 客服處交現金 819.50 CAD
  • And 在家長端訂單卡片點擊「我已付款」
  • Then 訂單狀態 → 待確認
  • And 倒數計時停止
  • And Lisa 後台「待確認訂單」列表新增訂單 #002
  • When Lisa 收到現金後,在後台點擊訂單 #002 的「確認收款」
  • And 填寫收款備註(可選)
  • Then 訂單狀態 → 已完成
  • And 學員加入課表 / Credit Pool 入帳
  • And 王女士收到「訂單 #002 已確認,學員已加入課表」通知

Scenario 7 Cash 倒數 48 小時

  • Given 訂單 #002 Cash,建立 14:00
  • When 王女士查看訂單卡片
  • Then 顯示倒數時鐘(hh:mm 格式)
  • And 倒數 ≥ 24 小時 → 綠色
  • And 倒數 24 ~ 6 小時 → 黃色
  • And 倒數 ≤ 6 小時 → 紅色
  • And 訂單顯示「請於 48 小時內到 Burnaby 客服處交現金 819.50 CAD」

Scenario 8 Cash 超時自動取消(48 小時)

  • Given 訂單 #002 建立於 2026-05-08 14:00,待付款 (Cash)
  • When 48 小時後(2026-05-10 14:00)王女士仍未到客服處付款
  • Then 系統自動將訂單 → 已取消
  • And capacity 釋放
  • And 王女士收到通知「訂單 #002 已自動取消(超時未到客服處付款)」

Scenario 9 Cash 客戶宣告已付但客服查無入帳 — 取消處理

  • Given 訂單 #002 Cash,王女士已點「我已付款」(狀態:待確認)
  • And 但 Lisa 在現金箱中查無對應金額
  • When Lisa 與王女士確認,發現王女士記憶有誤實際未交
  • And Lisa 在後台點擊訂單 #002 的「拒絕並取消」
  • And 填寫原因「查無入帳,客戶確認未實際付款」
  • Then 訂單狀態 → 已取消
  • And capacity 釋放
  • And 系統記錄 audit log(操作人 Lisa、時間、原因)
  • And 王女士收到「訂單 #002 因查無入帳被取消,如有疑問請聯繫客服」
待確認 Q07.3:「待確認 → 已取消」的轉換是否合法?正典 D 的 transition 表中我已預設此 transition 合法(QD.1)。此 scenario 確認該操作。

Scenario 10 純 Credit 抵扣訂單 — 跳過付款步驟

  • Given Lisa 為小明生成訂單 #003:總額 100 CAD
  • And 在 Step 6 用 100 Credit 全額抵扣(待付款 = 0)
  • When Lisa 點擊「生成訂單」
  • Then 訂單建立後狀態直接為 待確認(跳過待付款)
  • And Credit Pool 立即扣 100(FIFO 從最早訂單剩餘扣)
  • And Lisa 在後台點擊「確認」一鍵將訂單變為已完成
  • And 不需金流介入

Scenario 11 部分 Credit + Online 補差額

  • Given 訂單 #004 總額 500 CAD
  • And Credit 抵扣 200,待付款 300
  • And 付款方式 = Online
  • When Lisa 點擊「生成訂單」
  • Then 訂單狀態 = 待付款 (Online)
  • And Credit Pool 立即扣 200(鎖定,不可被另一筆訂單再用)
  • And 60 分鐘倒數啟動,待付 300
  • When 王女士完成 Online 付款 300
  • Then 訂單 → 待確認 → 已完成
  • When 訂單超時取消
  • Then 200 Credit 退回 Pool

Scenario 12 部分 Credit + Cash 補差額

  • Given 訂單 #005 總額 500,Credit 抵 200,Cash 應付 300
  • When Lisa 生成訂單(Cash)
  • Then 狀態 = 待付款 (Cash)
  • And Credit 立即鎖定 200
  • And 48 小時倒數,Cash 應付 300
  • When 王女士交現金 300 + 王女士點「已付」+ Lisa 確認
  • Then 訂單已完成

Scenario 13 已退款訂單不可復活

  • Given 訂單 #006 已退款
  • When 任何人嘗試將訂單 #006 改為任何其他狀態
  • Then 系統阻擋(已退款是終態)
  • And 即使 SUPER_ADMIN 也無法繞過
  • And 如需恢復報名,需走 F06 重新生成新訂單

Scenario 14 已取消訂單不可復活

  • Given 訂單 #007 因超時被自動取消
  • When 王女士在歷史訂單頁點擊「重新付款」(介面不應有此按鈕)
  • Then 介面不存在重新付款按鈕
  • And 若 API 直接呼叫,系統返回錯誤「已取消訂單不可恢復」
  • And 介面提示「如仍需報名,請聯繫客服重新生成訂單」

Scenario 15 倒數中客服緊急延長付款時限

  • Given 訂單 #008 待付款,倒數剩 5 分鐘
  • And 王女士打電話給 Lisa 說「我銀行剛被風控,正在解鎖,可否多給我時間」
  • When Lisa 在後台點擊訂單的「延長 60 分鐘」
  • Then 倒數重置為 60:00
  • And 系統記錄 audit log(操作人、原訂單建立時間、新到期時間、原因)
待確認 Q07.4:是否提供「客服延長付款時限」功能?我預設 是,但僅 SUPER_ADMIN 可操作,且延長有上限(最多延 1 次 + 60 分鐘)。請確認。

Scenario 16 訂單超時掃描定時任務 內部行為

  • Given 系統運行中
  • When 系統每 1 分鐘觸發一次「訂單超時掃描」cron
  • Then 找出所有 created_at 超過 60 分鐘的 Online 待付款訂單,標記為已取消
  • And 找出所有 created_at 超過 48 小時的 Cash 待付款訂單,標記為已取消
  • And 對每筆受影響的訂單:
  •  • 釋放 capacity(PENDING_PAYMENT enrollments → CANCELLED)
  •  • 退回鎖定的 Credit(若有部分 Credit 抵扣)
  •  • 通知家長
  •  • Audit log 記錄

Scenario 17 Stripe webhook 處理 內部行為

  • Given 系統暴露 webhook 端點 POST /api/webhooks/stripe
  • And Stripe 已配置該 webhook URL
  • When Stripe 推送 charge.succeeded 事件
  • Then 系統驗證 webhook 簽名(防偽造)
  • And 取出對應 payment_intent_id 找到關聯訂單
  • And 訂單狀態若為「待確認」→ 改為「已完成」
  • And 訂單狀態若為其他(如已取消)→ 觸發「重複付款處理」流程:自動退款給客戶 + audit log
  • And 對 charge.refunded 事件做對應處理
待確認 Q07.5:若 webhook 收到 charge.succeeded 但訂單已自動取消(極端競態),系統應 自動全額退款 + 通知客服 + 通知客戶。請確認此處理。

Scenario 18 「以小明角度」聲明:客戶端永遠看到「訂單對應課程」 家長端

  • Given 王女士的家長端「我的訂單」頁
  • When 她查看任意訂單卡片
  • Then 卡片顯示:
  •  • 訂單編號 + 狀態
  •  • 「對應課程:12 堂 1on2 / Coach Luke / 週二 17:00 / 2026 Spring 季度」
  •  • 費用明細
  •  • 付款時間
  • And 文案語氣強調「您支付的是這組課程」而非「儲值餘額」
  • And 客戶端任何地方都不出現「請充值 X CAD 到您的帳戶」這類字眼

08. 自動扣費(V2.1:廢除點名)已完成 內部行為 + 客服端

業務價值(V2.1 變更): 會議拍板廢除「人工點名」概念。改為「課程開始時系統自動扣費」:只要該堂課的學員未在 24 小時前請假,系統就在課程開始時自動從其餘額扣 1 堂的價格。 24 小時前請假的學員此時已從 enrollment 移除,不會被扣;24 小時內請假的學員仍在 enrollment 中,照常扣費(懲罰晚請假)。 這個改動極大簡化了客服日常工作(不需點名 / 不需 No-show 處理 / 不需課後修改),並避免人工誤操作。
主要角色:系統(自動扣費 cron)、客服(Lisa)、SUPER_ADMIN(Mark)、家長(王女士)
主要角色:客服(Lisa)、SUPER_ADMIN(Mark)、教練(Coach Luke)、系統

Background 共用前置條件

  • Given 小明訂單 #001 已完成(12 堂 1on2,不可消費餘額 960 CAD)
  • And 該訂單對應 12 個 sessions,當前已上 4 堂、剩 8 堂未來課程
  • And 4/14 17:00 是其中一個未來 session
  • And 該 session 還有同學員「小強」也已報名(也是訂單付款)

Scenario 1 課程開始時系統自動扣費(標準路徑)

  • Given 當前時刻 4/14 17:00(課程開始時刻)
  • And 小明仍在該 session 的 enrollment 中(沒有 24hr 前請假)
  • When 系統 cron(每分鐘掃)檢測到該 session 開始
  • Then 系統對 session 中的每位 enrolled 學員逐一扣費
  • And 對小明:從訂單 #001 對應的「不可消費餘額」扣 80 CAD(單堂價)
  • And 訂單 #001 對應餘額 = 960 - 80 = 880
  • And t_credit_log 新增 1 筆 SESSION_AUTO_DEDUCT 紀錄(綁 session_id + order_id)
  • And 王女士的家長端餘額頁 / 流水頁即時更新
  • And session 狀態標記為 PROCESSED(已扣費)
  • And 需要任何點名操作

Scenario 2 24hr 前請假學員 — 不在 enrollment 中,跳過扣費

  • Given 4/13 16:00 王女士為小明請假 4/14 17:00 那堂課(提前 25 小時)
  • And 系統在請假時刻立即將小明從該 session 的 enrollment 移除
  • When 4/14 17:00 系統觸發自動扣費
  • Then 該 session 的 enrolled 學員列表不含小明(已被請假時移除)
  • And 系統對小明扣費
  • And 訂單 #001 餘額不變(仍 960 - 已扣4堂 = 640)
  • And 系統為小明保留 1 個補課額度(依 F10 補課)

Scenario 3 24hr 內請假學員 — 仍在 enrollment 中,照常扣費

  • Given 4/14 10:00 王女士才為小明請假當天 17:00 課(僅提前 7 小時)
  • And 系統認定為「24 小時內請假」(依 F09)
  • And 小明仍在該 session 的 enrollment 中(未被移除)
  • And 該堂課狀態標記為「LEAVE_LATE(晚請假)」
  • When 4/14 17:00 系統觸發自動扣費
  • Then 系統照常對小明扣 80 CAD(懲罰晚請假)
  • And 訂單 #001 餘額減 80
  • And t_credit_log change_type = LATE_LEAVE_DEDUCT
  • And 不產生補課額度
  • And 王女士已在請假提交時收到「不可補課且仍扣費」的提示,不再重複通知

Scenario 4 訂單對應餘額不足 — 從自由消費餘額補

  • Given 小明的「補課差價」訂單 #003 對應某 session(單堂 80)
  • And 訂單 #003 的不可消費餘額為 60(不足以扣 80,因為先前情境扣過)
  • And 小明的自由消費餘額 = 50
  • When 系統觸發該 session 自動扣費
  • Then 系統先從訂單 #003 對應餘額扣 60(耗盡)
  • And 不足部分 20 從「自由消費餘額」扣(自由餘額 50 - 20 = 30)
  • And t_credit_log 寫入 2 筆紀錄(分別綁 source)
  • And 客戶端流水頁顯示「課程扣費 -80(從訂單 #003 扣 60 + 自由餘額補 20)」

Scenario 5 訂單與自由餘額都不足 — alert 客服

  • Given 訂單 #003 對應餘額為 0
  • And 自由消費餘額為 30(不足 80)
  • When 系統觸發扣費
  • Then 系統強制扣(避免負餘額)
  • And 該 session 的小明狀態標記為「待處理(缺費)」
  • And 系統發 alert 通知該校區所有 ADMIN:「小明 4/14 17:00 課程缺費 80 CAD,請聯繫家長」
  • And 王女士的家長端顯示「該堂課缺費,請聯繫客服」
  • And Lisa 可手動處理:(a) 要求家長充值 / (b) 從家長處收現金後客服任意金額充值 / (c) 取消該堂課
待確認 Q08.1:「待處理(缺費)」的後續處理 — 我預設客服手動跟進,48 小時內未處理則該堂課狀態 → 自動取消(學員視為缺席不扣費)。請確認此 fallback 政策。

Scenario 6 課程結束後不再扣費(防重複)

  • Given 4/14 17:00 課程已被自動扣費(session.status = PROCESSED)
  • When 系統 cron 再次掃到該 session(避免重複處理)
  • Then 系統檢測到 status = PROCESSED 跳過
  • And 不重複扣費

Scenario 7 客服 / 教練查看課程「出席情況」(替代點名介面)

  • Given 4/14 17:00 課程已開始
  • When Lisa 或 Coach Luke 進入該 session 詳情頁
  • Then 顯示學員出席狀態總覽:
  •  • 已扣費學員(24hr 內請假 / 未請假)— 顯示「應出席(已扣費)」
  •  • 24hr 前請假學員 — 顯示「正常請假」+ 補課額度標記
  • And 介面沒有「點名」「No-show」「出席」按鈕
  • And 教練 / 客服只看資訊,不做操作
  • And 教練只能查看自己負責 sessions 的出席情況

Scenario 8 SUPER_ADMIN 修正錯誤扣費

  • Given 系統因某 bug 對小明錯誤扣了 4/14 17:00 的 80 CAD(小明實際 25 小時前已請假)
  • When Mark 在學員詳細頁的「扣費紀錄」找到此筆
  • And 點擊「沖正」並填寫原因「系統誤扣,已 24hr 前請假」
  • Then 系統 INSERT 1 筆「沖正紀錄」(金額 +80,change_type = MANUAL_REVERSAL)
  • And 訂單 #001 對應餘額回升 80
  • And 補建補課額度 +1(如果原請假應產生補課但未生)
  • And Audit log 記錄
  • And 原扣費紀錄不修改(t_credit_log 不可變表)

Scenario 9 自動扣費 cron 詳細邏輯 內部行為

  • Given 系統 cron SessionAutoDeductScanner
  • When 每分鐘觸發
  • Then 找出 session.start_at <= now() AND status = SCHEDULED 的 sessions
  • And 對每個 session:
  •  • 取出當前 enrolled 學員列表(24hr 前請假已被移除)
  •  • 對每位學員逐筆扣費(依該 enrollment.order_id 找對應餘額)
  •  • 寫 t_credit_log + 更新餘額
  •  • 不足時觸發 alert 流程
  • And session.status → PROCESSED
  • And 整個 session 處理在單一事務中(避免部分扣費部分失敗)

Scenario 10 任意金額付款流入的「自由消費餘額」也能用於扣費

  • Given 王女士先前找客服任意金額充值了 100 CAD(進自由消費餘額)
  • And 小明訂單 #001 對應餘額剩 60(不足扣下節課 80)
  • When 系統扣費下節課
  • Then 從訂單 #001 餘額扣 60(耗盡)
  • And 從自由消費餘額補 20(剩 80)
  • And 該 session 順利扣費完成

10. 補課(V2.1:24hr 請假 / 同等級及以上 / 僅季度課)已完成 家長端 + 客服端

⚠️ V2.1 重大變更(2026-05-08 拍板):
請假時間:12 小時 → 24 小時(必須提前 24 小時請假才產生補課額度)
補課範圍:「同等級」→ 「同等級及以上」(L3 學員可補 L3 / L4 / L5 課)
補課額度:僅季度課有,每季 2 次免費(單次課和 Trial 無補課機會)
價差處理:如果補到較貴的課程,提示聯繫客服 → 前台生成價差付款訂單給家長付款
取消窗口:12 小時 → 24 小時(與請假規則一致)
主要角色:家長(王女士)、客服(Lisa)、學員(小明、小華、小強)、系統

Background 共用前置條件(V2.1)

  • Given 小明(L3)提前 25 小時請假了 4/14 17:00 那堂 1on2(依 F09,已從 enrollment 移除、不扣費、產生 1 個補課額度)
  • And 該課程是季度課(補課僅適用季度課)
  • And 小明剩餘補課額度:1 / 2 (本季)
  • And 課表上有以下備選(V2.1 規則:同等級 L3 及以上):
  •  • 4/16 18:00 1on2 by Coach Luke (L4),L3-L4,2 left(同類 + 同價)✅
  •  • 4/17 17:00 1on1 by Coach Luke (L4),L4-L5,1 left(類型不同,跳過)
  •  • 4/18 18:00 1on2 by Coach Eric (L2),L1-L2,2 left(教練等級 L2 < L3,跳過)
  •  • 4/20 19:00 1on2 by Coach Diana (L5),L4-L5,2 left(教練 L5 > L3,同類型 + 等級更高,但價格 100 > 80,需聯繫客服補差價
  •  • 4/19 19:00 Available 空堂 by Coach Luke(同教練等級)
  • And 王女士已登入家長端

Scenario 1 家長端「可補」標記顯示(自動篩選符合條件)

  • Given 小明有 1 個補課額度
  • When 王女士在小明課表上瀏覽
  • Then 4/16 18:00 那堂 1on2 顯示「可補」橙色標記
  • And 4/17 1on1 不顯示「可補」(類型不符)
  • And 4/18 1on2 by Coach Eric 不顯示「可補」(教練等級不符)
  • And 4/19 空堂顯示「可補」(依空堂規則,價格相同則可補)

Scenario 2 家長自助補課 — 條件完全符合

  • Given 王女士在課表上看到 4/16 18:00 1on2 「可補」
  • When 她點擊該課程的「補課」按鈕
  • And 系統提示「請選擇對應的請假紀錄」
  • And 王女士選擇「4/14 17:00 1on2 請假」
  • And 點擊「確認補課」
  • Then 系統驗證 3 條件全部符合(同類型 + 同等級 + 同價)
  • And 鎖定該 session 的 1 個名額(capacity 從 2 left 變 1 left)
  • And 4/16 18:00 那堂課加入小明(補課狀態 MAKEUP)
  • And 4/14 那堂的「待補課」紀錄狀態 → MATCHED(已成功補上)
  • And 不扣 Credit(補課不扣費,因為原請假已扣或將扣)
  • And 王女士收到「補課成功」通知
待確認 Q10.1:補課的 Credit 規則 — 我預設 「補上的這堂課不扣 Credit」+「原請假課照常從 Credit Pool 扣(因為訂單剩餘額度本就計入這堂)」。換言之:學員報名 12 堂 → 訂單剩 12 → 請假 1 + 補上 1 → 剩 11 已扣 + 1 待扣(補上的那堂在補上日才扣)。請確認此邏輯。

Scenario 3 補課條件不符 — 課程類型不同

  • Given 王女士嘗試將 4/14 1on2 請假補到 4/17 1on1
  • When 她點擊「補課」
  • Then 系統顯示「該課程類型(1on1)與您請假的課程(1on2)不符,無法自助補課」
  • And 顯示「如有特殊需求請聯繫客服」

Scenario 4 (V2.1) 補課到「同等級及以上」教練 — 同價直接補課

  • Given 小明請假的原課程:1on2 by Coach Luke (L4),單堂價 80
  • And 4/16 18:00 1on2 by Coach Diana (L5),價格也是 80
  • When 王女士嘗試補到該課程
  • Then 系統檢查:類型同 ✓ + 教練等級 L5 ≥ L4 ✓ + 同價 ✓
  • And 補課成功(直接占位)

Scenario 4b (V2.1) 補課到等級更低教練 — 阻擋

  • Given 小明請假的原課程教練 Coach Luke (L4)
  • And 嘗試補到 4/18 1on2 by Coach Eric (L2),L2 < L4
  • When 王女士點擊「補課」
  • Then 系統阻擋
  • And 顯示「目標教練等級(L2)低於原教練(L4),無法補課」

Scenario 5 (V2.1) 補課到較貴課程 — 提示聯繫客服 + 前台生成價差訂單

  • Given 小明原課程單堂 80 CAD
  • And 嘗試補到 4/20 19:00 1on2 by Coach Diana (L5),單堂 100 CAD(同類型 + 等級更高)
  • When 王女士點擊「補課」
  • Then 系統顯示「該課程單堂 100,比原課程 80 高 20 CAD。需聯繫客服由前台處理價差,前台會生成 20 CAD 的付款訂單給您」
  • And 介面提供「聯繫客服」按鈕
  • And 補課額度立即扣(須等價差訂單付款後才扣)
  • When Lisa 收到家長請求後,在後台「補課管理」為小明手動安排
  • And 系統依 F06 流程生成 1 筆 ONE_TIME 補課差價訂單(20 CAD)
  • Then 訂單狀態 = 待付款(依 F07)
  • And 王女士付款後,補課正式生效(小明加入 4/20 19:00 課程)
  • And 補課額度 -1
  • And 該堂課開始時,從 Diana 訂單對應的不可消費餘額扣 100(依 F08 自動扣費)

Scenario 6 空堂補課 — 價格相同可直接補

  • Given 4/19 19:00 是 Available 空堂,by Coach Luke (L4)
  • And 系統依「該空堂泳道 + 教練等級」自動推算,若改為 1on2 課單堂價 = 80(與請假同價)
  • When 王女士嘗試補到該空堂
  • Then 系統允許
  • And 後台自動建立 1 個 One-Time 課程(類型 = 1on2,依小明 Level)
  • And 小明加入該 session 為 MAKEUP 狀態
  • And 該空堂變為 1on2 課(1 left 給其他學員可加入)
待確認 Q10.2:空堂自動轉為「One-Time 課程」是否合理?或者該空堂應先由客服建立正式課程,家長才能補?我預設 家長可直接補空堂並自動建立 One-Time,但需 Lisa 在後台補一個課程資訊(課程類型 / Level 區間)。請確認。

Scenario 7 空堂補課 — 有價差需聯繫客服

  • Given 4/19 空堂 by Coach Eric (L2)
  • And Coach Eric L2 的 1on2 課單堂價 = 50(小明原請假是 80)
  • When 王女士嘗試補到該空堂
  • Then 系統顯示「該時段價格(50)與您請假課程(80)不符,請聯繫客服協助處理價差」
  • And 提示「客服會處理 30 CAD 的差額(退回 Credit 或抵扣下次)」

Scenario 8 FCFS — 名額已被他人補走

  • Given 4/16 1on2 課僅剩 1 left
  • And 王女士在補課介面停留時,學員小華的家長同時補同一節
  • When 小華家長先點擊「確認補課」
  • And 王女士隨後點擊「確認補課」
  • Then 系統檢測到該 session capacity 已滿
  • And 王女士看到「該名額已被他人選擇,請選擇其他時段」
  • And 王女士的補課額度仍未消耗(可重新挑時段)

Scenario 9 (V2.1) 補課取消 — 距離上課 ≥ 24 小時可取消,補課額度恢復

  • Given 王女士已補課到 4/16 18:00
  • And 當前時刻 4/14 18:00(距離上課還有 48 小時)
  • When 王女士點擊該補課的「取消補課」
  • Then 系統允許
  • And 4/16 那節課小明被從 enrollment 移除(capacity 釋放)
  • And 對應「待補課」紀錄狀態 → AVAILABLE(重新可用)
  • And 王女士可再選其他時段補課
  • And 通知「補課取消成功,補課額度已恢復」

Scenario 10 (V2.1) 補課取消 — 距離上課 < 24 小時視為已使用

  • Given 王女士已補課到 4/16 18:00
  • And 當前時刻 4/15 22:00(距離上課僅 20 小時)
  • When 王女士點擊「取消補課」
  • Then 系統提示「距離上課不足 24 小時,取消後將視為已使用該補課額度,且仍照常扣費」+ 二次確認
  • When 王女士確認
  • Then 4/16 該節課小明被移除 enrollment
  • But 對應「待補課」紀錄狀態 → CONSUMED(已使用,不退補課額度)
  • And 補課額度恢復(懲罰晚取消)
  • And 該堂課照常扣費(依 F08 自動扣費)
待確認 Q10.5:晚取消補課的扣費 — 是「從原請假對應訂單扣」還是「不扣(因為已從 enrollment 移除)」?我預設 仍扣(懲罰晚取消,與晚請假同等對待)。請確認。

Scenario 11 客服協助補課 — 處理價差

  • Given 王女士聯繫客服,希望將 4/14 1on2 (80) 請假補到 4/19 1on2 by Coach Eric (50)
  • When Lisa 在後台「補課管理」頁為小明手動安排
  • And 選擇來源請假紀錄 + 目標 session
  • And 選擇價差處理方式:「退回 30 CAD 到 Credit Pool」
  • Then 系統建立補課紀錄
  • And 從訂單 #001 退回 30 CAD 到 Credit Pool(綁定來源訂單)
  • And Audit log 紀錄
  • And 王女士收到「補課成功,價差 30 CAD 已退回您的 Credit」通知

Scenario 12 超過免費補課額度 — 客服特批時 V1 機制保留 vs V2 改造

  • Given 小明本季已用過 2 次免費補課額度
  • And 第 3 次請假產生「超額待補」紀錄
  • When 王女士嘗試自助補課該紀錄
  • Then 系統顯示「您已超出本季免費補課額度,請聯繫客服安排(將收取補課費)」
  • When Lisa 在後台為小明安排補課並選擇「收取補課費 30 CAD」
  • Then 系統建立補課紀錄
  • And 從小明 Credit Pool 扣 30 CAD(change_type = MAKEUP_FEE,綁定訂單 FIFO)
  • And 若 Credit 不足則 Lisa 必須生成新訂單收取補課費
待確認 Q10.3:補課額度上限 — 我預設 每季 2 次免費(V1 沿用)。是否改為「無上限但每次都收費」?或「上限可配置 per 等級」?
待確認 Q10.4:超額補課費金額 — 我預設 30 CAD/次。是否依課程類型不同收費?

Scenario 13 跨季度補課特批 — SUPER_ADMIN only

  • Given 小明在 2026 Spring 末尾請假 1 次(額度未使用)
  • And 2026 Spring 季度結束,小明未報名 2026 Summer
  • When 王女士聯繫客服希望保留該補課額度到 2026 Summer 開課時使用
  • And Lisa 認為合理,請示 Mark
  • And Mark 在後台對該紀錄選「跨季度補課特批」
  • Then 該補課紀錄在 2026 Summer 仍有效
  • And Audit log 記錄特批操作(操作人、原因)
  • And ADMIN 無法做此特批(僅 SUPER_ADMIN)

Scenario 14 補課的 Race Condition 內部行為

  • Given 4/16 1on2 課僅剩 1 left
  • And 王女士和小華媽媽幾乎同時點擊「確認補課」
  • When 兩個請求到達後端
  • Then 系統使用悲觀鎖(SELECT FOR UPDATE)對 session 加鎖
  • And 先到的請求扣減 capacity 並提交
  • And 後到的請求檢測 capacity 已滿,返回錯誤 SESSION_FULL
  • And 後到的家長端顯示「該名額已被他人選擇」

11. 退款(V2.1:表單簡化)已完成 客服端 / SUPER_ADMIN

⚠️ V2.1 變更: 表單簡化為三項:退款金額 / 退款路徑(Online or Cash)/ 退款原因。 其他欄位(已扣 / 未扣 / 手續費 / 退款後餘額)由系統自動計算顯示,不需 SUPER_ADMIN 手動填。
兩段狀態(退款中 → 已退款)保留
退款找零進入「自由消費餘額」(依 V2.1 雙層餘額模型)。
其他規則(註冊費不退、部分退款、僅 SUPER_ADMIN)保持不變。
主要角色:SUPER_ADMIN(Mark)、客服(Lisa)、家長(王女士)、金流系統、系統

Background 共用前置條件

  • Given 訂單 #001 已完成(小明,12 堂 1on2,總額 1039.50 含稅 / 990 未稅)
  • And 已上 4 堂課(已扣 320 CAD 未稅)
  • And 訂單剩餘 = 8 堂 / 640 CAD 未稅
  • And Mark(SUPER_ADMIN)已登入後台
  • And 小明因受傷需要全額退剩餘課程

Scenario 1 退款入口 — 僅在學員詳細頁

  • Given Mark 在後台
  • When 他在訂單列表頁尋找「退款」按鈕
  • Then 訂單列表頁提供退款按鈕
  • When Mark 進入小明的學員詳細頁
  • Then 在「訂單與付款」區塊看到每筆已完成訂單旁的「退款」按鈕
  • And 只有訂單狀態 = 已完成 的才有退款按鈕(其他狀態如待付款 / 已取消無此按鈕)

Scenario 2 僅 SUPER_ADMIN 可發起退款

  • Given Lisa 是 ADMIN
  • When 她進入小明的學員詳細頁
  • Then 介面隱藏所有「退款」按鈕
  • And 直接 API 呼叫退款端點返回 403
  • And Lisa 看到提示:「退款請聯繫主管處理」

Scenario 3 退款表單必填項

  • Given Mark 點擊訂單 #001 的「退款」按鈕
  • When 退款表單彈出
  • Then 表單包含以下欄位:
欄位必填說明
退款原因下拉選 + 自由文字(如「受傷 / 搬家 / 不適合」)
退款金額(未稅)系統自動計算建議值,可手動調整
退款方式EMT / Cash / Debit / Credit Pool(找零)
已扣金額顯示已上課的金額(不可編輯,自動計算)
未扣金額顯示剩餘額度(自動)
手續費金流退款手續費(預設 0,可填)
退款後 Credit Pool 餘額顯示計算結果,唯讀
備註給家長的訊息
  • And 任一必填欄位空白時「提交」按鈕禁用

Scenario 4 全額退款流程(已完成 → 退款中 → 已退款)

  • Given Mark 對訂單 #001 發起全額退款
  • And 退款明細:未稅可退 = 640(8 堂 × 80);註冊費不退;金流手續費 10;可退淨額 = 630
  • When Mark 提交退款表單
  • Then 訂單 #001 狀態 → 退款中
  • And 訂單剩餘 8 堂從 Credit Pool 凍結(不可再扣費)
  • And 8 個未來 sessions 中小明被移除(capacity 釋放給其他學員)
  • And 王女士的家長端顯示「訂單 #001 退款處理中」
  • And Mark 透過 EMT 推送 630 到王女士的 Email
  • When Mark 確認金流系統已成功推送(手動標記 or webhook)
  • Then 訂單 #001 狀態 → 已退款
  • And t_refund 紀錄補上 completed_at 時間
  • And 王女士收到通知「退款 630 CAD 已完成」
  • And 學員標籤更新為 REFUNDED(依 F12 標籤系統)
待確認 Q11.1:「退款中 → 已退款」的觸發 — 文檔說「金流系統顯示已完成退款後...狀態轉為已退款」+「客戶端顯示為待確認,客戶確認後 → 等待金流系統顯示已完成」。我理解為兩種觸發機制:(a) Stripe webhook 自動觸發;(b) 非 Stripe 退款(如 EMT / Cash)由 Mark 手動標記。客戶端的「待確認」是純資訊性提示(讓家長知道有處理中的退款),不是阻擋性節點。請確認此解讀。

Scenario 5 退款金額計算 — 註冊費不退

  • Given 訂單 #001 含 30 CAD 註冊費
  • And 已上 4 堂 = 320
  • And 剩 8 堂 = 640
  • When Mark 開啟退款表單
  • Then 系統自動計算建議退款金額 = 640 CAD(未稅)
  • And 表單明確顯示「註冊費 30 不退」
  • And Mark 可手動調整金額(但有上限為 640)

Scenario 6 部分退款(保留部分課程)

  • Given 訂單 #001 剩 8 堂 / 640,王女士想退 4 堂保留 4 堂
  • When Mark 在退款表單選「部分退款」並輸入金額 320
  • And 選擇「保留哪 4 堂」(提供未來 8 個 sessions 的清單)
  • And 提交
  • Then 訂單 #001 狀態仍為「已完成」(依 Q05.8 預設)
  • And 訂單剩餘從 8 堂變為 4 堂
  • And 被退款的 4 個 sessions 中小明移除
  • And 訂單詳情顯示「部分退款 320 CAD(2026-05-08)」標記
  • And t_refund.refund_type = PARTIAL
  • And Credit Pool 中該訂單剩餘從 640 → 320

Scenario 7 退款方式 — 4 種選項

  • Given Mark 在退款表單
  • When 他展開「退款方式」
  • Then 4 種選項:
方式適用後續處理
EMT (Email Transfer)退到原 EmailMark 手動發起 + 標記
Cash退現金家長到場領取,Mark 標記完成
Debit / Credit Card原路徑退回透過 Stripe API 發起,webhook 確認
Credit Pool(找零)進入該學員 Credit Pool立即生效,無需金流
  • And Mark 可組合多種方式(如「主退 EMT 600 + 找零 30 入 Pool」)

Scenario 8 找零回 Credit Pool

  • Given 訂單 #001 退款明細:應退 640;金流退 600(手續費 + 整數限制);找零 40
  • When Mark 在退款方式選「EMT 600 + Credit Pool 40」
  • And 提交
  • Then 訂單 #001 → 已退款
  • And Credit Pool 增加 40(來源:退款找零,from_order = #001)
  • And Credit 詳情頁顯示「退款找零 +40 CAD(來自訂單 #001 退款)」
  • And 該 40 CAD 可用於下次報名(FIFO 順序)
  • And t_refund 紀錄含 amount_to_credit_pool = 40

Scenario 9 退款完成後標籤更新為 REFUNDED

  • Given 訂單 #001 已退款
  • And 小明該季度沒有其他「已完成」訂單
  • When 系統異步重算學員標籤(依 F12 規則)
  • Then 小明的標籤變更為 REFUNDED
  • And 該標籤在客服的學員列表中顯示
  • And 列表篩選「REFUNDED」可找到此學員

Scenario 10 退款後學員回來繼續上課 — REFUNDED 標籤自動移除

  • Given 小明上次退款後當前 REFUNDED 標籤
  • When Lisa 為小明再次生成新訂單 #005 並走完付款流程(已完成)
  • Then 系統重算標籤
  • And REFUNDED 標籤自動移除
  • And 替換為 ACTIVE_CREDIT(依新訂單)

Scenario 11 退款金額超過訂單剩餘 — 阻擋

  • Given 訂單 #001 剩 8 堂 / 640 CAD
  • When Mark 在退款表單嘗試輸入退款金額 800(超過 640)
  • Then 系統阻擋
  • And 顯示「退款金額不可超過訂單剩餘額度(最多 640 CAD)」
  • And 自動修正為 640

Scenario 12 已退款訂單再次嘗試退款 — 阻擋

  • Given 訂單 #001 已退款
  • When Mark 在學員詳細頁查看訂單 #001
  • Then 訂單卡片顯示狀態「已退款」
  • And 「退款」按鈕被「已退款」標籤替代(無動作)
  • And API 直接呼叫返回「該訂單已退款,無法再次退款」

Scenario 13 退款全流程 audit log 內部行為

  • Given Mark 發起退款
  • When 系統執行退款
  • Then 寫入 audit log:
  •  • 操作人:Mark(user_id, role=SUPER_ADMIN)
  •  • 操作:REFUND_INITIATED
  •  • 對象:order_id = #001
  •  • 詳情 JSON:含退款金額、方式、原因、明細
  •  • 時間戳
  • And 退款完成時再寫一筆 REFUND_COMPLETED audit log
  • And t_refund 表為不可變表(INSERT only,不可 UPDATE / DELETE)

Scenario 14 客戶端展示退款進度

  • Given 訂單 #001 處於「退款中」狀態
  • When 王女士在家長端「我的訂單」頁查看 #001
  • Then 訂單卡片顯示:
  •  • 狀態徽章:「退款處理中」(藍色)
  •  • 退款明細:未稅可退 640、註冊費不退(30)、手續費 10、淨退 630
  •  • 退款方式:EMT 600 + Credit Pool 40
  •  • 預計到帳時間:「3-5 個工作日」
  • When 退款狀態 → 已退款
  • Then 訂單卡片狀態徽章變為「已退款」(綠色)
  • And 顯示完成時間

Scenario 15 退款撤銷 — 緊急情況下 SUPER_ADMIN 操作

  • Given 訂單 #001 處於「退款中」狀態
  • And 王女士改變主意,打電話請求繼續上課
  • When Mark 在退款管理頁點擊「撤銷退款」(僅在「退款中」階段可用)
  • And 確認「金流尚未推送」
  • Then 訂單 #001 狀態 → 已完成(恢復原狀)
  • And Credit Pool 中該訂單剩餘解凍
  • And 8 個未來 sessions 中小明重新加入(若 capacity 允許)
  • And 若 capacity 不足,提示 Mark「[date] 該節課已被別人補入,需要協調」
  • And Audit log 記錄撤銷操作
待確認 Q11.2:「撤銷退款」是否提供?實務上家長改主意是真實情境。我預設 提供,但僅在「退款中」階段且金流未推送前可用。請確認。

12. 學員標籤系統(V2.1:互斥規則 + 手動 CONTACTED)已完成 內部行為 + 客服端

⚠️ V2.1 重大變更(2026-05-08 拍板):
5 個標籤互斥(同時只能持有其中一個):ACTIVE_CREDIT / INACTIVE_CREDIT / INACTIVE_NO_CREDIT / REFUNDED / NEW
2 個標籤可獨立並列(與上述 5 個互斥標籤可共存):WAITLIST / CONTACTED
學員最多同時持有 3 個標籤(5 互斥之 1 + WAITLIST + CONTACTED)
CONTACTED 改為手動標記(客服打過電話跟進後手動勾選)
計算策略:每天 00:00 cron 重算(每天 1 次),不再使用即時 / 觸發式重算
主要角色:系統、客服(Lisa)、SUPER_ADMIN(Mark)

Background 共用前置條件

  • Given 系統有以下測試學員(用於各標籤情境):
  •  • 小明:剛完成註冊 5 天,有 Credit Pool 200,本季已上 3 堂課
  •  • 小華:3 個月前註冊,有 Credit Pool 100,本季因受傷未上任何課
  •  • 小強:2 個月前註冊,Credit Pool = 0,本季未上課
  •  • 小強的姐姐 Bobby:上月退款,至今未再報名
  •  • 小新:3 天前註冊,尚未報名任何課
  •  • Tom:1 個月前註冊,至今無動作(未報名 / 未登入)
  •  • Lily:在 Burnaby Waitlist 中

Scenario 1 WAITLIST 標籤 — 學員在 t_waitlist 有 ACTIVE 紀錄

  • Given Lily 在 Burnaby Waitlist 有 1 筆紀錄,狀態 = ACTIVE
  • When 系統重算 Lily 的標籤
  • Then Lily 持有 WAITLIST 標籤
  • When Lily 報名了該校區該季度的季度課(依 F13 自動移除 Waitlist)
  • Then 系統重算後 Lily 的 WAITLIST 標籤移除

Scenario 2 ACTIVE_CREDIT — 有餘額且當季有上課

  • Given 小明 Credit Pool = 200(> 0)
  • And 當前季度(2026 Spring)小明有 ≥ 1 筆 attendance 紀錄為 ATTENDED
  • When 系統重算標籤
  • Then 小明持有 ACTIVE_CREDIT 標籤

Scenario 3 INACTIVE_CREDIT — 有餘額但當季未上課(受傷例)

  • Given 小華 Credit Pool = 100(> 0)
  • And 當前季度小華 ATTENDED 紀錄(全部請假或 0 紀錄)
  • When 系統重算
  • Then 小華持有 INACTIVE_CREDIT 標籤
  • And 客服可篩選此標籤主動聯繫了解狀況
待確認 Q12.1:「當季未上課」判定 — 是「無 ATTENDED」還是「無任何 attendance 紀錄(含 LEAVE / NO_SHOW)」?我預設 無 ATTENDED(請假 / no-show 都視為未上)。請確認。

Scenario 4 INACTIVE_NO_CREDIT — 無餘額且未上課

  • Given 小強 Credit Pool = 0
  • And 當前季度小強無 ATTENDED 紀錄
  • When 系統重算
  • Then 小強持有 INACTIVE_NO_CREDIT 標籤
  • And 此標籤是「沉睡客戶」的指標,營運可主動接觸

Scenario 5 REFUNDED — 已退款且未繼續上課

  • Given Bobby 上月有 1 筆「已退款」訂單
  • And 退款後 Bobby 沒有任何「已完成」新訂單
  • When 系統重算
  • Then Bobby 持有 REFUNDED 標籤
  • When Bobby 後續被 Lisa 重新報名並完成新訂單付款
  • Then 系統重算後 REFUNDED 標籤移除(替換為 ACTIVE_CREDIT 或其他適用標籤)

Scenario 6 NEW — 新註冊客戶(2 週內)

  • Given 小新的 contact.created_at 是 3 天前
  • When 系統重算
  • Then 小新持有 NEW 標籤(< 14 天)
  • When 14 天後系統重算
  • Then NEW 標籤自動移除
  • And 依「2 週內是否有動作」決定是否替換為 CONTACTED
待確認 Q12.2:NEW 起算點 — 我預設 contact.created_at(聯絡人建立時間)。是否該用「首次成功登入」?預設聯絡人建立更穩定(可能客服建檔但客戶沒登入)。請確認。

Scenario 7 CONTACTED — 2 週內無任何動作

  • Given Tom 的 contact.created_at 是 1 個月前
  • And Tom 從未報名 / 從未登入 / 從未有任何動作
  • When 系統 cron 在 14 天後檢測
  • Then Tom 持有 CONTACTED 標籤(NEW 已移除)
  • And 此標籤是「需主動聯繫」的指標
待確認 Q12.3:「動作」定義 — 我預設包含:登入家長端、客服成功電話聯繫(需手動標記)、報名訂單、進過家長端任何頁面(access log)。請確認。

Scenario 8 多標籤同存

  • Given 小新註冊 3 天(NEW),剛報名第一筆並完成(ACTIVE_CREDIT)
  • When 系統重算
  • Then 小新同時持有 NEW + ACTIVE_CREDIT 兩個標籤
  • And 列表顯示時兩個標籤都顯示
  • And 各標籤之間無互斥規則(除了部分自然互斥,如 ACTIVE_CREDIT 與 INACTIVE_NO_CREDIT 不會同時持有)

Scenario 9 列表篩選使用標籤

  • Given Lisa 在學員列表頁
  • When 她在篩選器選「INACTIVE_CREDIT」
  • Then 列表只顯示有 INACTIVE_CREDIT 標籤的學員
  • And 可同時選多個標籤(如「INACTIVE_CREDIT OR INACTIVE_NO_CREDIT」找所有未上課的)

Scenario 10 標籤統計面板(後台 Dashboard)

  • Given Mark 進入 SUPER_ADMIN Dashboard
  • When 他查看「學員標籤分布」widget
  • Then 顯示各標籤的當前計數(餅圖 + 數字)
  • And 點擊任一標籤跳到該標籤的學員列表
  • And 顯示趨勢(過去 30 天 NEW / ACTIVE_CREDIT 變化)

Scenario 11 標籤計算策略:cron + 觸發式 內部行為

  • Given 系統採用混合策略
  • When 標籤需更新
  • Then 觸發機制如下:
  •  • 事件觸發:訂單狀態變更 / 報名 / 退款 / Waitlist 變動 / 點名 → 同步重算受影響學員的所有標籤
  •  • 每天 00:00 全量 cron:掃所有學員,重算所有標籤(補正觸發遺漏 + NEW→CONTACTED 時間到期轉換)
  • And 重算結果寫入 t_student.tags(JSON 陣列欄位)
  • And 列表查詢直接讀此欄位(不臨時計算)
待確認 Q12.4:計算策略偏好 — 我預設 「事件觸發 + 每日 cron 兜底」混合策略。是否改為「純 cron」(簡單但延遲)?預設混合最平衡。

Scenario 12 NEW → CONTACTED 自動轉換 cron 內部行為

  • Given 系統運行 cron 「標籤重算」每天 00:00
  • When 系統檢測有 NEW 標籤超過 14 天且 14 天內無任何動作的學員
  • Then 系統將其 NEW 移除,加上 CONTACTED
  • And 不發通知(內部標籤變更,家長無感)
  • And 客服列表立即看到該學員從 NEW 列表轉到 CONTACTED 列表

Scenario 13 標籤的資料儲存模型 內部行為

  • Given 學員小明同時持有 NEW + ACTIVE_CREDIT
  • When 系統儲存標籤
  • Then t_student.tags 欄位(JSON 陣列)= ["NEW", "ACTIVE_CREDIT"]
  • And 列表查詢使用 JSON_CONTAINS 索引以保證效能
  • And 標籤值為列舉常量(不允許自由輸入)
  • And tags_updated_at 欄位記錄最後重算時間

13. Waitlist 候補名單(V2.1:綁具體 session 的排隊)已完成 家長端 + 客服端

⚠️ V2.1 重大變更(2026-05-08 拍板): Waitlist 從原先的「需求登記簿(不綁 session)」改為「綁定具體 session 的排隊」。
主要場景:家長在排課表上點擊某堂特定課程的「加入 Waitlist」(多用於該課已滿員的情況); 前台亦可在課表上代家長添加(例如電話請求)。
不參與排課流程 — 純記錄,由前台 / 教練在課程詳情頁查看誰排在前面,FIFO 順序聯繫。
家長僅看到自己的 Waitlist 條目;前台 / 教練可看某天某課的完整排隊列表。
主要角色:家長(王女士)、客服(Lisa)、教練(Coach Luke)、系統

Background 共用前置條件(V2.1)

  • Given Burnaby 課表上的 4/16 18:00 1on2 by Coach Luke 已滿員(2/2)
  • And 小華(王女士的女兒,L1)想加入該課,但已滿
  • And 該 session 已有 2 筆現存 Waitlist 紀錄:
  •  1. Tina(媽媽 2026-05-01 10:00 加入)
  •  2. Bobby(爸爸 2026-05-02 14:00 加入)
  • And 王女士已登入家長端

Scenario 1 (V2.1) 家長在排課表上點擊某堂滿員課的「加入 Waitlist」

  • Given 王女士在家長端排課表查看 4/16 18:00 1on2 by Coach Luke(顯示為已滿)
  • And 該 session 已有 2 筆 Waitlist 紀錄(Tina, Bobby)
  • When 王女士點擊該堂課的「加入 Waitlist」按鈕
  • And 確認對話框中選擇學員「小華」並提交
  • Then 系統建立 t_waitlist 紀錄(綁 student_id + session_id + created_at)
  • And 紀錄狀態 = ACTIVE
  • And 小華在該 session Waitlist 排第 3 位(FIFO)
  • And 小華的標籤新增 WAITLIST
  • And Toast 提示「已加入 4/16 18:00 1on2 候補名單,您是第 3 位」

Scenario 2 家長查看 / 編輯 / 刪除自己的 Waitlist

  • Given 王女士在家長端 Waitlist 頁
  • When 她查看小華的 Waitlist 紀錄
  • Then 顯示她填的所有資訊 + 提交時間 + 排序位置(如「您是 Burnaby 校區週四下午 Group 的第 X 位候補」)
  • And 提供「修改」和「刪除」按鈕
  • When 她點擊「刪除」並確認
  • Then 該 Waitlist 紀錄狀態 → CANCELLED
  • And 小華的 WAITLIST 標籤移除(若無其他 Waitlist 紀錄)

Scenario 3 客服查看 per 校區 Waitlist + FIFO 排序

  • Given Lisa 在後台「Waitlist 管理」頁
  • When 她選擇校區 = Burnaby
  • Then 顯示 Burnaby 所有 ACTIVE Waitlist 紀錄
  • And 按提交時間升序排列(FIFO,最早提交在最上)
  • And 每行顯示:學員姓名 / 標籤 / 等級 / 季度 / 週幾 / 時間段 / 希望課程類型 / 備註 / 「報名」按鈕
待確認 Q13.1:排序是嚴格 FIFO 還是允許客服自訂優先順位?我預設 FIFO 為預設,但客服可手動「置頂」某筆紀錄(用於 VIP / 特殊情況)+ audit log。請確認。

Scenario 4 客服從 Waitlist 進入報名流程(捷徑)

  • Given Lisa 在 Waitlist 列表,看到 Tina 想要週四下午 Group
  • And Burnaby 課表有 4/17(週四)15:00 Group 課,1 left
  • When Lisa 點擊 Tina 行的「報名」按鈕
  • Then 進入 F06 報名嚮導 Step 1
  • And 自動帶入:學員 = Tina、聯絡人 = Tina 媽媽、校區 = Burnaby、Level = Tina 當前 Level
  • And Step 2 預設 Seasonal + 2026 Spring(依 Waitlist 偏好)
  • And Step 3 預設 Group + 週四(依 Waitlist 偏好;具體時間需 Lisa 選)

Scenario 5 報名完成自動移除 Waitlist(同校區同季度)

  • Given Tina 在 Burnaby Waitlist 有「2026 Spring 週四下午 Group」紀錄
  • When Lisa 為 Tina 在 Burnaby 完成 2026 Spring 的季度報名(任何週幾、任何類型)
  • And 訂單狀態變為「已完成」
  • Then 系統自動將 Tina 的「Burnaby + 2026 Spring」Waitlist 紀錄狀態 → MATCHED
  • And 該紀錄從 ACTIVE Waitlist 列表移除
  • And Tina 的 WAITLIST 標籤移除(若該校區季度只有這一筆)
待確認 Q13.2:「自動移除」的觸發時機 — 是「訂單狀態變為已完成」?還是「訂單建立(待付款)」就移除?我預設 已完成才移除(訂單超時取消的話 Waitlist 應保留)。請確認。

Scenario 6 單次課報名不移除 Waitlist

  • Given Bobby 在 Burnaby Waitlist 有「2026 Spring 週六上午 1on2」紀錄
  • When Lisa 為 Bobby 安排了一次 One-Time 補課(單次課)
  • And 該 One-Time 訂單已完成
  • Then Bobby 的 Waitlist 紀錄被移除(仍 ACTIVE)
  • And 因為「單次課不算滿足候補需求」

Scenario 9 客服手動為學員加入 Waitlist(從學員頁)

  • Given Lisa 在小華的學員詳細頁
  • And 王女士致電請客服幫忙加入 Waitlist
  • When Lisa 點擊「加入 Waitlist」按鈕並填寫
  • Then 系統建立 Waitlist 紀錄(建立人 = Lisa,標記為「客服代填」)
  • And Audit log 記錄
  • And 王女士可在家長端查看該紀錄

Scenario 10 客服開新課時推薦 Waitlist 候選人

  • Given Lisa 剛在 Burnaby 課表上建立一節新 Group 課(週四 15:00)
  • When 系統檢測到 Burnaby Waitlist 有匹配紀錄(季度 / 週幾 / 時段 / 類型)
  • Then 課表上該節課顯示「Waitlist 中有 2 位匹配候選」標記
  • And Lisa 點擊標記可看 FIFO 順序的候選清單(含 Tina、Lily)
  • And 一鍵為候選人發送通知(Email / 系統通知)「您候補的時段已開放,請聯繫客服報名」
待確認 Q13.3:新課開放時是否自動發通知給匹配的 Waitlist 候選人?預設 系統自動發通知 + 客服手動 follow-up 雙管齊下。是否要 SMS 通知?

Scenario 11 Waitlist 過期清理(6 個月無動作)內部行為

  • Given 系統有大量 ACTIVE Waitlist 紀錄
  • When 系統每天 00:00 執行「Waitlist 清理」cron
  • Then 找出 created_at 超過 6 個月且狀態仍為 ACTIVE 的紀錄
  • And 標記為 EXPIRED
  • And 對應家長收到通知「您的 Waitlist 紀錄已超過 6 個月未排到,已自動清理。如仍需要請重新登記」
待確認 Q13.4:過期時長 — 我預設 6 個月。是否更短(如 3 個月)或更長(季度結束自動清)?

Scenario 12 排序邊界:兩筆紀錄提交時間相同

  • Given Tina 和 Bobby 在同一秒提交 Waitlist
  • When 系統排序
  • Then 以 created_at + id 升序作為次序(id 較小的在前)
  • And 兩人都看到自己的明確排位

Scenario 13 (V2.1) Session 有人退出時,系統通知 Waitlist FIFO 第 1 位

  • Given 4/16 18:00 1on2 滿員,Waitlist 有 3 位(Tina #1、Bobby #2、小華 #3)
  • When 該 session 的某學員 24hr 前請假(依 F09,從 enrollment 移除,session 變 1 left)
  • Then 系統發通知給 Tina 媽媽:「您候補的 4/16 18:00 1on2 課程已有空位,請聯繫客服安排報名」
  • And Tina 的 Waitlist 紀錄狀態 → NOTIFIED
  • And 此時 Tina 在 24 小時內聯繫客服則可報名;若無動作 24 小時後系統自動 fallback 通知 Bobby(FIFO 下一位)
  • And 客服收到報名請求後走 F06 報名流程
  • And Tina 報名完成後 Waitlist 紀錄狀態 → MATCHED 並移除
待確認 Q13.5:Waitlist 第 1 位 24hr 內未動作 → 自動通知第 2 位。我預設 24hr 為 fallback 時間。請確認此時長。

Scenario 14 (V2.1) 教練 / 客服查看某 session 的完整 Waitlist 排隊

  • Given Coach Luke 在課程詳情頁查看 4/16 18:00 1on2
  • When 他展開「候補名單」
  • Then 顯示 3 位排隊者(FIFO 順序):
  •  • #1 Tina(5/1 10:00 加入)— 標記「已通知」
  •  • #2 Bobby(5/2 14:00 加入)
  •  • #3 小華(5/8 16:00 加入)
  • And 同樣資訊客服 Lisa 在課表上點擊該 session 也可看到
  • And 家長端看不到其他排隊者,只看到自己「您是第 X 位」

Scenario 15 (V2.1) Waitlist 過期 — Session 已開始或已結束

  • Given Waitlist 紀錄綁某 session(4/16 18:00)
  • When 4/16 18:00 該 session 已開始(無論最終是否有人補進來)
  • Then 系統自動將該 session 所有未匹配的 Waitlist 紀錄狀態 → EXPIRED
  • And 對應家長收到「Waitlist 已過期(課程已開始)」通知

⚠️ 過時 Scenarios 提示

上方 Scenarios 1-12 中,部分原本針對「需求登記簿」設計的內容(如多校區、多時段、季度自動移除等)在 V2.1 下不再適用。 建議實作時以本 feature 頂部 V2.1 變更說明 + Scenarios 1, 13, 14, 15 為主要依據。 其餘 Scenarios(2 家長自查/編輯、4 客服捷徑、9 客服代填、12 排序邊界)的核心邏輯仍適用,僅資料結構從「校區/季度/週幾」改為「session_id」。

14. 通知系統 已完成 家長端

業務價值: 通知系統讓家長即時掌握帳號狀態(付款結果、補課成功、課程變更等),減少反覆查詢。 三層機制:Toast(即時短暫)+ 鈴鐺(持久未讀)+ 歷史列表(完整紀錄)。
主要角色:家長(王女士)、系統

Background 共用前置條件

  • Given 王女士已登入家長端
  • And 通知中心當前有 3 條未讀紀錄
  • And 鈴鐺圖標顯示紅點 + 數字「3」

Scenario 1 Toast 即時提示 — 操作完成

  • Given 王女士剛完成請假操作
  • When 後端返回成功
  • Then 螢幕右上角出現 Toast「請假成功」
  • And Toast 3 秒後自動消失
  • And 同時通知中心新增 1 筆持久通知
  • And 鈴鐺紅點 +1(變為「4」)

Scenario 2 鈴鐺通知 — 即時更新(WebSocket / Polling)

  • Given 王女士在家長端任意頁面
  • When 後台有新事件觸發通知(如 Lisa 為小明調整 Level)
  • Then 鈴鐺圖標的紅點數字 +1(無需重整)
  • And 不顯示 Toast(因為非當前用戶操作觸發)
  • And 點擊鈴鐺打開下拉,看到新通知標記為「未讀」
待確認 Q14.1:即時更新機制 — 我預設 WebSocket(推送)。是否退回 polling(每 30 秒拉一次)?WebSocket 即時但增加複雜度。請確認。

Scenario 3 點擊鈴鐺打開通知下拉

  • Given 鈴鐺紅點顯示「4」
  • When 王女士點擊鈴鐺
  • Then 下拉面板顯示最近 10 條通知(時間倒序)
  • And 未讀紀錄左邊有藍色點標記
  • And 每條顯示:標題 / 摘要 / 時間 / 操作按鈕(如「查看訂單」)
  • And 底部「查看全部」按鈕跳到完整通知頁
  • And 「全部標記為已讀」按鈕

Scenario 4 通知標記為已讀

  • Given 王女士在通知下拉
  • When 她點擊任一未讀通知
  • Then 該通知標記為已讀
  • And 鈴鐺紅點數字 -1
  • And 跳轉到通知對應的詳情頁(如「查看訂單」跳到該訂單)

Scenario 5 6 種通知類型

  • Given 系統定義以下通知類型:
類型觸發點標題範例
PAYMENT_SUCCESS訂單已完成訂單 #001 已完成,學員已加入課表
REFUND_SUCCESS退款已退款訂單 #001 退款 630 CAD 已完成
MAKEUP_SUCCESS補課完成小明 4/16 18:00 補課成功
LEAVE_SUCCESS請假完成小明 4/14 17:00 請假成功,已保留補課額度
SESSION_CHANGED課程變更(時間 / 教練 / 取消)4/14 17:00 課程已調整為 4/14 18:00
REGISTRATION_DONE註冊完成歡迎使用 Aquaturbo,註冊完成

Scenario 6 通知偏好設定(哪些要 Email 推送)

  • Given 王女士在「我的資料 → 通知偏好」頁
  • When 她設定:
  •  • 付款 / 退款 → 鈴鐺 + Email
  •  • 補課 / 請假 → 鈴鐺(不 Email)
  •  • 課程變更 → 鈴鐺 + Email
  •  • 註冊完成 → 鈴鐺(不 Email)
  • And 點擊保存
  • Then 之後對應通知按設定發送
待確認 Q14.2:Email 推送是否第一版就要實作?預設 第一版只實作系統內通知(鈴鐺 / Toast / 歷史),Email 推送 V2.1 再加。請確認。

Scenario 7 全部標記為已讀

  • Given 王女士有 10 條未讀通知
  • When 她點擊「全部標記為已讀」
  • Then 所有未讀紀錄標記為已讀
  • And 鈴鐺紅點消失(顯示為空狀態)
  • And 通知列表內容不變(紀錄仍可見,只是不再標為未讀)

Scenario 8 歷史通知列表頁

  • Given 王女士進入完整通知頁
  • When 介面載入
  • Then 顯示所有歷史通知(無時間限制)
  • And 可按通知類型篩選
  • And 分頁載入(每頁 20 條)

Scenario 9 通知系統觸發點集合 內部行為

  • Given 系統各業務模組需發通知
  • When 觸發以下事件之一:
  •  • 訂單狀態 → 已完成 / 已取消 / 已退款(PAYMENT/REFUND)
  •  • 請假成功 / 撤回 / 特批
  •  • 補課成功 / 取消
  •  • 點名為 No-show(給家長知曉)
  •  • Level 調整
  •  • 課程變更(時間 / 教練 / 取消)
  •  • Waitlist 排到通知
  • Then 對應 Service 呼叫 NotificationService.send()
  • And NotificationService 寫入 t_notification + 推送 WebSocket(若用戶在線)
  • And 依用戶偏好決定是否同步 Email

15. 報表 已完成 客服端 / SUPER_ADMIN

業務價值: 報表給管理層運營決策依據:哪個校區賺錢?哪個時段空閒?哪個教練最受歡迎?哪個 Level 段學員最多? 多維度篩選 + 匯出 Excel 支援深度分析。
主要角色:SUPER_ADMIN(Mark)、客服 ADMIN(限本校區)

Background 共用前置條件

  • Given Mark(SUPER_ADMIN)已登入後台
  • And 系統有 2026 Q1(Spring)的完整課程 / 收款 / 點名數據

Scenario 1 各校區收入報表

  • Given Mark 進入「報表 → 收入分析」
  • When 他選擇時間範圍 = 2026-03-01 ~ 2026-05-31
  • Then 顯示各校區收入:
校區已完成訂單數含稅總收入未稅淨收入退款金額
Burnaby120125,640119,6573,200
Coquitlam8589,75085,4761,500
合計205215,390205,1334,700

Scenario 2 不同時段課程表現分析

  • Given Mark 在「報表 → 時段分析」
  • When 選擇時間範圍 + 校區
  • Then 系統將時段分組(早、中、晚):
  •  • 早(9:00-12:00):sessions 數 / 平均出席率 / 平均報名率
  •  • 中(12:00-17:00):同上
  •  • 晚(17:00-22:00):同上
待確認 Q15.1:時段定義 — 是「早 / 中 / 晚」三段,還是 hourly bucket,或允許自訂?我預設 早 9-12 / 中 12-17 / 晚 17-22 三段。請確認。

Scenario 3 課程類型分析

  • Given Mark 在「報表 → 課程類型」
  • When 篩選條件設定後
  • Then 顯示各類型統計:1on1 / 1on2 / Group / 長訓 各自的:sessions 數 / 報名數 / 收入 / 平均出席率

Scenario 4 學員年齡與等級分布

  • Given Mark 在「報表 → 學員分布」
  • When 介面載入
  • Then 顯示:
  •  • 年齡分布:4-6 / 7-9 / 10-12 / 13-15 / 16+ 各區段人數
  •  • Level 分布:L1-L5 各 Level 人數(圖表)
  •  • 交叉分析:每個年齡段各 Level 的學員數(heatmap)
待確認 Q15.2:年齡計算 — 以「當前日期」還是「報名時學員年齡」? V2 schema 沒有 birthdate 欄位,需新增(QC.3 含義已暗示)。請確認需新增 birthdate 欄位 + 年齡以當前日期計算。

Scenario 5 教練表現報表

  • Given Mark 在「報表 → 教練表現」
  • When 介面載入
  • Then 顯示每位教練的:
  •  • 教學時數(總分鐘)
  •  • 帶過 sessions 數
  •  • 帶過學員數(去重)
  •  • 平均出席率
  •  • 學員續報率(學員上完後是否繼續報名同教練的下個課程)
  •  • 平均單堂收入(收入 / sessions 數)
待確認 Q15.3:教練績效是否影響薪資計算?我預設 不影響(純展示),薪資由 HR 系統處理。請確認。

Scenario 6 多維度篩選

  • Given 任一報表頁
  • When Mark 套用組合篩選:時間範圍 + 校區 + 課程類型 + 教練
  • Then 報表即時更新
  • And 篩選器在頂部固定,方便快速調整
  • And 「重置篩選」按鈕

Scenario 7 匯出 Excel

  • Given Mark 在任一報表頁,已套用篩選
  • When 他點擊「匯出 Excel」
  • Then 系統產生 .xlsx 檔,含當前篩選條件下的完整數據
  • And 檔名格式:「Aquaturbo_收入報表_2026-03_2026-05_Burnaby.xlsx

Scenario 8 ADMIN 限本校區

  • Given Lisa 是 ADMIN(Burnaby)
  • When 她進入報表頁
  • Then 校區篩選器只顯示「Burnaby」(鎖定)
  • And 不可看其他校區資料
  • And 部分敏感報表(如全公司教練績效對比)對 ADMIN 隱藏

Scenario 9 月度自動報表 Email 內部行為

  • Given 系統 cron 每月 1 日 09:00 觸發
  • When 任務執行
  • Then 系統產生「上月運營月報」PDF
  • And Email 給所有 SUPER_ADMIN
待確認 Q15.4:月報 Email 是否第一版實作?預設 V2.1 再加,第一版只做頁面查詢 + Excel 匯出。請確認。

Scenario 10 報表查詢效能 內部行為

  • Given 系統有 10000+ 個 sessions / 50000+ 個 attendances
  • When Mark 查詢 1 年範圍報表
  • Then 響應時間 < 3 秒
  • And 資料庫查詢使用適當索引(branch_id, session_date, status)
  • And 大型聚合用 view 或物化(materialized view)

16. 換課 / 換時間(V2.1:拍板版 — 前台操作)已完成 客服端

✅ V2.1 拍板(2026-05-08 會議): 會議決策方案是「前台操作 + 自動生成價差訂單」(接近原方案 B 但更簡)。
家長不可自助換課:必須通知前台處理。
同價換課:前台一鍵在後台將 enrollment 從原 session 移到新 session,無金流變動。
換到較貴課程:前台立即生成「補差價」付款訂單給家長,付款完成後 enrollment 移轉。
換到較便宜課程:差價退到「不可消費餘額」(綁原訂單),等該訂單對應 sessions 全部結束後自動轉自由餘額。
下方 Scenarios 中的方案 A/B/C 並列段落已過時 — 直接看「拍板版」執行 scenarios。
主要角色:客服(Lisa)、SUPER_ADMIN(Mark)、家長(王女士)

Background 業務場景

  • Given 小明已報名訂單 #001:12 堂 1on2 by Coach Luke 週二 17:00(已上 4 堂,剩 8 堂)
  • And 王女士因家庭時程變動,希望剩下 8 堂改為週四 17:00(同教練、同類型、同價)
  • And 4/16 18:00 起的週四 1on2 課程有空位
核心待確認 Q16.0:需要從以下三方案中拍板選一(或混合):

方案 A 訂單項目級換課 — 不退款、不新訂單(最輕量)

  • Given Lisa 在訂單 #001 詳情頁
  • When 她點擊「批量換課」
  • And 在 8 個未來 sessions 中勾選要換的(如全選)
  • And 選擇目標 RecurrenceGroup(週四 17:00 1on2 by Coach Luke)
  • And 系統驗證:同類型 + 同教練 + 同單堂價(嚴格符合)
  • Then 8 個 enrollments 從原 sessions 移到目標 sessions
  • And 訂單 #001 不變(金額不變、狀態不變)
  • And 不產生退款 / 不產生新訂單
  • And Audit log 記錄此換課操作
  • And 王女士收到「課程已調整」通知
方案 A 評估: 優點:操作最簡、家長無感、無金流變動、capacity 直接互換。 缺點:審計鏈混亂(一個訂單對應的 sessions 中途換了);如果新 session 價格不同無法處理。

方案 B 「換課申請」狀態 + 客服一鍵切換(中等)

  • Given 王女士致電客服請求換課
  • When Lisa 在後台發起「換課申請」(新狀態 / 工作流)
  • And 填寫:來源 sessions、目標 sessions、價差處理(同價 / 補差價 / 退差價)
  • And 提交
  • Then 系統建立 1 筆 t_class_change 紀錄(新表)
  • And 同價 → 直接執行(同方案 A)
  • And 補差價 → 自動建一筆「One-Time 補差價」訂單,待付款
  • And 退差價 → 將差價退到 Credit Pool(找零模式)
  • And 王女士收到通知 / 確認
方案 B 評估: 優點:流程明確 + 處理價差 + 審計鏈清晰(換課動作獨立記錄)。 缺點:需新增 t_class_change 表 + 新狀態機 + 較複雜的工作流。

方案 C 退款 + 新訂單(最嚴格 / 當前 V1 方式)

  • Given 王女士致電請求換課
  • When Mark 對訂單 #001 發起部分退款(剩 8 堂 = 640)
  • And 退款方式選「Credit Pool」(直接入找零池)
  • And Lisa 走 F06 為小明新建訂單 #002(週四 17:00 8 堂)
  • And Step 6 用 640 Credit 全額抵扣(待付款 = 0)
  • Then 訂單 #001 → 已退款(部分退款情境見 F11 S6)
  • And 訂單 #002 → 已完成(純 Credit 抵扣)
  • And 對家長透明(看到一進一出)
方案 C 評估: 優點:審計清晰(每筆訂單獨立)、現有 F06/F11 已支援,無需新功能。 缺點:耗時(兩個操作)、家長疑惑(為什麼要退又付)、文檔明確抱怨「太麻煩」。

推薦 我的建議:方案 B(中等)

  • Given 三方案各有取捨
  • When 從「業務便捷度 × 審計清晰度 × 工程量」綜合考量
  • Then 我建議選 方案 B,理由:
  •  • 文檔抱怨「方案 C 太麻煩」 → 排除 C
  •  • 方案 A 審計鏈不清晰 + 無法處理價差 → 不夠完整
  •  • 方案 B 的 t_class_change 表只是 1 個新表,工程量可控
  •  • 方案 B 處理同價 / 補差價 / 退差價 三種情境

場景 1(方案 B) 同價換時間 — 直接執行

  • Given 方案 B 已被採用
  • And 王女士想將 8 堂週二 17:00 1on2 換為 8 堂週四 17:00 1on2(同教練同類型同價)
  • When Lisa 在後台發起「換課申請」
  • And 系統檢測同類型 + 同教練 + 同價
  • Then 自動執行(無需 SUPER_ADMIN 審批)
  • And 8 個 enrollments 移轉
  • And 訂單 #001 金額不變

場景 2(方案 B) 換到不同價格 — 補差價

  • Given 王女士想將 8 堂 80/堂 換為 8 堂 90/堂(教練升等)
  • And 差額 = 8 × 10 = 80 CAD
  • When Lisa 發起換課並選「補差價」
  • Then 系統建立「One-Time 補差價」訂單,總額 80 + GST = 84 CAD
  • And 訂單為「待付款」狀態,60 分鐘倒數
  • And 待付款完成後,enrollments 才實際移轉到新 sessions
  • And 補差價訂單超時取消的話,原訂單 sessions 保持不變

場景 3(方案 B) 換到較便宜課程 — 退差價到 Credit Pool

  • Given 王女士想將 8 堂 80/堂 換為 8 堂 50/堂(換教練降等)
  • And 差額 = 8 × 30 = 240 CAD
  • When Lisa 發起換課並選「退差價到 Credit Pool」
  • Then 8 個 enrollments 移轉
  • And 240 CAD 進入小明 Credit Pool(來源:換課退差價,from_order = #001)
  • And 訂單 #001 標記「換課退差 240」

17. 系統定時任務(V2.1 調整)已完成 內部行為

⚠️ V2.1 變更:
新增 SessionAutoDeductScanner(每分鐘)— F08 自動扣費(取代點名 + No-show)
新增 LockedBalanceTransferScanner(每天 00:00)— 訂單對應 sessions 全結束後,剩餘不可消費餘額轉自由餘額
移除 NoShowAutoCloser(取消點名後不再需要)
• StudentTagRecalculator 仍保留每天 00:00(V2.1 不再支援即時重算,純 cron)
其他 cron(訂單超時 / Waitlist 清理 / 認證碼清理 / 上課提醒)保持不變。
主要角色:系統

Background Cron 任務拓撲

  • Given 系統定時任務集中在 Spring Scheduler(@Scheduled)
  • And 採用分散式鎖(Redis)防止多實例重複執行
  • And 失敗紀錄寫入 audit log + Sentry alert

Scenario 1 訂單超時自動取消(最高頻)

  • Given 系統定時任務 OrderTimeoutScanner
  • When 每 1 分鐘觸發
  • Then 找出 Online 待付款 + created_at > 60 分鐘的訂單 → 自動取消
  • And 找出 Cash 待付款 + created_at > 48 小時的訂單 → 自動取消
  • And 對應 capacity 釋放 + 鎖定 Credit 解凍 + 通知家長
  • And 詳見 F07 Scenario 16

Scenario 2 No-show 自動結算(每 15 分鐘)

  • Given 定時任務 NoShowAutoCloser
  • When 每 15 分鐘觸發
  • Then 找出 session.end_at + 1 小時 < now() 且狀態 = OPEN 且仍有 PENDING attendance 的 sessions
  • And 將所有 PENDING attendance 標記為 NO_SHOW
  • And 從各自訂單扣費
  • And session.status → CLOSED
  • And 詳見 F08 Scenario 11

Scenario 3 標籤重算 + NEW → CONTACTED 轉換(每天 00:00)

  • Given 定時任務 StudentTagRecalculator
  • When 每天 00:00 觸發
  • Then 對所有學員執行 7 個標籤的重新計算
  • And 補正觸發式重算的遺漏
  • And 處理 NEW → CONTACTED 時間到期轉換
  • And 詳見 F12 Scenarios 11, 12

Scenario 4 Waitlist 過期清理(每天 00:00)

  • Given 定時任務 WaitlistCleaner
  • When 每天 00:00 觸發
  • Then 找出 created_at > 6 個月且狀態 = ACTIVE 的紀錄
  • And 標記為 EXPIRED
  • And 通知家長
  • And 詳見 F13 Scenario 11

Scenario 5 過期密碼重設碼 / 註冊碼清理(每天 02:00)

  • Given 定時任務 AuthCodeCleaner
  • When 每天 02:00 觸發
  • Then 刪除超過 10 分鐘的過期密碼重設碼
  • And 標記超過 30 天的未使用註冊碼為 EXPIRED
  • And 詳見 F01 Scenarios 13, 14

Scenario 6 課程上課提醒(每天 19:00 — 隔天提醒)

  • Given 定時任務 UpcomingSessionReminder
  • When 每天 19:00 觸發
  • Then 找出明天有課的學員
  • And 給家長發提醒通知(鈴鐺 + 可選 Email)
待確認 Q17.1:是否需要「課前 24 小時提醒」?文檔未明示,但實務上是常見功能。請確認是否第一版包含。

Scenario 7 季度開始時自動展開 sessions(事件觸發)

  • Given Mark 建立新季度(如 2026 Summer)
  • When 客服建立 RecurrenceGroup
  • Then 系統立即同步展開該季度範圍內所有 sessions(依 weekday 規則)
  • And 排除國定假日 + 校區自訂休館日
  • And 寫入 t_session 表
  • And 詳見 F04 Scenario 21

Scenario 8 月度報表自動產出(每月 1 日 09:00 — V2.1)

  • Given 定時任務 MonthlyReportGenerator(V2.1 含)
  • When 每月 1 日 09:00 觸發
  • Then 產生上月運營月報 PDF
  • And Email 給所有 SUPER_ADMIN
  • And 詳見 F15 Scenario 9

Scenario 9 系統健康檢查(每分鐘)

  • Given 定時任務 HealthCheck
  • When 每分鐘觸發
  • Then 檢查:DB 連線 / Redis / Stripe API / 各 cron 上次執行時間
  • And 異常時 alert 給運維

Scenario 10 Cron 任務拓撲總表

  • Given 完整 cron 表如下
頻率任務關聯 Feature
每分鐘OrderTimeoutScannerF07
每分鐘HealthCheckF17
每 15 分鐘NoShowAutoCloserF08
每天 00:00StudentTagRecalculatorF12
每天 00:00WaitlistCleanerF13
每天 02:00AuthCodeCleanerF01
每天 19:00UpcomingSessionReminderF14
每月 1 日 09:00MonthlyReportGenerator (V2.1)F15
事件觸發SessionExpander(季度建立時)F04
事件觸發StudentTagRecalculator (per 學員)F12