Change Status Reviews

對應 legacy API:

/quote_bids/change_status/reviews/{quote_bid_id}.json

快速結論

  • 用途:新增 / 更新 quote feedback。
  • actor:consumer 可評 provider;provider 可評 consumer。
  • legacy controller:/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Controller/QuoteBidsController.phpchange_status($filter, $quote_bid_id)$filter == 'reviews' 分支。
  • legacy review 寫入:同 controller 內呼叫 save_review($user_id, $quote_bid)
  • new API:Endpoint/V1/QuoteBids.php::change_status_reviews()
  • helper:PRO360\ActionHelper\QuoteBid\ChangeStatusReviewsHelper
  • routing:Lib/Common/RouterRule/Mapping.php
  • 狀態:一般 review 流程已補齊;is_completed / is_closed 狀態流仍拆到 completed_closed.md
  • 2026-05-12 正式機 get-lancer_access.log* 查詢:reviews/ 153 筆;其中 is_completed=1 0 筆,is_closed=1 0 筆。

Request

POST /quote_bids/change_status/reviews/{quote_bid_id}.json

常用欄位:

欄位說明
is_satisfied評分,通常 1 到 5;一般 review 流程必填
feedback評價文字
tag評價 tag array;低分評價會過濾合法 tag 後寫入負評通知內容
FeedbackAttachment評價附件 URL array;最多取前 3 筆,且只接受 legacy allowlist 內的 CloudFront URL

Request / Response 範例

Consumer 評 provider

curl 'https://api-staging.pro360.com.tw/quote_bids/change_status/reviews/{quote_bid_id}.json' \
  -H 'x-pro360-rest-api-key: {api_key}' \
  -H 'x-pro360-user-session-token: {consumer_session_token}' \
  -F 'is_satisfied=5' \
  -F 'feedback=服務很好,溝通清楚' \
  -F 'FeedbackAttachment[]=https://dfv9n9rpncj8c.cloudfront.net/review/a.jpg'

response:

{
  "status": "Success"
}

主要結果:

  • quote_feedbacks.user_id = quote_requests.user_id
  • quote_feedbacks.reviewed_user_id = quote_bids.provider_user_id
  • quote_feedbacks.is_creator = 1
  • 寫入 / 同步 quote_feedback_attachments
  • 建立 AddReview activity
  • 插入 event_queue.New_Review,或已被 worker 搬到 event_queue_log_1event_queue_log_4

Provider 評 consumer

curl 'https://api-staging.pro360.com.tw/quote_bids/change_status/reviews/{quote_bid_id}.json' \
  -H 'x-pro360-rest-api-key: {api_key}' \
  -H 'x-pro360-user-session-token: {provider_session_token}' \
  -F 'is_satisfied=4' \
  -F 'feedback=客戶需求明確,溝通順利'

response:

{
  "status": "Success"
}

主要結果:

  • quote_feedbacks.user_id = quote_bids.provider_user_id
  • quote_feedbacks.reviewed_user_id = quote_requests.user_id
  • quote_feedbacks.is_creator = 0
  • 建立 AddReview activity
  • 插入 event_queue.New_Review,或已被 worker 搬到 event_queue_log_1event_queue_log_4

更新既有評價

同一個 actor 對同一筆 quote_bid_id 再次送出評價時,更新原本的 quote_feedbacks row。

curl 'https://api-staging.pro360.com.tw/quote_bids/change_status/reviews/{quote_bid_id}.json' \
  -H 'x-pro360-rest-api-key: {api_key}' \
  -H 'x-pro360-user-session-token: {session_token}' \
  -F 'is_satisfied=3' \
  -F 'feedback=更新後的評價內容'

response:

{
  "status": "Success"
}

主要結果:

  • quote_feedbacks 不新增第二筆,改更新既有 row
  • 建立 UpdateReview activity
  • 插入 event_queue.Update_Review,或已被 worker 搬到 event_queue_log_1event_queue_log_4

低分評價

