跳至主要内容

客戶 API

概述

客戶 API 提供客戶資訊的完整 CRUD 功能,支援個人客戶與企業客戶管理、重複檢測、訂單歷史查詢、消費統計及備註管理。

相關 User Stories:


端點列表

方法路徑說明權限
GET/api/v1/customers列出客戶(支援搜尋、分頁)ROLE_SALES 或更高
GET/api/v1/customers/:id取得客戶詳情ROLE_SALES 或更高
POST/api/v1/customers創建客戶ROLE_SALES 或更高
PATCH/api/v1/customers/:id更新客戶(部分更新)ROLE_SALES 或更高
GET/api/v1/customers/check-duplicate檢查電話是否重複ROLE_SALES 或更高
PATCH/api/v1/customers/:id/status停用/啟用客戶ROLE_OWNER 或 ROLE_MANAGER
GET/api/v1/customers/:id/orders取得客戶訂單歷史ROLE_SALES 或更高
GET/api/v1/customers/:id/stats取得客戶消費統計ROLE_SALES 或更高
GET/api/v1/customers/:id/notes取得客戶備註列表ROLE_SALES 或更高
POST/api/v1/customers/:id/notes新增客戶備註ROLE_SALES 或更高

資料模型

客戶類型 (CustomerType)

說明
individual個人客戶
corporate企業客戶

客戶等級 (CustomerTier)

說明累計消費門檻
regular普通客戶< NT$5,000
vipVIP 客戶>= NT$5,000
vvipVVIP 客戶>= NT$20,000

客戶狀態 (CustomerStatus)

說明
active啟用(可下單)
inactive停用(不可下單)

性別 (Gender)

說明
male男性
female女性
other其他

個人客戶實體 (IndividualCustomer)

interface IndividualCustomer {
id: string; // 客戶 ID(UUID)
customerNumber: string; // 客戶編號(自動生成)
tenantId: string; // 租戶 ID
type: 'individual'; // 客戶類型
status: CustomerStatus; // 客戶狀態
tier: CustomerTier; // 客戶等級(自動計算)
name: string; // 姓名(必填)
gender?: Gender; // 性別
birthday?: string; // 生日(ISO 8601 date)
phone: string; // 電話(必填)
email?: string; // Email
addresses?: Address[]; // 地址列表
source?: string; // 客戶來源
preferences?: string[]; // 喜好標籤
importantDates?: ImportantDate[]; // 重要日期
totalSpent: number; // 累計消費金額
totalOrders: number; // 總訂單數
lastOrderDate: string | null; // 最後消費日期(ISO 8601)
createdAt: string; // 創建時間(ISO 8601)
updatedAt: string; // 更新時間(ISO 8601)
}

企業客戶實體 (CorporateCustomer)

interface CorporateCustomer {
id: string; // 客戶 ID(UUID)
customerNumber: string; // 客戶編號(自動生成)
tenantId: string; // 租戶 ID
type: 'corporate'; // 客戶類型
status: CustomerStatus; // 客戶狀態
tier: CustomerTier; // 客戶等級(自動計算)
companyName: string; // 公司名稱(必填)
taxId?: string; // 統一編號(8 位數字)
industry?: string; // 產業類別
phone: string; // 公司電話(必填)
address?: string; // 公司地址
email?: string; // Email
contacts: Contact[]; // 聯絡人列表(至少一位)
cooperationStartDate?: string; // 合作開始日期(ISO 8601 date)
paymentTerms?: PaymentTerms; // 月結帳期
totalSpent: number; // 累計消費金額
totalOrders: number; // 總訂單數
lastOrderDate: string | null; // 最後消費日期(ISO 8601)
createdAt: string; // 創建時間(ISO 8601)
updatedAt: string; // 更新時間(ISO 8601)
}

地址 (Address)

interface Address {
address: string; // 地址
isDefault: boolean; // 是否為預設地址
label?: string; // 標籤(如「住家」、「公司」)
}

聯絡人 (Contact)

