仕訳の削除、直したのに直ってなかった話

公開: 2026-09-15
開発日記SupabasePostgreSQLバグ修正accounting-app

ちょうぼっち(accounting-app)の仕訳一覧まわりで、9月12日から14日にかけて 削除関連の修正を3本続けて入れました。月次一括コピー編集の失敗、選択削除の 確認ダイアログ撤廃、ブロッキングalert/confirmの撤廃。どれも書き込み側の話です。

3本目まで終えた時点で、削除は直ったはずでした。ところが本番で実際に 選択削除を試すと、レスポンスは成功しているのに一覧をリロードしても 対象の仕訳が消えない。特に元々空の仕訳だと、見た目の変化がないぶん 「削除が効いていない」がそのまま体感になっていました。

まず疑ったのは書き込み側

確認ダイアログを外したPRの副作用を疑い、RPC呼び出しの前後を読み直しました。 append_journal_revision は正常に呼ばれていて、レスポンスも200。 ここで「じゃあ何が起きているんだ」と手が止まりました。

09-12#1507 月次コピー編集/削除失敗09-13#1508 確認ダイアログ撤廃09-14朝#1510 alert/confirm撤廃09-15#1511 一覧が取消を見ていない最初の3本はすべて書き込み経路。最後の1本だけ読み取り経路だった。
削除が「効いていないように見える」原因は、書き込み側3本を直し終えたあとも残っていた。

journalsは物理削除しない設計だった

コードを遡ると、仕訳テーブルは監査目的で行を物理削除しない設計になっていました。 取消操作は journal_revisionsrevision_kind = cancellation の新しいリビジョンを 追記し、journals.current_revision_id をそちらへ差し替えるだけ。元の行は残ります。

つまり「削除できたかどうか」は、journals テーブル単体を見ても分かりません。 current_revision_id が指すリビジョンの種別まで見て、初めて今そのレコードが 生きているかどうかが決まる設計でした。

一覧を出している fetchJournals を確認すると、案の定この判定がありませんでした。 一覧クエリも件数クエリも、取消済みかどうかを一切見ていない。書き込みは成功して リビジョンは正しく積まれているのに、読み取り側がそれを反映していなかったわけです。

直した内容

const { data: cancelledRevisionRows } = await supabase
  .from('journal_revisions')
  .select('id')
  .eq('company_id', companyId)
  .eq('revision_kind', 'cancellation');

const cancelledRevisionIds = (cancelledRevisionRows || []).map((r) => r.id);
if (cancelledRevisionIds.length > 0) {
  const idList = cancelledRevisionIds.map((id) => `"${id}"`).join(',');
  query = query.not('current_revision_id', 'in', `(${idList})`);
  countQuery = countQuery.not('current_revision_id', 'in', `(${idList})`);
}

取消済みリビジョンのIDを先に取得し、current_revision_id がそこに含まれる行を 一覧・件数の両方から除外します。片方だけ直すと「全120件」の表示と実際に 並ぶ行数がずれるので、必ずセットで直す必要がありました。

何が原因だったか

書き込み経路と読み取り経路が別のPRに分かれていたことです。3本目まで終えた 時点で「削除まわりは直った」という感覚になっていましたが、それは 書き込みが成功することの確認であって、画面に反映されることの確認では ありませんでした。RPCのレスポンスを見て止まらず、実際に操作して 一覧をリロードするところまでやって、ようやく気づけました。

次はどうするか

同じ「追記による取消」パターンは他のテーブルにもあります。書き込み経路の PRを出すときは、対応する一覧・件数クエリの除外条件が既にあるかをセットで 確認する。無ければ同じPRで直す。これを次からのチェック項目に入れます。

赤い糸でつながれたペンと虫眼鏡。書き込みと読み取り、両方を疑うことを表す

書き込みの成功だけを見て終わりにせず、読み取り側まで糸をたどる。 このワンテンポが、今回の4本目のPRそのものでした。

一般化した内容は 取消は追記、削除じゃない にまとめました。