curl 'https://api-staging.pro360.com.tw/quote_bids/change_status/reviews/{quote_bid_id}.json' \
  -H 'x-pro360-rest-api-key: {api_key}' \
  -H 'x-pro360-user-session-token: {consumer_session_token}' \
  -F 'is_satisfied=1' \
  -F 'feedback=沒有依約完成' \
  -F 'tag[]=late'

response:

{
  "status": "Success"
}

額外結果:

  • 寄送 badReview admin email
  • 寫入 user_mute_matches
  • 寫入 user_mute_match_logs

未聯繫 provider 前評價

quote_bids.is_want_to_contact_provider = 0 時拒絕。

{
  "error": 19,
  "status": "您必須先與專家聯繫後才能評價專家"
}

缺少 is_satisfied

一般 review 流程沒有帶 is_satisfied 時拒絕。

{
  "status": "error"
}

Actor / Guard

允許:

  • quote_requests.user_id == session user id:consumer 評 provider。
  • quote_bids.provider_user_id == session user id:provider 評 consumer。

拒絕:

條件response
actor 不是 consumer / providererror=1status=發生錯誤,請稍後再試
quote_bids.is_want_to_contact_provider = 0error=19status=您必須先與專家聯繫後才能評價專家
is_satisfied 空值status=error

成功流程

  1. 更新 read timestamp:
    • consumer 評 provider:quote_bids.requestor_last_read_on = now
    • provider 評 consumer:quote_bids.provider_last_read_on = now
  2. 建立或更新 quote_feedbacks
    • consumer 評 provider:user_id = requestorreviewed_user_id = provideris_creator = 1
    • provider 評 consumer:user_id = providerreviewed_user_id = requestoris_creator = 0
    • 同一組 user_id + reviewed_user_id + foreign_id 已存在時更新原 row。
  3. 同步 quote_feedback_attachments
    • 最多取 FeedbackAttachment 前 3 筆。
    • 只接受 legacy allowlist 內的 CloudFront URL。
    • 已存在但本次未傳入的附件會改 is_active = 0
  4. 更新統計:
    • user feedback stats
    • service feedback stats
    • quote service profile score
  5. quote_activities
    • 新增評價:AddReview
    • 更新評價:UpdateReview
  6. 若 provider 已封鎖 consumer,保留 feedback / stats / activity,但不送 review notification event。
  7. 送 review notification event:
    • 新增評價:event_queue.New_Review
    • 更新評價:event_queue.Update_Review
    • queue 可能已被 worker 搬到 event_queue_log_1event_queue_log_4,測試與排查要接受 queue / log 任一存在。
  8. 送 review auto message:
    • 1 星:review1 + review0
    • 5 星:review5 + review0
    • 其他分數:review0

低分評價

is_satisfied = 12 時,額外 side effect:

  • 寄送 badReview admin email。
  • QuoteFeedbackTag::getValidTags($is_satisfied) 過濾 tag 後放入 email 內容。
  • 寫入 user_mute_matches
    • provider_user_id = quote_bids.provider_user_id
    • consumer_user_id = quote_requests.user_id
    • is_pro = 0
  • 寫入 user_mute_match_logs

設定來源 / 環境差異檢查

這支 API 不走報價付款流程,因此不需要檢查 TapPay / Stripe / wallet 相關設定。

會用到的設定來源:

設定用途新專案來源staging 參考值
site.urlreview email / web push target URLProConfig::get('site.url'),通常在 Lib/Common/Configuration.phphttps://staging.pro360.com.tw
api.urlShortUrl::addShortUrl() 產生 /click/{hash} 短網址;##CHATROOM_PROVIDER_URL## 會依此產生 {api.url}/click/{hash}ProConfig::get('api.url')https://api-staging.pro360.com.tw
EmailTemplate.from_emailbadReview admin email 預設寄件者ProConfig::get('EmailTemplate.from_email')notification@pro360.email
site_environmentEmailQueue::sendEmail() 在非 production subject 前加環境名稱Lib/Common/Configuration.php define / ConstantUtil::get('site_environment')staging
email_admin_listsbadReview admin email 收件人DB:EmailAdminList::getEmailLists('badReview')email_type=badReview 目前有 mei-te@pro360.com.tw