interface Contact {
name: string; // 姓名
title?: string; // 職稱
phone: string; // 電話
email?: string; // Email
isPrimary: boolean; // 是否為主要聯絡人
}

重要日期 (ImportantDate)

interface ImportantDate {
date: string; // 日期(ISO 8601 date)
label: string; // 標籤(如「結婚紀念日」)
}

月結帳期 (PaymentTerms)

說明
none無月結(現金交易)
net1515 天月結
net3030 天月結

客戶編號生成規則

格式:{租戶代碼}-CUST-{流水號}

範例:FS01-CUST-0001


端點詳細規格

GET /api/v1/customers

描述: 列出客戶,支援搜尋和分頁

權限: ROLE_SALES 或更高

Query Parameters:

參數類型必填說明預設值
searchstringNo搜尋關鍵字(姓名、公司名、電話)-
typestringNo客戶類型過濾(individual, corporate)-
statusstringNo客戶狀態過濾(active, inactive)-
tierstringNo客戶等級過濾(regular, vip, vvip)-
pageintegerNo頁碼(從 1 開始)1
limitintegerNo每頁數量20
sortBystringNo排序欄位(name, createdAt, totalSpent)createdAt
sortOrderstringNo排序方向(asc, desc)desc

請求範例:

GET /api/v1/customers?search=李&type=individual&status=active&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/customers?page=1&limit=20>; rel="first", </api/v1/customers?page=2&limit=20>; rel="next", </api/v1/customers?page=3&limit=20>; rel="last"

Body:

[
{
"id": "cust-001",
"customerNumber": "FS01-CUST-0001",
"tenantId": "tenant-001",
"type": "individual",
"status": "active",
"tier": "vip",
"name": "李大華",
"phone": "0912-345-678",
"email": "lihua@example.com",
"totalSpent": 15000,
"totalOrders": 12,
"lastOrderDate": "2025-12-15T10:30:00Z",
"createdAt": "2025-06-01T08:00:00Z",
"updatedAt": "2025-12-15T10:30:00Z"
}
]

GET /api/v1/customers/:id

描述: 取得單一客戶詳情

權限: ROLE_SALES 或更高

Path Parameters:

參數類型說明
idstring客戶 ID

請求範例:

GET /api/v1/customers/cust-001
Authorization: Bearer {access_token}

響應範例 (200 OK) - 個人客戶:

{
"id": "cust-001",
"customerNumber": "FS01-CUST-0001",
"tenantId": "tenant-001",
"type": "individual",
"status": "active",
"tier": "vip",
"name": "李大華",
"gender": "male",
"birthday": "1985-03-15",
"phone": "0912-345-678",
"email": "lihua@example.com",
"addresses": [
{
"address": "台北市信義區信義路五段 7 號",
"isDefault": true,
"label": "公司"
}
],
"source": "網路廣告",
"preferences": ["玫瑰", "紅色系"],
"importantDates": [
{ "date": "2020-06-15", "label": "結婚紀念日" }
],
"totalSpent": 15000,
"totalOrders": 12,
"lastOrderDate": "2025-12-15T10:30:00Z",
"createdAt": "2025-06-01T08:00:00Z",
"updatedAt": "2025-12-15T10:30:00Z"
}

響應範例 (200 OK) - 企業客戶:

{
"id": "cust-002",
"customerNumber": "FS01-CUST-0002",
"tenantId": "tenant-001",
"type": "corporate",
"status": "active",
"tier": "vvip",
"companyName": "台灣科技股份有限公司",
"taxId": "12345678",
"industry": "科技業",
"phone": "02-2345-6789",
"address": "台北市內湖區瑞光路 123 號",
"email": "contact@taiwantech.com",
"contacts": [
{
"name": "王小明",
"title": "總務經理",
"phone": "0922-333-444",
"email": "wang@taiwantech.com",
"isPrimary": true
}
],
"cooperationStartDate": "2024-01-15",
"paymentTerms": "net30",
"totalSpent": 85000,
"totalOrders": 25,
"lastOrderDate": "2025-12-20T14:00:00Z",
"createdAt": "2024-01-15T09:00:00Z",
"updatedAt": "2025-12-20T14:00:00Z"
}

