登録済みの仕訳を直接書き換えるのではなく、「改訂」として扱う修正機能を追加した。
削除機能の時と同じ考え方で、過去のデータを消してしまうと後から追えなくなるので、旧仕訳は無効化して残し、修正後の内容は新しい仕訳として登録する。新旧はrevised_from_transaction_idで繋げる。
見た目は1件の修正でも、内部的には「旧版の無効化 + 新版の作成」になる。
修正できるのは、今のところ金額と勘定科目(実装上はsub_account_id)だけ。日付・摘要・取引先などはひとまず対象外で、旧取引の値をそのまま新版に引き継ぐ。
修正の単位もJournalEntry1行ではなくTransaction全体にした。借方だけ・貸方だけを部分更新できるようにすると、貸借一致や税区分の整合が簡単に壊れるため。
入口はTransactionRegistrarとは別にTransactionRevisorを新設。TransactionRegistrarに更新の責務まで混ぜたくなかったので、期首仕訳の時(OpeningEntryRegistrar)と同じ方針で専用サービスに切り出す。
Revisorがやること
- 修正対象が修正可能な状態か判定
- 入力のバリデーション
- 旧取引をロックして再取得し、二重修正を防ぐ
- TransactionRegistrarを使って改訂版を新規作成
- 旧取引をdeactivate()
- 全部を1つのDBトランザクションにまとめる
修正対象外にした取引
- 期首仕訳
- 予定取引
- 決算整理仕訳
- 定期取引計画由来の取引
- クレジットカード取込由来の取引
- 減価償却仕訳が紐づいている取引
- 決算済み年度に属する取引
これらは発生源ごとに業務ルールが強いので、通常仕訳の修正と一緒くたにしない。必要になったら別APIで対応する。
並行修正のガードは二段構え。DBトランザクション内でlockForUpdate()して無効化済みなら弾くのに加えて、revised_from_transaction_idにunique制約を張って、万一すり抜けても2件目の改訂作成をDB側で失敗させる。
監査のために、旧版にはdeactivated_by(誰が)とdeactivation_reason(修正による改訂、固定文言)、新版にはcreated_by と revision_reason(ユーザー入力の修正理由)を記録する。
伝票番号(entry_number)は改訂版で新しく採番される。旧版の番号が引き継がれるわけではないので、修正のタイミング次第で日付順と番号順がずれることがあるが、採番ロジックに特例を作るコストの方が高いと判断して、初期実装ではこのまま。
削除機能と修正機能、どちらも「状態遷移+履歴保持」という同じ流儀で統一できた。