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

ちょうぼっち(accounting-app)の仕訳一覧まわりで、9月12日から14日にかけて 削除関連の修正を3本続けて入れました。月次一括コピー編集の失敗、選択削除の 確認ダイアログ撤廃、ブロッキングalert/confirmの撤廃。どれも書き込み側の話です。
3本目まで終えた時点で、削除は直ったはずでした。ところが本番で実際に 選択削除を試すと、レスポンスは成功しているのに一覧をリロードしても 対象の仕訳が消えない。特に元々空の仕訳だと、見た目の変化がないぶん 「削除が効いていない」がそのまま体感になっていました。
まず疑ったのは書き込み側
確認ダイアログを外したPRの副作用を疑い、RPC呼び出しの前後を読み直しました。
append_journal_revision は正常に呼ばれていて、レスポンスも200。
ここで「じゃあ何が起きているんだ」と手が止まりました。
journalsは物理削除しない設計だった
コードを遡ると、仕訳テーブルは監査目的で行を物理削除しない設計になっていました。
取消操作は journal_revisions に revision_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そのものでした。
一般化した内容は 取消は追記、削除じゃない にまとめました。