← 回看板
story

管理員端顧客卡券管理:查詢顧客卡券狀態與撤銷卡券

manage-customer-vouchers · 建立於 2026-05-17

prospec 流程進度

探索需求規劃設計任務實作驗證歸檔

跨 repo 進度

owl(產品層)
定意圖
定義需求與契約意圖
cockatiel(後端)
無需求
本變更未涉及
raven(前端)
無需求
本變更未涉及

manage-customer-vouchers

Epic 1:顧客卡券管理 (Customer Voucher Management) — 管理頁面

Background

美髮系統的管理員經常需要協助顧客處理卡券相關問題(確認餘額、排除使用疑慮、處理退費、修正錯誤輸入的卡券)。目前管理端缺乏統一的卡券查詢與撤銷工具,導致客服效率低落且異動缺乏稽核軌跡。本變更新增「顧客卡券管理」管理頁面,涵蓋查詢與撤銷兩項核心能力。

User Stories

US-1: 查詢顧客卡券狀態 [P1]

As a 分店管理員, I want 透過顧客姓名、手機或卡券序號搜尋並查看顧客擁有的卡券清單, So that 我能協助顧客確認餘額或排除使用疑慮.

Acceptance Scenarios:

  • WHEN 管理員於搜尋框輸入顧客姓名、手機或卡券序號,THEN 系統顯示符合條件且屬於該管理員所屬分店的卡券清單
  • WHEN 卡券清單顯示,THEN 每筆需呈現顧客資訊、卡券類型(洗/剪/燙等)、剩餘次數或金額、購買日期、失效日期、卡券狀態
  • WHEN 搜尋條件查無相符卡券,THEN 系統顯示「查無卡券」提示而非空白畫面
  • WHEN 搜尋命中的卡券屬於其他分店,THEN 該卡券不出現於結果中

Independent Test: 以分店管理員身分登入,以已知顧客手機號搜尋,驗證回傳清單欄位完整、且僅含所屬分店資料。

US-2: 撤銷顧客卡券 [P1]

As a 具高權限角色的管理員(店長或總部管理員), I want 撤銷(作廢)特定顧客的卡券, So that 我能處理退費或修正錯誤輸入的卡券.

Acceptance Scenarios:

  • WHEN 高權限管理員於卡券清單點選「撤銷」,THEN 系統彈出二次確認視窗並顯示該卡券資訊
  • WHEN 管理員於確認視窗點選「確認撤銷」,THEN 該卡券標記為已作廢,不再出現於顧客端
  • WHEN 卡券被撤銷,THEN 系統寫入一筆異動 log(含操作者、操作時間、卡券識別資訊、撤銷前狀態)供審核
  • WHEN 不具高權限的管理員檢視卡券清單,THEN 「撤銷」操作不可見或不可點選
  • WHEN 管理員於確認視窗點選「取消」,THEN 卡券狀態維持不變

Independent Test: 以店長身分撤銷一張測試卡券,驗證 ① 顧客端查不到 ② 管理端與 log 仍可查到該異動 ③ 一般管理員看不到撤銷按鈕。

Edge Cases

  • 已失效/已用完的卡券:仍可被查詢並顯示(標示對應狀態)
  • 跨分店資料:管理員以序號搜尋到非所屬分店的卡券時,不顯示於結果
  • 重複撤銷:對已撤銷的卡券再次點選撤銷時,系統需阻止並提示「已撤銷」
  • 空白搜尋:搜尋條件為空時不執行查詢,提示需輸入條件
  • 同名顧客:多位顧客同名時,清單以手機等顧客資訊區辨

Functional Requirements

  • FR-001: 提供卡券搜尋功能,支援顧客姓名、手機號碼、卡券序號三種查詢條件
  • FR-002: 卡券清單每筆需顯示顧客資訊、卡券類型、剩餘次數/金額、購買日期、失效日期、卡券狀態
  • FR-003: 卡券查詢結果需限制於操作管理員所屬分店範圍
  • FR-004: 所有管理員角色皆可執行卡券查詢
  • FR-005: 卡券撤銷採作廢(soft delete)方式 — 撤銷後於顧客端隱藏,資料仍保留於後端
  • FR-006: 卡券撤銷限定高權限角色(店長、總部管理員)方可執行
  • FR-007: 撤銷操作需經二次確認視窗
  • FR-008: 每次撤銷需寫入可稽核的異動 log(操作者、時間、卡券識別資訊、撤銷前狀態)
  • FR-009: 退費金流處理不在本變更範圍內(明確排除)

