consumer_want_provider_quote
文件狀態:已對 legacy / 已對 new API / 主要路徑已實測
最後驗證:2026-05-07
來源:legacy QuoteBidsController / QuoteBid model、new Endpoint/V1/QuoteBids.php / Lib/Model/QuoteBid.php、PHPUnit、專案 migration detail
目的
consumer_want_provider_quote 是 consumer 從 narrow match / 新客源候選卡片中,邀請指定 provider 報價的流程。
API:
POST /quote_bids/consumer_want_provider_quote/{quote_bid_id}.json這支和 want_to_contact_provider 不同。它不是「正式聯絡並扣款」,而是把一筆 narrow match bid 推進到「等待 provider 回覆報價」狀態。
完整 narrow match 業務 trace 請先看 業務流程/narrow_match。這篇只回答 consumer 邀請 provider 報價時,quote_bids 與相鄰資料表如何變化。
主流程簡圖
consumer 呼叫 consumer_want_provider_quote
-> 驗 API key 與 user session
-> 鎖定 quote_bids
-> 確認呼叫者是 quote_requests.user_id
-> 確認 request 還可操作
-> 確認 bid 是 narrow match 且尚未邀請
-> 檢查 blocked user / provider disabled / contact limit
-> 如有預約時間,寫 quote_request_reserves
-> 更新 quote_bids narrow 狀態
-> 寫 WantProviderQuote activity
-> 更新 provider unread
-> 通知 provider
-> 回 success輸入資料
| 欄位 | 來源 | 說明 |
|---|---|---|
quote_bid_id | URL raw param | 要邀請的 bid |
reason | body | 邀請原因,必須是 ConstPreserveActionKey 允許值 |
preserve_note | body | consumer 留給 provider 的備註 |
begin_date | body | 預約開始時間,可不傳 |
end_date | body | 預約結束時間,可不傳 |
| user | session token | 呼叫者必須是 quote_requests.user_id |
主要資料表
| 階段 | 資料表 | 重點欄位 |
|---|---|---|
| 判斷 bid | quote_bids | id, quote_request_id, provider_user_id, is_narrow_match, narrow_status_id |
| 判斷 requestor | quote_requests | id, user_id, is_archived, is_closed, is_expired, hired_count |
| 判斷封鎖 | blocked_chat_users | apply_user_id, blocked_user_id |
| 判斷 provider 是否可配對 | blocked_user_configs | user_id, is_active_disable_match |
| 預約時間 | quote_request_reserves | quote_request_id, quote_service_id, begin_time, end_time |
| 活動紀錄 | quote_activities | WantProviderQuote, secondary_foreign_id |
成功時的 quote_bids 變化
成功後重點欄位會變成:
quote_bids.narrow_status_id = WAITING
quote_bids.is_contact_charge = 0
quote_bids.reason = <reason>
quote_bids.requestor_note = <preserve_note 或 reserve note>如果 provider 原本先選擇不感興趣,流程會把 bid 拉回可報價狀態:
quote_bids.is_interested = 1
quote_bids.quote_status_id = Send
quote_bids.provider_status_id = SendGuard 規則
這支 API 成功前會擋下以下狀況:
| 規則 | 對應資料表 / 欄位 |
|---|---|
| bid 不存在 | quote_bids.id |
| 呼叫者不是 request owner | quote_requests.user_id |
| request 已封存 / 關閉 / 過期 / 已 hired | quote_requests.is_archived, is_closed, is_expired, hired_count |
| consumer 封鎖 provider | blocked_chat_users.apply_user_id, blocked_user_id |
| provider 被停用配對 | blocked_user_configs.is_active_disable_match |
| bid 不是 narrow match | quote_bids.is_narrow_match |
| bid 狀態不可邀請 | quote_bids.quote_status_id |
| 已送過 narrow invitation | quote_bids.narrow_status_id |
| 聯繫 / 邀請人數達上限 | 同 request 下已聯絡 auto quote 與 narrow status 數量 |
可邀請狀態重點:
quote_bids.is_narrow_match = 1
quote_bids.narrow_status_id = NOT_SEND
quote_bids.quote_status_id IN (Send, NotInterested)預約時間規則
如果 body 同時有 begin_date 和 end_date:
- 新增
quote_request_reserves - 依 requestor 語系產生服務日期文字
- 將日期文字併入
quote_bids.requestor_note
requestor note 多段內容使用 legacy 分隔符:
#|#|#Activity / Notification
成功後會建立:
quote_activities.quote_activity_type_id = WantProviderQuote
quote_activities.secondary_foreign_id = quote_bid_id也會更新 provider my work unread:
setMyWorkUnreadCount(provider_user_id, [quote_bid_id => ['t' => 'n', 'c' => 1]])非 direct reserve pro 時,會通知 provider。
與 want_to_contact_provider 的差異
| 流程 | 主要目的 | 核心欄位 |
|---|---|---|
want_to_contact_provider | consumer 正式聯絡 provider,可能扣款 | is_want_to_contact_provider, contact_provider_on, quote_user_subscription_log_id |
consumer_want_provider_quote | consumer 邀請 narrow match provider 報價 | is_narrow_match, narrow_status_id, requestor_note |
不要用 is_want_to_contact_provider = 1 判斷這支是否成功。這支的主狀態是:
narrow_status_id = WAITING常用 SQL
查 bid 與 request 狀態
SELECT
qb.id,
qb.quote_request_id,
qr.user_id AS requestor_user_id,
qb.provider_user_id,
qb.quote_service_id,
qb.is_narrow_match,
qb.narrow_status_id,
qb.quote_status_id,
qb.provider_status_id,
qb.is_interested,
qb.is_contact_charge,
qb.reason,
qb.requestor_note,
qr.is_archived,
qr.is_closed,
qr.is_expired,
qr.hired_count
FROM quote_bids qb
INNER JOIN quote_requests qr ON qr.id = qb.quote_request_id
WHERE qb.id = <quote_bid_id>;查同 request 已聯絡 / 已邀請數
SELECT
COUNT(id) AS contact_or_invite_count
FROM quote_bids
WHERE quote_request_id = <quote_request_id>
AND (
(is_auto_quote = 1 AND is_want_to_contact_provider = 1)
OR narrow_status_id IN (1, 2, 3)
);查 reserve
SELECT
id,
quote_request_id,
quote_service_id,
begin_time,
end_time,
created
FROM quote_request_reserves
WHERE quote_request_id = <quote_request_id>
ORDER BY id DESC;查 activity
SELECT
id,
created,
quote_activity_type_id,
provider_user_id,
requestor_user_id,
receiver_user_id,
model,
foreign_id,
secondary_model,
secondary_foreign_id,
param1,
param2
FROM quote_activities
WHERE secondary_foreign_id = <quote_bid_id>
ORDER BY id DESC
LIMIT 20;判斷重點
- 這支是 narrow match 邀請流程,不是正式聯絡扣款流程。
- 成功主狀態看
quote_bids.narrow_status_id = WAITING。 - 若有預約時間,要一起看
quote_request_reserves與quote_bids.requestor_note。 - 若要確認通知,queue 可能已被 worker 搬到 log,要同時查 queue 與 log。