QuoteBids Provider Request Review

API:

POST /quote_bids/provider_request_review/{quote_bid_id}.json

quote_bid_idquote_bids.id。web-app 使用 provider session token,body 是 FormData,可選 invite_info

功能說明

provider 在已聯絡 consumer 的案件中,點選「邀請評價」時會呼叫此 API。

成功後會:

  • 建立 ProviderInviteReview activity。
  • 更新 quote_bids.review_requested_at
  • invite_info 有值,更新 quote_services.invite_info;沒送或 legacy empty() 判斷為空時,沿用既有 quote_services.invite_info 放進通知內容。
  • 寫入 event_queue.Request_Review
  • 若 consumer 有 Fetnet user,更新 Fetnet unread counter。

成功 response:

{"error":0,"status":"Success"}

快速結論

  • actor:quote bid owner provider session。
  • legacy API:QuoteBidsController::provider_request_review($quote_bid_id)
  • web-app path:/quote_bids/provider_request_review/${bid_id}.json
  • web-app body:只有使用者有填邀請文字時才送 invite_info
  • new API:已補 Endpoint/V1/QuoteBids.php::provider_request_review()
  • routing:已補 Mapping.php,支援 legacy path。
  • 正式機流量:2026-06-01 查詢 ip-10-5-2-242 access log,POST 200 有 961 筆,OPTIONS 200 有 41 筆。

正式機 Access Log

2026-06-01 查詢機器:ip-10-5-2-242

normalized path:

筆數method / normalized pathstatus
961POST /quote_bids/provider_request_review/{quote_bid_id}.json200
41OPTIONS /quote_bids/provider_request_review/{quote_bid_id}.json200

caller 來源包含 iOS app Alamofire、Android app okhttp 與 web https://www.pro360.com.tw/

Web-App 呼叫

web-app 檔案:

/Users/mattsu/Documents/Site/web-app/modules/utils/api-manager.js:2357-2369
/Users/mattsu/Documents/Site/web-app/modules/containers/QuoteRequestDetailContainer.js:290-318
/Users/mattsu/Documents/Site/web-app/modules/containers/QuoteRequestDetailContainer.js:613-623

呼叫條件:

  • provider 已聯絡 consumer:QuoteBid.is_want_to_contact_provider == 1
  • bid 未 refund:QuoteBid.is_provider_refunded == 0
  • 不是 one-on-one。
  • chat 沒被 block。
  • 尚未送過邀請:QuoteBid.review_requested_at 為空。
  • consumer 尚未評價。

request:

curl -X POST 'http://localhost:12351/quote_bids/provider_request_review/<quote_bid_id>.json' \
  -H 'X-PRO360-Rest-Api-Key: <api_key>' \
  -H 'X-PRO360-User-Session-Token: <provider_session_token>' \
  -F 'invite_info=<optional_text>'

invite_info 可省略;省略時 legacy 會使用 quote_services.invite_info
web-app 只有 invite_info truthy 時才 append 到 FormData

web-app 若目前有 chat room,會先呼叫 this.chatRoom.sendMsg(this.state.askReviewMessage),成功後才呼叫 requestReviewForBid()。因此同一操作可能先出現 SendChat activity (quote_activity_type_id = 22);那是 message/chat endpoint 的 side effect,不屬於 provider_request_review 本身。

Legacy Rule

legacy 檔案:

/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Controller/QuoteBidsController.php:1579-1775
/Users/mattsu/Documents/Site/get-lancer-php56/app/Model/AppModel.php:707-713

規則:

  1. 從 session 取 provider user_id
  2. quote_bids.id = quote_bid_idquote_bids.provider_user_id = user_id
  3. 查不到 bid 時回 {"error":9,"status":"invalid request"}
  4. quote_requests,取得 request owner consumer。正常資料路徑必須存在;new 對 missing request 多補 error=9/status=invalid request guard,避免 legacy undefined behavior。
  5. 若 provider / consumer 任一方 block 對方,直接回 {"error":9,"status":"invalid request"}
  6. 建立 consumer review URL,寫 ProviderInviteReview activity:
    • receiver_user_id = provider_user_id
    • param1 = requestor_user_id
    • param2 = requestor review URL
  7. 呼叫 FetnetUser::add_unread(quote_request_id, requestor_user_id)
  8. 更新 quote_bids.review_requested_at = NOW()
  9. 讀取 quote_services
    • !empty($invite_info) 時,寫回 quote_services.invite_info
    • empty($invite_info) 時,沿用 quote_services.invite_info
  10. 組 notification replace content:
    • provider name、consumer name、request title、provider photo
    • ##STAR_ICON##mail.icon.star_full
    • public review URL
    • invite_info
    • requestor hash / language
  11. life55688 request:
    • Request_Review event,tasks 只有 email
    • email template:life55688-180 Vendor Mark Hired Notification
  12. 非 life55688 request:
    • Request_Review event
    • tasks:MoEngage、push、web push
    • MoEngage template:Mark_Self_Hire
    • push / web push template:push.content.invite.review

