← 回看板
spec

002-voucher-ui-enhancement

功能規格:卡券系統 UI/UX 優化

功能分支: 002-voucher-ui-enhancement 建立日期: 2025-11-30 狀態: 草稿 輸入: 使用者描述:「根據 2025-11-30 會議記錄,針對卡券匯入與帳號綁定系統進行 UI/UX 優化,包含:全名搜尋調整、顧客搜尋顯示生日、卡券表格字體調整、頁籤佈局調整、綁定記錄顯示優化、開卡功能整合、卡券作廢功能、卡券來源欄位、綁定/解綁歷程獨立頁籤等。」

依賴: 此規格為 001-voucher-account-binding 的延續與增強

澄清

Session 2025-11-30

  • Q: 「全名」欄位的資料來源? → A: 系統自動從「姓」+「名」組合產生
  • Q: 「開卡」功能是什麼? → A: 開卡是既有功能(已有 API 實作),用於設計師手動新增單張卡券
  • Q: 卡券拿掉/開錯卡的處理方式? → A: 軟刪除,將卡券標記為「已作廢」
  • Q: 卡券 source 欄位的值? → A: manual(手動開卡)vs imported(批次匯入)
  • Q: 已移除顧客卡卷(視覺化)的呈現方式? → A: 使用不同 Icon 區分操作類型:🔗(綁定)vs 🔓(解綁)

Grooming Session 2025-11-30

設計決策確認

  1. 全名欄位實作

    • 決策:新增 User.full_name 實際欄位(String(100), nullable, indexed)
    • 值:由 last_name + first_name 組合產生
    • 當姓名都為空時:full_name 允許為 null
  2. 卡券綁定架構(雙軌系統)

    • 匯入卡券imported_vouchers):
      • 匯入後可綁定客戶,使用 bound_customer_id 欄位記錄
      • 綁定時同步新增 voucher_bindings 記錄(用於歷程追蹤)
      • 支援解綁操作(bound_customer_id 設為 NULL)
    • 手動開卡vouchers):
      • 開卡時直接指定 customer_id,不可變更
      • 不透過 voucher_bindings 記錄綁定關係
      • 使用 source 欄位區分來源(manual | imported
    • 綁定歷程voucher_bindings):
      • 只記錄 imported_vouchers 的綁定/解綁/撤銷歷程
      • 欄位:imported_voucher_idcustomer_idoperation_type(BIND/UNBIND/REVOKE)、operator_idoperated_atremarksvoucher_id
  3. 三種卡券操作定義

    • 解綁(Unbind)
      • 對象:imported_vouchers 已綁定的卡券
      • 操作:bound_customer_id = NULL, status = 'UNBOUND'
      • 權限:EMPLOYEE 以上
      • UI:✅ 有介面
    • 撤銷(Revoke)
      • 對象:已綁定的匯入卡券(透過 voucher_bindings 綁定的)
      • 操作:新增一筆 operation_type = REVOKE 的綁定紀錄 + 軟刪除 vouchersvoucher_items
      • 權限:僅 ADMIN
      • UI:✅ API + 管理後台 UI(2026-08-22 更新,見 US-4/FR-019)
    • 剪卡(Void)
      • 對象:手動開卡的 vouchers(未綁定客戶)
      • 操作:軟刪除 vouchersvoucher_itemsis_deleted = TRUE
      • 權限:僅 ADMIN
      • UI:✅ API + 管理後台 UI(2026-08-22 更新,見 US-4/FR-024)
  4. 客戶搜尋功能

    • 搜尋範圍:全名 + 電話 + 生日(複合搜尋)
    • 效能要求:500ms 內回應,需建立 full_name 索引
    • 無結果處理:顯示「查無符合的客戶」提示文字

使用者場景與測試 (必填)

使用者故事 1 - 客戶搜尋與顯示優化 (優先級: P1)

設計師在進行卡券綁定時,需要快速且準確地搜尋客戶。系統需新增「全名」欄位(由姓+名自動組合),並使用此全名欄位進行模糊搜尋,取代原本的姓名分開搜尋方式。此外,搜尋結果必須顯示客戶生日資訊,方便設計師確認客戶身份。

