US-101: 商品基本資料管理
User Story
作為 花店管理者 我想要 能夠新增、編輯、查看和上/下架商品 以便 維護完整的商品目錄供訂單創建時選用
驗收標準 (Acceptance Criteria)
Scenario 1: 新增標準化商品(必填欄位)
- Given 我是已登入的管理者
- And 我在「商品管理」頁面
- When 我點擊「新增商品」按鈕
- And 我選擇商品類型為「花束」
- And 我填寫商品名稱「玫瑰花束(12 朵)」
- And 我填寫基礎價格「1200」
- And 我上傳主圖
- And 我點擊「儲存」按鈕
- Then 系統應創建商品並自動分配 SKU 編號(格式:
{租戶代碼}-BQT-{流水號}) - And 我應看到商品創建成功的通知
- And 商品狀態應預設為「已上架」
- And 庫存數量應預設為 0
Scenario 2: 新增商品(完整欄位)
- Given 我是已登入的管理者
- And 我在「新增商品」頁面
- When 我選擇商品類型為「盆花」
- And 我填寫商品名稱「蝴蝶蘭(3 株)」
- And 我填寫商品描述「優雅大方,適合開幕送禮」
- And 我填寫基礎價格「2500」
- And 我填寫成本價格「1500」
- And 我填寫庫存數量「20」
- And 我填寫低庫存警戒值「5」
- And 我勾選「熱門商品」
- And 我上傳主圖和 3 張副圖
- And 我點擊「儲存」按鈕
- Then 系統應創建商品並儲存所有填寫的資訊
- And SKU 編號應為「{租戶代碼}-POT-{流水號}」
Scenario 3: 編輯商品基本資訊
- Given 我是已登入的管理者
- And 我在商品「玫瑰花束(12 朵)」的詳情頁
- When 我點擊「編輯」按鈕
- And 我修改商品名稱為「紅玫瑰花束(12 朵)」
- And 我修改基礎價格為「1350」
- And 我修改商品描述為「經典浪漫,情人節首選」
- And 我點擊「儲存」按鈕
- Then 系統應更新商品資訊
- And 我應看到「儲存成功」的通知
- And 更新應立即反映在詳情頁
- And 價格變更應記錄在審計日誌
Scenario 4: 下架商品
- Given 我是已登入的管理者
- And 商品「玫瑰花束(12 朵)」目前狀態為「已上架」
- When 我點擊「下架」按鈕
- And 系統要求我確認
- And 我點擊「確認下架」
- Then 系統應將商品狀態改為「已下架」
- And 此商品不應再出現在創建訂單時的商品選擇列表
- And 但商品仍可在後台商品列表中查看(標記為已下架)
- And 歷史訂單中已使用此商品的記錄不受影響
Scenario 5: 重新上架商品
- Given 我是已登入的管理者
- And 商品「玫瑰花束(12 朵)」目前狀態為「已下架」
- When 我點擊「上架」按鈕
- And 我點擊「確認」按鈕
- Then 系統應將商品狀態改為「已上架」
- And 此商品應重新出現在商品選擇列表
Scenario 6: 刪除商品
- Given 我是已登入的店主(ROLE_OWNER)
- And 我在商品「測試商品」的詳情頁
- And 此商品從未被任何訂單使用過
- When 我點擊「刪除」按鈕
- And 系統要求我輸入商品名稱確認刪除
- And 我輸入「測試商品」
- And 我點擊「確認刪除」
- Then 系統應永久刪除此商品
- And 商品照片也應一併刪除
- And 我應被導航回商品列表頁
Scenario 7: 無法刪除已被訂單使用的商品
- Given 我是已登入的店主
- And 商品「玫瑰花束(12 朵)」已被歷史訂單使用過
- When 我嘗試刪除此商品
- Then 系統應顯示錯誤訊息「此商品已被訂單使用,無法刪除。請改為下架此商品。」
- And 商品不應被刪除
Scenario 8: 新增商品時缺少必填欄位(錯誤場景)
- Given 我是已登入的管理者
- And 我在「新增商品」頁面
- When 我僅填寫商品名稱「測試商品」(未填寫價格、未上傳主圖)
- And 我點擊「儲存」按鈕
- Then 系統應顯示錯誤訊息:
- 「請填寫基礎價格」
- 「請上傳商品主圖」
- And 錯誤欄位應以紅色邊框標示
- And 商品不應被創建
- And 表單應保留我已填寫的資料
Scenario 9: 價格驗證(錯誤場景)
- Given 我是已登入的管理者
- And 我在「新增商品」頁面
- When 我填寫基礎價格為「-100」
- Then 系統應顯示錯誤訊息「價格必須大於 0」
- When 我填寫基礎價格為「abc」
- Then 系統應顯示錯誤訊息「請輸入有效的數字」
業務規則 (Business Rules)
-
SKU 編號規則
- 格式:
{租戶代碼}-{類型代碼}-{流水號} - 類型代碼對應表:
- BQT = 花束 (bouquet)
- POT = 盆花 (pot)
- STD = 高架 (stand)
- BSK = 花籃 (basket)
- BOX = 盒花 (box)
- GFT = 禮品 (gift)
- CST = 客製化 (custom)
- 範例:
ABC-BQT-000001 - 流水號從 000001 開始,每次創建同類型商品遞增
- SKU 編號創建後不可修改
- 格式:
-
必填欄位
- 商品名稱
- 商品類型
- 基礎價格(> 0)
- 主圖
-
選填欄位
- 商品描述
- 成本價格
- 庫存數量(預設 0)
- 低庫存警戒值(預設 5)
- 副圖(最多 5 張)
-
價格規則
- 基礎價格必須 > 0
- 成本價格必須 >= 0(若填寫)
- 價格支援小數點後兩位(如 NT$ 99.50)
- 價格變更需記錄審計日誌
-
庫存規則
- 庫存數量必須 >= 0
- 當庫存 <= 低庫存警戒值時,列表顯示「低庫存」警示
- 當庫存 = 0 時,列表顯示「缺貨」警示
-
上架/下架權限
ROLE_MANAGER或更高權限可上架/下架商品- 下架商品不出現在訂單創建的商品選擇列表
- 下架商品仍可在後台查看與編輯
-
刪除權限與限制
- 僅
ROLE_OWNER可刪除商品 - 已被訂單使用的商品無法刪除(需改為下架)
- 刪除前需輸入商品名稱確認(防止誤刪)
- 刪除後商品照片一併刪除
- 僅
-
多租戶隔離
- 商品自動關聯到當前用戶的
tenantId - 商品列表僅顯示當前租戶的商品
- 跨租戶訪問返回 404
- 商品自動關聯到當前用戶的
-
成本價格可見性
- 成本價格僅
ROLE_MANAGER或更高權限可見 ROLE_STAFF看不到成本價格欄位
- 成本價格僅
UI/UX 需求 (UI/UX Requirements)
新增/編輯商品表單佈局
左側 - 照片區
- 主圖上傳:
- 拖曳區域或點擊上傳
- 預覽已上傳的主圖
- 「更換」按鈕
- 副圖上傳:
- 最多 5 張副圖
- 網格式顯示
- 支援拖曳排序
- 每張圖有刪除按鈕
右側 - 表單區
-
基本資訊區:
- 商品類型(必填,下拉選單)
- 商品名稱(必填)
- 商品描述(選填,多行文字)
-
價格與庫存區:
- 基礎價格(必填)
- 成本價格(選填,僅管理者可見)
- 庫存數量(預設 0)
- 低庫存警戒值(預設 5)
- 庫存單位(預設「個」)
-
狀態設定區:
- 上架狀態(開關)
- 熱門商品(勾選框)
- 新品標記(勾選框)
商品詳情頁佈局
頂部區域
- 商品主圖(大圖)
- 副圖縮圖列表
- 商品名稱
- SKU 編號
- 商品類型標籤
- 上架狀態標籤
操作按鈕
- 編輯(管理者以上)
- 上架/下架(管理者以上)
- 刪除(僅店主,已使用的商品不可刪除)
資訊區塊
-
價格資訊:
- 基礎價格
- 成本價格(僅管理者可見)
- 毛利率(自動計算,僅管理者可見)
-
庫存資訊:
- 當前庫存
- 低庫存警戒值
- 庫存狀態(正常/低庫存/缺貨)
-
統計資訊:
- 創建日期
- 最後更新日期
- 銷售數量(來自訂單統計)
互動行為
-
照片上傳:
- 支援拖曳上傳
- 上傳前預覽
- 上傳進度顯示
- 上傳失敗時顯示錯誤訊息
-
表單驗證:
- 即時驗證(輸入後驗證)
- 錯誤欄位紅色邊框
- 錯誤訊息顯示在欄位下方
-
刪除確認:
- Modal 彈窗
- 需輸入商品名稱確認
- 輸入正確後才啟用「確認刪除」按鈕
錯誤訊息
- 缺少必填欄位: 紅色邊框 + 欄位下方顯示錯誤訊息
- 價格格式錯誤: 「請輸入有效的價格」
- 價格為負數: 「價格必須大於 0」
- 主圖未上傳: 「請上傳商品主圖」
- 照片上傳失敗: 「照片上傳失敗,請重試」
- 照片過大: 「照片大小不能超過 10MB」
- 照片格式錯誤: 「僅支援 JPEG、PNG、WebP 格式」
成功反饋
- 商品創建成功: Toast 通知「商品 {SKU} 創建成功」
- 商品更新成功: Toast 通知「儲存成功」
- 商品下架成功: Toast 通知「商品已下架」
- 商品上架成功: Toast 通知「商品已上架」
- 商品刪除成功: Toast 通知「商品已刪除」
響應式設計
- 桌面: 雙欄佈局(左側照片,右側表單)
- 平板: 雙欄佈局(縮小間距)
- 手機: 單欄佈局(照片在上,表單在下)
技術規格 (Technical Specifications)
API 端點
1. 創建商品
- 端點:
POST /api/v1/products - 權限要求:
ROLE_MANAGER或更高 - 多租戶隔離: 自動附加
tenantId
請求 Payload:
{
"category": "bouquet", // 商品類型
"name": "玫瑰花束(12 朵)",
"description": "經典浪漫,情人節首選", // 選填
"basePrice": 1200,
"costPrice": 800, // 選填
"stock": 50, // 選填,預設 0
"lowStockThreshold": 5, // 選填,預設 5
"stockUnit": "個", // 選填,預設「個」
"status": "active", // 選填,預設 "active"
"isFeatured": true, // 選填,預設 false
"isNew": true, // 選填,預設 false
"mainImageUrl": "https://...", // 先上傳照片獲得 URL
"galleryImageUrls": ["https://...", "https://..."] // 選填
}
響應 Payload:
{
"id": "prod-abc123",
"tenantId": "tenant-001",
"sku": "ABC-BQT-000001",
"category": "bouquet",
"categoryLabel": "花束",
"name": "玫瑰花束(12 朵)",
"description": "經典浪漫,情人節首選",
"basePrice": 1200,
"costPrice": 800, // 僅管理者可見
"stock": 50,
"lowStockThreshold": 5,
"stockUnit": "個",
"status": "active",
"isFeatured": true,
"isNew": true,
"mainImageUrl": "https://...",
"galleryImageUrls": ["https://...", "https://..."],
"createdAt": "2025-12-03T10:00:00Z",
"updatedAt": "2025-12-03T10:00:00Z",
"createdBy": "user-001"
}
2. 獲取單一商品
- 端點:
GET /api/v1/products/{id} - 權限要求:
ROLE_STAFF或更高
3. 更新商品
- 端點:
PUT /api/v1/products/{id} - 權限要求:
ROLE_MANAGER或更高
請求 Payload: 同創建商品(部分欄位更新)
4. 上架/下架商品
- 端點:
PATCH /api/v1/products/{id}/status - 權限要求:
ROLE_MANAGER或更高
請求 Payload:
{
"status": "inactive" // "active" | "inactive"
}
5. 刪除商品
- 端點:
DELETE /api/v1/products/{id} - 權限要求:
ROLE_OWNER
響應: 204 No Content(成功)或 409 Conflict(已被訂單使用)
6. 上傳商品照片
- 端點:
POST /api/v1/products/upload-image - 權限要求:
ROLE_MANAGER或更高 - Content-Type:
multipart/form-data
請求: FormData with file field
響應 Payload:
{
"url": "https://cdn.example.com/tenant-001/products/abc123.webp",
"thumbnailUrl": "https://cdn.example.com/tenant-001/products/abc123_thumb.webp"
}
前端驗證規則
// Zod Schema
const ProductSchema = z.object({
category: z.enum(['bouquet', 'pot', 'stand', 'basket', 'box', 'gift', 'custom'], {
required_error: '請選擇商品類型',
}),
name: z.string()
.min(1, '請填寫商品名稱')
.max(100, '商品名稱過長'),
description: z.string().max(500, '商品描述過長').optional(),
basePrice: z.number({
required_error: '請填寫基礎價格',
invalid_type_error: '請輸入有效的數字',
}).positive('價格必須大於 0'),
costPrice: z.number()
.nonnegative('成本價格必須 >= 0')
.optional(),
stock: z.number()
.int('庫存數量必須為整數')
.nonnegative('庫存數量必須 >= 0')
.default(0),
lowStockThreshold: z.number()
.int('警戒值必須為整數')
.nonnegative('警戒值必須 >= 0')
.default(5),
stockUnit: z.string().default('個'),
status: z.enum(['active', 'inactive']).default('active'),
isFeatured: z.boolean().default(false),
isNew: z.boolean().default(false),
mainImageUrl: z.string().url('請上傳商品主圖'),
galleryImageUrls: z.array(z.string().url()).max(5, '副圖最多 5 張').optional(),
});
元件架構
src/applets/product-applet/
├── ProductApplet.tsx # 容器組件
├── ProductForm.tsx # 商品表單(新增/編輯)
├── ProductDetailPage.tsx # 商品詳情頁
├── ProductListPage.tsx # 商品列表頁(參見 US-102)
├── components/
│ ├── ProductImageUploader.tsx # 照片上傳組件
│ ├── ProductGallery.tsx # 照片畫廊組件
│ ├── ProductStatusBadge.tsx # 狀態標籤組件
│ ├── ProductCategorySelect.tsx # 類型選擇組件
│ └── StockStatusBadge.tsx # 庫存狀態標籤
├── hooks/
│ ├── useProductForm.ts # 商品表單邏輯
│ ├── useImageUpload.ts # 照片上傳邏輯
│ └── useProductMutation.ts # 商品 CRUD 邏輯
└── types.ts # TypeScript 類型定義
測試需求 (Test Requirements)
單元測試
- 表單驗證邏輯(必填欄位、格式驗證、價格驗證)
- SKU 編號生成邏輯
- 庫存狀態計算邏輯
- 毛利率計算邏輯
整合測試
- 創建商品 API 整合
- 更新商品 API 整合
- 上架/下架 API 整合
- 刪除商品 API 整合
- 照片上傳 API 整合
E2E 測試
- Scenario 1: 新增標準化商品(必填欄位)
- Scenario 2: 新增商品(完整欄位)
- Scenario 3: 編輯商品基本資訊
- Scenario 4: 下架商品
- Scenario 5: 重新上架商品
- Scenario 6: 刪除商品
- Scenario 7: 無法刪除已被訂單使用的商品
- Scenario 8: 表單驗證錯誤
驗收檢查清單 (Acceptance Checklist)
- 可創建商品(必填欄位)
- 可創建商品(完整欄位)
- SKU 編號自動生成且格式正確
- 可編輯商品資訊
- 價格變更記錄審計日誌
- 管理者可上架/下架商品
- 下架商品不出現在訂單商品選擇列表
- 店主可刪除未使用的商品
- 已被訂單使用的商品無法刪除
- 表單驗證錯誤訊息正確顯示
- 照片上傳功能正常
- 成本價格僅管理者可見
- 響應式設計正常(桌面/平板/手機)
- 審計日誌記錄正確
- 多租戶隔離正常
Story Points
估算: 8 points
理由:
- 需要處理商品 CRUD 四種操作
- 包含複雜的表單驗證(必填、格式、價格)
- 照片上傳功能較複雜(主圖/副圖、拖曳、預覽)
- 上架/下架與刪除需要權限檢查
- SKU 編號生成邏輯
- 成本價格的權限控制
相關文檔
最後更新: 2025-12-03 撰寫者: AI Assistant