Side Effect

同步 DB:

  • quote_bids.review_requested_at
  • quote_activities
  • quote_activity_consumers
  • quote_services.invite_info,僅在 legacy 判斷 !empty($invite_info) 時更新
  • security_hashes
  • short_urls
  • fetnet_users.unread_homefetnet_users.unread_lesson,僅 consumer 有 Fetnet row 時

非同步:

  • event_queue.Request_Review
  • worker 可能搬到 event_queue_log_1event_queue_log_4

不做:

  • 不改 quote bid status。
  • 不建立 SendChat activity;web-app 可能在呼叫本 API 前先送 chat message,那是另一支 message/chat 流程。
  • 不寫 transaction / wallet / subscription log。
  • 不新增 review 本身;真正評價仍由 POST /quote_bids/change_status/reviews/{quote_bid_id}.json 處理。

Config 對照

這支 API 的 email replace content 直接需要 mail.icon.star_full,用於 ##STAR_ICON##,不能讓 new config 回 NULL

legacy keylegacy 正式機來源與值new 對應用途
mail.icon.star_full/var/www/html/app/Config/settings.ymlhttps://s3-ap-northeast-1.amazonaws.com/pro360/images/icon/star_full.pngLib/Common/Configuration.phpdocker/staging/Configuration.phpProConfig::$config 要補同值ProviderRequestReviewHelper##STAR_ICON##

注意:Configuration.php.gitignore 忽略,這是環境設定同步,不會自動跟一般 commit / push 部署。切 staging / production 前要確認實際部署 config 有同一個 key。

其他 mail.icon.* key 不是這支 API 的直接依賴;後續 review / self hire / quote service 類 API 若有使用,需依各自 detail doc 補對應 config。

Legacy Staging 實測

2026-06-01 以 legacy staging FormData 呼叫:

POST /quote_bids/provider_request_review/2147689479.json
invite_info = 請幫我按個評價,只需要1分鐘,讓我們越來越好!您的評價會幫助我們提升服務品質,並提供其他消費者參考。

response:

{"error":0,"status":"Success"}

DB / queue 結果:

項目結果
quote_bids.id = 2147689479review_requested_at = 2026-06-01 09:45:17
quote_services.id = 9930invite_info 寫入本次 request body,modified = 2026-06-01 09:45:17
quote_activities.id = 406525quote_activity_type_id = 51param1 = 6591param2 = https://p36.net/sc/tjYAs
quote_activity_consumers.id = 43028指向 quote_activity_id = 406525quote_bid_id = 2147689479
security_hashes.id = 82750model = QuoteBidforeign_id = 2147689479,hash 用於 public review URL
event_queue_log_4.id = 60067event_key = Request_Review,worker 已從 event_queue 搬到 log

同一操作前一秒有 quote_activities.id = 406524quote_activity_type_id = 22param1 = invite_info。legacy provider_request_review() code 本身沒有寫 type 22;這是 web-app chatRoom.sendMsg() 先送出的 chat message,不納入本 API parity。

Request_Review payload 重點:

  • task:moengage:Mark_Self_Hirepush:push.content.invite.reviewweb_push:push.content.invite.review
  • logForeignId = 406525
  • INVITE_INFO 等於本次 request body
  • STAR_ICON = https://s3-ap-northeast-1.amazonaws.com/pro360/images/icon/star_full.png
  • REVIEW_URL = https://staging.pro360.com.tw/public/review/2147689479/41f9c43984943e4650f289d4119713d0

判斷:new response mapping 與目前 helper side effect 已對齊上述 legacy staging 結果。event_queue row 被 worker 搬到 event_queue_log_4 屬正常 queue/log 行為。

New PHP 8.2 對照

new 檔案行號對應 legacy職責狀態
Lib/Common/RouterRule/Mapping.php:157-160web-app / access log path將 legacy path route 到 QuoteBids::provider_request_review()已補
Endpoint/V1/QuoteBids.php:3059-3094QuoteBidsController.php:1579-1586session guard、path id / invite_info 解析、response mapping。已補
Lib/ActionHelper/QuoteBid/ProviderRequestReviewHelper.php:33-66QuoteBidsController.php:1587-1623provider owner guard、request lookup、blocked chat guard。已補
Lib/ActionHelper/QuoteBid/ProviderRequestReviewHelper.php:68-123QuoteBidsController.php:1625-1641建 activity、Fetnet unread、更新 review_requested_at已補
Lib/ActionHelper/QuoteBid/ProviderRequestReviewHelper.php:126-144QuoteBidsController.php:1662-1668invite_info 有值就保存,沒值沿用 service 既有值。已補
Lib/ActionHelper/QuoteBid/ProviderRequestReviewHelper.php:146-258QuoteBidsController.php:1649-1763建 security hash、short URL、notification payload,寫 Request_Review event。已補
tests/QuoteBidsProviderRequestReviewTest.php:110-224success / no invite_info / "0" invite_info / invalid owner / blocked chat覆蓋主要 DB side effect 與 guard。已補

