家事按分機能を追加

水道光熱費・地代家賃・通信費など、事業とプライベートが混在する支出を、事業割合分だけ経費にする「家事按分」に対応した。これがなかなか手強く、設計を何度もひっくり返すことになった。

まず考えた方式:集計時に割合を掛ける

最初に思いついたのは、Transactionに割合を持たせておいて、集計のタイミングで「経費 × 割合」を計算する方式。実装コストは低そうに見えたけど、よく考えると問題が多い。

  • サマリー、元帳、試算表、将来の消費税集計……すべての集計サービスが「按分を意識する」必要が出てくる。どこかで掛け忘れたら、帳簿と申告がズレる。
  • そもそも元帳上の経費が全額のままになるので、簿記としてそれは単純に不正確。

却下。

次に考えた方式:決算時に一括で振替仕訳を作る

年末にまとめて「家事分を事業主貸に振り替える」仕訳を1本作る方式も検討した。日々の入力は楽になるが、これも問題がある。

  • 予定取引の集計が全額経費のままになるので、期中の見通し(予実サマリー)が実態より過大に出てしまう。
  • 月ごとに按分割合を変えたい(夏は電気代の按分率を変える、など)場合に、年次の一括計算では表現しづらい。
  • 月次締めのたびに振替仕訳の生成・再生成が必要になり、締め処理が重くなる。

これも却下。

採用した方式:登録時にその場で仕訳を実分割する

結局、「入力の時点で、経費行を事業分と家事分の2行に分割して保存する」方式に落ち着いた。

例えば、電気代10,000円(税込)、事業割合60%の場合、

借方: 水道光熱費 6,000(事業分)
借方: 事業主貸   4,000(家事分)
貸方: 現金     10,000

という3行の仕訳として保存する。これなら帳簿は登録した瞬間から常に正しい金額になるし、集計サービス側は一切按分を意識しなくていい。月次締めも「検証だけのオペレーション」で済む。

遠回りしたが、「正しい状態を都度作って、参照側は何も知らなくていいようにする」という、これまでの設計(削除は物理削除でなくis_active、修正はTransactionRevisorでの改訂)と同じ方向性に戻ってきた形。

割合はどこに持たせるか

Transactionに割合を持たせない設計にした。1つの取引の中に按分対象の経費行が複数入るケース(家賃と駐車場代を1回の振込でまとめて払う、など)では、行ごとに割合が違うことがあるので、割合はJournalEntry(分割後の事業分の行)に持たせることにした。

business_ratio(1〜100の整数)に加えて、allocation_group_idというUUIDも持たせている。これは「1回の按分入力から生まれた事業分の行と家事分の行を、あとから組として復元する」ための紐付け用。行と行を直接IDで繋ぐ(FK)のではなくグループIDにしたのは、TransactionRegistrarの正規化の段階ではまだ仕訳行のidが採番されていないので、相互参照を組めないため。

business_ratioはnullと100を意図的に使い分けている。nullは「按分の仕組みを通っていない行」(貸方や収益行など)、100は「按分を通った結果、全額事業経費と判定された行」。だから、既存データを一律で100に埋める、みたいな移行はしない。

端数はどう丸めるか

10,001円を60%で按分すると、事業分は6,000.6円になって割り切れない。ここは「事業分を切り捨て」で処理することにした。国税庁のルールがあるわけではないけど、納税者有利に切り上げるのは変な話なので、機械的に切り捨てにしてテストで固定した。

予定取引の確定が一番厄介だった

一番苦労したのがここ。今までは「予定取引の確定」は、すでにある2行の仕訳(借方・貸方)をそのまま本登録用に書き換えるだけの単純な処理だった。

でも按分が入ると、1つの予定取引が3行以上の仕訳を持つことになる。かといって確定の時だけ特別なロジックで行を編集すると、通常登録の保存形とズレてしまう。

なので、予定取引の確定を専用のユースケース(PlannedTransactionConfirmer)として独立させた。確定時の入力も、分割前のraw input(gross_amount + business_ratio)として受け取り、TransactionRegistrarと同じ正規化ロジックを通して仕訳明細を丸ごと差し替える。今まであったTransactionRegistrar::confirmPlanned()やRecurringTransactionPlan::confirmTransaction()は、この新しいサービスに統合・委譲するように整理した。

これで、新規登録・予定取引の生成・予定取引の確定・仕訳の修正(改訂)が、すべて同じ「raw inputをTransactionRegistrarで正規化する」という1本のパスを通ることになった。按分ロジックをあちこちに重複実装せずに済んでいる。

割合を変更したくなったら

按分割合を後から変えたい場合は、専用の再計算機能を作らず、既存の仕訳修正(TransactionRevisor)に乗せることにした。割合の変更 = 旧仕訳を無効化して、新しい割合で改訂版を再登録、という扱い。Revisor側も自前で按分計算をせず、TransactionRegistrarの正規化を再利用して展開させている。

今回はスコープ外にしたこと

  • 固定資産の事業専用割合(車の按分など)。これは本来、決算時の振替ではなくFixedAssetに事業専用割合を持たせて、償却費の計上時に事業分だけ計上する形が正しいはずなので、固定資産側の拡張として別で扱う。
  • 補助科目ごとのデフォルト割合(入力時に「この科目はいつも60%」と自動で出す)。UIの利便機能であってモデルの正しさには関わらないので後回し。
  • 定期取引計画(RecurringTransactionPlan)の複数明細化。今は単一の借方・貸方前提のままで、その1明細に対する按分だけに対応。
  • 月次締めの詳細設計。登録時分割方式なら締めは検証だけで済むはず、という前提だけ置いて、詳細は別途。

まとめ

按分は「取引ごとの入力時点での分割」として実装した。集計側には一切手を入れず、入力の正規化層(TransactionRegistrar)に按分展開を一元化することで、新規登録・予定生成・予定確定・修正のすべてで一貫した扱いにできた。設計を2回却下してからのこの形なので、思ったよりも骨が折れた。