優先級理由: 這是設計師日常操作最頻繁的功能,搜尋效率直接影響工作流程。全名搜尋能更準確地匹配客戶,生日顯示能協助確認同名客戶。

獨立測試: 可透過輸入客戶姓名搜尋,驗證系統使用全名欄位進行模糊搜尋,並確認搜尋結果中顯示客戶生日。

驗收場景:

  1. Given 系統中有客戶資料(姓:王、名:小明、生日:03/15),When 系統計算全名欄位,Then 全名欄位值為「王小明」
  2. Given 設計師在客戶搜尋輸入「王小」,When 執行搜尋,Then 系統使用全名、電話、生日欄位進行複合模糊搜尋,返回所有符合條件的客戶,回應時間應在 500ms 內
  3. Given 搜尋結果 Modal 顯示客戶清單,When 設計師檢視清單內容,Then 每位客戶的資訊必須包含:全名、電話、生日(MMDD格式)
  4. Given 系統中有兩位客戶全名都是「王小明」但生日不同(0315 和 0720),When 搜尋結果顯示,Then 設計師可透過生日欄位區分兩位客戶
  5. Given 設計師搜尋「不存在的客戶」,When 搜尋完成,Then 系統顯示「查無符合的客戶」提示文字

使用者故事 2 - 綁定/解綁歷程獨立頁籤 (優先級: P1)

設計師需要查看匯入卡券的綁定與撤銷歷程記錄(透過 voucher_bindings 記錄的操作),以追蹤操作歷史和責任歸屬。系統需提供獨立的「綁定/解綁歷程」頁籤,顯示完整的操作記錄,並使用視覺化 Icon 區分不同操作類型。歷程支援依操作類型和時間區間篩選,預設顯示最近一週的記錄。

優先級理由: 操作歷程是追蹤和稽核的重要依據,獨立頁籤能讓設計師快速找到所需資訊,Icon 區分能提升可讀性。篩選功能能快速定位特定時間段的操作。

獨立測試: 可透過切換至歷程頁籤,驗證系統顯示所有綁定/撤銷記錄,並確認 Icon 正確區分操作類型,篩選功能正常運作。

驗收場景:

  1. Given 設計師進入卡券管理介面,When 檢視頁籤列表,Then 系統顯示獨立的「綁定/解綁歷程」頁籤
  2. Given 設計師點擊「綁定/解綁歷程」頁籤,When 頁面載入完成,Then 系統顯示歷程記錄表格,包含欄位:操作設計師、卡券 ID、操作類型、顧客名稱(全名)、操作時間,預設顯示最近一週的記錄
  3. Given 歷程記錄中有一筆 operation_type = BIND 的操作,When 顯示該筆記錄,Then 操作類型欄位顯示 🔗 Icon 並標示「綁定」
  4. Given 歷程記錄中有一筆 operation_type = REVOKE 的操作,When 顯示該筆記錄,Then 操作類型欄位顯示 🔓 Icon 並標示「已撤銷」,並顯示該筆的 operated_at 作為撤銷時間
  5. Given 歷程記錄表格有多筆資料,When 檢視排序,Then 記錄依操作時間倒序排列(最新在最上方)
  6. Given 設計師使用篩選功能,When 選擇「僅綁定」,Then 系統只顯示 operation_type = BIND 且之後未被 REVOKE/UNBIND 的記錄
  7. Given 設計師使用篩選功能,When 選擇「已撤銷」,Then 系統只顯示已存在對應 REVOKE 紀錄的綁定
  8. Given 設計師使用時間區間篩選,When 選擇「最近一個月」,Then 系統顯示最近 30 天內的記錄
  9. Given 歷程記錄超過 20 筆,When 檢視表格,Then 系統提供分頁功能,每頁顯示 20 筆記錄

使用者故事 3 - 開卡功能與紀錄整合 (優先級: P2)

設計師除了批次匯入卡券外,也需要能夠手動開立單張卡券。系統需整合既有的開卡 API,並在卡券資料中新增 source 欄位區分來源(manual 手動開卡 vs imported 批次匯入)。開卡紀錄需與綁定紀錄分開顯示,便於追蹤。

