US-201: 創建訂單
User Story
作為 店員 我想要 能夠為客戶創建訂單 以便 記錄客戶的購買需求並啟動訂單處理流程
驗收標準 (Acceptance Criteria)
Scenario 1: 為個人客戶創建標準訂單
- Given 我是已登入的店員
- And 系統中有現有客戶「李大華」
- And 有可用商品「玫瑰花束(12 朵)」價格 NT$1,200
- When 我選擇客戶「李大華」
- And 我添加商品「玫瑰花束(12 朵)」數量 2
- And 我填寫配送地址「台北市信義區信義路五段 7 號」
- And 我選擇配送日期「2025-11-01」與時段「14:00-18:00」
- And 我填寫卡片訊息「生日快樂!」
- And 我點擊「創建訂單」按鈕
- Then 系統應創建訂單並自動分配訂單編號(格式:
{租戶代碼}-{YYYYMMDD}-{流水號}) - And 訂單狀態應為「待確認」
- And 訂單總金額應為 NT$2,400(含稅)
- And 我應看到訂單創建成功的通知
- And 系統應發送訂單確認通知給客戶(Email/SMS)
Scenario 2: 為企業客戶創建訂單
- Given 我是已登入的店員
- And 系統中有企業客戶「XX 科技公司」
- When 我選擇企業客戶「XX 科技公司」
- And 我選擇子帳戶「人資部」
- And 我添加商品「祝賀花籃」
- And 我填寫收件人資訊(與訂購客戶不同)
- And 我選擇支付方式為「月結」
- And 我點擊「創建訂單」按鈕
- Then 系統應創建訂單並關聯到企業主帳戶
- And 訂單應標記為「月結付款」
- And 系統應記錄子帳戶資訊
Scenario 3: 創建客製化專案訂單(無預設商品)
- Given 我是已登入的店員
- And 客戶要求「會場佈置」服務
- When 我選擇訂單類型為「客製化專案」
- And 我輸入需求描述「婚禮會場佈置,粉色系,100 坪空間」
- And 我手動輸入協商價格 NT$50,000
- And 我填寫調整原因「客製化專案無標準價格」
- And 我點擊「創建訂單」按鈕
- Then 系統應創建訂單且不包含商品項目
- And 訂單應標記為「客製化專案」
- And 審計日誌應記錄價格調整原因
Scenario 4: 創建訂單時缺少必填欄位(錯誤場景)
- Given 我是已登入的店員
- When 我開始創建訂單但未選擇客戶
- And 我點擊「創建訂單」按鈕
- Then 系統應顯示錯誤訊息「請選擇客戶」
- And 訂單不應被創建
- And 表單應保留我已填寫的資料
Scenario 5: 配送日期早於當天(錯誤場景)
- Given 我是已登入的店員
- And 今天是 2025-10-31
- When 我選擇配送日期為 2025-10-20(早於今天)
- And 我點擊「創建訂單」按鈕
- Then 系統應顯示錯誤訊息「配送日期不能早於今天」
- And 訂單不應被創建
業務規則 (Business Rules)
-
訂單編號規則
- 格式:
{租戶代碼}-{YYYYMMDD}-{流水號} - 範例:
ABC-20251031-0001 - 流水號每日從 0001 開始重新計數
- 格式:
-
必填欄位
- 客戶資訊(必須選擇現有客戶或創建新客戶)
- 至少一個商品項目(客製化專案除外)
- 配送地址與聯絡電話
- 期望配送日期與時段
-
價格計算規則
- 小計 = Σ(商品單價 × 數量)
- 稅額 = 小計 × 稅率(從租戶設定獲取,預設 5%)
- 總計 = 小計 + 稅額
-
價格調整
- 店員可手動調整最終價格
- 調整時必須填寫調整原因(記錄至審計日誌)
- 調整後的價格不能為負數
-
配送日期限制
- 不能早於當天
- 不能選擇店休日(從租戶設定獲取)
-
初始狀態
- 新創建的訂單狀態固定為「待確認」
-
多租戶隔離
- 訂單自動關聯到當前用戶的
tenantId - 客戶選擇列表僅顯示當前租戶的客戶
- 商品選擇列表僅顯示當前租戶的商品
- 訂單自動關聯到當前用戶的
UI/UX 需求 (UI/UX Requirements)
頁面結構
實作位置: src/applets/order-applet/order-editor.tsx
┌────────────────────────────────────────────────────────────────┐
│ Header Bar (sticky) │
│ [📦 創建訂單] [創建訂單] [✕] │
├─────────────────────────────────────┬──────────────────────────┤
│ │ │
│ 訂單表單 (OrderForm) │ 說明側邊欄 │
│ - 客戶選擇 │ - 建立訂單說明 │
│ - 商品列表 │ - 注意事項 │
│ - 配送資訊 │ - 相關連結 │
│ - 付款方式 │ - 需要協助 │
│ │ │
│ grid-cols-1 lg:grid-cols-3 │ card bg-base-200 │
│ lg:col-span-2 │ lg:col-span-1 │
└─────────────────────────────────────┴──────────────────────────┘
頂部工具列
- 固定位置:
sticky top-0 z-40 bg-base-100 border-b border-base-300 - 左側: 頁面標題(ShoppingBag 圖示 + 「創建訂單」)
- 右側:
- 「創建訂單」按鈕(
variant="soft" color="primary") - 取消按鈕(X 圖示)
- 「創建訂單」按鈕(
草稿自動儲存
- 表單變更時自動儲存至 Redux Store(
draft-orders-slice) - 草稿 ID 從 URL 參數取得或自動生成(
draft-{timestamp}) - 訂單創建成功後自動刪除草稿
互動行為
- 客戶選擇: 支援自動完成(輸入姓名或電話搜尋)
- 商品選擇: 顯示商品名稱、價格、庫存狀態
- 配送日期: 排除店休日(日曆組件中標灰)
- 價格即時計算: 數量/單價變更時自動更新
錯誤處理
- 欄位驗證: 紅色邊框 + 欄位下方顯示錯誤訊息
- API 錯誤: 頁面頂部顯示
alert alert-error - 成功通知:
prompt.success()Toast 通知
響應式設計
- 桌面 (
lg:): 三欄 Grid(表單 2 欄 + 側邊欄 1 欄) - 平板/手機: 單欄佈局(垂直排列)
技術規格 (Technical Specifications)
API 端點
- 端點:
POST /api/v1/orders - 權限要求:
ROLE_STAFF或更高 - 多租戶隔離: 自動附加
tenantId(從 JWT Token 提取)
請求 Payload
interface CreateOrderRequest {
customerId: string; // 客戶 ID(必填)
items: OrderItem[]; // 商品列表(至少 1 個,客製化專案除外)
deliveryAddress: string; // 配送地址(必填)
deliveryPhone: string; // 聯絡電話(必填)
deliveryDate: string; // 配送日期 ISO 8601(必填)
deliveryTimeSlot: string; // 時段(如 "14:00-18:00")
cardMessage?: string; // 卡片訊息(可選)
specialRequests?: string; // 特殊要求(可選)
recipientName?: string; // 收件人姓名(可選)
recipientPhone?: string; // 收件人電話(可選)
internalNotes?: string; // 內部備註(可選)
isCustomProject?: boolean; // 是否為客製化專案(可選)
customProjectDescription?: string; // 客製化專案描述(isCustomProject=true 時必填)
manualPriceAdjustment?: number; // 手動價格調整(可選)
priceAdjustmentReason?: string; // 價格調整原因(manualPriceAdjustment 存在時必填)
}
interface OrderItem {
productId: string; // 商品 ID
quantity: number; // 數量(必須 > 0)
price: number; // 單價(從商品獲取,可調整)
notes?: string; // 項目備註(如「不要百合」)
}
響應範例
{
"orderId": "ABC-20251031-0001",
"orderNumber": "ABC-20251031-0001",
"status": "pending_confirmation",
"customerId": "cust-123",
"items": [...],
"deliveryInfo": {...},
"pricing": {
"subtotal": 2400,
"tax": 120,
"total": 2520
},
"createdAt": "2025-10-31T10:30:00Z",
"createdBy": "user-001"
}
數據模型
- 主要 Entity:
Order - 關聯 Entity:
OrderItem,Customer,Product - 詳細定義: 參考訂單數據模型(data-models/order.md 待建立)
前端組件
組件架構:
src/applets/order-applet/
├── order-editor.tsx # 訂單編輯/創建頁面
├── components/
│ └── order-form.tsx # 訂單表單組件
├── types.ts # OrderFormData 型別定義
└── (order-finder.tsx) # 訂單列表(US-203)
src/services/sales/
└── order-service.ts # 訂單 Service(CRUD 操作)
src/features/sales/
└── draft-orders-slice.ts # 草稿狀態管理
使用框架組件:
@appfuse/appfuse-web/components的Input,Select,Button@appfuse/appfuse-web的prompt(Toast 通知)@appfuse/appfuse-web/utils的i18n,logger,ErrorResponselucide-react的ShoppingBag,X
狀態管理:
- Redux Toolkit:
draft-orders-slice管理草稿 - React Hook Form:表單驗證與狀態
orderService:API 調用(透過ajax自動處理 Token 與錯誤)
SBE 場景 (Specification by Example)
詳細的測試場景與範例數據:
- Scenario 1: 為個人客戶創建標準訂單 🟢
- Scenario 2: 為企業客戶創建訂單
- Scenario 3: 創建客製化專案訂單
- Scenario 4: 錯誤處理 - 缺少必填欄位(待建立)
估算 (Estimation)
- Story Points: 5 點
- 預估工時: 2-3 天
- 複雜度: 中等
工作拆分
-
後端開發 (1 天)
- 實作
POST /api/v1/ordersAPI - 訂單編號生成邏輯
- 價格計算與驗證
- 多租戶隔離
- 實作
-
前端開發 (1-1.5 天)
- 創建
OrderForm組件 - 客戶選擇與商品選擇邏輯
- 價格即時計算
- 表單驗證與錯誤處理
- 創建
-
整合與測試 (0.5 天)
- 整合測試(API + UI)
- E2E 測試(關鍵流程)
- Mock API 更新
依賴 (Dependencies)
前置條件
- US-101: 創建產品 - 需要商品資料
- US-301: 創建客戶 - 需要客戶資料
- Epic 0: 認證系統 - 需要 JWT Token 與角色驗證
並行開發
- 可與 US-202(確認訂單)並行開發
外部依賴
- 租戶設定 API(獲取稅率、店休日)
- 通知系統 API(發送訂單確認通知)
測試策略 (Testing Strategy)
單元測試
- 測試價格計算邏輯(小計、稅額、總計)
- 測試訂單編號生成邏輯
- 測試表單驗證規則
整合測試
- 測試
POST /api/v1/ordersAPI 端點 - 測試數據庫訂單創建與關聯
- 測試多租戶隔離(確保不同租戶的訂單編號獨立)
E2E 測試
- 測試完整的訂單創建流程(選擇客戶 → 添加商品 → 提交)
- 測試錯誤處理(缺少必填欄位、無效日期)
- 測試成功反饋(Toast 通知、導航)
手動測試
- 測試響應式佈局(桌面、平板、手機)
- 測試無障礙性(鍵盤導航、螢幕閱讀器)
完成定義 (Definition of Done)
- 後端 API 實作完成並通過單元測試
- 前端 UI 實作完成並符合設計規範
- 整合測試通過(API + UI)
- E2E 測試通過(至少涵蓋 Scenario 1 與 Scenario 4)
- 單元測試覆蓋率 > 80%
- Code Review 通過
- API 文檔已更新(訂單 API 規格)
- Mock API 已更新(支援創建訂單)
- 多租戶隔離驗證通過
- 無障礙性測試通過(WCAG 2.1 AA)
- 產品經理驗收通過
備註 (Notes)
設計決策
- 為何價格可手動調整? 因為花店常有特殊需求(如加花材、VIP 客戶折扣),需要彈性調整價格。
- 為何需要調整原因? 為了審計追蹤與財務透明,所有價格調整都需記錄原因。
未來優化
- 支援商品組合包(Bundle)的快速選擇
- 支援歷史訂單範本(快速創建重複訂單)
- 支援批量導入訂單(Excel/CSV)
最後更新: 2025-12-03