2026-05-13 staging 確認:

  • Lib/Common/Configuration.phpsite.url=https://staging.pro360.com.twapi.url=https://api-staging.pro360.com.twEmailTemplate.from_email=notification@pro360.emailsite_environment=staging
  • 本機容器可為了 local web 測試覆寫 site.url=http://localhost:12347;migration 對 staging / production 驗證時以部署環境設定為準。
  • email_templates.name = 340 Feedback Received Notification 存在且 is_active=1
  • push_templates.name IN (push.content.review.new, push.content.review.update) 存在且 is_active=1

若 staging / production 行為不同,優先確認:

  • Lib/Common/Configuration.php 是否有正確的 site.url / api.url / EmailTemplate.from_email
  • badReview 收件名單來源是 EmailAdminList::getEmailLists('badReview'),不是寫死在此 helper。
  • notification template key 由 event worker 後段解析;API 只負責插入 New_Review / Update_Review event。
  • Endpoint/V1/EventQueue.php::index() 會依時間分片寫 event_queue_log_1event_queue_log_4,不要只查舊的 event_queue_log 主表。

新舊程式對應

以下行號是本次 migration 實作後的定位,用來追 code review / 後續補差異;若檔案再改動,行號可能會漂移,仍以區塊職責為準。

Legacy PHP 5.6

主要入口:

/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Controller/QuoteBidsController.php

change_status(..., reviews, ...) 分支:

legacy 行號職責
3959-3965進入 filter == reviews;初始化 is_new / is_updatedreviewed_user_id = other_user_id
3967-3975JSON request 若沒有 is_completed 且沒有 is_satisfied,直接回 {"status":"error"}
3978-3983is_want_to_contact_provider = 0 時回 error=19,禁止未聯繫先評價。
3986-3997feedback / is_satisfied,並依 actor 更新 requestor_last_read_onprovider_last_read_on
3999-4013查既有 quote_feedbacks,判斷這次是新增或更新。
4015-4022一般 review 主線:is_satisfied 有值時呼叫 save_review($user_id, $quote_bid)
4024-40561 / 2 星低分評價:過濾合法 tag、寄 badReview admin email、寫 UserMuteMatch
4087-4096依星等送 review auto message:1 星 / 5 星加專屬 key,最後都加 review0
4098+is_completed / is_closed 狀態流;這批不混在一般 review helper,另見 completed_closed.md

save_review($user_id, $quote_bid)

legacy 行號職責
5232-5244判斷 provider / consumer actor,決定 reviewed_user_idis_creator
5246-5256user_id + reviewed_user_id + foreign_id 查既有評價。
5258-5275載入附件 model、取 feedback、取並過濾合法 tag。
5280-5309新增或更新 quote_feedbacks;同一 actor 對同一 bid 不新增第二筆。
5310-5313更新 user / service feedback 統計。
5314-5346同步 quote_feedback_attachments:最多 3 張、UploadUtil::filterByValidUrl() allowlist、未出現在本次 request 的舊附件設 is_active = 0
5348-5350更新 quote service profile score。
5352-5369建立 AddReviewUpdateReview activity;consumer 評 provider 時也更新 provider unread。
5372-5377provider 已封鎖 consumer 時,評價仍保存,但不送 review notification event。
5379-5433組 email / push / web push 內容;新增用 New_Review,更新用 Update_Review
5435-5493建立 notification task payload,寫入 EventQueue::insertEvent($event_type, ...)

New PHP 8.2

route / endpoint:

new 檔案行號職責
Lib/Common/RouterRule/Mapping.php:157-160explicit route:quote_bids/change_status/reviews/{id}.json -> change_status_reviews
Endpoint/V1/QuoteBids.php:2926-2941API action:驗 session、取 quote_bid_id / post body,呼叫 ChangeStatusReviewsHelper,回 JSON。

