コンテンツにスキップ

0016. 横乗り (当日ルート単位の同乗者)

コンテキストと課題

回収業務では、ドライバー 1 人に対し 同乗者 (横乗り) が付くことがあります。新人の同行教育や 人手の要る回収先への増員などで、別の車両は使わず ドライバーの当日ルートに「同行する人」を 記録したいという要件です。

これを既存の配車モデル (dispatch_stops) で表現しようとすると噛み合いません。dispatch_stops回収先 (collection_site) への訪問stop_order で並べたもので、生成 RPC・ドラッグ&ドロップ 編集・完了状態を持ちます。同乗者は「どの回収先に行くか」ではなく「誰のルートに同行するか」だけが 本質で、ストップの並びや生成 RPC・D&D に 干渉させたくありません

決定

横乗りを dispatch_ride_alongs テーブルに独立して持ち、紐付け単位を「ドライバーの当日ルート (driver_id × dispatch_date)」とする。配車ルート (dispatch_stops / stop_order)・生成 RPC・D&D には一切干渉しない、当日割当の 付与と表示のみ の機能です。

  • データモデル: 1 行 = ある配車日・あるドライバーの当日ルートに同乗する 1 メンバー。 dispatch_ride_alongs (id, tenant_id, dispatch_date, driver_id → profiles, profile_id → profiles, created_by → profiles nullable, created_at)。両 FK は on delete cascade
  • 本人除外: そのルートのドライバー本人は横乗りに選べない (冗長)。driver_id <> profile_idDB の check 制約 でも担保し、候補算出 computeRideAlongCandidates でも除外する。
  • 複数可・重複不可: テナント全メンバー (admin / normal / driver) から 複数選択可。 同一ルートへ同じ人を二重登録しないよう unique (tenant_id, dispatch_date, driver_id, profile_id)
  • グルーピング: groupRideAlongsByDriverdriver_id 単位で束ね、配車ボードの列ヘッダーに 同乗者チップとして表示する (driver_id × dispatch_date がルート単位のため driverId で束ねれば その列の同乗者一覧になる)。
  • 候補算出: computeRideAlongCandidates = 全メンバー − そのルートのドライバー本人 − 既存の同乗者。 候補が空なら追加導線を出さない。
  • RLS (dispatch_stops の mig25 / 41 / 55 に倣う):
    • SELECT: テナント全件 (tenant_id = current_tenant_id())。driver も自テナントの横乗りを閲覧可。
    • 書込 (ALL): admin / normal のみ。WITH CHECK で driver_id / profile_id が同一テナントに 属することを exists で検証する (ADR 0017 の 越境参照ハードニング (c) を踏襲)。
    • tenant が collector であることは ensure_collector_tenant トリガで担保 (dispatch_stops と同様)。

影響 (Consequences)

良い点

  • 同乗者を配車ルートから 独立 させたことで、dispatch_stops の生成 RPC・stop_order・D&D・ 完了状態に一切影響を与えずに付与・表示できる (責務分離)。
  • 紐付けを driver_id × dispatch_date にしたことで、ボード上では driverId で束ねるだけで列ヘッダーの チップになり、実装がシンプル。
  • 本人除外・二重登録防止を DB 制約 (check / unique) と純粋ロジック (候補算出) の 両方 で担保。
  • 越境参照検証を WITH CHECK の exists で行うため、他テナントの profilesdriver_id / profile_id に紐付けられない (FK は存在のみ保証で越境を防げないため)。

トレードオフ / 注意点

  • 横乗りは「同行者」の記録であり、車両割当や capacity 計算には関与しない (別の車両を使わない前提)。 車両を伴う増車が必要な場合は配車ルート側の操作になる。
  • ストップ単位ではなくルート単位のため、「特定の回収先だけ同行」のような粒度は表現しない (運用上の割り切り)。
  • 列ヘッダーにチップが増えると視覚的に密になりうるが、当日 1 ルートあたりの同乗者は少数の想定。

根拠 (典拠)

  • supabase/migrations/00000000000057_dispatch_ride_alongs.sql:25-37 — テーブル定義 (driver_id / profile_idprofiles on delete cascade、created_by nullable、driver_id <> profile_id check、unique (tenant_id, dispatch_date, driver_id, profile_id))。
  • supabase/migrations/00000000000057_dispatch_ride_alongs.sql:42-46ensure_collector_tenant トリガ (collector テナント担保)。
  • supabase/migrations/00000000000057_dispatch_ride_alongs.sql:51-91 — RLS (SELECT テナント全件 / 書込 admin・normal + WITH CHECK で driver_idprofile_id の同一テナントを exists 検証 / service_role)。
  • apps/web/src/lib/dispatch-ride-along.ts:33-41groupRideAlongsByDriver (driverId 単位グルーピング・登録順保持)。
  • apps/web/src/lib/dispatch-ride-along.ts:48-56computeRideAlongCandidates (全メンバー − ドライバー本人 − 既存 profileId)。
  • apps/web/src/lib/db/dispatch-ride-alongs.ts:21-66 — 読み書きフック (useDispatchRideAlongs / useAddRideAlong / useRemoveRideAlong、SELECT は date でフィルタ・書込は admin/normal)。
  • apps/web/src/pages/collector/dispatch/DispatchBoardPage.tsx:274-276,1080-1105 — 列ヘッダーへのチップ表示と追加導線 (RideAlongSection)。