Success Criteria

  • SC-001: 管理員以姓名、手機、序號三種條件皆能成功取得正確的卡券清單
  • SC-002: 撤銷後的卡券 100% 不再出現於顧客端,且 100% 保留於後端可查
  • SC-003: 每筆撤銷操作皆產生對應的可稽核 log,log 完整率 100%
  • SC-004: 非高權限管理員無法執行撤銷(權限阻擋 100% 有效)

Related Modules

尚無模組可比對 — prospec/ai-knowledge/_index.md 模組索引目前為空,/prospec-knowledge-generate 尚未執行。模組關聯待知識庫建立後或於 /prospec-plan 階段補充。

Open Questions

  • NEEDS CLARIFICATION: 已失效/已用完的卡券是否允許撤銷?「修正錯誤輸入」情境可能需要,但對已失效卡券撤銷的業務意義待確認。
  • NEEDS CLARIFICATION: 異動 log 是否需提供管理端查詢介面?本 Story 僅要求「寫入」log,未定義檢視方式。
  • NEEDS CLARIFICATION: 卡券類型(洗/剪/燙)是否為固定列舉,或可由系統設定擴充?
  • WARN: Related Modules 無法自動比對 — _index.md 模組索引為空。

Constitution Check

  • Reviewed against prospec/CONSTITUTION.md
  • Violations noted: CONSTITUTION.md 目前為未填寫的空白模板,無實質原則可比對。建議於 /prospec-plan 前先補齊專案原則(可用 /speckit.constitution 或手動填寫)。

UI Scope

Scope: full

Design Spec: Manage Customer Vouchers

Generated from: proposal.md (ui_scope: full) Platform: figma Last updated: 2026-05-17

Overview

An admin (management-side) page that lets branch administrators search a customer's vouchers and lets high-privilege roles revoke (void) a voucher with a confirmation step. Covers proposal stories US-1 (search) and US-2 (revoke).

This design reuses the existing M+ hair-salon design system — the shared "Design Tokens" variable collection (warm gold/cream identity) already used by the M+ Customer and Designer portals — so the admin surface is visually consistent with the rest of the product.


Visual Identity

Color Palette

Token Value Usage
--color-primary #ca8a04 Primary actions, active states, links
--color-primary-hover #a16207 Primary hover
--color-primary-muted #ca8a0415 Subtle tinted backgrounds, pills
--color-primary-border #ca8a0433 Card / input borders
--color-surface #ffffffe6 Card backgrounds
--color-surface-solid #ffffff Inputs, table surface
--color-surface-muted #fafaf9 Table header / zebra rows
--color-background #f5f5f4 Page background
--color-text #0c0a09 Primary text
--color-text-secondary #44403c Secondary text
--color-text-muted #78716c Labels, captions
--color-text-disabled #a8a29e Disabled / revoked rows
--color-border #e7e5e4 Table dividers
--color-success #16a34a / bg #dcfce7 Status: 有效 (active)
--color-error #dc2626 / bg #fee2e2 Destructive action, status: 已撤銷
--color-disabled-bg #f3f4f6 Status: 已失效 / 已用完

Typography

Family: Noto Sans TC (CJK-capable). Latin/numeric also rendered with Noto Sans TC.

Token Size Weight Usage
--font-heading-lg 28px 700 Page title
--font-heading 20px 700 Card / dialog titles
--font-title 16px 500 Table column headers, row primary text
--font-body 14px 400 Table cell text, buttons
--font-small 13px 400 Secondary cell text
--font-caption 12px 400 Captions, helper text

Spacing Scale

Token Value Usage
--space-xs 4px Inline gaps
--space-sm 8px Compact padding, pill padding
--space-md 16px Default padding, cell padding
--space-lg 24px Card padding, section spacing

