コンテンツにスキップ

運行日報

本書は AGENTS.md の分冊(正典・AGENTS.md と同格)。優先順位は「実コード → AGENTS.md(本書を含む)→ DESIGN.md → PRODUCT.md」。 節は AGENTS.md から逐語で移設した(PR #nnn・2026-09-16)。対象パス: apps/web/src/pages/driver/DriverDailyReportPage.tsx` `apps/web/src/lib/driver/**` `apps/web/src/lib/db/driver-daily-report.ts` `supabase/migrations/*daily*。この領域に触るワーカー / レビュアは本書を読む。

運行日報(不変条件ラチェット・mig174)

ドライバーが入力し事務が 1 日 1 枚で印刷する法定記録(輸送安全規則 第8条 運行日報・1 年保存)。 点呼は 2026-07-31 に製品から撤去した(ユーザー指示・mig176 節参照)driver_roll_calls 表・ RLS・保護トリガは mig174 のまま休眠温存(法定記録の器として既存行を消さない・書込 UI なし・ web に読み書きコードを持たない)。実体: supabase/migrations/00000000000174_driver_daily_report.sqlapps/web/src/lib/db/driver-daily-report.tsapps/web/src/lib/driver/daily-report-view.tsapps/web/src/lib/driver/daily-report-sheet.tsapps/web/src/lib/pdf/driverDailyReportPdf.tspages/driver/DriverDailyReportPage.tsxpages/collector/DriverDailyReportsPage.tsx。 人向けの説明は docs/features/driver-daily-report.md、操作手順は docs/operations-manual/11-driver-daily-report.md

  • 既存 11 項目を再入力させない: 紙の 17 項目のうち 11 は既存表が原本(配車 / 計量 / 運行ログ / vehicle_cost_entriesetc)。日報へ総重量・高速代・走行距離の入力欄を 足してはならない(計量実績・収支と乖離する)。入力は出庫・帰庫・連絡事項のみ (点呼は撤去済み)。
  • 出庫・帰庫は vehicle_daily_logs に置く(日報専用表を作らない): departed_at / returned_attimestamptz(宵積みは 23:30 出庫→翌 01:00 帰庫なので time 不可)+ vehicle_daily_logs_run_window_check。拘束時間管理(KYO-62)が同じ列を読む=二重管理禁止。 VehicleDailyLogSchema の 2 列は必須(欠落は古いマッパーの兆候として拒否する)。
  • driver_roll_calls(休眠)の契約は mig174 のまま: checked_at/tenant_id/created_by は サーバー所有・soft-delete 列 guard・物理削除遮断(block_physical_delete_legal)。mig174 proof の D 系検査が引き続き固定する。表を drop したり保護を外したりしない(既存行は法定記録)。
  • localDateTimeToIso は不正値で undefined を返す: null は「時刻を消す」意味なので、 不正入力を null に丸めると記録済みの値を壊す。呼出元は undefined で送信を止める。
  • 連絡事項の可視性は本人 + 自テナント staff のみ: 他のドライバーへ開かない。 dispatch_stops.note(事務のみ編集)/ driver_incidents(admin 限定の事故歴)へ相乗りさせない。
  • マニフェスト No は導出できない: manifests に driver / 回収先 / 車両の列が無い (issuer_name テキストと issue_date だけ)。issue_date だけで並べると他ドライバーが 運んだ分を日報に載せるため、空欄で印刷して手書きにする。buildDailyReportPdfRowsmanifestNo を埋める変更を入れてはならない(恒久解は manifests と stop / 計量の紐付けで別スライス)。
  • 取得中・取得失敗を空の日報として印刷させないcanPrintDailyReport): 空欄だらけの 日報が綴じられると「記録していない」証拠になる。
  • 車両ラベルは active に限らず当時の記録に紐づく全車両から解決し、1 台でも未解決なら印刷を止めるuseVehicles(tenantId, false) / canPrintDailyReport)。
  • 二次搬出は取引先名を出さず排出元 → 排出先(画面・PDF の両方)。stop-labels.ts の 越境マスキングと同じ扱いで、daily-report-sheetpartnerName を null にし PDF 側も 取引先名を参照しない二重防御にする。
  • 高速代はその車両・その日の etc のみ。運行ログが無く車両不明な日は集計しない (テナント全体の ETC を載せると他車両を混ぜる)。非有限値は捨て(sumEtcAmount)、 1 件も無ければ null で「0 円」と「記録が無い」を区別する。
  • PDF 基盤を新設しない: 既存の計量証明書(walkInReceiptPdf / manifestPdf)と同じ pdf-lib + Noto Sans JP 埋め込み・A4 を写経し、動的 import で chunk 分離する。
  • 無界クエリ禁止: fetchDailyNotes / fetchDriverDayWeighingItemsfetchAllPages + id tiebreaker。日報の重量が 1000 件で黙って過少になるのを防ぐ。
  • 恒久チェック: docker proof scripts/proofs/mig174-driver-daily-report.sql(D174-SENT / D1-D13。D8a は deleted_at が RLS 単独で落ちること・D8b は deleted_by が guard で 落ちることを分けて固定する = guard を外しても D8a だけでは通ってしまうため)を scripts/proofs/*.sql 全流しで [NG]=0。web は daily-report-view.test.ts / daily-report-sheet.test.ts / driverDailyReportPdf.test.ts / db/driver-daily-report.test.ts(書込先の表・server 所有列を送らない・patch の片側のみ・ 0 件更新の拒否を .range() を反映するモックで固定) / mappers/vehicle-daily-logs.test.ts / schemas/vehicle-daily-log.test.ts
  • 業務日は useLocalDateStr() をそのまま使わないresolveBusinessDay): 1 分ごとに 更新されるため、宵積みで 23:30 出庫 → 翌 01:00 帰庫すると帰庫が翌日の別行に入り、 出庫だけの日報と帰庫だけの日報に分断されるreturned_at >= departed_at の CHECK は 行が分かれるので止められない)。画面を開いた時点の日付を既定にし、前日が「出庫済み・ 未帰庫」ならその日を続け、利用者の明示選択を最優先する。日付は画面に出して選び直せること。
  • localDateTimeToIsonew Date() の正規化を通さない: new Date('2026-02-31T23:30') は Invalid ではなく 3/3 へ繰り上がる(実測)。素で通すと存在しない日を黙って別の日として 記録する。正規表現で分解し、往復で繰り上がりが無いことを確認する。
  • PDF は行数・文字数で内容を落とさない: 列幅で折り返し(wrapPdfText)、残り高さが 足りなければ改ページする(ensureSpace)。1 ページ固定に戻してはならない — pdf-lib は ページ外にも描画を受け付けるので、連絡事項が多い日は下の行が見えないまま 「記録が無い日報」になる(法定記録の内容欠落)。回帰は 30 件の連絡事項・40 件の 立ち寄り先で getPageCount() > 1 を固定する。
  • 翌日の帰庫は「翌」を付けるformatReportClock(iso, baseDate)): HH:mm だけだと 宵積みの帰庫(01:00)が出庫(23:30)より前に見え、読む人が順序を誤解する。画面と PDF は 同じ関数を使うdaily-report-sheet に置く。PDF 側へ持つと動的 import の chunk 分離が 壊れて pdf-lib がページ bundle に入る)。
  • 複数車両の日は 1 台を「主」として他を捨てないbuildRunWindows): 応援で 2 台使った日に 片方だけ出すと、他方の出庫・帰庫・走行距離・給油・高速代が日報から消える。立ち寄り先と 重量はドライバー単位なので合算で正しい。並びは (vehicleId, id) の全順序(log_date 降順 だけのクエリでは同日 2 件の順序が不定で、再読込ごとに数字が入れ替わる)。
  • 全部が未記録の日を素で印刷させないhasAnyDailyRecord + allowEmpty): 白紙の日報は 「非稼働日」と「記録の取りこぼし」のどちらとも読め、綴じた側が区別できない。非稼働日として 出すときは明示的なチェックを要求する。
  • UPDATE は影響行数を確認するassertUpdatedOne): PostgREST は 0 件更新でも error を 返さないので、RLS 拒否や競合で行が消えたときに成功トーストが嘘になる
  • 既知の限界:
  • 別端末での同時編集を検出しないupdated_at/revision による楽観ロックは未実装)。 連絡事項は append-only(mig176)なので上書き消失は起きない。出庫・帰庫は最後の打刻が残る。
  • 本スライス対象外: stop 単位の到着 / 出発時刻(completed_at のサーバー時刻統制と 混在させない・次スライス)。

運行日報フォローアップ(不変条件ラチェット・mig176)

点呼の製品撤去(ユーザー指示 2026-07-31)+ mig174 で受容した既知の限界の是正 (wl-bty 連絡事項 / wl-3fd 出庫・帰庫)。実体: supabase/migrations/00000000000176_daily_report_followups.sql と運行日報の web 各ファイル。

  • 点呼は製品から撤去(入力・表示・PDF 欄・db 読み書きコードすべて)。driver_roll_calls 表は mig174 のまま休眠温存(既存行は法定記録・書込 UI なし)。撤去する機能に 履歴基盤を足さない — 当初計画にあった点呼訂正履歴(shadow 表)は作らなかった。 復活させる場合は改めて設計する(このとき訂正履歴・楽観ロックも同時に検討する)。
  • 連絡事項は append-only。mig174 の driver UPDATE policy("driver updates own daily notes") を撤去した。UI に編集導線は元々無く、この policy だけが「黙って本文を書き換えられる」口だった。 訂正は「もう 1 件書く」。再追加してはならない(proof NT1 が 0 行 + 本文不変を固定・ NT2 が pg_policies で UPDATE 系 policy 0 本をカタログ固定・soft-delete guard / 物理削除遮断は mig174 のまま)。
  • 出庫・帰庫(法定時刻)だけを所有者限定(ユーザー確定 2026-07-31・wl-3fd)。 vehicle_daily_logs_guard_driver_update(mig029)を再定義し、driver による departed_at/returned_at の変更は行の担当ドライバー本人だけ許可する。legacy の driver_id is null 行は claim_vehicle_daily_log を先に成功させた後だけ更新できる (未 claim の打刻/点検は 42501)。オドメーター・給油・メモは従来どおり他人の行も 再保存できる(mig029 の resolveDriverIdForUpsert 前提の既存フローを壊さない)。staff は 非発火(代行入力の運用)。UI ガード(logOwnedByOther)は先回りの案内として温存し、 DB エラーは人向け文言に写す。
  • driver の INSERT も塞ぐ(codex terra 敵対レビュー 2026-07-31 P1)。UPDATE だけ守っても、 行がまだ無い (vehicle, date) へ他人名義 + 任意時刻で先回り INSERT できると所有者ガードが 無意味になる(unique の先取り妨害も可能)。トリガを BEFORE INSERT OR UPDATE へ張り直し、 driver の INSERT は (a) driver_id = 自分(NULL 不可)、(b) factory_id は車両から強制再導出、 を要求する。staff の INSERT も driver_id is not null を policy の WITH CHECK で必須化する。 mig029 の fill は NULL のときしか factory を補完しないことに依存した越工場 factory 供給も 同時に封鎖。
  • 恒久チェック: docker proof scripts/proofs/mig176-daily-report-followups.sql (FU-SENT / NT1-2 / VW1-8。VW5=他人名義 INSERT 拒否 / VW6=未設定+時刻 INSERT 拒否 / VW7=越工場 factory の強制再導出 / VW8=自分名義+時刻の INSERT 許可・VW1b=拒否後の値不変)を scripts/proofs/*.sql 全流しで [NG]=0。歯はミューテーションで確認済み (INSERT 素通し版へ退行→VW5/6/7 赤 / notes policy 再追加→NT1/NT2 赤 / 時刻条項を外す→VW1 赤)。 mig197 proof scripts/proofs/mig197-daily-log-claim.sql は claim allow / 先客 deny / 越境 deny / staff・driver の担当なし INSERT deny / claim 後の所有者 guard を固定する。web は driver-daily-report.test.ts / daily-report-view.test.ts(点呼系テストは撤去済み)。
  • claim の呼出元規則: 自テナント profile の role='driver' or can_drive=true(mig200。 mig197 の全ロール can_drive 要求は既定値ドライバーを弾く実バグだった)。JWT role whitelist (driver/admin/normal・null 拒否)は維持・profiles.role と JWT の一致は要求しない。

運行日報統合(不変条件ラチェット・mig179)

ドライバーの /driver/daily-report を単一入口とし、選択車両の種別で1ページ内のモード分岐を行う。二次搬出専用車(isSecondaryDedicated)は現行の 2 段運行日報、それ以外は復元した運行ログ(DriverVehicleLogForm)を表示する。 実体: supabase/migrations/00000000000179_daily_log_inspection.sqlapps/web/src/pages/driver/DriverDailyReportPage.tsxapps/web/src/lib/driver/run-stage.tsapps/web/src/lib/driver/submission-status.ts

  • モード解決: resolveDriverDailyModeisSecondaryDedicated === truereport、それ以外を log とする。DriverVehicleLogForm はオドメーター・給油・メモだけを扱い、運行ログ upsert は departed_at / returned_at / *_at_source / 点検列を送らない(mig176/180/187 の契約に抵触させない)。旧 /driver/daily-log/driver/daily-report への redirect として残す。
  • 点検は三値の boolean 2 列だけ: vehicle_daily_logs.departure_inspection_ok / return_inspection_okNULL=未確認 / true=点検OK / false=点検して異常ありを区別し、 既存行へ backfill しない。Web は既存の Button primitive による「点検OK / 異常あり」の 2 択トグル(未操作は未確認)で表現し、NULL のときは保存 payload に点検キーを載せない (次回保存でも NULL を保つ)。点検メモ列・新しい表・提出専用列は作らない。異常の内容は 既存の append-only driver_daily_notes の「異常・事故」へ追記する。
  • 点検の所有者: driver の点検列 UPDATE は行の担当ドライバー本人だけ許可する。legacy の driver_id is null 行は claim 後に限る。staff は代行入力できる。オドメーター・給油・メモは mig176 の既存契約どおり、他人の行も再保存できる。
  • 2 ステージの DOM: resolveRunStagenot_departed のとき、入庫メーターと給油 3 列を DOM に出さない。出庫は点検 + 必須出庫メーター + 出庫時刻、running / returned は給油 3 列 + 必須入庫メーター + 帰庫点検 + 帰庫時刻を保存する。走行距離は log.distanceKm(生成列)の表示だけで、 手入力させない。点検未チェックでも打刻はブロックせず、警告から連絡事項欄へ移動できるようにする。
  • 提出の導出: 提出済みは、その日その driver の vehicle_daily_logsreturned_at が 1 件以上 あること。1 日 2 台は 1 台以上の帰庫で提出済みとする。事務の提出状況の母集合はその日に配車 stop がある driver(配車がなくても当日 vehicle_daily_logs がある driver を含む)。取得失敗を未記録へ 化かさず、エラー表示にする。
  • 業務日・車両: resolveBusinessDayfilterMyFactoryVehicles、専用車優先の既定選択を維持する。 他人の当日行では出庫・帰庫・点検 UI を無効化し、現行の事務連絡文言を出す。旧 /driver/daily-log/driver/daily-report へ replace redirect。事務の代理入力 /collector/fleet/daily-logs/new は残す。
  • mig180 の順序・帰属ガード: vehicle_daily_logs_guard_driver_update は driver 経路だけ returned_at is not null and departed_at is null42501run window requires a departure first で拒否する。staff は代行入力のためこの guard の対象外。 vdl_g_check_driver_tenantvdl_fill_from_vehicle の後、driver guard の前に全ロールで発火し、 非 NULL の driver_id が同じ tenant_idprofiles に属することを検証する(既存 legacy 行の NULL は postgres/DEFINER fixture と claim 対象としてのみ残る)。新規 client INSERT は staff/driver とも担当必須。
  • 恒久チェック: proof scripts/proofs/mig179-daily-log-inspection.sql を既存 proof と全流しし、 I179-1〜13 と mig180 proof scripts/proofs/mig180-run-window-order.sql の O180-SENT/1〜6 を含め [NG]=0 かつ psql exit 0 を確認する。web は run-stage.test.tssubmission-status.test.tsdb/driver-daily-report.test.tsschemas/vehicle-daily-log.test.tsmappers/vehicle-daily-logs.test.ts・テンキー移設先の keypad-input.test.ts で固定する。
  • wl-owk 解決済み(mig197): 担当未設定 legacy 行の初回打刻は、呼出元の自テナント driver-capable profile を検証する claim_vehicle_daily_log(SECURITY DEFINER・search_path='')を 先に呼び、driver_id is null を含む条件付き UPDATE + ROW_COUNT で先勝ち確定する。競合時は 現担当 display_name を返し、Web は「既に◯◯さんの記録になっています。誤りの場合は事務に担当の変更を 依頼してください」と案内する。claim 内の identity guard carve-out は transaction-local GUC のみ。 新規の担当未設定行は、staff INSERT policy と driver INSERT guard の担当必須で発生源を断つ。
  • claim の呼出元規則: 自テナント profile の role='driver' or can_drive=true(mig200。 mig197 の全ロール can_drive 要求は既定値ドライバーを弾く実バグだった)。JWT role whitelist (driver/admin/normal・null 拒否)は維持・profiles.role と JWT の一致は要求しない。

出庫・帰庫時刻の信頼性(不変条件ラチェット・mig187)

vehicle_daily_logs の出庫・帰庫時刻は端末時計を原本にせず、打刻でない時刻には必ず記録元の印を付ける。 実体: supabase/migrations/00000000000187_run_window_timestamp_trust.sqlscripts/proofs/mig187-run-window-timestamp-trust.sqlapps/web/src/lib/driver/run-stage.tsapps/web/src/lib/driver/daily-report-view.ts

  • 打刻は server now: driver の打刻 payload は departed_at / returned_at を送らず、 *_at_source='driver_stamp' だけを送る。DB trigger が client の時刻を捨てて now() を入れ、 mig176 の所有者ガード・mig180 の順序/tenant ガードが確定後の時刻を見る。
  • source の詐称・据え置き禁止: *_at_sourcedriver_stamp / driver_manual / staff_manual の3値。driver は staff_manual を名乗れないが、打刻忘れ時は自分で driver_manual を書ける。時刻を変更する UPDATE は対応する source も同じ UPDATE で変更 しなければならず、時刻だけの変更や既存の印を NULL に戻す迂回を許さない。staff の訂正は 既存 upsert 経路で staff_manual を同時に送る。
  • NULL は不明: *_at_source の NULL は本機能より前の記録元不明を表す。既存行を driver_stamp へ backfill してはならず、画面は 打刻 / 手入力 / 不明 の 3 状態を区別する。
  • 法定記録は復元可能な削除のみ: vehicle_daily_logs の物理 DELETE は全 role で遮断する。 deleted_at / deleted_by は client 直書き不可で、admin 限定 SECURITY DEFINERsoft_delete_vehicle_daily_log / restore_vehicle_daily_log RPC だけが変更できる。SELECT、 提出判定、DEFINER 集計は deleted_at is null を必ず母集合条件に含める。UPDATE policy も USINGWITH CHECK の双方で deleted_at is null を要求する(mig195)。
  • PDF は印を出さない: 印は画面表示だけで、driverDailyReportPdf の紙面へ 打刻 / 手入力 / 不明 を追加してはならない。回帰は mig187 proof の T1〜T10 と web の source/payload/ soft-delete/PDF テストで固定し、Docker proof 全流しで [NG]=0 かつ psql exit 0 を確認する。

運行日報の再入力(不変条件ラチェット・mig189)

  • mig189: vehicle_daily_logs の部分 unique vdl_unique_vehicle_date_activewhere deleted_at is null)は、削除済み履歴にキーを占有させない。Web の唯一の書込経路は SECURITY INVOKER RPC upsert_vehicle_daily_log(jsonb) であり、部分 unique に対する PostgREST .upsert(onConflict) は 42P10 クラスなので直呼びへ戻してはならない。