優先級理由: 開卡是既有功能,需確保與新系統整合順暢。來源欄位能幫助追蹤卡券的建立方式,對於資料分析和問題排查很重要。

獨立測試: 可透過手動開卡操作,驗證系統正確記錄卡券來源為 manual,並在紀錄中與匯入卡券區分顯示。

驗收場景:

  1. Given 設計師透過手動開卡功能建立新卡券,When 卡券建立成功,Then 該卡券的 source 欄位值為 manual
  2. Given 設計師透過批次匯入建立卡券,When 匯入成功,Then 這些卡券的 source 欄位值為 imported
  3. Given 卡券列表中有手動開卡和匯入的卡券,When 顯示列表,Then 系統能透過來源欄位區分並標示卡券來源
  4. Given 設計師手動開卡失敗(例如卡號重複),When 錯誤發生,Then 系統顯示明確錯誤訊息,並將錯誤記錄到系統日誌

使用者故事 4 - 卡券操作功能(解綁/撤銷/剪卡) (優先級: P2)

⚠️ 2026-08-22 規格更新(跨 change 決議):本 Story 原訂撤銷(Revoke)與剪卡(Void)為 API only。經 Jacky 拍板,改為管理後台 UI 亦可操作——管理員於「顧客卡券」管理頁面 即可刪除/撤銷卡券,並須選填異動原因。權限限制「僅 ADMIN」維持不變,改變的只有操作介面。 權威來源:.prospec/changes/manage-customer-vouchers/(US-2、FR-007b)。 2026-08-23 已完成同步:驗收場景補上管理後台 UI 情境(含異動原因必填); 撤銷的資料模型描述由不存在的 revoked_at 欄位改為實作採用的 append-only operation_type = REVOKE 紀錄;Change Log 已補。

系統需要提供三種卡券操作功能,分別對應不同的業務場景:

  1. 解綁(Unbind):設計師需要解除匯入卡券與客戶的綁定關係,使卡券可重新綁定給其他客戶。此操作透過 UI 執行,權限為 EMPLOYEE 以上。

  2. 撤銷(Revoke):管理員需要撤銷錯誤的綁定操作並移除相關卡券記錄。此操作僅透過 API 執行2026-08-22 更新:管理後台 UI 與 API 皆可執行,權限僅限 ADMIN,操作具永久性(軟刪除)。

  3. 剪卡(Void):管理員需要作廢手動開錯的卡券。此操作僅透過 API 執行2026-08-22 更新:管理後台 UI 與 API 皆可執行,權限僅限 ADMIN,操作具永久性(軟刪除)。

優先級理由: 這些操作是資料維護的基本需求,能處理人為錯誤。解綁是日常操作,撤銷和剪卡是管理功能,軟刪除確保資料可追溯性,符合稽核需求。

獨立測試: 可分別測試三種操作,驗證權限控制、資料變更正確性,以及軟刪除的卡券不再出現於可用清單中。

驗收場景:

解綁操作

  1. Given 設計師(EMPLOYEE 角色)檢視一張已綁定客戶的匯入卡券(imported_vouchers.bound_customer_id 不為 NULL),When 點擊「解綁」按鈕,Then 系統彈出確認視窗,顯示卡券資訊和綁定客戶姓名
  2. Given 解綁確認視窗已開啟,When 設計師點擊「確認解綁」,Then 系統更新 imported_vouchers.bound_customer_id = NULLstatus = 'UNBOUND',顯示「解綁成功」訊息
  3. Given 卡券已解綁,When 設計師再次搜尋該卡券,Then 該卡券顯示為未綁定狀態,可重新綁定給任何客戶(包含原客戶)

