訂單 API
概述
訂單 API 提供訂單的完整生命週期管理,包括創建、查詢、搜尋、過濾、狀態流轉和歷史記錄。支援多租戶隔離和角色權限控制。
端點列表
| 方法 | 路徑 | 說明 | 權限 |
|---|---|---|---|
| GET | /api/v1/orders | 列出訂單(支援搜尋、過濾、分頁) | ROLE_SALES 或更高 |
| GET | /api/v1/orders/:id | 獲取單一訂單詳情 | ROLE_SALES 或更高 |
| POST | /api/v1/orders | 創建訂單 | ROLE_SALES 或更高 |
| PATCH | /api/v1/orders/:id | 部分更新訂單 | ROLE_SALES 或更高 |
| PUT | /api/v1/orders/:id | 更新訂單(完整替換)⚠️ 已棄用 | ROLE_SALES 或更高 |
| DELETE | /api/v1/orders/:id | 刪除訂單 | ROLE_OWNER |
| PATCH | /api/v1/orders/:id/status | 更新訂單狀態 | 根據狀態流轉規則 |
| GET | /api/v1/orders/:id/status-history | 獲取訂單狀態歷史 | ROLE_SALES 或更高 |
端點詳細規格
GET /api/v1/orders
描述: 列出訂單,支援搜尋、過濾、分頁和排序
權限: ROLE_SALES 或更高
Query Parameters:
| 參數 | 類型 | 必填 | 說明 | 預設值 |
|---|---|---|---|---|
| search | string | No | 搜尋關鍵字(訂單編號、客戶姓名、電話、地址) | - |
| status | string[] | No | 訂單狀態過濾(可多選) | - |
| deliveryDateFrom | string | No | 配送日期起始(YYYY-MM-DD) | - |
| deliveryDateTo | string | No | 配送日期結束(YYYY-MM-DD) | - |
| amountMin | number | No | 最低金額 | - |
| amountMax | number | No | 最高金額 | - |
| assignedFlorist | string | No | 分配的設計師 ID | - |
| assignedDelivery | string | No | 分配的送貨員 ID | - |
| page | integer | No | 頁碼(從 1 開始) | 1 |
| limit | integer | No | 每頁數量(10, 20, 50, 100) | 20 |
| sortBy | string | No | 排序欄位(createdAt, deliveryDate, amount) | createdAt |
| sortOrder | string | No | 排序順序(asc, desc) | desc |
請求範例:
GET /api/v1/orders?search=李&status=PENDING&status=CONFIRMED&page=1&limit=20
Authorization: Bearer {access_token}
響應範例 (200 OK):
Headers:
X-Total-Count: 45
X-Page: 1
X-Per-Page: 20
Link: </api/v1/orders?page=2&limit=20>; rel="next", </api/v1/orders?page=3&limit=20>; rel="last"
Body:
[
{
"id": "order-001",
"orderNumber": "ABC-20251104-0001",
"customerId": "cust-123",
"customerName": "李大華",
"customerPhone": "0912-345-678",
"status": "PENDING",
"statusLabel": "待確認",
"deliveryAddress": "台北市信義區信義路五段 7 號",
"deliveryDate": "2025-11-05",
"deliveryTimeSlot": "morning",
"totalAmount": 2520,
"createdAt": "2025-11-04T10:30:00Z",
"updatedAt": "2025-11-04T10:30:00Z"
}
]
錯誤響應:
| 狀態碼 | 錯誤碼 | 說明 |
|---|---|---|
| 401 | AUTH_TOKEN_INVALID | Token 無效或過期 |
| 403 | FORBIDDEN | 權限不足 |
GET /api/v1/orders/:id
描述: 獲取單一訂單的完整詳情
權限: ROLE_SALES 或更高
路徑參數:
| 參數 | 類型 | 說明 |
|---|---|---|
| id | string | 訂單 ID |
請求範例:
GET /api/v1/orders/order-001
Authorization: Bearer {access_token}
響應範例 (200 OK):
{
"id": "order-001",
"orderNumber": "ABC-20251104-0001",
"tenantId": "tenant-abc",
"customerId": "cust-123",
"customerName": "李大華",
"customerPhone": "0912-345-678",
"status": "PENDING",
"statusLabel": "待確認",
"items": [
{
"productId": "prod-001",
"productName": "玫瑰花束(12 朵)",
"quantity": 2,
"price": 1200,
"subtotal": 2400,
"notes": "不要包含百合"
}
],
"deliveryAddress": "台北市信義區信義路五段 7 號",
"deliveryPhone": "0912-345-678",
"deliveryDate": "2025-11-05",
"deliveryTimeSlot": "morning",
"deliveryTimeSlotLabel": "上午 (09:00-12:00)",
"recipientName": "張三",
"recipientPhone": "0987-654-321",
"cardMessage": "生日快樂!",
"specialRequirements": "",
"internalNotes": "",
"subtotal": 2400,
"taxAmount": 120,
"totalAmount": 2520,
"assignedFlorist": null,
"assignedDelivery": null,
"createdAt": "2025-11-04T10:30:00Z",
"updatedAt": "2025-11-04T10:30:00Z",
"createdBy": "user-001"
}
錯誤響應:
| 狀態碼 | 錯誤碼 | 說明 |
|---|---|---|
| 404 | ORDER_NOT_FOUND | 訂單不存在 |
| 403 | FORBIDDEN | 權限不足(跨租戶訪問) |
POST /api/v1/orders
描述: 創建新訂單
權限: ROLE_SALES 或更高
請求 Body:
{
"customerId": "cust-123",
"items": [
{
"productId": "prod-001",
"quantity": 2,
"price": 1200,
"notes": "不要包含百合"
}
],
"deliveryAddress": "台北市信義區信義路五段 7 號",
"deliveryPhone": "0912-345-678",
"deliveryDate": "2025-11-05",
"deliveryTimeSlot": "morning",
"recipientName": "張三",
"recipientPhone": "0987-654-321",
"cardMessage": "生日快樂!",
"specialRequirements": "",
"internalNotes": ""
}
欄位說明:
| 欄位 | 類型 | 必填 | 說明 | 驗證規則 |
|---|---|---|---|---|
| customerId | string | Yes | 客戶 ID | - |
| items | array | Yes | 商品項目 | 至少 1 個 |
| items[].productId | string | Yes | 商品 ID | - |
| items[].quantity | integer | Yes | 數量 | ≥ 1 |
| items[].price | number | Yes | 單價 | ≥ 0 |
| items[].notes | string | No | 備註 | - |
| deliveryAddress | string | Yes | 配送地址 | - |
| deliveryPhone | string | Yes | 聯絡電話 | - |
| deliveryDate | string | Yes | 配送日期(YYYY-MM-DD) | ≥ 今天,非店休日 |
| deliveryTimeSlot | string | Yes | 配送時段(morning, afternoon, evening) | - |
| recipientName | string | No | 收件人姓名 | - |
| recipientPhone | string | No | 收件人電話 | - |
| cardMessage | string | No | 卡片訊息 | - |
| specialRequirements | string | No | 特殊需求 | - |
| internalNotes | string | No | 內部備註 | - |
響應範例 (201 Created):
{
"id": "order-001",
"orderNumber": "ABC-20251104-0001",
"status": "PENDING",
"totalAmount": 2520,
"createdAt": "2025-11-04T10:30:00Z"
}
錯誤響應:
| 狀態碼 | 錯誤碼 | 說明 |
|---|---|---|
| 400 | VALIDATION_ERROR | 驗證失敗(缺少必填欄位、日期無效等) |
| 400 | DELIVERY_DATE_INVALID | 配送日期早於今天 |
| 400 | DELIVERY_DATE_CLOSED | 配送日期為店休日 |
錯誤響應範例 (400):
{
"message": "配送日期不能早於今天",
"error": "DELIVERY_DATE_INVALID",
"field": "deliveryDate",
"timestamp": "2025-11-04T10:30:00Z"
}
PATCH /api/v1/orders/:id
描述: 部分更新訂單(僅更新提供的欄位)
根據 ADR-008 表單部分更新策略,建議使用此端點進行訂單編輯。
權限: ROLE_SALES 或更高
路徑參數:
| 參數 | 類型 | 說明 |
|---|---|---|
| id | string | 訂單 ID |
請求 Body: 僅包含需要更新的欄位
{
"deliveryAddress": "新地址",
"deliveryDate": "2025-12-30"
}
可更新欄位:
| 欄位 | 類型 | 說明 |
|---|---|---|
| customerId | string | 客戶 ID |
| items | array | 商品項目(整體替換) |
| deliveryAddress | string | 配送地址 |
| deliveryPhone | string | 聯絡電話 |
| deliveryDate | string | 配送日期(YYYY-MM-DD) |
| deliveryTimeSlot | string | 配送時段 |
| recipientName | string | 收件人姓名 |
| recipientPhone | string | 收件人電話 |
| cardMessage | string | 卡片訊息 |
| specialRequirements | string | 特殊需求 |
| internalNotes | string | 內部備註 |
| productionPhotos | string[] | 作品照片 URL |
| deliveryPhotos | string[] | 簽收照片 URL |
請求範例:
PATCH /api/v1/orders/order-001
Authorization: Bearer {access_token}
Content-Type: application/json
{
"deliveryDate": "2025-12-30",
"paymentMethod": "cash"
}
響應範例 (200 OK):
{
"id": "order-001",
"orderNumber": "ABC-20251104-0001",
"status": "PENDING",
"deliveryDate": "2025-12-30",
"updatedAt": "2025-12-21T10:30:00Z"
}
錯誤響應:
| 狀態碼 | 錯誤碼 | 說明 |
|---|---|---|
| 404 | ORDER_NOT_FOUND | 訂單不存在 |
| 403 | FORBIDDEN | 權限不足 |
| 400 | VALIDATION_ERROR | 驗證失敗 |
PUT /api/v1/orders/:id
⚠️ 已棄用: 請使用
PATCH /api/v1/orders/:id進行部分更新
描述: 更新訂單資訊(完整替換)
權限: ROLE_SALES 或更高
路徑參數:
| 參數 | 類型 | 說明 |
|---|---|---|
| id | string | 訂單 ID |
請求 Body: 部分欄位更新(與 POST 相同結構,但所有欄位可選)
響應範例 (200 OK):
{
"id": "order-001",
"message": "訂單已更新"
}
錯誤響應:
| 狀態碼 | 錯誤碼 | 說明 |
|---|---|---|
| 404 | ORDER_NOT_FOUND | 訂單不存在 |
| 403 | FORBIDDEN | 權限不足 |
DELETE /api/v1/orders/:id
描述: 刪除訂單(軟刪除)
權限: ROLE_OWNER(僅店主)
路徑參數:
| 參數 | 類型 | 說明 |
|---|---|---|
| id | string | 訂單 ID |
響應範例 (200 OK):
{
"message": "訂單已刪除"
}
錯誤響應:
| 狀態碼 | 錯誤碼 | 說明 |
|---|---|---|
| 404 | ORDER_NOT_FOUND | 訂單不存在 |
| 403 | FORBIDDEN | 權限不足(非店主) |
PATCH /api/v1/orders/:id/status
描述: 更新訂單狀態(部分更新,支援狀態流轉驗證)
權限: 根據狀態流轉規則(見下方)
路徑參數:
| 參數 | 類型 | 說明 |
|---|---|---|
| id | string | 訂單 ID |
請求 Body:
{
"newStatus": "CONFIRMED",
"notes": "訂單已確認",
"photoUrls": []
}
欄位說明:
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
| newStatus | string | Yes | 新狀態(見下方狀態對照表) |
| notes | string | No | 狀態變更備註 |
| photoUrls | string[] | 條件必填 | 照片 URL 陣列(特定狀態流轉必填) |
狀態值對照表:
| 狀態值 | 說明 | 中文標籤 |
|---|---|---|
pending_confirmation | 待確認 | 待確認 |
confirmed | 已確認 | 已確認 |
in_production | 設計中 | 設計中 |
ready_for_delivery | 待出貨 | 待出貨 |
out_for_delivery | 配送中 | 配送中 |
delivered | 已簽收 | 已簽收 |
completed | 已完成 | 已完成 |
cancelled | 已取消 | 已取消 |
delivery_failed | 配送失敗 | 配送失敗 |
照片上傳要求:
特定狀態流轉需要上傳照片:
| 狀態流轉 | 照片類型 | 最少數量 | 說明 |
|---|---|---|---|
in_production → ready_for_delivery | production | 1 | 作品照片 |
out_for_delivery → delivered | delivery | 1 | 簽收照片 |
請求範例(含照片):
{
"newStatus": "ready_for_delivery",
"notes": "設計完成",
"photoUrls": [
"https://cdn.example.com/photos/order-001/design-1.jpg",
"https://cdn.example.com/photos/order-001/design-2.jpg"
]
}
響應範例 (200 OK):
{
"orderId": "order-001",
"oldStatus": "in_production",
"newStatus": "ready_for_delivery",
"updatedAt": "2025-12-02T16:30:00Z",
"updatedBy": {
"userId": "user-002",
"userName": "王小花",
"role": "ROLE_FLORIST"
},
"message": "訂單狀態已更新為「待出貨」",
"photoCount": 2
}
錯誤響應:
| 狀態碼 | 錯誤碼 | 說明 |
|---|---|---|
| 400 | INVALID_STATUS_TRANSITION | 無效的狀態流轉(如逆向流轉) |
| 400 | PHOTO_REQUIRED | 此狀態流轉需要上傳照片 |
| 403 | FORBIDDEN | 權限不足(角色無法執行此狀態變更) |
| 404 | ORDER_NOT_FOUND | 訂單不存在 |
狀態流轉規則:
pending_confirmation → confirmed (店員確認)
confirmed → in_production (設計師開始設計)
in_production → ready_for_delivery (設計師完成,需照片)
ready_for_delivery → out_for_delivery (配送員開始配送)
out_for_delivery → delivered (配送員簽收,需照片)
delivered → completed (系統自動,支付完成後)
任何狀態 → cancelled (管理者取消)
out_for_delivery → delivery_failed (配送失敗)
權限對應:
| 狀態流轉 | 所需角色 | 工作台路徑 | 需要照片 |
|---|---|---|---|
pending_confirmation → confirmed | ROLE_SALES | /sales | 否 |
confirmed → in_production | ROLE_FLORIST | /design | 否 |
in_production → ready_for_delivery | ROLE_FLORIST | /design | 是 |
ready_for_delivery → out_for_delivery | ROLE_DELIVERY | /delivery | 否 |
out_for_delivery → delivered | ROLE_DELIVERY | /delivery | 是 |
delivered → completed | system | - | 否 |
任何 → cancelled | ROLE_OWNER | - | 否 |
GET /api/v1/orders/:id/status-history
描述: 獲取訂單的狀態流轉歷史
權限: ROLE_SALES 或更高
路徑參數:
| 參數 | 類型 | 說明 |
|---|---|---|
| id | string | 訂單 ID |
請求範例:
GET /api/v1/orders/order-001/status-history
Authorization: Bearer {access_token}
響應範例 (200 OK):
{
"orderId": "order-001",
"orderNumber": "ABC-20251104-0001",
"history": [
{
"id": "history-001",
"fromStatus": "PENDING",
"toStatus": "CONFIRMED",
"fromStatusLabel": "待確認",
"toStatusLabel": "已確認",
"changedBy": "user-001",
"changedByName": "王小明",
"changedByRole": "ROLE_SALES",
"notes": "訂單已確認",
"timestamp": "2025-11-04T11:00:00Z"
},
{
"id": "history-002",
"fromStatus": "CONFIRMED",
"toStatus": "DESIGNING",
"fromStatusLabel": "已確認",
"toStatusLabel": "設計中",
"changedBy": "user-002",
"changedByName": "李設計",
"changedByRole": "ROLE_FLORIST",
"notes": "開始設計",
"timestamp": "2025-11-04T14:00:00Z"
}
]
}
錯誤響應:
| 狀態碼 | 錯誤碼 | 說明 |
|---|---|---|
| 404 | ORDER_NOT_FOUND | 訂單不存在 |
| 403 | FORBIDDEN | 權限不足 |
業務規則
訂單編號生成規則
- 格式:
{租戶代碼}-{YYYYMMDD}-{序號} - 範例:
ABC-20251104-0001 - 序號: 每天從 0001 開始遞增
價格計算
- 小計:
Σ(商品單價 × 數量) - 稅額:
小計 × 5% - 總計:
小計 + 稅額
配送日期驗證
- 配送日期不能早於今天
- 配送日期不能為店休日(由系統配置)
多租戶隔離
- 所有訂單自動附加
tenantId(從 JWT Token 提取) - 查詢自動過濾為當前租戶的訂單
- 跨租戶訪問返回
404 Not Found(而非403)
相關 User Stories
核心流程
角色工作台
- US-204: 店員工作台 - 確認訂單
- US-205: 設計師工作台 - 設計與上傳照片
- US-206: 配送員工作台 - 配送與簽收
最後更新: 2025-12-21