💻 システム開発

SCALE_CRM_HubSpot体制移行_実装計画

最終更新 2026年07月06日 / 31_システム開発部/_SCALE_CRM_HubSpot体制移行_実装計画.md

SCALE CRM 架電リスト HubSpot/Salesforce型 体制移行 実装計画

大串FB(2026-06-25)「やれることは全部やって100点にしたい。HubSpotやSalesforceと同じ体制で」。
= サーバー駆動+DBインデックスの本格CRM基盤へ。将来の数十万行スケールに備える長期投資。

ゴール

クライアントに全行を送らず、サーバーがインデックスで絞って・並べて・ページングして返す(真のO(1))。HubSpot/Salesforceと同じ「見える分だけ・DB索引で一瞬」の体制。

重要な技術判断(記録)

  • 現規模(最大6,534行/有効5,043)では ①仮想スクロール(クライアント全件→可視範囲のみDOM)で体感は既に解決。Phase 2-4 は「数十万行スケール」への布石。大串は長期視点でHubSpot体制を選択。
  • ①は「一度取得→以後ローカルで一瞬」、サーバー駆動は「操作のたびサーバー往復」。数十万行未満では①の方がスクロール体感は速い。Phase 4(クライアント駆動化)は規模が増えてから本番ONが合理的(フラグで段階導入)。

4フェーズ

✅ Phase 1: DBインデックス基盤(完了・2026-06-25)

本番D1 call_rowsgenerated VIRTUAL column + index を追加(データ無変更・既存コード無変更・退化ゼロ)。
- 追加列: c_company/c_phone/c_person/c_segment/c_listtype/c_deleted/c_latest_date(=MAX(call1〜5Date))。すべて json_extract(data,...) の VIRTUAL(保存なし・読み取り時計算)。
- index: idx_cr_proj_company/_deleted/_listtype/_person/_latestdate/_segment(複合 (project_id, c_xxx))。
- 実証: EXPLAIN QUERY PLANSEARCH ... USING INDEX idx_cr_proj_person (project_id=? AND c_person=?) =索引検索を確認。11クエリ261ms。
- ロールバック: DROP INDEX idx_cr_proj_*; ×6 + ALTER TABLE call_rows DROP COLUMN c_*; ×7。既存未使用なので戻す必要は通常なし(無害)。

⏳ Phase 2: latest_status を索引化(次)

preset フィルタ(target/apo/open/nurture/lost/dormant)は getLatestCallSt(STATUS_PRIORITY最大)に依存。これを索引で絞るため latestStatus を持たせる。
- 案A(推奨): 書き込み経路(buildPutBody core.js:3897付近)で data.latestStatus = getLatestCallSt(o) を注入 → generated c_latest_status = json_extract(data,'$.latestStatus') + index。既存6千行はバックフィル(全行 UPDATE or アプリ再保存)。
- 案B: generated column を argmax CASE式(STATUS_PRIORITY×5スロット)で直接計算 → 書き込み改修・バックフィル不要だが式が巨大。
- 退化チェック: c_latest_status と クライアント getLatestCallSt の一致をサンプル検証。

⏳ Phase 3: サーバークエリAPI(新エンドポイント・既存無変更)

GET /api/calls/query?project=&list=call|inquiry|phone&q=&st=&sg=&sc=&cp=&segId=&preset=&sort=&offset=&limit=&recentIds=&user=&isPartner=
- WHERE/ORDER BY/LIMIT をindex使用で構築(c_ 列)。{rows, total} を返す。
-
退化防止の肝: フィルタ述語は _clRowPasses と1:1対応させる。_clIsRecentlyEdited(直近30分編集)は recentIds をクライアントから配列で受け取り OR 条件*で再現(クライアント状態をサーバーへ引き継ぐ)。
- 既存クライアント無変更=退化ゼロ。curlでクライアントの filterCL 結果と件数一致を検証してから Phase 4。

⏳ Phase 4: クライアントをサーバー駆動に(退化リスク最大・テスト必須)

  • ①の仮想スクロールを「サーバー範囲取得」に拡張。スクロール位置→query→描画。フィルタ/検索/ソート変更→offset=0再query。
  • window._clServerMode フラグ(既定OFF→検証→ON)。OFFで①のクライアント全件にフォールバック。
  • 必ずテスト環境 or フラグ限定で検証してから本番ON。フィルタ10次元・選択・編集・同期・統計・ゴミ箱・対象外の全整合を確認。

退化防止の原則

  • Phase 1-3 は既存クライアント無変更(退化ゼロ)。Phase 4 のみフラグで段階導入。
  • 各Phase完了時に基準点へ記録+本番退化チェック。
  • フィルタ述語は _clRowPasses(lib/calllist_core.js:1204)を単一の真実とし、サーバーは1:1移植。

追記(2026-07-06 v11.6.3)

  • Phase2の実装形の確定: c_latest_status は案B(argmax CASE式のgenerated column)で実装済みだったことを本番実測で確認。「ステータス有り×c_latest_status NULL」の矛盾行=0(NULL=未架電=JSの''に対応)。data.latestStatus のクライアント注入(案A)は存在しない=不要。
  • query.ts 1次絞り強化済み(v11.6.3): st/cp/sg/preset(apo/nurture/lost/unreachable/open/target)を索引列でSQLプレフィルタ化。原則=JS述語(rowPasses)は最終判定に不変維持・SQLは上位集合のみ・early-return系preset(today_recall/today_called/dormant/max_called)ではst/cp/sgを付けない・recentIdsはOR call_id INで包む。
  • Phase4 表示系の整合テスト完了(2026-07-07 v11.6.5): 本番mnpg72の14,900行実データで、クライアント述語(_clRowPasses)のNode実行件数と本番 /api/calls/query の total を機械照合。全preset+st/cp/sg/q/複合+early-return検証の19ケースで不一致0=完全一致。SQLプレフィルタ(v11.6.3)の上位集合保証も実データで実証。検証ハーネス=scratchpad/phase4_verify.js方式(dump→Node eval照合・再現可能)。
  • Phase4をONにする時の残条件: ①フィルタ10次元・選択・編集・同期・統計・ゴミ箱・対象外の全整合テスト ②編集経路のサーバー反映レイテンシ確認 ③リロード時ページング(C2)の設計。※最大案件は2026-07-06時点で現役14,900行(6/24比2.4倍のペースで成長中)— 数万行に達したらON検討。

関連