撤銷操作(2026-08-22 起:API + 管理後台 UI)

  1. Given 管理員在管理後台的顧客卡券頁看到一張已綁定的匯入卡券,When 在清單或詳情面板點「刪除/撤銷此卡券」,Then 跳出確認視窗,要求先選異動原因(顧客退費/輸入錯誤撤銷/店家補償重發/其他)
  2. Given 確認視窗已開啟但未選原因,When 管理員點確認,Then 無法送出
  3. Given 管理員已選原因並確認,When 系統執行撤銷,Then 新增一筆 REVOKE 綁定操作紀錄,並軟刪除相關的 vouchers.is_deleted = TRUEvoucher_items.is_deleted = TRUE
  4. Given 卡券綁定已撤銷,When 查詢有效綁定記錄,Then 該記錄不出現(因最新一筆操作為 REVOKE
  5. Given 管理員(ADMIN 角色)改以 API 呼叫撤銷端點,When API 執行成功,Then 結果與 UI 操作一致
  6. Given 非 ADMIN 角色嘗試撤銷(不論從 UI 或 API),When 執行,Then 系統拒絕並返回 403 Forbidden

剪卡操作(2026-08-22 起:API + 管理後台 UI)

  1. Given 管理員在管理後台看到一張手動開的卡券,When 在清單或詳情面板點「刪除/撤銷此卡券」並選填異動原因後確認,Then 系統軟刪除 vouchers.is_deleted = TRUEvoucher_items.is_deleted = TRUE
  2. Given 卡券已剪卡,When 設計師搜尋卡券,Then 該卡券不出現在可用卡券列表中(因 is_deleted = TRUE
  3. Given 管理員嘗試剪卡一張已綁定客戶的卡券,When 執行,Then 系統返回錯誤「此卡券已綁定客戶,無法剪卡」
  4. Given 管理員(ADMIN 角色)改以 API 呼叫剪卡端點,When API 執行成功,Then 結果與 UI 操作一致
  5. Given 非 ADMIN 角色嘗試剪卡(不論從 UI 或 API),When 執行,Then 系統拒絕並返回 403 Forbidden

UI 操作與 API 的分流(2026-08-23 補充):管理後台的「刪除」是一個入口, 但底層依卡券來源走不同路徑——手動開的卡走剪卡(Void),匯入且已綁定的卡走撤銷(Revoke)。 解綁(Unbind)是可逆動作,不屬於刪除。


使用者故事 5 - 介面佈局與視覺優化 (優先級: P3)

為提升使用者體驗,系統需調整頁籤順序(共 4 個頁籤:卡券綁定 → 卡券管理 → 綁定/解綁歷程 → 卡券匯入)、將綁定按鈕放置於 footer 區域、以及將卡券表格字體從 14px 增大到 16px 以提升可讀性。

優先級理由: 這是視覺優化,不影響核心功能但能提升使用體驗。相對於功能性需求,優先級較低。

獨立測試: 可透過檢視介面佈局,驗證頁籤順序、按鈕位置和字體大小符合設計要求。

驗收場景:

  1. Given 設計師進入卡券管理介面,When 檢視頁籤順序,Then 頁籤依序為:1) 卡券綁定、2) 卡券管理、3) 綁定/解綁歷程、4) 卡券匯入(最右邊)
  2. Given 設計師在卡券綁定頁面,When 檢視頁面佈局,Then「確認綁定」按鈕位於頁面 footer 區域(固定於底部)
  3. Given 設計師搜尋卡券後,When 檢視卡券表格,Then 表格字體大小為 16px(原為 14px),提升可讀性
  4. Given 綁定記錄列表顯示,When 檢視記錄內容,Then 顯示完整的顧客全名(非 ID)、綁定時間、操作設計師

邊界案例

  • 全名為空: 當客戶只有姓或只有名時,全名欄位應正確處理(僅顯示有值的部分);當姓名都為空時,full_name 為 null
  • 同名同生日: 若存在全名和生日都相同的客戶,搜尋結果應顯示其他可區分資訊(如電話末四碼)
  • 解綁後重新綁定: 匯入卡券解綁後,可重新綁定給任何客戶(包含原客戶),無限制
  • 軟刪除後的卡券: 已執行撤銷或剪卡的卡券(is_deleted = TRUE),在所有查詢中應被過濾,不出現在任何可用清單中
  • 歷程記錄分頁: 歷程記錄每頁固定顯示 20 筆,超過時自動分頁
  • Icon 顯示異常: 當作業系統不支援 emoji 時,應有文字替代方案(「綁定」/「已撤銷」)
  • 撤銷永久性: 已撤銷的綁定記錄和已剪卡的卡券無法恢復,操作具永久性

