• #連結会計
  • #リファクタリング
  • #TypeScript
  • #テスト駆動開発
開発financial-dataメモ

はじめに

連結精算表の計算ロジックには、追加取得・段階取得・持分法など複数のパターンがある。当初、各パターンごとにデータファイルを作成していたが、計算ロジック(特に buildAccountRows 関数)が5ファイルにコピペされている状態だった。

今回、この5パターンの差分を分析し、共通部分を抽出して buildWorksheetCore 関数として統一した。

5パターンの差分分析

対象パターン

パターン持分比率処理タイプ特徴
1 子会社株式の追加取得70% → 80%連結開始仕訳あり、単純合算あり
2 段階取得(その他→連結)10% → 80%連結開始仕訳なし、段階取得差益
3 段階取得(持分法→連結)30% → 80%連結持分法修正 + 段階取得差益
4 関連会社の追加取得30% → 40%持分法単純合算なし
5 段階取得→持分法10% → 30%持分法最もシンプル

共通構造の発見

5パターンを並べて比較すると、計算フローには共通の構造があった。

個別F/S → [合算?] → [修正?] → [開始仕訳?] → 当期仕訳(1〜N本) → 連結精算表

パターンごとの違いは以下の4点に集約される。

  1. 合算するか否か — 連結は P社 + S社 を合算、持分法は P社のみ
  2. S社修正仕訳の金額 — 土地の時価評価額など
  3. 開始仕訳の有無と金額 — 2年目以降の連結で必要
  4. 当期仕訳の種類・本数・金額 — パターンごとに異なる

仕訳の「意味」(のれん償却なのか、持分法投資利益なのか)は計算結果に影響しない。影響するのは勘定科目コードと金額だけ。

buildCodeValues 関数の設計

引数: ColumnPipelineConfig

interface ColumnPipelineConfig {
  companies: CompanyValues[]          // 会社別の個別F/S数値
  simpleAggregation: boolean          // 単純合算を行うか
  subAdjustments?: Record<string, number>    // S社修正仕訳
  openingAdjustments?: Record<string, number> // 開始仕訳
  currentAdjustments: AdjustmentColumn[]     // 当期仕訳(順序付き)
}

戻り値: CodeValuesResult

interface CodeValuesResult {
  codeValues: Map<string, Record<string, number>>  // 科目code → 列ID → 金額
  colKeys: string[]                                 // 列IDの配列(順序保持)
}

内部実装のポイント

実装で重要なのは runningTotal 変数。精算表の各列を左から右へ処理しながら、累積値を追跡する。

// 連結の場合: sub-total が累積の起点
let runningTotal = subTotal  // = simpleTotal + subAdj

// 持分法の場合: P社の値がそのまま起点
let runningTotal = companies[0].values[code] ?? 0

開始仕訳があれば runningTotal を更新。

if (openingAdjustments) {
  const ajeOpen = openingAdjustments[code] ?? 0
  row['aje-open-adjusted'] = runningTotal + ajeOpen
  runningTotal = runningTotal + ajeOpen
}

当期仕訳は runningTotal に加算せず、別途 currentTotal として集計。

let currentTotal = 0
for (const adj of currentAdjustments) {
  const val = adj.values[code] ?? 0
  row[adj.columnId] = val
  currentTotal += val
}
row['aje-current-total'] = currentTotal
row['aje-total'] = runningTotal + currentTotal

createHelpers 関数

buildCodeValues が返した codeValuescolKeys を受け取り、精算表の行データ構築に必要なヘルパー関数群を生成する。

function createHelpers(
  codeValues: Map<string, Record<string, number>>,
  colKeys: string[],
): WorksheetHelpers

ヘルパー関数一覧

関数用途
cv(code)指定codeの全列の値を取得
sumCols(codes)複数codeを列ごとに合算
linearCols(specs)加重和を列ごとに計算
addFs(vals, section)F/S列を付加
negateCols(vals)全列の符号を反転
detail(section, code, name)明細行を生成
summary(section, name, vals)合計行を生成
subtotal(section, name, vals)小計行を生成
addRecords(...records)複数Recordを列ごとに加算
zeroVals全列が0のRecord
t(vals)vals['aje-total'] を取得

