quote_activities

文件狀態:已整理 / 待持續補實例
最後驗證:2026-06-01
來源:new Lib/Model/QuoteActivity.phpEndpoint/V1/QuoteActivities.phpLib/Constant/ConstQuoteActivityType.php、legacy QuoteActivity.php、DB schema

目的

quote_activities 是 quote flow 的事件時間線表。它不是 queue,也不是 application log;它記錄已發生的使用者或系統事件,供聊天室、案件 inbox、my work / my request 列表、後台 activity 查詢、統計與部分 unread 判斷使用。

常見例子:

  • consumer 建立需求:CreateRequest
  • provider 送出報價:SubmitQuote
  • consumer / provider 進聊天室讀取:ViewBid
  • 聊天訊息:SendChat
  • consumer 聯絡 provider:AutoQuoteContact
  • consumer 邀請 provider 報價:WantProviderQuote
  • provider 接受 / 拒絕 narrow match:NarrowProviderAccept / NarrowProviderReject
  • provider 取得電話:ProReceivePhone
  • 付款、儲值、refund、第三方付款、voice contact、Google reserve、Zendesk 等流程事件

不是什麼

  • 不是非同步 queue。通知或統計工作仍看 event_queue / task_event_queue
  • 不是金流真實交易紀錄。扣款與付款狀態要看 transactionspayment_ordersthird_party_payment_logs 等。
  • 不是唯一 unread 來源。部分列表會搭配 quote_bids.*_last_read_onis_*_readedquote_activity_consumers、Redis unread count。
  • 不是每次 view 都一定新增 quote_activities row。ViewBid 有去重邏輯,重複 view 可能寫入 quote_activity_views

主要欄位

欄位用途
idactivity 主鍵,也是多數列表排序與引用依據。
created / modifiedactivity 建立與更新時間。inbox 常用最新 modified 排序。
quote_activity_type_id事件類型,對應 ConstQuoteActivityTypequote_activity_types
provider_user_id此 activity 所屬 provider。某些 user-level activity 可為 NULL
requestor_user_id此 activity 所屬 requestor / consumer。某些 user-level activity 可為 NULL
receiver_user_id事件發起者或接收呈現的主體。程式常用它判斷這筆 activity 對哪個 user 顯示。
model / foreign_id主關聯。quote flow 通常是 QuoteRequest + quote_requests.id。user-level activity 會是 User + users.id
secondary_model / secondary_foreign_id次關聯。quote bid 相關事件通常是 QuoteBid + quote_bids.id
is_viewed舊欄位,實際 read/unread 判斷不能只看它。
param1 / param2類型相依參數,例如 message、reason、狀態、金額、付款 id、reserve event id。不可跨 type 套同一語意。

寫入入口

主要 helper:

Lib/Model/QuoteActivity.php
helper寫入形狀用途
createActivity(...)完整指定 modelforeign_id、actor、secondary、param最底層寫入。
createQuoteRequestActivityById(...)model = QuoteRequest,無 secondaryrequest-level event,例如建立需求。
createQuoteBidActivityById(...)model = QuoteRequestsecondary_model = QuoteBidbid / chat / contact / review / reserve 事件主入口。
createUserActivity(...)model = Useruser-level event,例如儲值、付款失敗、每日 access。

createQuoteBidActivityById() 寫入的典型結構:

model = QuoteRequest
foreign_id = quote_request_id
secondary_model = QuoteBid
secondary_foreign_id = quote_bid_id
provider_user_id = quote_bids.provider_user_id
requestor_user_id = quote_requests.user_id
receiver_user_id = 本次事件的動作者或顯示主體
quote_activity_type_id = ConstQuoteActivityType::<type>
param1 / param2 = type-specific payload

關聯表

quote_activity_consumers

QuoteActivity::createActivity() 會在特定 consumer-facing activity 發生時 upsert quote_activity_consumers