需求 (必填)

功能需求

客戶全名欄位與搜尋

  • FR-001: 系統必須在 User 資料表新增 full_name 欄位(String(100), nullable, indexed),值由 last_name + first_name 組合產生。新增使用者或更新姓名時,系統應自動同步更新 full_name 欄位。當姓名都為空時,full_name 允許為 null。
  • FR-002: 客戶搜尋功能必須支援複合搜尋,同時對 full_namephonebirthday 欄位進行模糊查詢,回應時間應在 500ms 內
  • FR-003: 客戶搜尋結果 Modal 必須顯示客戶生日資訊(MMDD 格式)
  • FR-004: 客戶搜尋無結果時,系統必須顯示「查無符合的客戶」提示文字

卡券表格與佈局

  • FR-005: 卡券搜尋結果表格字體大小必須為 16px(原為 14px)
  • FR-006: 頁籤順序必須為:1) 卡券綁定、2) 卡券管理、3) 綁定/解綁歷程、4) 卡券匯入(最右邊)
  • FR-007: 綁定卡券的「確認綁定」按鈕必須放置於頁面 footer 區域

綁定記錄顯示

  • FR-008: 綁定記錄必須顯示顧客全名(非 ID 或部分姓名)
  • FR-009: 綁定記錄必須顯示綁定時間(完整日期時間格式)
  • FR-010: 綁定記錄必須顯示操作設計師姓名

開卡功能整合

  • FR-011: 系統必須整合既有開卡 API,支援設計師手動新增單張卡券
  • FR-012: vouchers 表必須新增 source 欄位(ENUM: manual | imported),區分卡券來源
  • FR-013: 手動開卡的卡券 sourcemanual;從 imported_vouchers 轉入的卡券 sourceimported
  • FR-014: 開卡紀錄失敗時,系統必須將錯誤記錄到系統日誌

卡券操作功能

解綁(Unbind)
  • FR-015: 系統必須提供解綁功能(有 UI),允許 EMPLOYEE 以上角色解除 imported_vouchers 與客戶的綁定
  • FR-016: 解綁操作必須彈出確認視窗,顯示卡券資訊和綁定客戶姓名
  • FR-017: 解綁執行時,系統必須更新 imported_vouchers.bound_customer_id = NULLstatus = 'UNBOUND'
  • FR-018: 解綁後的卡券可重新綁定給任何客戶(包含原客戶),無限制
撤銷(Revoke)
  • FR-019: 系統必須提供撤銷功能(API + 管理後台 UI — 2026-08-22 更新),僅 ADMIN 角色可執行
  • FR-020: 撤銷操作對象為已透過 voucher_bindings 綁定的匯入卡券
  • FR-021: 撤銷執行時,系統必須:1) 新增一筆 operation_type = REVOKE 的綁定紀錄(含 operator_idoperated_at),2) 軟刪除 vouchers.is_deleted = TRUE,3) 軟刪除所有相關 voucher_items.is_deleted = TRUE
  • FR-022: 撤銷操作具永久性,無法恢復
  • FR-023: 非 ADMIN 角色呼叫撤銷 API 時,系統必須返回 403 Forbidden 錯誤
剪卡(Void)
  • FR-024: 系統必須提供剪卡功能(API + 管理後台 UI — 2026-08-22 更新),僅 ADMIN 角色可執行
  • FR-025: 剪卡操作對象為手動開卡的 vouchers(未綁定客戶)
  • FR-026: 剪卡執行時,系統必須:1) 軟刪除 vouchers.is_deleted = TRUE,2) 軟刪除所有相關 voucher_items.is_deleted = TRUE
  • FR-027: 剪卡操作具永久性,無法恢復
  • FR-028: 嘗試剪卡已綁定客戶的卡券時,系統必須返回錯誤「此卡券已綁定客戶,無法剪卡」
  • FR-029: 非 ADMIN 角色呼叫剪卡 API 時,系統必須返回 403 Forbidden 錯誤

