Quote Request Copy Implementation Plan
這份文件目前改成記錄 POST /quote_requests/copy/{quote_request_id}.json 已落地到哪裡,以及距離 legacy parity 還差什麼。
文件中若簡寫 copy.json,指的是 copy action / 流程;實際主要公開 caller 會把 source quote_request_id 放在 path segment。
目標
以 legacy parity 為目標,分批落地,但不在文件上把任何已知規則降級成 optional。
建議切法
API 入口
責任只保留:
- 驗 session
- 取
quote_request_id - 呼叫 handler
- 回舊格式 json
目前已完成:
Endpoint/V1/QuoteRequests.php::copy()Lib/QuoteRequestCopy/CopyRequestHandler.phpLib/QuoteRequestCopy/SourceRequestLoader.phpLib/QuoteRequestCopy/FormSubmissionCloner.phpLib/QuoteRequestCopy/CopyRequestFeeUpdater.php
目前已觀察到的 staging caller 形狀:
POST /quote_requests/copy/{id}.json- headers:
X-PRO360-Rest-Api-KeyX-PRO360-User-Session-Token
- body:
- 可為空 multipart
- success response:
{"error":0,"message":"success","quote_request_id":"<new_id>"}
這代表目前主要公開 caller 至少支援:
- source
quote_request_id走 path param - session flow 為主要成功路徑
- 不依賴 request body 的必填欄位
已確認 baseline
已用 staging 成功案例 20212 -> 20213 回查 DB,以下規則已可直接視為 parity baseline:
quote_form_submissions會新增一筆quote_form_submission_fields會完整複製,這筆案例是12筆- copied request 會寫
copied_quote_request_id = 20212 - source request 會寫
last_copied_on - copied request 會建立
CreateRequestactivity - copied request 會立刻產生 auto quote 與後續 quote 活動
task_queue_log會出現action = newQuoteRequest
同時也已確認下列行為不能用簡化假設處理:
- fee 不是沿用 source,也不是單純 category 固定 fee
manual_fee_discount會重算expired_on不會直接沿用 source- tags 在這筆案例是
["回客"];ConstJobCenterLabel::MULTI_REQUEST的實際值也就是回客
copy 專用流程
Lib/QuoteRequestCopy/CopyRequestHandler.phpLib/QuoteRequestCopy/SourceRequestLoader.phpLib/QuoteRequestCopy/FormSubmissionCloner.php
建議責任如下。
SourceRequestLoader
負責:
- 載入來源 request
- 驗證 owner
- 驗證 category active
- 驗證 reserve_type_a 是否仍可 copy
- 載入來源 form fields
FormSubmissionCloner
負責:
- 建立新
quote_form_submissions - 複製來源
quote_form_submission_fields
CopyRequestHandler
負責:
- blocked user 檢查
- 防重點擊
- 24h 同 category 限制
- 建立新 request
- clone submission / fields
- fee / summary 更新
- abnormal scan 與停配對補寫
- 未命中 abnormal 時才跑 auto quote 與 HK task
- 建立
CreateRequestactivity - 更新來源 request
last_copied_on - 回傳結果
實作順序
第一批:同步主線
目前已完成:
/quote_requests/copy/{quote_request_id}.jsonendpoint- owner / session 驗證
- legacy parity 的 blocked user / invalid session response shape
- hash copy 驗證
- blocked user 檢查
- reserve_type_a 過期檢查
- category active 檢查
- 24h 同 category 限制
- Redis 防連點
- 建立新 request
- 寫
copied_quote_request_id - fee parity
- summary parity
- 複製 submission / fields
- 設
expired_on - 設
last_copied_on - 寫
CreateRequest
仍需持續補強:
- staging
20212 -> 20213的欄位逐項對表 - fee / bid 細節是否完全 parity
第二批:legacy side effects 對齊
目前已完成:
- abnormal / keyword / overseas phone 掃描
- auto quote dispatcher 進入主線
- HK / area 特例 task
- recommend source
第三批:證據補齊與 parity 驗收
- baseline compare
- 區域差異驗證
- log / queue / downstream side effect 驗證
測試建議
不要沿用 Step* 命名。copy 建議獨立用規則導向命名。
目前測試檔
tests/QuoteRequestCopyIntegrationTest.phptests/QuoteRequestCopyBaselineIntegrationTest.phptests/QuoteRequestCopyAbnormalIntegrationTest.php
目前主要覆蓋:
testCopiesOwnedRequestThroughRouter()testCopiesRequestSuccessfullyViaHash()testFallsBackToSessionWhenHashIsInvalid()testReturnsInvalidSessionWhenHashIsInvalidAndNoSessionExists()testReturnsInvalidRequestWhenQuoteRequestIdIsMissing()testReturnsInvalidRequestWhenSourceRequestIsNotOwned()testDispatchesAutoQuoteAfterCopy()testEnqueuesNewQuoteRequestTaskInHongKong()testWritesRecommendSourceLogWhenProvided()testCopiesBaseFeesAndExpiredOnForStableFixture()testDueDateFieldOverridesCategoryExpiredOn()tests/QuoteRequestCopyBaselineIntegrationTest::testBaselineRequestRowsMatchExpectedCopyRelationship()tests/QuoteRequestCopyBaselineIntegrationTest::testBaselineCopiedRequestHasExpectedAutoQuoteBidShape()tests/QuoteRequestCopyBaselineIntegrationTest::testBaselineCopiedRequestHasExpectedNarrowMatchBidShape()testRejectsBlockedUser()testRejectsExpiredReserveTypeARequest()testRejectsInactiveCategory()testRejectsWhenSameCategoryRequestCountReachedLimit()testPausesMatchingWhenForcedAbnormalScanFound()testPausesMatchingForHighRiskForeignPhoneScan()testReturnsSuccessButPausesMatchingForForcedOldKeywordWarning()
斷言重點
- response
error/message/quote_request_id - 新 request row
copied_quote_request_id- cloned submission / field count
last_copied_onCreateRequestactivity
已知風險
-
舊
copy()其實混了很多search_add後處理- 如果只做同步主線,不能聲稱 parity 完成
-
fee / expired_on 已有明確 baseline 差異
- staging
20212 -> 20213已證明這些欄位不是目前簡化實作可覆蓋的 - 若不先回補 legacy 規則,API 雖然可回成功,但資料不會對
- staging
-
allow_copy目前下游是靠last_copied_on- 這欄位若漏寫,前端顯示會不對
-
如果
fee / summary / auto quote / abnormal scan / newQuoteRequest task沒對齊- 即使 endpoint 可以回成功,也不能算 parity 完成
目前進度
已實作並有測試或執行證據:
- session copy
- hash copy
- invalid hash fallback session
- invalid session
- invalid request
- cloned submission / field
copied_quote_request_idlast_copied_onCreateRequest- fee updater 主線
- variable fee helper 已修正
- fee / expired_on integration test
- due_date override integration test
- auto quote dispatcher 進入點
- HK
newQuoteRequesttask - recommend_source log
- abnormal pause matching
- foreign phone high-risk pause matching
- old keyword forced warning 在 copy 仍回 success 但停配對
- blocked user
- inactive category
- expired
reserve_type_a - 24h same-category limit
目前 copy 相關測試集合:
tests/QuoteRequestCopyIntegrationTest.phptests/QuoteRequestCopyAbnormalIntegrationTest.phptests/QuoteRequestCopyGuardIntegrationTest.phptests/QuoteRequestCopyBaselineIntegrationTest.php
目前整包執行結果:
docker exec -w /project-data cd63f9147e8d ./vendor/bin/phpunit tests --filter 'QuoteRequestCopy(Integration|AbnormalIntegration|GuardIntegration|BaselineIntegration)Test'OK (32 tests, 288 assertions)
已確認 staging reference baseline:
quote_request_match_infos:r / sc / tpquote_bids:total_bids = 100、auto_quote_bids = 7、narrow_bids = 14quote_activities:type1=1、2=8、4=27、52=7、55=1task_queue_log:存在action = newQuoteRequest、value = 20213- auto quote bid 抽樣形狀:
quote_status_id = 2provider_status_id = 2is_auto_quote = 1is_narrow_match = 0is_want_to_contact_provider = 0total_site_fee = 111pricing_unit = 小時
- narrow match bid 抽樣形狀:
quote_status_id = 1provider_status_id = 1is_auto_quote = 0is_narrow_match = 1is_want_to_contact_provider = 1total_site_fee = 101pricing_unit = 小時
- 已補其他真實 baseline:
- booking baseline
14241 -> 14306 - abnormal baseline
14691 -> 17216 - keyword baseline
10650 -> 10652
- booking baseline
新專案目前 matching 對齊進度:
- 已補回 manual handler 的
read_segment >= 9分流 - 已補回 related general pool 對原 category service 的移除
- 已補回 additional narrow 的 legacy exclusion 與 temp-table score boost
- 已補回 additional narrow 同 provider / 同 score 時的穩定 tie-break
- 已補回 narrow pool 的 self-owned service 過濾
- 但
20212 / 8617這組 reference case 目前不能再直接當成「可完全重跑的同一組 live 資料」- 原因不只 repeated copy / daily limit,還包含 reference provider 資料已漂移
- 目前已確認一個實例:
- staging reference 使用的是 provider
8085的quote_service_id = 9115 - 但 live DB 目前
9115.confirmed_prohibited_type = 2,已不符合新舊 matching 都共用的confirmed_prohibited_type in (0, 3, 4)條件 - 因此現在重跑同一 source request 時,runtime 只會選到同 provider 的
quote_service_id = 9169
- staging reference 使用的是 provider
- 這代表
20212 -> 20213仍是 staging reference baseline,但不應再被表述成「今天在同一份 live DB 上一定能原樣重現」
仍需補齊或再釘更實的部分:
- HK / area 的真實 baseline
- 目前這份 staging DB
\ProConfig::get('area') = tw,若要驗 HK 分支,需改到 HK 區域環境或另一份對應 baseline - HK 主線本身已補到兩層證據:
- QuoteRequestCopyIntegrationTest.php 已用
QUOTE_REQUEST_COPY_TEST_FORCE_AREA=hk驗newQuoteRequesttask - 已直接比對 legacy controller 與新 handler,
area=hk與!is_scan_found的條件一致
- QuoteRequestCopyIntegrationTest.php 已用
20212 -> 20213以外更多一般 baseline 是否也都維持相同 tag 行為
後續維護方式
後續若 copy 規則、測試或 baseline 有變更,照這個順序更新文件:
- 先更新
rule_catalog.md的現況與備註。 - 再更新
rule_details.md的規則細節、測試 case 與 baseline。 - 如果新舊流程順序改變,再更新
step_by_step_compare.md。 - 若只是新測試或新 baseline,不要把 reference baseline 寫成今天重跑一定能得到的結果。