Quote Requests Edit
對應 legacy API:
/quote_requests/edit.json操作路徑
消費者進入需求管理或需求詳情頁。
另一個已驗證入口是在發案成功後的完成 modal:
成功提出需求 -> 點選「電話隱私設定」-> 設定電話可聯絡時間 / 是否顯示電話在修改電話可聯絡時間後,前端呼叫 API:
POST /quote_requests/edit.jsonpayload 範例:
quote_request_id=21897
fields[phone_available_time]=morning,afternoon,night快速結論
- 用途:consumer 修改需求的電話可聯絡時間,並同步控制是否把電話資訊顯示給 provider。
- actor:quote request owner / consumer。
- new API:已存在
Endpoint/V1/QuoteRequests.php::edit()。 - helper:無;legacy / new 都是 controller action 內完成。
- 測試:
tests/QuoteRequestsEditTest.php - 測試指令:
docker exec -w /project-data cd63f9147e8d vendor/bin/phpunit tests/QuoteRequestsEditTest.php - 狀態:已完成 strict parity 收尾;
newQuoteRequestqueue guard 已對齊 legacy。 - 注意:這不是完整 quote request edit;legacy 只支援
fields.phone_available_time,不更新 form answer、summary、fee、activity。
New API routing
目前未找到此 endpoint 的 explicit route mapping;依一般 controller/action routing:
quote_requests/edit.json
-> Endpoint/V1/QuoteRequests.php::edit()相關 routing 檔案:
| 檔案行號 | 職責 |
|---|---|
Lib/Common/RouterRule/Mapping.php:65-67 | 只有 singular /quote_request/{id}.json explicit mapping 到 QuoteRequests::view();未包含 /quote_requests/edit.json。 |
Endpoint/V1/QuoteRequests.php:1040 | edit() action 實作。 |
Response contract
成功:
{"error":0,"message":"success"}request 不存在或 actor 不是 owner:
{"error":1,"message":"not found"}fields 不是 array,或 fields.phone_available_time 不是 string:
{"error":2,"message":"parameter is invalid"}差異注意:
- legacy 在
fields是 array 但不含phone_available_time時,程式沒有設定 json response。 - 新版維持既有行為:同情境回
{"error":0,"message":"success"},且不更新 DB、不 enqueue。這不是有效更新 payload,現有測試未把它列為 parity 條件。
Request contract
{
"quote_request_id": 123,
"fields": {
"phone_available_time": "morning,afternoon,night"
}
}| 欄位 | 型別 | 必填 | 規則 |
|---|---|---|---|
quote_request_id | scalar | 是 | 必須屬於目前 actor;否則回 error=1。 |
fields | array | 是 | 非 array 回 error=2。 |
fields.phone_available_time | string | 有效更新時必填 | 非 string 回 error=2。trim 後空字串代表不顯示電話。 |
fields.* 其他 key | mixed | 否 | legacy / new 都忽略。 |
新版 RequestHttp 會合併 $_POST 與 JSON body;legacy getRequestData() 讀 Cake $this->request->data。
Legacy 規則
成功條件:
- actor 是 quote request owner。
fields是 array。fields.phone_available_time存在且型別是 string。
成功 side effect:
- 更新
quote_requests.phone_available_time。 - 若
trim(phone_available_time)為空,更新quote_requests.is_show_phone_to_providers = 0。 - 若
trim(phone_available_time)非空,更新quote_requests.is_show_phone_to_providers = 1。 - 不更新
quote_form_submission_fields。 - 不更新 form summary。
- 不重算 fee / site fee。
- 不寫 quote activity。
- 不直接寫 notification event。
- legacy 會計算
$is_allow_contact_charge,但保存欄位與QuoteService::updateAllowContactCharge()都被註解,實際沒有 side effect。
legacy queue 規則:
- 只有
Configure::read('area') === 'hk'時才可能 enqueue。 - Redis key
quote_request_edit_{quote_request_id}做 60 秒去重。 - 只有
quote_requests.match_type in (Multiple, Authorize)時 enqueue。 - enqueue row:
task_queue.action = newQuoteRequest、value = quote_request_id、param = preserve_quote_service_id。
失敗:
- request 找不到或 actor 不符,回
error=1。 fields非 array,回error=2。fields.phone_available_time非 string,回error=2。
資料表變化
成功更新時:
| table | 欄位 / row | 變化 |
|---|---|---|
quote_requests | phone_available_time | 寫入 fields.phone_available_time 原字串;空白字串不會被 trim 後保存。 |
quote_requests | is_show_phone_to_providers | trim(phone_available_time) 為空時寫 0;非空時寫 1。 |
task_queue | action = newQuoteRequest | 只有 area = hk、match_type in (Multiple, Authorize)、Redis 60 秒去重第一次命中時新增 row。value = quote_request_id,param = preserve_quote_service_id。 |
明確不變:
| table | 欄位 / row | 規則 |
|---|---|---|
quote_requests | is_allow_contact_charge | legacy 有計算但保存欄位被註解;新版不更新。 |
quote_requests | form_summary | 不更新。 |
quote_requests | fee 相關欄位 | 不重算 manual_fee / auto_fee / direct_fee / contact_charge_fee / site_fee。 |
quote_form_submission_fields | all rows | 不新增、不更新、不刪除。 |
quote_form_submissions | all rows | 不新增、不更新、不刪除。 |
quote_activities | all rows | 不新增 activity。 |
event_queue / event_queue_log_* | notification event | 不直接寫 notification event。 |
失敗或無效 payload 時:
| 情境 | 資料表變化 |
|---|---|
| request 不存在 / actor 不符 | 不更新 quote_requests,不 enqueue。 |
fields 非 array | 不更新 quote_requests,不 enqueue。 |
fields.phone_available_time 非 string | 不更新 quote_requests,不 enqueue。 |
fields 是 array 但不含 phone_available_time | 新版回 success,但不更新 quote_requests,不 enqueue。 |
驗證 SQL 範例:
SELECT id, phone_available_time, is_show_phone_to_providers, is_allow_contact_charge,
form_summary, manual_fee, auto_fee, direct_fee, contact_charge_fee, site_fee
FROM quote_requests
WHERE id = :quote_request_id;
SELECT id, action, value, param
FROM task_queue
WHERE action = 'newQuoteRequest'
AND value = :quote_request_id
ORDER BY id DESC;
SELECT COUNT(*) AS c
FROM quote_form_submission_fields
WHERE quote_request_id = :quote_request_id;
SELECT COUNT(*) AS c
FROM quote_activities
WHERE model = 'QuoteRequest'
AND foreign_id = :quote_request_id;實作狀態
- 已有同路徑 new API:
Endpoint/V1/QuoteRequests.php::edit()。 - 已補 API session actor guard。
- 已補 owner guard:以
quote_request_id + user_id查 request。 - 已補
fieldsarray guard。 - 已補
phone_available_timestring guard。 - 已補
quote_requests.phone_available_time更新。 - 已補
quote_requests.is_show_phone_to_providers更新。 - 已補 strict legacy queue guard:只有
area = hk且match_type in (Multiple, Authorize)時 enqueue。 - 已補專用測試
tests/QuoteRequestsEditTest.php。 - 已用 API test 驗證 DB side effect 與 queue guard。
Queue 規則
legacy:
if ('hk' === Configure::read('area')) {
if ($redisUtil->incrementKey('quote_request_edit_' . $quote_request_id, 60) == 1) {
if (in_array($quote_request['QuoteRequest']['match_type'], [ConstCategoryMatchType::Multiple, ConstCategoryMatchType::Authorize])) {
$taskQueue->insertWithParam(
'newQuoteRequest',
$quote_request_id,
$quote_request['QuoteRequest']['preserve_quote_service_id']
);
}
}
}new:
if (ProConfig::get('area') === 'hk'
&& in_array((int)$quote_request['match_type'], [ConstCategoryMatchType::Multiple, ConstCategoryMatchType::Authorize], true)
&& $redisUtil->incrementKey('quote_request_edit_' . $quote_request_id, 60) == 1) {
$taskQueue->insert(
'newQuoteRequest',
$quote_request_id,
$quote_request['preserve_quote_service_id']
);
}新版已對齊 legacy:非 HK 不 enqueue;HK 也只允許 Multiple / Authorize match type enqueue。測試查 task_queue 與 task_queue_log,避免 worker timing 造成誤判。
測試指令
docker exec -w /project-data cd63f9147e8d vendor/bin/phpunit tests/QuoteRequestsEditTest.phpVerification 結果
2026-05-25 結果:
OK (7 tests, 38 assertions)覆蓋情境:
- owner 成功更新
phone_available_time。 phone_available_time非空時is_show_phone_to_providers = 1。phone_available_time空白字串時is_show_phone_to_providers = 0。- 非 owner 回
error=1。 - request 不存在回
error=1。 fields非 array 回error=2。fields.phone_available_time非 string 回error=2。- TW / 非 HK 不 enqueue。
- HK + Multiple 會 enqueue
newQuoteRequest。 - HK + 非 Multiple / Authorize 不 enqueue。
新舊程式對應
以下行號是本次靜態對照定位;若檔案再改動,行號可能會漂移,仍以區塊職責為準。
Legacy PHP 5.6
主要入口:
/Users/mattsu/Documents/Site/get-lancer-php56/app/Plugin/Quotes/Controller/QuoteRequestsController.php| legacy 行號 | 職責 |
|---|---|
5828 | QuoteRequestsController::edit() endpoint 入口。 |
5830-5831 | 讀取 quote_request_id / fields。 |
5833-5838 | actor 判定;一般用 Cake Auth user,json + valid API session 時改用 API session user。 |
5840-5845 | 以 QuoteRequest.id + QuoteRequest.user_id 查 request。 |
5847-5855 | request 不存在或 actor 不符,回 error=1。 |
5857-5866 | fields 非 array,回 error=2。 |
5867-5880 | 只接受 fields.phone_available_time,且必須是 string。 |
5882-5887 | 計算 is_show_phone_to_providers 與 $is_allow_contact_charge;後者沒有實際保存。 |
5889-5898 | 更新 phone_available_time / is_show_phone_to_providers。 |
5900-5905 | contact charge 更新邏輯被註解,無 side effect。 |
5907-5919 | HK + Redis 60 秒去重 + match_type in (Multiple, Authorize) 時 enqueue newQuoteRequest。 |
5922-5927 | 成功 response:{"error":0,"message":"success"}。 |
New PHP 8.2
route / endpoint:
| new 檔案行號 | 職責 |
|---|---|
Lib/Common/RouterRule/Mapping.php:65-67 | 未提供 /quote_requests/edit.json explicit mapping;只看到 singular /quote_request/{id}.json mapping。 |
Endpoint/V1/QuoteRequests.php:1040 | QuoteRequests::edit() action。 |
endpoint:
Endpoint/V1/QuoteRequests.php| new 行號 | 對應 legacy | 職責 |
|---|---|---|
1042-1043 | 5830-5831 | 讀取 quote_request_id / fields。 |
1045-1047 | 5833-5838 | API session actor guard;新版固定要求 API session。 |
1049-1053 | 5840-5845 | 以 id + user_id 查 request。 |
1055-1059 | 5847-5855 | request 不存在或 actor 不符,回 error=1。 |
1062-1068 | 5857-5866 | fields 非 array,回 error=2。 |
1069-1078 | 5867-5880 | 只接受 fields.phone_available_time,且必須是 string。 |
1080-1087 | 5882-5898 | 更新 phone_available_time / is_show_phone_to_providers。 |
1089-1097 | 5907-5919 | HK + Redis 60 秒去重 + match_type in (Multiple, Authorize) 時 enqueue newQuoteRequest。 |
1100-1103 | 5922-5927 | 成功 response:{"error":0,"message":"success"}。 |
tests:
| test 行號 | 職責 |
|---|---|
tests/QuoteRequestsEditTest.php:98-118 | owner 在非 HK 更新成功;is_show_phone_to_providers = 1;不 enqueue。 |
tests/QuoteRequestsEditTest.php:120-138 | 空白 phone_available_time 會更新並讓 is_show_phone_to_providers = 0。 |
tests/QuoteRequestsEditTest.php:140-158 | 非 owner 回 error=1,不更新、不 enqueue。 |
tests/QuoteRequestsEditTest.php:160-172 | fields 非 array 回 error=2。 |
tests/QuoteRequestsEditTest.php:174-188 | phone_available_time 非 string 回 error=2。 |
tests/QuoteRequestsEditTest.php:190-205 | HK + Multiple 會 enqueue newQuoteRequest。 |
tests/QuoteRequestsEditTest.php:207-222 | HK + 非 Multiple / Authorize 不 enqueue。 |
流量來源
quote_requests 系列流量盤點與 access log 查詢語法放在 README.md。