綁定/解綁歷程頁籤

  • FR-030: 系統必須提供獨立的「綁定/解綁歷程」頁籤,只顯示 voucher_bindings 表的記錄(匯入卡券的綁定/解綁/撤銷歷程)
  • FR-031: 歷程記錄必須包含欄位:操作設計師(Employee.name)、卡券 ID、操作類型、顧客名稱(全名)、操作時間
  • FR-032: 操作類型必須使用 Icon 視覺化區分:🔗(BIND)、🔓(REVOKE)、🔁(UNBIND
  • FR-033: 歷程記錄必須依操作時間倒序排列(最新在最上方)
  • FR-034: 已撤銷的綁定記錄必須顯示撤銷時間(該筆 REVOKE 紀錄的 operated_at
  • FR-035: 歷程頁籤必須支援篩選功能:1) 操作類型(全部/僅綁定/已撤銷),2) 時間區間(最近一週/最近一個月/最近三個月/自訂區間),預設顯示最近一週
  • FR-036: 歷程記錄必須提供分頁功能,每頁固定顯示 20 筆

關鍵實體

  • User(使用者/客戶):

    • 新增 full_name 欄位(String(100), nullable, indexed),由 last_name + first_name 自動組合
  • ImportedVoucher(匯入卡券):

    • 匯入的待綁定卡券清單
    • 關鍵欄位:bound_customer_id (nullable, 綁定的客戶)、status (UNBOUND | BOUND)
    • 支援解綁操作:設定 bound_customer_id = NULL
  • Voucher(正式卡券):

    • 手動開卡的卡券記錄(開卡時直接指定客戶)
    • 新增欄位:source (ENUM: manual | imported)、customer_idis_deleted (軟刪除)
    • 不透過 voucher_bindings 記錄綁定關係
  • VoucherItem(卡券項目):

    • 卡券的詳細項目(次數、金額等)
    • 新增欄位:is_deleted (軟刪除,跟隨 voucher 一起作廢)
  • VoucherBinding(綁定歷程):

    • 只記錄 imported_vouchers 的綁定/解綁/撤銷歷程
    • 關鍵欄位:imported_voucher_id (foreign key)、customer_idoperation_type(BIND/UNBIND/REVOKE)、operator_idoperated_atremarksvoucher_id

成功標準 (必填)

可衡量成果

  • SC-001: 設計師使用全名搜尋客戶的成功率達到 95% 以上(首次搜尋即找到目標客戶)
  • SC-002: 客戶搜尋結果顯示生日後,同名客戶的選擇錯誤率降低 80%
  • SC-003: 設計師能在 5 秒內透過歷程頁籤找到特定卡券的綁定記錄
  • SC-004: 卡券作廢操作在 3 步驟內完成(點擊作廢 → 確認 → 完成)
  • SC-005: 新頁籤佈局(匯入置右、綁定按鈕在 footer)在使用者測試中獲得 80% 正面評價
  • SC-006: 卡券表格字體調整後,設計師閱讀卡券資訊的時間縮短 20%

假設

  • 既有開卡 API 已正常運作,本規格僅需整合而非重新開發
  • 客戶資料已有 first_namelast_name 欄位,系統可據此計算 full_name
  • 系統日誌機制已存在,可直接寫入開卡失敗記錄
  • 使用者裝置支援 emoji 顯示(作為 Icon 主要方案)
  • 此規格變更與 001-voucher-account-binding 的既有功能相容
  • VoucherBinding 表已存在 operator_id 欄位(String(36),不設 FK,可能指向 employees 表的共用帳號),可複用記錄操作者;表上沒有 operator_role 欄位,需要操作者角色時(例如稽核顯示或權限檢查)須另查 employees/users 表
  • ⚠️ 後端落差(2026-08-26 盤點):本規格要求撤銷原因寫入 REVOKE 紀錄的 remarks(US-4 場景 4-6),但 cockatiel 現況 RevokeBindingRequest 僅有 operator_id 欄位、revoke_binding() 寫死 remarks=None——此路徑待後端實作(需新增 reason 欄位並接通 remarks 寫入),實作前 UI 的原因選單無處落地
  • 資料庫支援為 User.full_name 欄位建立索引,以達成 500ms 搜尋效能要求
  • 綁定/解綁歷程記錄永久保留,無保留期限限制
  • 手動開卡功能(vouchers 表)在開卡時就指定客戶,不需要額外的綁定操作
  • 系統已有角色權限控制機制(EMPLOYEE, ADMIN, OWNER),可用於操作權限管理