錯誤響應:

狀態碼錯誤訊息說明
404客戶不存在找不到指定的客戶

POST /api/v1/customers

描述: 創建新客戶

權限: ROLE_SALES 或更高

請求體 - 個人客戶 (CreateIndividualCustomerRequest):

欄位類型必填說明
typestringYes固定為 individual
namestringYes姓名
phonestringYes電話
genderstringNo性別(male, female, other)
birthdaystringNo生日(YYYY-MM-DD)
emailstringNoEmail
addressesAddress[]No地址列表
sourcestringNo客戶來源
preferencesstring[]No喜好標籤
importantDatesImportantDate[]No重要日期

請求體 - 企業客戶 (CreateCorporateCustomerRequest):

欄位類型必填說明
typestringYes固定為 corporate
companyNamestringYes公司名稱
phonestringYes公司電話
contactsContact[]Yes聯絡人列表(至少一位)
taxIdstringNo統一編號(8 位數字)
industrystringNo產業類別
addressstringNo公司地址
emailstringNoEmail
cooperationStartDatestringNo合作開始日期(YYYY-MM-DD)
paymentTermsstringNo月結帳期(none, net15, net30)

請求範例 (個人客戶):

POST /api/v1/customers
Authorization: Bearer {access_token}
Content-Type: application/json

{
"type": "individual",
"name": "張小美",
"phone": "0933-456-789",
"gender": "female",
"email": "mei@example.com",
"addresses": [
{
"address": "台北市大安區忠孝東路四段 100 號",
"isDefault": true,
"label": "住家"
}
],
"source": "朋友推薦"
}

請求範例 (企業客戶):

POST /api/v1/customers
Authorization: Bearer {access_token}
Content-Type: application/json

{
"type": "corporate",
"companyName": "美麗花園有限公司",
"taxId": "87654321",
"phone": "02-8765-4321",
"address": "新北市板橋區文化路一段 50 號",
"contacts": [
{
"name": "陳經理",
"title": "採購經理",
"phone": "0955-666-777",
"isPrimary": true
}
],
"paymentTerms": "net15"
}

響應範例 (201 Created):

{
"id": "cust-003",
"customerNumber": "FS01-CUST-0003",
"type": "individual",
"status": "active",
"tier": "regular",
"name": "張小美",
"phone": "0933-456-789",
"totalSpent": 0,
"totalOrders": 0,
"lastOrderDate": null,
"createdAt": "2025-12-22T10:00:00Z",
"updatedAt": "2025-12-22T10:00:00Z"
}

錯誤響應:

狀態碼錯誤訊息說明
400請填寫姓名和電話個人客戶必填欄位缺失
400請填寫公司名稱和公司電話企業客戶必填欄位缺失
400至少需要一位聯絡人企業客戶缺少聯絡人

PATCH /api/v1/customers/:id

描述: 部分更新客戶(僅更新提供的欄位)

權限: ROLE_SALES 或更高

請求體: 所有欄位皆為可選,僅更新提供的欄位

請求範例:

PATCH /api/v1/customers/cust-001
Authorization: Bearer {access_token}
Content-Type: application/json

{
"phone": "0912-999-888",
"email": "newemail@example.com",
"preferences": ["百合", "白色系"]
}

響應範例 (200 OK):

返回更新後的完整客戶資料

錯誤響應:

狀態碼錯誤訊息說明
404客戶不存在找不到指定的客戶

GET /api/v1/customers/check-duplicate

描述: 檢查電話號碼是否已被其他客戶使用

權限: ROLE_SALES 或更高

Query Parameters:

參數類型必填說明
phonestringYes要檢查的電話號碼
excludeIdstringNo排除的客戶 ID(編輯模式時排除自身)

請求範例:

GET /api/v1/customers/check-duplicate?phone=0912-345-678
Authorization: Bearer {access_token}