白話理解:

  • quote_activities 是完整歷史紀錄。每發生一次 activity,就新增一筆。
  • quote_activity_consumers 是 consumer 列表用的「每個 bid 最新一筆摘要」。
  • 同一個 quote_bid_idquote_activity_consumers 只會有一筆;新的 consumer-facing activity 發生時,這筆會被更新到最新 activity。
  • 所以查歷史要看 quote_activities,查 consumer 案件列表最後狀態 / 排序 / unread 相關索引,才看 quote_activity_consumers

例子:

quote_activities
1. SubmitQuote
2. SendChat
3. ProviderInviteReview
4. AddReview
 
quote_activity_consumers
同一個 quote_bid_id 只保留最新一筆,例如 AddReview。

用途:

  • 記錄 consumer 每個 quote bid 最新的 activity。
  • Endpoint/V1/QuoteActivities.php::inbox() 用它快速組 consumer inbox / my request 列表。
  • key concept 是「每個 consumer + quote bid 的最新可見活動」,不是完整 activity history。

觸發條件:

  • activity type 在 QuoteBid::$myRequestConsumerActivities
  • UserReportIssuereceiver_user_id = requestor_user_id

quote_activity_views

ViewBid 有去重邏輯:

  • 如果同一 receiver / provider / request / empty params 已存在 ViewBid activity,新的 view 不再新增 quote_activities
  • 這種重複 view 會寫到 quote_activity_views

因此查「是否曾讀取」時不能只看 quote_activities 新增筆數,也要理解去重行為。

讀取入口

quote_bid chat room

Endpoint/V1/QuoteActivities.php::quote_bid()

用途:

  • 回傳單一 quote bid 的聊天室 / timeline。
  • 可在 is_update_last_read_on=1 時建立 ViewBid activity。
  • 同步更新:
    • quote_bids.provider_last_read_on / requestor_last_read_on
    • quote_bids.is_provider_readed / is_requestor_readed
    • quote_requests.provider_last_read_on / requestor_last_read_on
    • unread count / badge queue

它讀取:

quote_activities
WHERE secondary_model = 'QuoteBid'
  AND secondary_foreign_id = quote_bid_id
  AND quote_activity_type_id IN QuoteBid::$chatRoomActivities

並依 actor 做過濾與轉換,例如:

  • provider 看不到部分 closed / hidden activity。
  • blocked chat 之後的 activity 不顯示。
  • auto quote 的重複 ViewBid 不顯示。
  • Zendesk event 會把 param1 轉成顯示文字。
  • third-party payment logs 會被合併成 timeline row。

consumer inbox

Endpoint/V1/QuoteActivities.php::inbox()

用途:

  • quote_activity_consumers.modified 排序,組 consumer 案件 / quote bid 列表。
  • 不是直接掃整張 quote_activities

常見 activity type

完整定義在:

Lib/Constant/ConstQuoteActivityType.php

常見 quote flow type:

typeid用途
CreateRequest1建立需求。
SubmitQuote2provider 送出報價。
ViewBid4requestor / provider 查看 bid 或聊天室。
BidStatusChange5bid 狀態變更,例如 reject / undo reject。
AddReview / UpdateReview12 / 13評價新增 / 更新。
CreateQuoteBid21quote bid 建立,chat room 可能補一筆 synthetic row。
SendChat22聊天訊息。
HireVendor35hired / self hire。
PurchaseCredit / PurchaseCreditAutoRefill / PurchaseCreditFailure38 / 39 / 40儲值與付款失敗 user activity。
NoReadRefund / AdminRefund43 / 44refund 事件。
AutoQuoteContact50consumer 正式聯絡 auto quote provider。
ProviderInviteReview51provider 邀請 consumer review。
AutoQuoteRestoreBalance52auto quote restore balance。
AutoQuotePayDebt53auto quote debt payment。
SwitchToManualQuote54auto quote 轉手動報價。
ProReceivePhone55provider 取得 consumer phone。
WantProviderQuote56consumer 邀請 provider 報價。
NarrowProviderAccept / NarrowProviderReject57 / 58provider 回應 narrow match invitation。
HireVendorReminder59洽談後提醒 hire。
TP*60-75third-party payment timeline。
VoiceContact*76-86語音接通流程。
ZendeskTicketEventPro / ZendeskTicketEventConsumer87 / 88Zendesk ticket 顯示事件。
BidReserveTimeAdd/Update/Remove89 / 90 / 91已配對 provider 預約時間變更。
UserAccess92使用者每日 access。
Nshop*93-98N-shop 商品 / order / review consumer event。
GReserve*99-101Google reservation event。