範圍界定

包含在此功能內

  • 客戶全名欄位新增與複合搜尋邏輯(全名+電話+生日)
  • 客戶搜尋結果顯示生日與無結果提示
  • 客戶搜尋效能優化(500ms 內,建立索引)
  • 卡券表格字體調整(14px → 16px)
  • 頁籤順序調整(4 個頁籤:綁定 → 管理 → 歷程 → 匯入)
  • 綁定按鈕位置調整(移至 footer)
  • 綁定記錄顯示欄位擴充
  • 開卡功能整合與來源欄位(source: manual | imported)
  • 三種卡券操作功能:
    • 解綁(有 UI,EMPLOYEE 以上)
    • 撤銷(API + 管理後台 UI,ADMIN — 2026-08-22 更新)
    • 剪卡(API + 管理後台 UI,ADMIN — 2026-08-22 更新)
  • 綁定/解綁歷程獨立頁籤(只顯示 voucher_bindings 記錄)
  • 歷程篩選功能(操作類型 + 時間區間)
  • 歷程分頁功能(每頁 20 筆)
  • 操作類型 Icon 視覺化

不包含在此功能內

  • 開卡 API 的重新開發(使用既有 API)
  • 卡券硬刪除功能(僅支援軟刪除)
  • 已撤銷綁定的恢復功能
  • 已剪卡卡券的恢復功能
  • 客戶 first_namelast_name 欄位的新增(假設已存在)
  • 歷程記錄的匯出功能
  • 多語系支援
  • imported_vouchers 的解綁歷程記錄(只記錄透過 voucher_bindings 的操作)
  • 手動開卡(vouchers)的綁定/解綁功能(開卡時就指定客戶,不可變更)

Change Log

v0.4.0 - 撤銷/剪卡改為管理後台 UI 操作,資料模型描述校正 (2026-08-23)

