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_idURL raw param要邀請的 bid
reasonbody邀請原因,必須是 ConstPreserveActionKey 允許值
preserve_notebodyconsumer 留給 provider 的備註
begin_datebody預約開始時間,可不傳
end_datebody預約結束時間,可不傳
usersession token呼叫者必須是 quote_requests.user_id

主要資料表

階段資料表重點欄位
判斷 bidquote_bidsid, quote_request_id, provider_user_id, is_narrow_match, narrow_status_id
判斷 requestorquote_requestsid, user_id, is_archived, is_closed, is_expired, hired_count
判斷封鎖blocked_chat_usersapply_user_id, blocked_user_id
判斷 provider 是否可配對blocked_user_configsuser_id, is_active_disable_match
預約時間quote_request_reservesquote_request_id, quote_service_id, begin_time, end_time
活動紀錄quote_activitiesWantProviderQuote, 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 = Send

Guard 規則

這支 API 成功前會擋下以下狀況:

規則對應資料表 / 欄位
bid 不存在quote_bids.id
呼叫者不是 request ownerquote_requests.user_id
request 已封存 / 關閉 / 過期 / 已 hiredquote_requests.is_archived, is_closed, is_expired, hired_count
consumer 封鎖 providerblocked_chat_users.apply_user_id, blocked_user_id
provider 被停用配對blocked_user_configs.is_active_disable_match
bid 不是 narrow matchquote_bids.is_narrow_match
bid 狀態不可邀請quote_bids.quote_status_id
已送過 narrow invitationquote_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_dateend_date

  1. 新增 quote_request_reserves
  2. 依 requestor 語系產生服務日期文字
  3. 將日期文字併入 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_providerconsumer 正式聯絡 provider,可能扣款is_want_to_contact_provider, contact_provider_on, quote_user_subscription_log_id
consumer_want_provider_quoteconsumer 邀請 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_reservesquote_bids.requestor_note
  • 若要確認通知,queue 可能已被 worker 搬到 log,要同時查 queue 與 log。