Visual Style

  • Border radius: 22px (cards/dialog), 12px (inputs/buttons-rect), 999px (pill badges)
  • Shadow: 0 25px 50px #ca8a042e (card), 0 16px 40px #00000026 (dialog)
  • Transition: 150ms ease-in-out
  • Page background: warm radial gradient (#fff8e1 → #fafaf9 → #f8f5f0)

Components

TopBar

Layout: flex, horizontal, space-between, height 64px, full width, padding 0 32px States: single static state. Design Tokens: background --color-surface, bottom border --color-primary-border. Children:

  • Brand: M+ logo mark + "M+ 管理後台" label
  • Admin meta: admin display name + role badge + logout button

PageHeader

Layout: flex, vertical, gap 8px Children:

  • Title "顧客卡券管理" (--font-heading-lg, --color-text)
  • Description "搜尋並管理顧客卡券,協助確認餘額或撤銷錯誤卡券。" (--font-body, --color-text-muted)

SearchBar

Layout: card, flex horizontal, gap 12px, align center, padding --space-lg States:

State Visual Changes
Default Empty input, placeholder visible
Focused Input border --color-primary
Disabled (search btn) Empty input → search button --color-disabled-bg

Children:

  • Search input: leading search icon + text field, placeholder "輸入顧客姓名、手機號碼或卡券序號"
  • Search button: primary, label "搜尋"
  • Helper text (empty submit): "請輸入搜尋條件" (--color-error)

VoucherTable

Layout: card; vertical; header row + data rows; columns separated by --space-md Columns: 顧客 | 卡券類型 | 剩餘 | 購買日期 | 失效日期 | 狀態 | 操作

States:

State Visual Changes
Default row Surface background, --color-text text
Revoked row --color-text-disabled text, no revoke action
Hover row --color-surface-muted background

Design Tokens: header background --color-surface-muted, row divider --color-border. Children: rows of [CustomerCell, TypeCell, RemainingCell, DateCell ×2, StatusBadge, RevokeButton]

StatusBadge

Layout: pill, padding --space-sm --space-xs, radius 999px States (variants):

Variant Background Text Meaning
active --color-success-bg --color-success 有效
used-up --color-disabled-bg --color-text-disabled 已用完
expired --color-disabled-bg --color-text-disabled 已失效
revoked --color-error-bg --color-error 已撤銷

RevokeButton

Layout: text/ghost button, --font-body States:

State Visual Changes
Default (high-priv) --color-error label "撤銷"
Hover underline
Hidden (normal admin) Not rendered — only high-privilege roles see it
Disabled (already revoked) Not rendered for revoked rows

EmptyState

Layout: flex vertical, center aligned, gap --space-md, padding 48px Children: package icon (64px circle, --color-surface-muted), title "查無卡券", description "找不到符合搜尋條件的卡券,請確認姓名、手機或序號是否正確。"

RevokeConfirmDialog

Layout: modal — full-screen overlay (--color-overlay) + centered dialog card (radius 22px, dialog shadow, max-width 440px, padding --space-lg) States:

State Visual Changes
Visible Overlay + dialog shown
Submitting Confirm button spinner, both buttons disabled
Already-revoked block Replaces actions with notice "此卡券已撤銷"

Children:

  • Title "撤銷卡券確認"
  • Voucher summary block: 顧客 / 卡券類型 / 卡券序號 / 剩餘 / 購買日期
  • Warning text "撤銷後此卡券將於顧客端隱藏且無法復原,系統會記錄本次操作。"
  • Actions: 取消 (secondary) + 確認撤銷 (destructive, --color-error fill)

Responsive Strategy

This is a desktop-first admin tool. Primary target is desktop (≥1024px).

Breakpoints

Name Min Width Layout
Tablet 768px Content full-width with 24px gutter; table scrolls horizontally
Desktop 1024px Centered content container, max-width 1200px

Layout Adaptations

Component Tablet Desktop
SearchBar Input + button stack-friendly, still inline Inline, input grows
VoucherTable Horizontal scroll inside card All columns visible
RevokeConfirmDialog 90% viewport width Fixed 440px width

Navigation

  • Desktop: persistent top bar; admin sections reachable from top bar (out of scope here).
  • The page itself is a single-view destination — no in-page navigation.

Interaction Spec: Manage Customer Vouchers

Generated from: proposal.md + design-spec.md DSL Version: draft-1 Last updated: 2026-05-17

Screens

Screen: VoucherManagement

States:

State Description Entry Condition
Idle Search bar shown, no query run yet Initial visit
InvalidSubmit Empty search submitted Search clicked with blank input
Loading Fetching voucher results Valid search submitted
Results Voucher table rendered with rows Fetch success, ≥1 match
NoResults Empty state shown Fetch success, 0 matches
Error Fetch failed notice Fetch error

Transitions:

Idle          -> InvalidSubmit : search click with empty input
InvalidSubmit -> Loading       : search click with non-empty input
Idle          -> Loading       : search click with non-empty input
Loading       -> Results       : fetch success (matches > 0)
Loading       -> NoResults     : fetch success (matches == 0)
Loading       -> Error         : fetch error
Error         -> Loading       : retry click
Results       -> Loading       : new search submitted

Branch scoping (FR-003) is applied server-side: results only ever contain vouchers of the administrator's own branch. Cross-branch matches never enter the Results state.

Component: RevokeConfirmDialog

States:

State Description Entry Condition
Hidden Not shown Default
Visible Overlay + dialog with voucher summary Revoke clicked on an active row
Submitting Confirm pressed, awaiting result Confirm click
AlreadyRevoked Notice "此卡券已撤銷", revoke blocked Revoke attempted on an already-revoked voucher

Transitions:

Hidden     -> Visible        : revoke click (row not revoked)
Hidden     -> AlreadyRevoked : revoke click (row already revoked)
Visible    -> Submitting     : confirm revoke click
Visible    -> Hidden         : cancel click / overlay click
Submitting -> Hidden         : revoke success (row updates to 已撤銷)
Submitting -> Visible        : revoke error (inline error, re-enable)
AlreadyRevoked -> Hidden     : dismiss click

Flows

Flow: Search a customer's vouchers (US-1)

Description: An administrator looks up a customer's vouchers to confirm balance.

Steps:

1. User types a name / phone / voucher serial into the search input
   -> Search button becomes enabled

2. User clicks 搜尋
   -> If input empty: stay on screen, show helper "請輸入搜尋條件"
   -> If valid: show Loading state

3. System fetches branch-scoped voucher results
   -> On success with matches: render VoucherTable (customer, type,
      remaining, purchase date, expiry date, status)
   -> On success with no match: render EmptyState "查無卡券"
   -> On error: show Error notice with retry

Flow: Revoke a voucher (US-2)

Description: A high-privilege administrator voids an incorrect voucher.

Steps:

1. (Precondition) Revoke action is visible only to high-privilege roles
   (store manager / HQ admin). Normal admins never see the 撤銷 button.

2. User clicks 撤銷 on an active voucher row
   -> Open RevokeConfirmDialog (Visible) showing the voucher summary
   -> If the voucher is already revoked: open AlreadyRevoked state instead

3. User reviews voucher info + warning text
   -> Click 取消: close dialog, voucher unchanged
   -> Click 確認撤銷: dialog enters Submitting (confirm spinner)

4. System soft-deletes the voucher and writes an audit log
   (operator, timestamp, voucher identity, prior status)
   -> On success: close dialog, row status updates to 已撤銷 (greyed, no action),
      voucher is hidden on the customer side
   -> On error: stay in dialog, show inline error, re-enable buttons

Gestures & Micro-interactions

Trigger Animation Duration
Button press Background color shift to hover/active 150ms
Dialog appear Overlay fade in + dialog scale 0.96 → 1.0 150ms
Row status change to 已撤銷 Row text fades to disabled color 200ms
Search submit Search button shows inline spinner

Responsive Interactions

Interaction Tablet Desktop
Browse table columns Horizontal scroll within table card All columns visible at once
Revoke action Tap 撤銷 → dialog at 90% width Click 撤銷 → fixed 440px dialog
Search Inline input + button Inline input + button