常見流程關係

流程常見 activity說明
search_add / 建單RequestorSignup, CreateRequest, SubmitQuote, CreateQuoteBid建立 request / bid 及初始 timeline。
consumer 聯絡 providerSendChat, AutoQuoteContact訊息與正式聯絡分開記錄。
narrow matchWantProviderQuote, NarrowProviderAccept, NarrowProviderRejectinvitation 與 provider 回應。
provider 送手動報價SubmitQuote, SwitchToManualQuote手動報價與 auto quote 轉手動。
聊天室讀取ViewBid會搭配 read flags / last_read_on 更新。
review / hireProviderInviteReview, AddReview, UpdateReview, HireVendor評價與成交狀態。
refund / paymentPurchaseCredit*, AutoQuotePayDebt, NoReadRefund, AdminRefund, TP*activity 只是 timeline,交易事實要看 payment / transaction 表。
reserve / voice / ZendeskBidReserveTime*, GReserve*, VoiceContact*, ZendeskTicketEvent*對外流程事件顯示。

判讀規則

  1. 先用 secondary_model = 'QuoteBid' AND secondary_foreign_id = quote_bid_id 查 bid 相關 timeline。
  2. request-level activity 用 model = 'QuoteRequest' AND foreign_id = quote_request_id 查。
  3. user-level payment / access activity 用 model = 'User' AND foreign_id = user_id 查。
  4. 不要單看 param1 / param2。必須先看 quote_activity_type_id,再判斷 param 意義。
  5. 不要把 activity 當成唯一事實來源。狀態仍以核心表為主:
    • bid 狀態看 quote_bids
    • request 狀態看 quote_requests
    • 扣款看 transactions
    • queue / notification 看 event_queue / task_event_queue
  6. 同一流程可能先寫 message activity,再因 guard / classification 失敗而沒有後續正式 activity。例如 consumer contact message 被拒時,可能有 SendChat,但不會有 AutoQuoteContact
  7. ViewBid 可能因去重寫到 quote_activity_views,不要用「沒有新增 quote_activities row」推論沒有查看。

常用查詢

查單一 bid timeline:

SELECT *
FROM quote_activities
WHERE secondary_model = 'QuoteBid'
  AND secondary_foreign_id = :quote_bid_id
ORDER BY id ASC;

查單一 request activity:

SELECT *
FROM quote_activities
WHERE model = 'QuoteRequest'
  AND foreign_id = :quote_request_id
ORDER BY id ASC;

查某個 type:

SELECT *
FROM quote_activities
WHERE secondary_model = 'QuoteBid'
  AND secondary_foreign_id = :quote_bid_id
  AND quote_activity_type_id = :type_id
ORDER BY id ASC;

查 consumer inbox 索引:

SELECT *
FROM quote_activity_consumers
WHERE user_id = :user_id
ORDER BY modified DESC;

Migration 注意事項

  • 搬移 API 時,不能只確認核心 table 狀態,也要比對是否建立正確 activity type。
  • receiver_user_id 要跟 legacy 對齊;它會影響聊天室、inbox、unread、後台顯示。
  • model/foreign_idsecondary_model/secondary_foreign_id 不能反放。quote bid event 通常仍以 QuoteRequest 為主 model,QuoteBid 放 secondary。
  • 若 legacy 會避免重複 activity,新版也要確認是否有 existence check,例如 AutoQuoteContact、reserve、ViewBid。
  • 測試 downstream timeline 時,不要只 assert quote_activities,也要看流程是否同步更新 read flags、quote_activity_consumers、queue 或核心表狀態。