運行日報
本書は 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.sql・
apps/web/src/lib/db/driver-daily-report.ts・apps/web/src/lib/driver/daily-report-view.ts・
apps/web/src/lib/driver/daily-report-sheet.ts・apps/web/src/lib/pdf/driverDailyReportPdf.ts・
pages/driver/DriverDailyReportPage.tsx・pages/collector/DriverDailyReportsPage.tsx。
人向けの説明は docs/features/driver-daily-report.md、操作手順は
docs/operations-manual/11-driver-daily-report.md。
- 既存 11 項目を再入力させない: 紙の 17 項目のうち 11 は既存表が原本(配車 / 計量 /
運行ログ /
vehicle_cost_entriesのetc)。日報へ総重量・高速代・走行距離の入力欄を 足してはならない(計量実績・収支と乖離する)。入力は出庫・帰庫・連絡事項のみ (点呼は撤去済み)。 - 出庫・帰庫は
vehicle_daily_logsに置く(日報専用表を作らない):departed_at/returned_atはtimestamptz(宵積みは 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だけで並べると他ドライバーが 運んだ分を日報に載せるため、空欄で印刷して手書きにする。buildDailyReportPdfRowsのmanifestNoを埋める変更を入れてはならない(恒久解は manifests と stop / 計量の紐付けで別スライス)。 - 取得中・取得失敗を空の日報として印刷させない(
canPrintDailyReport): 空欄だらけの 日報が綴じられると「記録していない」証拠になる。 - 車両ラベルは active に限らず当時の記録に紐づく全車両から解決し、1 台でも未解決なら印刷を止める(
useVehicles(tenantId, false)/canPrintDailyReport)。 - 二次搬出は取引先名を出さず排出元 → 排出先(画面・PDF の両方)。
stop-labels.tsの 越境マスキングと同じ扱いで、daily-report-sheetがpartnerNameを null にし PDF 側も 取引先名を参照しない二重防御にする。 - 高速代はその車両・その日の
etcのみ。運行ログが無く車両不明な日は集計しない (テナント全体の ETC を載せると他車両を混ぜる)。非有限値は捨て(sumEtcAmount)、 1 件も無ければnullで「0 円」と「記録が無い」を区別する。 - PDF 基盤を新設しない: 既存の計量証明書(
walkInReceiptPdf/manifestPdf)と同じ pdf-lib + Noto Sans JP 埋め込み・A4 を写経し、動的 import で chunk 分離する。 - 無界クエリ禁止:
fetchDailyNotes/fetchDriverDayWeighingItemsはfetchAllPages+idtiebreaker。日報の重量が 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 は 行が分かれるので止められない)。画面を開いた時点の日付を既定にし、前日が「出庫済み・ 未帰庫」ならその日を続け、利用者の明示選択を最優先する。日付は画面に出して選び直せること。 localDateTimeToIsoはnew 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 proofscripts/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'orcan_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.sql・
apps/web/src/pages/driver/DriverDailyReportPage.tsx・apps/web/src/lib/driver/run-stage.ts・
apps/web/src/lib/driver/submission-status.ts。
- モード解決:
resolveDriverDailyModeがisSecondaryDedicated === trueをreport、それ以外を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_okはNULL=未確認 /true=点検OK /false=点検して異常ありを区別し、 既存行へ backfill しない。Web は既存のButtonprimitive による「点検OK / 異常あり」の 2 択トグル(未操作は未確認)で表現し、NULLのときは保存 payload に点検キーを載せない (次回保存でも NULL を保つ)。点検メモ列・新しい表・提出専用列は作らない。異常の内容は 既存の append-onlydriver_daily_notesの「異常・事故」へ追記する。 - 点検の所有者: driver の点検列 UPDATE は行の担当ドライバー本人だけ許可する。legacy の
driver_id is null行は claim 後に限る。staff は代行入力できる。オドメーター・給油・メモは mig176 の既存契約どおり、他人の行も再保存できる。 - 2 ステージの DOM:
resolveRunStageがnot_departedのとき、入庫メーターと給油 3 列を DOM に出さない。出庫は点検 + 必須出庫メーター + 出庫時刻、running/returnedは給油 3 列 + 必須入庫メーター + 帰庫点検 + 帰庫時刻を保存する。走行距離はlog.distanceKm(生成列)の表示だけで、 手入力させない。点検未チェックでも打刻はブロックせず、警告から連絡事項欄へ移動できるようにする。 - 提出の導出: 提出済みは、その日その driver の
vehicle_daily_logsにreturned_atが 1 件以上 あること。1 日 2 台は 1 台以上の帰庫で提出済みとする。事務の提出状況の母集合はその日に配車 stop がある driver(配車がなくても当日vehicle_daily_logsがある driver を含む)。取得失敗を未記録へ 化かさず、エラー表示にする。 - 業務日・車両:
resolveBusinessDay、filterMyFactoryVehicles、専用車優先の既定選択を維持する。 他人の当日行では出庫・帰庫・点検 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 nullを42501のrun window requires a departure firstで拒否する。staff は代行入力のためこの guard の対象外。vdl_g_check_driver_tenantはvdl_fill_from_vehicleの後、driver guard の前に全ロールで発火し、 非 NULL のdriver_idが同じtenant_idのprofilesに属することを検証する(既存 legacy 行の NULL は postgres/DEFINER fixture と claim 対象としてのみ残る)。新規 client INSERT は staff/driver とも担当必須。 - 恒久チェック: proof
scripts/proofs/mig179-daily-log-inspection.sqlを既存 proof と全流しし、 I179-1〜13 と mig180 proofscripts/proofs/mig180-run-window-order.sqlの O180-SENT/1〜6 を含め[NG]=0かつ psql exit 0 を確認する。web はrun-stage.test.ts・submission-status.test.ts・db/driver-daily-report.test.ts・schemas/vehicle-daily-log.test.ts・mappers/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'orcan_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.sql・
scripts/proofs/mig187-run-window-timestamp-trust.sql・apps/web/src/lib/driver/run-stage.ts・
apps/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_sourceはdriver_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 DEFINERのsoft_delete_vehicle_daily_log/restore_vehicle_daily_logRPC だけが変更できる。SELECT、 提出判定、DEFINER 集計はdeleted_at is nullを必ず母集合条件に含める。UPDATE policy もUSINGとWITH 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の部分 uniquevdl_unique_vehicle_date_active(where deleted_at is null)は、削除済み履歴にキーを占有させない。Web の唯一の書込経路は SECURITY INVOKER RPCupsert_vehicle_daily_log(jsonb)であり、部分 unique に対する PostgREST.upsert(onConflict)は 42P10 クラスなので直呼びへ戻してはならない。