helper:

Lib/ActionHelper/QuoteBid/ChangeStatusReviewsHelper.php
new 行號對應 legacy職責
41-643959-3965, actor guard讀 bid / request,確認 actor 是 consumer 或 provider;不符合回 error=1
65-693978-3983未聯繫 provider 前回 error=19
70-723967-3975一般 review 缺 is_satisfied 時回 {"status":"error"}
80-973986-4022更新 read timestamp,呼叫新版 save_review()
100-1264024-40561 / 2 星低分評價:合法 tag、badReview email、UserMuteMatch
128-1364087-4096review auto message。
140legacy reviews success response成功 response 必須維持 {"status":"Success"},不可自行加 error:0
142-145legacy error branches錯誤 response 維持 error + status

save_review() 對照:

new 行號對應 legacy職責
150-1605232-5244判斷 provider / consumer actor,決定 reviewed_user_idis_creator
162-1685246-5256user_id + reviewed_user_id + foreign_id 查既有評價。
170-1825261-5275正規化 feedback / star / tag。
184-2185280-5309新增或更新 quote_feedbacks,並取得 quote_feedback_id
218, 374-4235314-5346同步 feedback attachments。
220-2245310-5313, 5348-5350更新 user / service feedback stats 與 profile score。
226-2795352-5369建立 AddReview / UpdateReview activity;consumer 評 provider 時處理 provider archived 與 unread。
281-2835372-5377blocked chat 時保留評價與 activity,但跳過 review notification event。
285-3295379-5433組 email / push / web push replace content,決定 New_ReviewUpdate_Review
332-3715435-5493建立 task payload,寫入 EventQueue::insertEvent($event_type, ...)
374-4235314-5346附件同步:新增不存在的 URL,未傳入的既有 URL 設 is_active = 0
425-450UploadUtil::filterByValidUrl()新版以 helper 內 allowlist 重建 legacy 可接受 CloudFront 網域。

Response contract 注意

這支 API 和 self_hire 不同:legacy 一般成功只回:

{
  "status": "Success"
}

不要因為其他 change_status filter 會回 {"status":"Success","error":0} 就套到 reviews。測試必須鎖住「成功不含 error」。

PHPUnit

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

覆蓋:

  • consumer 評 provider
  • provider 評 consumer
  • 未聯繫 provider 前拒絕評價
  • 更新既有評價
  • 低分評價的 mute match 與 badReview email
  • feedback attachment 同步
  • blocked chat user 不送 review event

test 對照:

test 行號職責
tests/QuoteBidsChangeStatusReviewsTest.php:142-178consumer 評 provider:response 不含 error、feedback 欄位、read timestamp、activity、New_Review
tests/QuoteBidsChangeStatusReviewsTest.php:180-236feedback attachment:最多 3 張、allowlist、更新時停用未傳入附件。
tests/QuoteBidsChangeStatusReviewsTest.php:238-261blocked chat:仍保存 feedback,但不送 review event。
tests/QuoteBidsChangeStatusReviewsTest.php:263-297provider 評 consumer:actor / reviewed user / read timestamp / activity / event。
tests/QuoteBidsChangeStatusReviewsTest.php:299-318未聯繫 provider 前回 error=19,不寫 feedback。
tests/QuoteBidsChangeStatusReviewsTest.php:320-354更新既有評價:維持一筆 feedback,建立 UpdateReviewUpdate_Review event。
tests/QuoteBidsChangeStatusReviewsTest.php:356-392低分評價:建立 user_mute_matchesuser_mute_match_logs

Legacy gap

legacy reviews 分支還包含狀態流,但這批不混在一般 review helper:

  • is_completed = 1 的 work completed 狀態流
  • is_closed = 1 的 payment completed / closed 狀態流
  • Wallet / Sudopay 完工或結案付款處理

completed / is_closed 詳細見 completed_closed.md