これらは5つの既存ファイルで100%同一のコードがコピペされていたもの。1箇所に集約できた。

列パイプラインの可視化

パターンによって生成される列が異なる。

パターン1(追加取得・開始仕訳あり)

P社 → S社 → 単純合算 → 合算 → S社修正 → 修正後 → 修正後 → 開始仕訳 → 開始計 → 調整後 → NCI損益 → のれん償却 → 追加取得 → 当期計 → 連結精算表

パターン5(持分法・最もシンプル)

P社 → 修正後 → 持分法適用 → 当期計 → 連結精算表

テストコードの作成

35テストを作成し、以下を検証。

各パターンのテスト項目

  1. 全codeのaje-totalが既存実装と一致 — 既存データセットの accountRows から値を抽出して比較
  2. 列キーの順序が正しい — 期待される列の配列と完全一致
  3. 全列でB/Sバランスが成立 — 資産合計 = 負債純資産合計
  4. 中間列の値が正しい — 土地の単純合算、S社修正後などスポットチェック
  5. のれん計算が正しい — 開始仕訳 + 償却の累積

横断テスト

const configs = [
  { name: 'パターン1', config: pattern1Config, dataset: additionalAcqDataset, ... },
  { name: 'パターン2', config: pattern2Config, dataset: stepAcquisitionDataset, ... },
  // ...
]

it.each(configs)('$name: 全codeの全列が既存実装と完全一致', ({ config, dataset }) => {
  const { codeValues, colKeys } = buildCodeValues(config)
  // 検証ロジック
})

テスト結果

✓ パターン1 子会社株式の追加取得(70%→80%)
  ✓ 全codeのaje-totalが既存実装と一致
  ✓ 列キーの順序が正しい
  ✓ 全列でB/Sバランスが成立
  ✓ 中間列の値が既存実装と一致
  ✓ のれん計算が正しい
// ... 全35テストパス

インタラクティブなテストページ

/consolidated-worksheet/function-test にテストページを作成した。

機能

  • 左サイドバーで5パターンを切り替え
  • パターン概要(持分比率、処理タイプ、時価評価の有無など)を表示
  • 入力: currentAdjustments(当期仕訳)を借方・貸方に分解して表示
  • 出力: codeValues(精算表の全セル)をテーブル表示
  • 検証: 全列のB/Sバランスチェック結果
  • 列パイプラインの可視化

実装のポイント

<script setup lang="ts">
import { buildCodeValues, createHelpers } from '~/components/consolidated-worksheet/data/build-worksheet-core'

const result = computed(() => {
  const preset = currentPreset.value
  const { codeValues, colKeys } = buildCodeValues(preset.config)
  const helpers = createHelpers(codeValues, colKeys)
  // ...
})
</script>

その他の改善

個別財務諸表ヘッダーに持分割合行を追加

持分計算表で「持分割合が何%か」がわかるよう、ヘッダー行を追加した。

持分計算表ヘッダーの3行分割

従来は1行にすべての情報を詰め込んでいたが、以下の3行に分割。

  1. 時点ラベル(X1年3月31日など)
  2. イベントラベル(支配獲得、NI按分など)
  3. 持分割合(80%など)

まとめ

5パターンにコピペされていた計算ロジックを1つの関数に統一できた。

  • buildCodeValues: 列パイプラインの値を計算
  • createHelpers: 行データ構築用のヘルパー関数群

パターンごとの違いは ColumnPipelineConfig で吸収。仕訳の「意味」ではなく「勘定科目コードと金額」だけを見て計算するため、新しいパターンが追加されても同じ関数で対応できる。

ファイル構成

apps/web/app/components/consolidated-worksheet/data/
  build-worksheet-core.ts    # 共通関数

apps/web/tests/
  build-worksheet-core.test.ts  # 35テスト

apps/web/app/pages/consolidated-worksheet/
  function-test.vue          # インタラクティブテストページ

.claude/plans/
  2026-02-03-build-worksheet-core-explained.md  # 設計ドキュメント