QuoteBids Provider Request Review
API:
POST /quote_bids/provider_request_review/{quote_bid_id}.jsonquote_bid_id 是 quote_bids.id。web-app 使用 provider session token,body 是 FormData,可選 invite_info。
功能說明
provider 在已聯絡 consumer 的案件中,點選「邀請評價」時會呼叫此 API。
成功後會:
- 建立
ProviderInviteReviewactivity。 - 更新
quote_bids.review_requested_at。 - 若
invite_info有值,更新quote_services.invite_info;沒送或 legacyempty()判斷為空時,沿用既有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-242access log,POST200 有 961 筆,OPTIONS200 有 41 筆。
正式機 Access Log
2026-06-01 查詢機器:ip-10-5-2-242
normalized path:
| 筆數 | method / normalized path | status |
|---|---|---|
| 961 | POST /quote_bids/provider_request_review/{quote_bid_id}.json | 200 |
| 41 | OPTIONS /quote_bids/provider_request_review/{quote_bid_id}.json | 200 |
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規則:
- 從 session 取 provider
user_id。 - 查
quote_bids.id = quote_bid_id且quote_bids.provider_user_id = user_id。 - 查不到 bid 時回
{"error":9,"status":"invalid request"}。 - 查
quote_requests,取得 request owner consumer。正常資料路徑必須存在;new 對 missing request 多補error=9/status=invalid requestguard,避免 legacy undefined behavior。 - 若 provider / consumer 任一方 block 對方,直接回
{"error":9,"status":"invalid request"}。 - 建立 consumer review URL,寫
ProviderInviteReviewactivity:receiver_user_id = provider_user_idparam1 = requestor_user_idparam2 = requestor review URL
- 呼叫
FetnetUser::add_unread(quote_request_id, requestor_user_id)。 - 更新
quote_bids.review_requested_at = NOW()。 - 讀取
quote_services:!empty($invite_info)時,寫回quote_services.invite_infoempty($invite_info)時,沿用quote_services.invite_info
- 組 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
- life55688 request:
- 寫
Request_Reviewevent,tasks 只有 email - email template:
life55688-180 Vendor Mark Hired Notification
- 寫
- 非 life55688 request:
- 寫
Request_Reviewevent - 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_atquote_activitiesquote_activity_consumersquote_services.invite_info,僅在 legacy 判斷!empty($invite_info)時更新security_hashesshort_urlsfetnet_users.unread_home或fetnet_users.unread_lesson,僅 consumer 有 Fetnet row 時
非同步:
event_queue.Request_Review- worker 可能搬到
event_queue_log_1到event_queue_log_4
不做:
- 不改 quote bid status。
- 不建立
SendChatactivity;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 key | legacy 正式機來源與值 | new 對應 | 用途 |
|---|---|---|---|
mail.icon.star_full | /var/www/html/app/Config/settings.yml:https://s3-ap-northeast-1.amazonaws.com/pro360/images/icon/star_full.png | Lib/Common/Configuration.php、docker/staging/Configuration.php 的 ProConfig::$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 = 2147689479 | review_requested_at = 2026-06-01 09:45:17 |
quote_services.id = 9930 | invite_info 寫入本次 request body,modified = 2026-06-01 09:45:17 |
quote_activities.id = 406525 | quote_activity_type_id = 51,param1 = 6591,param2 = https://p36.net/sc/tjYAs |
quote_activity_consumers.id = 43028 | 指向 quote_activity_id = 406525,quote_bid_id = 2147689479 |
security_hashes.id = 82750 | model = QuoteBid,foreign_id = 2147689479,hash 用於 public review URL |
event_queue_log_4.id = 60067 | event_key = Request_Review,worker 已從 event_queue 搬到 log |
同一操作前一秒有 quote_activities.id = 406524、quote_activity_type_id = 22、param1 = invite_info。legacy provider_request_review() code 本身沒有寫 type 22;這是 web-app chatRoom.sendMsg() 先送出的 chat message,不納入本 API parity。
Request_Review payload 重點:
- task:
moengage:Mark_Self_Hire、push:push.content.invite.review、web_push:push.content.invite.review logForeignId = 406525INVITE_INFO等於本次 request bodySTAR_ICON = https://s3-ap-northeast-1.amazonaws.com/pro360/images/icon/star_full.pngREVIEW_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-160 | web-app / access log path | 將 legacy path route 到 QuoteBids::provider_request_review()。 | 已補 |
Endpoint/V1/QuoteBids.php:3059-3094 | QuoteBidsController.php:1579-1586 | session guard、path id / invite_info 解析、response mapping。 | 已補 |
Lib/ActionHelper/QuoteBid/ProviderRequestReviewHelper.php:33-66 | QuoteBidsController.php:1587-1623 | provider owner guard、request lookup、blocked chat guard。 | 已補 |
Lib/ActionHelper/QuoteBid/ProviderRequestReviewHelper.php:68-123 | QuoteBidsController.php:1625-1641 | 建 activity、Fetnet unread、更新 review_requested_at。 | 已補 |
Lib/ActionHelper/QuoteBid/ProviderRequestReviewHelper.php:126-144 | QuoteBidsController.php:1662-1668 | invite_info 有值就保存,沒值沿用 service 既有值。 | 已補 |
Lib/ActionHelper/QuoteBid/ProviderRequestReviewHelper.php:146-258 | QuoteBidsController.php:1649-1763 | 建 security hash、short URL、notification payload,寫 Request_Review event。 | 已補 |
tests/QuoteBidsProviderRequestReviewTest.php:110-224 | success / no invite_info / "0" invite_info / invalid owner / blocked chat | 覆蓋主要 DB side effect 與 guard。 | 已補 |
逐段行數對照
| Legacy 行號 | New 行號 | 對照內容 | 狀態 |
|---|---|---|---|
QuoteBidsController.php:1581-1594 | Endpoint/V1/QuoteBids.php:3061-3086, ProviderRequestReviewHelper.php:33-46 | session user 與 provider owner bid lookup。 | 已對齊 |
QuoteBidsController.php:1604-1617 | ProviderRequestReviewHelper.php:48-66 | request lookup 與 blocked chat guard;blocked chat 失敗回 error=9/status=invalid request,new 對 missing request 另補相同 guard。 | 正常路徑已對齊 |
QuoteBidsController.php:1627-1630, AppModel.php:707-713 | ProviderRequestReviewHelper.php:73-93 | 建立 requestor review URL 與 ProviderInviteReview activity;receiver 是 provider。 | 已對齊 |
QuoteBidsController.php:1632-1641 | ProviderRequestReviewHelper.php:95-100 | Fetnet unread 與 quote_bids.review_requested_at。 | 已對齊 |
QuoteBidsController.php:1649-1653 | ProviderRequestReviewHelper.php:162-168 | requestor hash、public review URL。 | 已對齊 |
QuoteBidsController.php:1662-1668 | ProviderRequestReviewHelper.php:126-144 | invite_info 更新或 fallback。 | 已對齊 |
QuoteBidsController.php:1683-1699 | ProviderRequestReviewHelper.php:170-186 | notification replace content 與 recommend feature。 | 已對齊 |
QuoteBidsController.php:1710-1718 | ProviderRequestReviewHelper.php:191-211 | life55688 email event task。 | 已對齊 |
QuoteBidsController.php:1719-1758 | ProviderRequestReviewHelper.php:212-254 | 非 life55688 MoEngage、push、web push event tasks。 | 已對齊 |
QuoteBidsController.php:1761-1763 | ProviderRequestReviewHelper.php:257 | 寫 event_queue.Request_Review。 | 已對齊 |
QuoteBidsController.php:1765-1774 | ProviderRequestReviewHelper.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_Reviewevent。 - request 沒帶
invite_info時沿用quote_services.invite_info。 invite_info = "0"時也依 legacyempty()規則沿用既有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 = 2147691285 | review_requested_at = 2026-06-17 09:40:18,quote_request_id = 22516,quote_service_id = 9930 |
quote_services.id = 9930 | invite_info 寫入本次 request body,modified = 2026-06-17 09:40:19 |
quote_activities.id = 407493 | quote_activity_type_id = 51,provider_user_id = 8617,requestor_user_id = 14738,param1 = 14738,param2 = https://p36.net/sc/REYJh |
quote_activity_consumers.id = 43214 | 指向 quote_activity_id = 407493,quote_bid_id = 2147691285 |
security_hashes.id = 83060 | model = QuoteBid,foreign_id = 2147691285,hash 用於 public review URL |
event_queue_log_3.id = 54151 | event_key = Request_Review,worker 已從 event_queue 搬到 log |
Request_Review payload 重點:
- task:
moengage:Mark_Self_Hire、push:push.content.invite.review、web_push:push.content.invite.review logForeignId = 407493INVITE_INFO等於本次 request bodySTAR_ICON = https://s3-ap-northeast-1.amazonaws.com/pro360/images/icon/star_full.pngREVIEW_URL = https://staging.pro360.com.tw/public/review/2147691285/e9b6567fab29fb2818586c4e1128afe8
補充:
- 同一操作前一秒有
quote_activities.id = 407492、quote_activity_type_id = 22、param1 = invite_info。這是 web-appchatRoom.sendMsg()先送出的 chat message,不屬於本 API parity。 - requestor
14738沒有fetnet_usersrow,因此本次沒有 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 實測。