響應範例 (200 OK) - 無重複:

{
"isDuplicate": false
}

響應範例 (200 OK) - 有重複:

{
"isDuplicate": true,
"existingCustomer": {
"id": "cust-001",
"customerNumber": "FS01-CUST-0001",
"name": "李大華",
"phone": "0912-345-678"
}
}

錯誤響應:

狀態碼錯誤訊息說明
400請提供電話號碼缺少 phone 參數

PATCH /api/v1/customers/:id/status

描述: 停用或啟用客戶

權限: ROLE_OWNERROLE_MANAGER

請求體 - 停用客戶 (DeactivateCustomerRequest):

欄位類型必填說明
statusstringYes固定為 inactive
reasonstringYes停用原因(blacklist, duplicate, other)
reasonNotestringNo停用備註說明

請求體 - 啟用客戶 (ActivateCustomerRequest):

欄位類型必填說明
statusstringYes固定為 active

停用原因對照:

說明
blacklist黑名單(惡意客戶)
duplicate重複帳號
other其他原因

請求範例 (停用):

PATCH /api/v1/customers/cust-001/status
Authorization: Bearer {access_token}
Content-Type: application/json

{
"status": "inactive",
"reason": "blacklist",
"reasonNote": "多次惡意取消訂單"
}

請求範例 (啟用):

PATCH /api/v1/customers/cust-001/status
Authorization: Bearer {access_token}
Content-Type: application/json

{
"status": "active"
}

響應範例 (200 OK):

返回更新後的完整客戶資料

錯誤響應:

狀態碼錯誤訊息說明
400停用客戶時必須填寫停用原因停用時缺少 reason
404客戶不存在找不到指定的客戶

GET /api/v1/customers/:id/orders

描述: 取得客戶的訂單歷史(排除已取消訂單)

權限: ROLE_SALES 或更高

Path Parameters:

參數類型說明
idstring客戶 ID

Query Parameters:

參數類型必填說明預設值
pageintegerNo頁碼(從 1 開始)1
limitintegerNo每頁數量10

請求範例:

GET /api/v1/customers/cust-001/orders?page=1&limit=10
Authorization: Bearer {access_token}

響應範例 (200 OK):

Headers:

X-Total-Count: 12
X-Page: 1
X-Per-Page: 10

Body:

[
{
"id": "order-001",
"orderNumber": "FS01-20251215-0001",
"status": "completed",
"total": 2500,
"deliveryDate": "2025-12-15",
"createdAt": "2025-12-14T10:00:00Z"
},
{
"id": "order-002",
"orderNumber": "FS01-20251210-0003",
"status": "delivered",
"total": 1800,
"deliveryDate": "2025-12-10",
"createdAt": "2025-12-09T14:30:00Z"
}
]

錯誤響應:

狀態碼錯誤訊息說明
404客戶不存在找不到指定的客戶

GET /api/v1/customers/:id/stats

描述: 取得客戶的消費統計資料

權限: ROLE_SALES 或更高

Path Parameters:

參數類型說明
idstring客戶 ID

請求範例:

GET /api/v1/customers/cust-001/stats
Authorization: Bearer {access_token}

響應範例 (200 OK):

{
"totalOrders": 12,
"totalSpent": 45000,
"averageOrderAmount": 3750,
"lastOrderDate": "2025-12-15T10:30:00Z",
"topProducts": [
{
"productId": "prod-001",
"productName": "經典紅玫瑰花束",
"purchaseCount": 5,
"percentage": 42
},
{
"productId": "prod-003",
"productName": "百合盆栽",
"purchaseCount": 3,
"percentage": 25
},
{
"productId": "prod-007",
"productName": "向日葵花束",
"purchaseCount": 2,
"percentage": 17
}
],
"monthlyTrend": [
{ "month": "2025-01", "amount": 0 },
{ "month": "2025-02", "amount": 2500 },
{ "month": "2025-03", "amount": 0 },
{ "month": "2025-04", "amount": 5000 },
{ "month": "2025-05", "amount": 3500 },
{ "month": "2025-06", "amount": 0 },
{ "month": "2025-07", "amount": 8000 },
{ "month": "2025-08", "amount": 2500 },
{ "month": "2025-09", "amount": 0 },
{ "month": "2025-10", "amount": 6000 },
{ "month": "2025-11", "amount": 12000 },
{ "month": "2025-12", "amount": 5500 }
]
}