跨 change 決議(來源:.prospec/changes/manage-customer-vouchers

  • 撤銷(Revoke)與剪卡(Void)由「API only」改為 API + 管理後台 UI 皆可執行; 權限限制「僅 ADMIN」不變,改變的是操作介面
  • UI 是單一「刪除」入口,底層依卡券來源分流:手動開卡走 Void、匯入且已綁定走 Revoke; 解綁(Unbind)為可逆動作,不屬於刪除
  • 刪除時必須選擇異動原因(顧客退費/輸入錯誤撤銷/店家補償重發/其他)

資料模型描述校正(依 cockatiel 實作查證)

  • 原文 14 處以 voucher_binding.revoked_at 描述撤銷狀態,但實作無此欄位。 實際採 append-only 設計:新增一筆 operation_type = REVOKE 的紀錄, 時間欄位為 operated_at
  • voucher_bindings 實際欄位:imported_voucher_idcustomer_idoperation_type (BIND/UNBIND/REVOKE)、operator_idoperated_atremarksvoucher_id
  • operator_role 欄位不存在於 voucher_bindings;操作者角色須另查 employees/users 表
  • 撤銷原因為 UI 層選項,寫入該筆 REVOKE 紀錄的 remarks,表上無獨立原因欄位
  • 受影響:US-2 的 AC 3/4/6/7、FR-021、FR-032、FR-034、Key Entities、Assumptions

驗收場景擴充

  • US-4 撤銷與剪卡各補上管理後台 UI 的操作情境(確認視窗、原因必填、UI 與 API 結果一致)

2026-08-24 補漏同步

  • Grooming Session 決策 3 的撤銷/剪卡「UI:❌ 僅 API」改為「API + 管理後台 UI」,與 US-4/FR-019/FR-024 對齊
  • Assumptions 的 operator_role 假設校正為實際欄位
  • DDD 文件同步:ddd/domain-model.md 的 VoucherBinding 定義、invariants、索引設計改為 append-only 模型; ddd/ubiquitous-lang.md 的 REVOKED 定義、撤銷/剪卡介面欄位、資料影響描述一併校正

v0.3.0 - Phase 2 Grooming 完成 (2025-12-07)

架構重大調整

  • 確認雙軌卡券綁定系統:
    • imported_vouchers 使用 bound_customer_id 直接綁定,同步記錄到 voucher_bindings
    • vouchers 使用 customer_id 直接綁定,開卡時指定,不透過 voucher_bindings
  • 釐清三種卡券操作:
    • 解綁(Unbind):UI 操作,EMPLOYEE 權限,針對 imported_vouchers
    • 撤銷(Revoke):API 操作,ADMIN 權限,撤銷 voucher_bindings 並軟刪除 vouchers(→ 2026-08-23 v0.4.0 改為 API + UI)
    • 剪卡(Void):API 操作,ADMIN 權限,軟刪除手動開卡的 vouchers(→ 2026-08-23 v0.4.0 改為 API + UI)
  • 確認歷程頁籤只顯示 voucher_bindings 的記錄(匯入卡券的綁定/解綁/撤銷歷程)

功能需求大幅擴充

  • FR-001~004: 補充搜尋效能要求(500ms)和無結果提示
  • FR-005~007: 明確字體大小(16px)和完整頁籤順序
  • FR-012~014: 新增 vouchers.source 欄位定義
  • FR-015~029: 新增三種卡券操作的完整需求(解綁/撤銷/剪卡)
  • FR-030~036: 新增歷程篩選功能(操作類型 + 時間區間)和分頁功能

User Story 重寫

  • US2: 補充篩選功能(操作類型 + 時間區間,預設一週)和分頁規格(每頁 20 筆)
  • US4: 從單一「作廢功能」重寫為三種操作(解綁/撤銷/剪卡),明確權限和 UI/API 區分
  • US5: 補充具體規格(字體 16px、4 個頁籤順序)

關鍵實體修訂

  • ImportedVoucher: 新增 bound_customer_idstatus 欄位說明
  • Voucher: 新增 source, customer_id, is_deleted 欄位
  • VoucherItem: 新增 is_deleted 欄位
  • VoucherBinding: 明確只記錄 imported_vouchers 的綁定,欄位包含 imported_voucher_id

假設新增

  • 資料庫索引支援(達成 500ms 搜尋效能)
  • 歷程記錄永久保留
  • 角色權限控制機制已存在

邊界案例更新

  • 補充解綁後重新綁定規則
  • 補充撤銷和剪卡的永久性說明
  • 更新歷程分頁規格

v0.2.0 - Grooming Session (2025-11-30)

設計決策

  • 確認 full_nameUser 表的實際欄位,非計算欄位
  • 確認撤銷狀態以 append-only 的 operation_type = REVOKE 紀錄表示(非 revoked_at 欄位;2026-08-23 依實作校正)
  • 確認客戶搜尋支援全名+電話+生日複合搜尋
  • 確認開卡功能已實作綁定客戶流程

功能需求修訂

  • FR-001: 明確 full_name 欄位規格(String(100), nullable, indexed)
  • FR-002: 擴展為複合搜尋(全名+電話+生日)
  • FR-014~018: 從「卡券作廢」調整為「綁定撤銷」概念
  • FR-020: 新增撤銷狀態欄位顯示
  • FR-023: 新增已撤銷記錄的視覺標記需求

關鍵實體修訂

  • 移除「作廢記錄 (Void Record)」實體
  • 新增 VoucherBinding 綁定操作紀錄欄位說明
  • 更新 User.full_name 欄位說明

v0.1.0 - Initial Draft (2025-11-30)

  • 初始規格草稿建立