逐段行數對照

Legacy 行號New 行號對照內容狀態
QuoteBidsController.php:1581-1594Endpoint/V1/QuoteBids.php:3061-3086, ProviderRequestReviewHelper.php:33-46session user 與 provider owner bid lookup。已對齊
QuoteBidsController.php:1604-1617ProviderRequestReviewHelper.php:48-66request lookup 與 blocked chat guard;blocked chat 失敗回 error=9/status=invalid request,new 對 missing request 另補相同 guard。正常路徑已對齊
QuoteBidsController.php:1627-1630, AppModel.php:707-713ProviderRequestReviewHelper.php:73-93建立 requestor review URL 與 ProviderInviteReview activity;receiver 是 provider。已對齊
QuoteBidsController.php:1632-1641ProviderRequestReviewHelper.php:95-100Fetnet unread 與 quote_bids.review_requested_at已對齊
QuoteBidsController.php:1649-1653ProviderRequestReviewHelper.php:162-168requestor hash、public review URL。已對齊
QuoteBidsController.php:1662-1668ProviderRequestReviewHelper.php:126-144invite_info 更新或 fallback。已對齊
QuoteBidsController.php:1683-1699ProviderRequestReviewHelper.php:170-186notification replace content 與 recommend feature。已對齊
QuoteBidsController.php:1710-1718ProviderRequestReviewHelper.php:191-211life55688 email event task。已對齊
QuoteBidsController.php:1719-1758ProviderRequestReviewHelper.php:212-254非 life55688 MoEngage、push、web push event tasks。已對齊
QuoteBidsController.php:1761-1763ProviderRequestReviewHelper.php:257event_queue.Request_Review已對齊
QuoteBidsController.php:1765-1774ProviderRequestReviewHelper.php:41-46查無 bid 回 invalid request。已對齊

測試

docker exec -w /project-data cd63f9147e8d vendor/bin/phpunit tests/QuoteBidsProviderRequestReviewTest.php

結果:

OK (5 tests, 28 assertions)

覆蓋:

  • success:更新 review_requested_at、保存 invite_info、建立 activity、寫 Request_Review event。
  • request 沒帶 invite_info 時沿用 quote_services.invite_info
  • invite_info = "0" 時也依 legacy empty() 規則沿用既有 quote_services.invite_info
  • 非 bid owner provider 回 error=9,不寫 side effect。
  • blocked chat 回 error=9,不寫 side effect。

New Staging 實測

2026-06-17 new staging FormData 呼叫:

POST /quote_bids/provider_request_review/2147691285.json
invite_info = 請幫我按個評價,只需要1分鐘,讓我們越來越好!您的評價會幫助我們提升服務品質,並提供其他消費者參考。

response:

{"error":0,"status":"Success"}

DB / queue 結果:

項目結果
quote_bids.id = 2147691285review_requested_at = 2026-06-17 09:40:18quote_request_id = 22516quote_service_id = 9930
quote_services.id = 9930invite_info 寫入本次 request body,modified = 2026-06-17 09:40:19
quote_activities.id = 407493quote_activity_type_id = 51provider_user_id = 8617requestor_user_id = 14738param1 = 14738param2 = https://p36.net/sc/REYJh
quote_activity_consumers.id = 43214指向 quote_activity_id = 407493quote_bid_id = 2147691285
security_hashes.id = 83060model = QuoteBidforeign_id = 2147691285,hash 用於 public review URL
event_queue_log_3.id = 54151event_key = Request_Review,worker 已從 event_queue 搬到 log

Request_Review payload 重點:

  • task:moengage:Mark_Self_Hirepush:push.content.invite.reviewweb_push:push.content.invite.review
  • logForeignId = 407493
  • INVITE_INFO 等於本次 request body
  • STAR_ICON = https://s3-ap-northeast-1.amazonaws.com/pro360/images/icon/star_full.png
  • REVIEW_URL = https://staging.pro360.com.tw/public/review/2147691285/e9b6567fab29fb2818586c4e1128afe8

補充:

  • 同一操作前一秒有 quote_activities.id = 407492quote_activity_type_id = 22param1 = invite_info。這是 web-app chatRoom.sendMsg() 先送出的 chat message,不屬於本 API parity。
  • requestor 14738 沒有 fetnet_users row,因此本次沒有 unread counter 可更新;符合「consumer 有 Fetnet row 時才更新」規則。
  • staging queue message 有 notification template missing 訊息,但 push / web_push / moengage 執行結果為 true;這是 staging notification config,不影響本 API DB parity。

判斷:new staging response、DB side effect、activity、security hash、queue/log 已與 legacy staging 行為一致。

待實測

  • life55688 request 的 email-only task path 目前依 legacy 搬移,尚未用 fixture 實測。