響應欄位說明:

欄位類型說明
totalOrdersinteger總訂單數(排除已取消)
totalSpentnumber累計消費金額(僅計算已完成訂單)
averageOrderAmountnumber平均訂單金額
lastOrderDatestring最後消費日期(ISO 8601)
topProductsarray最常購買的前 3 名商品
monthlyTrendarray近 12 個月的消費趨勢

錯誤響應:

狀態碼錯誤訊息說明
404客戶不存在找不到指定的客戶

GET /api/v1/customers/:id/notes

描述: 取得客戶的備註列表

權限: ROLE_SALES 或更高

Path Parameters:

參數類型說明
idstring客戶 ID

請求範例:

GET /api/v1/customers/cust-001/notes
Authorization: Bearer {access_token}

響應範例 (200 OK):

[
{
"id": "note-001",
"content": "客戶偏好粉色系花材,送花時請附上手寫卡片",
"createdAt": "2025-12-15T10:30:00Z",
"createdBy": {
"id": "user-001",
"name": "王小明"
}
},
{
"id": "note-002",
"content": "每年母親節都會訂購康乃馨花束",
"createdAt": "2025-11-20T14:00:00Z",
"createdBy": {
"id": "user-002",
"name": "李小華"
}
}
]

錯誤響應:

狀態碼錯誤訊息說明
404客戶不存在找不到指定的客戶

POST /api/v1/customers/:id/notes

描述: 新增客戶備註

權限: ROLE_SALES 或更高

Path Parameters:

參數類型說明
idstring客戶 ID

請求體 (CreateCustomerNoteRequest):

欄位類型必填說明
contentstringYes備註內容

請求範例:

POST /api/v1/customers/cust-001/notes
Authorization: Bearer {access_token}
Content-Type: application/json

{
"content": "客戶反映上次配送時間太早,下次請安排下午時段"
}

響應範例 (201 Created):

{
"id": "note-003",
"content": "客戶反映上次配送時間太早,下次請安排下午時段",
"createdAt": "2025-12-22T10:00:00Z",
"createdBy": {
"id": "user-001",
"name": "王小明"
}
}

錯誤響應:

狀態碼錯誤訊息說明
400備註內容不能為空content 為空
404客戶不存在找不到指定的客戶

業務規則

電話號碼規範化

  • 系統會自動移除電話號碼中的空白、括號、連字號
  • 搜尋時同樣會規範化後比對
  • 範例:0912-345-6780912345678 視為相同

客戶等級自動計算

  • 基於累計消費金額(totalSpent)自動計算
  • 僅計算已完成訂單的金額
  • 等級門檻見上方 CustomerTier 定義

停用客戶

  • 停用的客戶無法創建新訂單
  • 停用時必須填寫原因(reason)
  • 停用記錄會寫入審計日誌

電話重複檢測

  • 同一租戶內,電話號碼不可重複
  • 創建/編輯客戶前應先呼叫 check-duplicate 端點
  • 僅為警告,不強制阻擋(允許家庭成員共用電話)

多租戶隔離

  • 所有客戶資料自動附加 tenantId
  • 查詢自動過濾為當前租戶的客戶
  • 跨租戶訪問返回 404 或空列表

錯誤碼總覽

HTTP 狀態碼錯誤碼說明
400BAD_REQUEST請求格式錯誤或缺少必填欄位
401AUTH_TOKEN_INVALIDToken 無效或過期
403FORBIDDEN權限不足
404NOT_FOUND資源不存在
500INTERNAL_ERROR伺服器內部錯誤

最後更新: 2025-12-22