Quote Requests Edit

對應 legacy API:

/quote_requests/edit.json

操作路徑

消費者進入需求管理或需求詳情頁。

另一個已驗證入口是在發案成功後的完成 modal:

成功提出需求 -> 點選「電話隱私設定」-> 設定電話可聯絡時間 / 是否顯示電話

在修改電話可聯絡時間後,前端呼叫 API:

POST /quote_requests/edit.json

payload 範例:

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 收尾;newQuoteRequest queue 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:1040edit() 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_idscalar必須屬於目前 actor;否則回 error=1
fieldsarray非 array 回 error=2
fields.phone_available_timestring有效更新時必填非 string 回 error=2。trim 後空字串代表不顯示電話。
fields.* 其他 keymixedlegacy / 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 = newQuoteRequestvalue = quote_request_idparam = preserve_quote_service_id

失敗:

  • request 找不到或 actor 不符,回 error=1
  • fields 非 array,回 error=2
  • fields.phone_available_time 非 string,回 error=2

資料表變化

成功更新時:

table欄位 / row變化
quote_requestsphone_available_time寫入 fields.phone_available_time 原字串;空白字串不會被 trim 後保存。
quote_requestsis_show_phone_to_providerstrim(phone_available_time) 為空時寫 0;非空時寫 1
task_queueaction = newQuoteRequest只有 area = hkmatch_type in (Multiple, Authorize)、Redis 60 秒去重第一次命中時新增 row。value = quote_request_idparam = preserve_quote_service_id

明確不變:

table欄位 / row規則
quote_requestsis_allow_contact_chargelegacy 有計算但保存欄位被註解;新版不更新。
quote_requestsform_summary不更新。
quote_requestsfee 相關欄位不重算 manual_fee / auto_fee / direct_fee / contact_charge_fee / site_fee
quote_form_submission_fieldsall rows不新增、不更新、不刪除。
quote_form_submissionsall rows不新增、不更新、不刪除。
quote_activitiesall 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。
  • 已補 fields array guard。
  • 已補 phone_available_time string guard。
  • 已補 quote_requests.phone_available_time 更新。
  • 已補 quote_requests.is_show_phone_to_providers 更新。
  • 已補 strict legacy queue guard:只有 area = hkmatch_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_queuetask_queue_log,避免 worker timing 造成誤判。

測試指令

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

Verification 結果

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 行號職責
5828QuoteRequestsController::edit() endpoint 入口。
5830-5831讀取 quote_request_id / fields
5833-5838actor 判定;一般用 Cake Auth user,json + valid API session 時改用 API session user。
5840-5845QuoteRequest.id + QuoteRequest.user_id 查 request。
5847-5855request 不存在或 actor 不符,回 error=1
5857-5866fields 非 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-5905contact charge 更新邏輯被註解,無 side effect。
5907-5919HK + 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:1040QuoteRequests::edit() action。

endpoint:

Endpoint/V1/QuoteRequests.php
new 行號對應 legacy職責
1042-10435830-5831讀取 quote_request_id / fields
1045-10475833-5838API session actor guard;新版固定要求 API session。
1049-10535840-5845id + user_id 查 request。
1055-10595847-5855request 不存在或 actor 不符,回 error=1
1062-10685857-5866fields 非 array,回 error=2
1069-10785867-5880只接受 fields.phone_available_time,且必須是 string。
1080-10875882-5898更新 phone_available_time / is_show_phone_to_providers
1089-10975907-5919HK + Redis 60 秒去重 + match_type in (Multiple, Authorize) 時 enqueue newQuoteRequest
1100-11035922-5927成功 response:{"error":0,"message":"success"}

tests:

test 行號職責
tests/QuoteRequestsEditTest.php:98-118owner 在非 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-172fields 非 array 回 error=2
tests/QuoteRequestsEditTest.php:174-188phone_available_time 非 string 回 error=2
tests/QuoteRequestsEditTest.php:190-205HK + Multiple 會 enqueue newQuoteRequest
tests/QuoteRequestsEditTest.php:207-222HK + 非 Multiple / Authorize 不 enqueue。

流量來源

quote_requests 系列流量盤點與 access log 查詢語法放在 README.md