最終更新: 2026/06/22 18:56
MAS-091: 月次残高試算表(T/B)の自動生成
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-091 |
| 案件名 | 月次残高試算表(T/B)の自動生成 |
| カテゴリ | データマート・経理実務 |
| Phase | P2 |
| 優先度 | ★★ |
| 所要時間目安 | 2時間 |
| 対象ファイル | 600_report/610_datamart_tb.js(新規)/ 100_config/101_sys_config.js(CONFIG_SHEET システムキー登録)/ templates/operations_sidebar.html(メニュー追加) |
| 前提案件 | MAS-093(CF実績専用モード)・MAS-095(B/Sスナップ実績専用化)で確立した「既存実績マートを唯一のデータソースとする」パターンに準拠 |
目的
11_mst_account の全勘定科目について「月初残高+当月発生(借方/貸方)+月末残高」を集計した月次残高試算表(T/B)を、既存の P/L マート(61_pl_monthly)と B/S マート(71_bs)を唯一のデータソースとして自動生成する。税理士提出用の残高確認・試算表整合チェックを手作業から解放し、月次決算の正確性担保と経理実務の効率化を同時に達成する。
現在のコード
T/B 生成機能は現状存在しない。データソースとなる実績マートは以下のファイルが生成している(600_report/602_datamart_main.js にて一括パイプライン実行):
// 600_report/602_datamart_main.js L172-178
// P/L (61-64)
let sheetPlM = Utils.getSheetByKey('PL_M_ACT', '61_pl_monthly');
let sheetPlY = Utils.getSheetByKey('PL_Y_ACT', '62_pl_ytd');
...
// B/S (71-73)
let sheetBs = Utils.getSheetByKey('BS_ACT', '71_bs');
システムキーはそれぞれ PL_M_ACT(61_pl_monthly)・BS_ACT(71_bs)。T/B 用の 67_tb_monthly は未定義。600_report/ の既存ファイルは 601_datamart_ingest 〜 609_datamart_kpi まで使用済みで、次の空き番号は 610。
修正方針
アーキテクチャ
| 項目 | 内容 |
|---|---|
| 新規ファイル | 600_report/610_datamart_tb.js |
| 新規シート | 67_tb_monthly(システムキー: TB_M_ACT、01_sys_config に登録) |
| データソース | 61_pl_monthly(P/L 科目の月次発生額)+ 71_bs(B/S 科目の月初/月末残高) |
| 非データソース | 42_trn_journal は使用しない(既存の検証済み集計ロジックを再利用して車輪の再発明と不整合リスクを回避する) |
| メニュー追加先 | templates/operations_sidebar.html §📊 マート更新 セクション(onOpen() は openOperationsSidebar を呼ぶだけで実メニューはサイドバーにある) |
処理フロー(Step 分割)
- Step 1 — UI: 対象年月の取得
SpreadsheetApp.getUi().prompt()でYYYY-MM形式の対象年月を取得する。- 不正形式(
/^\d{4}-\d{2}$/にマッチしない)はエラーダイアログを表示して中断する。
- Step 2 — 科目リストの取得
AccountRepository.findAll()で{ headers, dtos }を取得し、AccountRepository.findAsMap()で{ [科目名]: { stmt, cat } }マップを生成する。stmt(諸表区分)は003_contracts.jsの InvoiceDTO の定義通り"P/L"または"B/S"の文字列で分岐する(=== 'P/L'/=== 'B/S'の完全一致)。cat(大分類)は借方/貸方ホームポジション判定に使用する(後述)。
- Step 3 — P/L データ取得(
61_pl_monthly)Utils.getSheetByKey('PL_M_ACT', '61_pl_monthly')でシートを取得。Contracts.toDtoList(sheet.getDataRange().getValues())で{ headers, rows }に変換する(返却プロパティはrowsでありdtosではない)。- 対象年月列はヘッダー配列から
indexOf(targetYm)で特定する(列番号ハードコード禁止)。 - 科目ごとに当月の発生額(符号付き数値)を取得する。
- Step 4 — B/S データ取得(
71_bs)Utils.getSheetByKey('BS_ACT', '71_bs')で取得し、同様にContracts.toDtoList()で変換する。- 月初残高 = 前月末残高列の値、月末残高 = 対象月末列の値を取得する。
- 当月発生額 = 月末残高 − 月初残高 として計算する。
- Step 5 — T/B 組立
- 各科目について、
catから借方/貸方ホームポジションを判定する:- 資産・費用 → 借方ホーム(残高・発生額を借方列)
- 負債・純資産・収益 → 貸方ホーム(残高・発生額を貸方列)
- B/S 科目で当月発生額がマイナスの場合は、差額をホームポジションの逆サイドに計上する(資産減少→貸方発生、負債減少→借方発生)。
- 行検索には
MATCH数式をシート側に埋め込まない。GAS 側でfindLabelRowIndex_(values, label)ヘルパーを新規実装し、0 列目をスキャンしてラベル正規化(.replace(/[\s\u3000]+/g, ''))で一致する行インデックスを返す(失敗パターン #22・#24 対策)。
- 各科目について、
- Step 6 — 書き込み(冪等性)
67_tb_monthlyのデータ行(行2以降)をクリアしてから結果を一括setValues()で書き込む。- 実行のたびに全クリア+再生成で冪等性を確保する。
- Step 7 — 貸借チェック + 通知
- 借方合計 ≠ 貸方合計の場合は、差額を「貸借差額」行として最下部に追記し警告ダイアログを表示する(不整合を隠蔽せずユーザーに提示)。
- 正常時は処理結果サマリー(対象年月・計上科目数・借貸合計)をダイアログで通知し、Human-in-the-Loop で目視確認を促す。
メニュー追加
templates/operations_sidebar.html の §📊 マート更新 セクション(現状 L62-68)に以下を追記する。
<button class="btn" onclick="run('generateTrialBalance', this)">📋 月次残高試算表(T/B)生成</button>
(onOpen() 本体は openOperationsSidebar を呼ぶだけのため編集不要。実メニューはサイドバー HTML 側で定義されている。)
影響範囲
| 種別 | ファイル | 変更内容 |
|---|---|---|
| 新規 | 600_report/610_datamart_tb.js | generateTrialBalance() 公開関数と findLabelRowIndex_() ヘルパーを実装 |
| 変更 | templates/operations_sidebar.html | §📊 マート更新 に T/B 生成ボタンを1行追加 |
| 新規シート | 67_tb_monthly | 初回実行時に ss.insertSheet() で作成。以降は clearContent() で上書き |
| システムキー登録 | 01_sys_config(Constants.CONFIG_SHEET) | TB_M_ACT キー(シート名 67_tb_monthly)を手動または initConfigs 経由で登録 |
| 前提データ | 61_pl_monthly / 71_bs | 既存マート。T/B 生成前に buildBudgetTrendDataMart() が実行済みであることが前提 |
注意事項
- 列参照はヘッダー名ベース。列番号ハードコード禁止(CLAUDE.md 規約)。月列は
headers.indexOf(targetYm)で動的に特定する。 getLastColumn()による動的列範囲取得は使用しない。書式のみの空列が含まれ意図しない列膨張が起きる(失敗パターン #21)。対象月列はヘッダー文字列で特定する。- 年月文字列をセルに書き込む場合は
setValue("'YYYY-MM")(apostrophe 前置)または事前にsetNumberFormat('@')を適用する。YYYY-MMをそのまま書くと Sheets が減算式と誤認する(失敗パターン #23)。 - ラベル正規化には
.replace(/[\s\u3000]+/g, '')を使用する。String.trim()は全角スペース U+3000 を除去しない(失敗パターン #22)。 - シート名のハードコード禁止。
Utils.getSheetByKey(key, fallbackName)で取得する。本案件でのキーはPL_M_ACT/BS_ACT/TB_M_ACT。 AccountRepository._cacheのリセット: 科目マスタ変更後に残高がずれた場合はAccountRepository.resetCache()を先頭で呼ぶ。stmt値の完全一致:=== 'P/L'/=== 'B/S'で分岐する。部分一致やincludes()は使わない。- MATCH 数式の埋込禁止: 行特定は GAS 側の
findLabelRowIndex_()で行い、結果はsetValues()で静的書き込みする(失敗パターン #24)。
エッジケース
| 条件 | 挙動 | 理由 |
|---|---|---|
対象年月の P/L データが 61_pl_monthly に存在しない(対象月列が見つからない) | エラーダイアログ「対象年月 YYYY-MM の P/L データが見つかりません。先に『財務3表の更新』を実行してください。」を表示して処理中断 | データなしで誤った T/B を生成しない |
71_bs シートが未生成または Utils.getSheetByKey() で取得不可 | エラーダイアログ「71_bs が存在しません。先に『財務3表の更新』を実行してください。」を表示して処理中断 | B/S マートが前提条件であることを明示 |
科目マスタ(11_mst_account)に存在するが P/L・B/S 両シートに未出現 | 月初残高・月末残高・当月発生額をすべて 0 として T/B 行を生成(行はスキップしない) | 科目マスタ基準で網羅的に表示する |
| B/S 科目の当月発生額(月末残高 − 月初残高)がマイナス | 差額をホームポジションの逆サイド(資産減少→貸方発生、負債減少→借方発生)に計上 | 複式簿記の原則に従い借方/貸方を正しく分離 |
P/L 科目で cat(大分類)が空文字 | 警告ログに記録しつつ、stmt='P/L' であれば「収益」か「費用」を cat から再判定できない場合はデフォルト借方(費用扱い)に計上 | マスタ不備を運用者にフィードバックする |
| 借方合計 ≠ 貸方合計(貸借不一致) | 差額を「貸借差額」行として最下部に追記し、警告ダイアログ「⚠️ 貸借不一致 差額=¥NNN」を表示 | 不整合を隠蔽せずユーザーに提示する |
対象年月として不正な文字列(例: 2026/4、26-04、空文字) | バリデーションエラーダイアログ「対象年月は YYYY-MM 形式で入力してください。」を表示して中断 | ^\d{4}-\d{2}$ 以外を受け付けない |
67_tb_monthly シートが既に存在する場合の再実行 | データ行(行2以降)を clearContent() してから書き込み。ヘッダー行と書式は維持 | 冪等性確保。書式・フィルタを毎回再設定する必要がない |
stmt が "P/L" / "B/S" のいずれでもない科目(例: 空文字、"-") | スキップして警告ログに記録 | 科目マスタ未整備の科目を T/B に混入させない |
実データ検証
実装前に MCP 経由で下記を確認する:
11_mst_accountのstmt(諸表区分)列の実格納値パターン:003_contracts.jsの InvoiceDTO 定義では"P/L" | "B/S"想定。実データに空文字や表記ゆれ("PL"など)が混入していないか確認する。11_mst_accountのcat(大分類)列の実格納値パターン: 借方/貸方ホームポジション判定用。資産/負債/純資産/収益/費用 の 5 区分に対応する日本語表記を確認し、マッピングテーブルを実装前に確定する。61_pl_monthlyのヘッダー行: 科目名列(通常は A 列 or B 列)・月別金額列(2026-04などのYYYY-MM表記か、別形式か)を実測する。71_bsのヘッダー行: 月初残高・月末残高列の有無とヘッダー文字列を実測する。604_datamart_bs.jsの出力ロジック(dmBuildBsOutput_())から実シートに出力される列構造を確認し、月末残高 − 前月末残高(≒月初残高)で「当月発生額」を導出できる構造かを検証する。- 境界値データ: 売上ゼロ月・科目ゼロ月・B/S 残高が全科目で 0 の月など、エッジケースに該当するテストデータが実環境にあるか確認する。
関連ドキュメント
| 仕様書 | 関連箇所 |
|---|---|
| CLAUDE.md | 変更時の動作確認テスト: 600_report/6*_datamart_*.js → マート更新テスト。列参照はヘッダー名ベース規約 |
| E.4.1 MAS-093 CFタブ実績専用モード | 「既存マートを唯一のデータソースとする」パターンの先行案件 |
| E.4.2 MAS-095 B/Sスナップ実績専用化 | 同じく 71_bs を参照する派生マート実装例 |
| 失敗パターン #21-#24 | 数式設計で防げた落とし穴(全角スペース・YYYY-MM 減算誤認・MATCH+ARRAYFORMULA 脆弱性・getLastColumn 膨張) |
| 失敗パターン #18-#20 | 仕様書記述で防げた落とし穴(Read 裏取り不足による固有名詞誤り) |
| docs/spec/spec_bs.md | B/S 科目分類と残高計算の会計ロジック |
人間が検討すべき事項
- T/B 出力フォーマット(税理士との擦り合わせ): TODO_future.md 記載の「T/B の出力フォーマット(税理士との擦り合わせ)」に対応。本仕様書で定義するフォーマット(列順: 科目名 / 月初残高(借方・貸方)/ 当月発生(借方・貸方)/ 月末残高(借方・貸方)/ 小計行の有無)は初期案であり、顧問税理士の要求に応じて変更される可能性がある。
- B/S 科目の「月初残高」の定義: 前月末残高を採用するか、会計期初残高からの累積を採用するかは税理士と要確認。本仕様では前月末残高を採用する(初期案)。期初月(例: 開業月)は OB(期首残高)を月初残高として扱う。
cat(大分類)による借方/貸方ホームポジションの判定マッピング: 大分類の実際の値パターン(Phase 1-F/実データ検証で確認)と借方/貸方の対応表を実装前に確定する。想定マッピング:- 資産 → 借方ホーム / 負債 → 貸方ホーム / 資本(純資産)→ 貸方ホーム / 収益 → 貸方ホーム / 費用 → 借方ホーム
- 小計・合計行の粒度: 大分類単位(資産合計・負債合計等)で小計行を出すか、科目単位のフラットリストのみか、税理士の閲覧習慣を確認する。
- 対象月が未集計の場合の挙動: 現仕様では「先に財務3表の更新を実行してください」でエラー終了するが、自動で
buildBudgetTrendDataMart()を呼ぶ連鎖実行に変更するかは運用側の選好による。
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-091「月次残高試算表(T/B)の自動生成」を実装してください。
## 実行前タスク(Read で裏取り必須)
- `600_report/602_datamart_main.js` L166-189 を Read し、`Utils.getSheetByKey('PL_M_ACT', '61_pl_monthly')` / `Utils.getSheetByKey('BS_ACT', '71_bs')` のシステムキーと実シート名を確認する
- `600_report/603_datamart_pl.js` と `600_report/604_datamart_bs.js` を Read し、`61_pl_monthly` / `71_bs` に書き込まれる列構造(科目名列・月別列・月初/月末残高列のヘッダー文字列)を確認する
- `200_data/202_repository.js` の `AccountRepository.findAll()` / `findAsMap()` を Read し、返却型(`{ headers, dtos }` / `{ [科目名]: { stmt, cat } }`)を確認する
- `000_infra/003_contracts.js` の `Contracts.toDtoList()` を Read し、返却プロパティが `{ headers, rows }` であること(`dtos` ではない)を確認する
- `templates/operations_sidebar.html` の §`📊 マート更新` セクション(L62-68)を Read し、メニューボタンの追記位置と `onclick="run('関数名', this)"` の書式を確認する
- MCP で `11_mst_account` の `stmt` 列・`cat` 列の実格納値パターン、`61_pl_monthly` / `71_bs` のヘッダー行を確認する
## 修正対象ファイル
- **新規作成**: `600_report/610_datamart_tb.js`
- **変更**: `templates/operations_sidebar.html`(§`📊 マート更新` に 1 行追加)
- **手動作業**: `01_sys_config` シートに `TB_M_ACT` → `67_tb_monthly` のシステムキーを登録(または `initConfigs` 経由で一括登録)
## 実装内容
### 1. `600_report/610_datamart_tb.js` を新規作成
以下の関数を実装する:
- **`generateTrialBalance()`**(公開関数・メニューから呼び出し):
- `SpreadsheetApp.getUi().prompt()` で対象年月を取得し `^\d{4}-\d{2}$` でバリデーション
- `AccountRepository.findAsMap()` で科目マップを取得
- `Utils.getSheetByKey('PL_M_ACT', '61_pl_monthly')` / `Utils.getSheetByKey('BS_ACT', '71_bs')` でシート取得
- 各シートを `Contracts.toDtoList(sheet.getDataRange().getValues())` で `{ headers, rows }` に変換
- ヘッダー配列から対象月列のインデックスを `indexOf(targetYm)` で特定(列番号ハードコード禁止)
- 各科目について借方/貸方ホームポジションを `cat` から判定し、月初残高・当月発生(借方・貸方)・月末残高を振り分ける
- B/S は「当月発生額 = 月末残高 − 月初残高」、マイナス時は逆サイドに計上
- `Utils.getSheetByKey('TB_M_ACT', '67_tb_monthly')` でシート取得(存在しなければ `ss.insertSheet('67_tb_monthly')` で作成)
- データ行を `clearContent()` してから `setValues()` で一括書込(冪等性)
- 年月をセルに書く場合は `setValue("'" + targetYm)` または `setNumberFormat('@')` 併用
- 借方合計 ≠ 貸方合計のとき「貸借差額」行を追記+警告ダイアログ
- 正常終了時は「対象年月 / 計上科目数 / 借貸合計」のサマリーダイアログを表示
- **`findLabelRowIndex_(values, label)`**(ヘルパー・プライベート):
- 2 次元配列 `values` の 0 列目をスキャンし、ラベル正規化(`.replace(/[\s\u3000]+/g, '')`)で一致する行インデックス(1 始まり)を返す
- 見つからない場合は `-1` を返す
### 2. `templates/operations_sidebar.html` の §`📊 マート更新` に以下を追加
<button class="btn" onclick="run('generateTrialBalance', this)">📋 月次残高試算表(T/B)生成</button>
挿入位置は `savePlSnapshot` ボタンの直後(§`📊 マート更新` セクションの末尾)を推奨。
## 制約
- `42_trn_journal` から直接 T/B を計算しない。`61_pl_monthly` と `71_bs` を唯一のデータソースとする
- 列参照はヘッダー名ベース。列番号ハードコード禁止(CLAUDE.md 規約)
- `getLastColumn()` による動的列範囲取得は使用しない(失敗パターン #21)
- シートへの MATCH 数式の埋め込み禁止。行インデックスの特定は `findLabelRowIndex_()` で GAS 側で行う(失敗パターン #24)
- 年月文字列の書き込みは `setValue("'YYYY-MM")` または `setNumberFormat('@')` 併用(失敗パターン #23)
- ラベル正規化は `.replace(/[\s\u3000]+/g, '')` を使用。`String.trim()` のみは不可(失敗パターン #22)
- `stmt` の値分岐は `=== 'P/L'` / `=== 'B/S'` の完全一致のみ使用
- `onOpen()` 本体は編集しない。メニューはサイドバー HTML 側で定義されている
## エッジケース
(本仕様書の「## エッジケース」テーブル 9 項目をそのまま転記する:
P/L データなし / `71_bs` 未取得 / マスタにあるが両シート未出現 / B/S 発生額マイナス /
`cat` 空文字 / 貸借不一致 / 対象年月フォーマット不正 / 再実行(冪等性)/ `stmt` 想定外値)
## 実データ検証
実装前に MCP で:
- `11_mst_account` の `stmt` / `cat` の実格納値パターン
- `61_pl_monthly` / `71_bs` のヘッダー行(科目名列・月別金額列・月初/月末残高列の実ヘッダー文字列)
を確認し、コード側の参照名と一致させる。
## 動作確認
1. `npm run push:dev` で開発用 GAS にデプロイする
2. 開発用スプレッドシートで「🚀 BizLP → 操作パネルを開く」でサイドバーを表示
3. 事前準備: 「📊 マート更新 → 財務3表の更新」を実行し、`61_pl_monthly` / `71_bs` を最新化
4. 「📊 マート更新 → 📋 月次残高試算表(T/B)生成」を実行
5. 対象年月(例: `2026-03`)を入力し、`67_tb_monthly` シートが生成されることを確認
6. 借方合計と貸方合計が一致していること(貸借差額行が表示されないこと)を確認
7. 存在しない年月を指定してエラーダイアログが表示されることを確認
8. 同じ年月で再実行し、シートが重複せず正しく上書きされること(冪等性)を確認
9. 不正形式(例: `26/03`)を入力してバリデーションエラーが出ることを確認
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| 仕様書作成(本ドキュメント) | Claude Sonnet 4.6 | 複数ファイル横断の調査と設計判断が必要 |
| 実装 | Claude Sonnet 4.6 | 既存マートパターンの適用・findLabelRowIndex_ 等の新規ヘルパー実装・借方/貸方判定ロジックに中程度の会計知識が必要 |
| 動作確認 | 人間 | Human-in-the-Loop。税理士レビューを想定した残高整合性の目視確認 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-19 | 初版作成 |
仕様書作成プロンプト
展開して表示
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
1. **拡張思考の使い分け**: Phase 1(設計)では拡張思考をフル活用し、ファイル名形式・エッジケース一覧・Step 分割粒度・固有名詞(関数名/シート名/列名/行番号)を完全に確定させる。Phase 2(清書)の各 Step 内では拡張思考を最小限に抑え、Phase 1 で確定済みの内容の書き下しに徹する。出力途中で再考しない。
2. **テキスト報告の禁止**: 「〜を作成します」等の text のみで tool_use なしに turn を終了しない。説明は 1 文以内。直ちに tool を呼ぶ。
3. **4-5 分割の Write/Edit 実行**: 2-1(骨格)/2-2(概要〜注意事項)/2-3a(エッジケース〜人間検討事項)/2-3b(実装プロンプト〜変更履歴)/2-4(`<details>` 記録)に分割。1 回の Write/Edit は 300 行以内を目安にする。
4. **各 Step で何を書くかを具体指示**: 設計判断を Phase 2 実行時に持ち込まないよう、Phase 1 で確定した内容の清書に徹する。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
CLIエージェント「Claude Code」として、案件 S-19「月次残高試算表(T/B)の自動生成」の開発仕様書を作成してください。
開発仕様書を新規作成した後、`docs/_config.json` の `nav` 配列の適切なセクションにも必ず追記してください。
---
## Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)
以下の各ステップを順に実行し、Phase 2 の清書に必要な情報をすべて確定させること。
**Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず Read で裏取りする(失敗パターン #18-#20)。推測した瞬間に手を止めて Read。**
### 1-A: 案件定義の読み込み
`docs/_internal/TODO_future.md` を検索し、S-19 の案件名・概要・期待される効果・人間が検討すべき事項を取得する。
### 1-B: プロジェクト規約の確認
`CLAUDE.md` を読み込み、コーディング規約(列参照はヘッダー名ベース・列番号ハードコード禁止等)・ファイル番号体系・会計ロジックのルールを把握する。
### 1-C: 既存マートビルダーの調査(最重要)
まず Grep で `61_pl_monthly` および `71_bs` を検索し、これらのシートに書き込んでいる実在のファイル名を特定する。次にそのファイルを Read し、以下を確認する:
- P/L マートビルダー: `61_pl_monthly` への書き込みロジック・列構造(特に「科目名」列と月別金額列のヘッダー名)
- B/S マートビルダー: `71_bs` への書き込みロジック・列構造(特に「科目名」「年月」「残高」列のヘッダー名)
- 両シートのシステムキー名(`Utils.getSheetByKey()` の第1引数として使われているキー文字列、例: `'PLY_MTHLY'` 等)
- 既存の `600_report/` ファイル番号の使用状況(新規ファイル `60X_datamart_tb.js` の番号を確定するため)
### 1-D: データアクセス層・DTO の確認
以下のファイルを Read し、実際のシグネチャ・返却型を確認する(記憶や推測で書かない):
- `200_data/202_repository.js` — `AccountRepository.findAll()` の返却型 `{ headers: string[], dtos: Object[] }` および `AccountRepository.findAsMap()` の返却型 `{ [科目名]: { stmt: string, cat: string } }` を確認する。`stmt`(諸表区分)の実際の値パターン("P/L" / "B/S" 等の文字列)を Read で確認すること
- `000_infra/003_contracts.js` — `Contracts.toDtoList(data)` のシグネチャ(引数: `any[][]`、返却: `{ headers: string[], rows: Object[] }`)を確認する。返却プロパティ名が `rows` であること(`dtos` ではない)に注意
- `000_infra/004_utils.js` — `Utils.getSheetByKey(key, fallbackName)` のシグネチャを確認する
### 1-E: システム設定・メニュー構造の確認
`100_config/101_sys_config.js` の `onOpen()` を Read し、既存メニューの文字列リテラル・構造・追加位置を確認する。**存在しないメニュー名を造語しない**(失敗パターン #20)。新メニュー項目の挿入箇所と文言を確定させる。
### 1-F: MCP でのシート実態確認
MCP で以下を確認し、DDL コード値と実データの乖離がないかチェックする:
- `11_mst_account` シートの `stmt`(諸表区分)列の実際の格納値パターン
- `61_pl_monthly` シートのヘッダー行(列構造)
- `71_bs` シートのヘッダー行(列構造)
### 1-G: 参考仕様書の読み込み(フォーマット把握)
`docs/dev/` 配下の既存仕様書(`dev_mas-001_variance_analysis.md` または `dev_mas-093_cf_actual_only.md`)を 1 件 Read し、セクション構成とフォーマットを把握する。
---
## Phase 2: 仕様書の分割作成
出力先: `docs/dev/dev_mas-091_trial_balance.md`
**【重要】絶対に 1 回のツール呼び出しで全内容を出力せず、以下の Step に必ず分割して実行すること。**
### Step 2-1: 骨格の作成(Write、~20行)
見出しのみ。本文は空で可。以下のセクションを列挙する:
# S-19: 月次残高試算表(T/B)の自動生成
## 概要
## 目的
## 現在のコード
## 修正方針
## 影響範囲
## 注意事項
## エッジケース
## 実データ検証
## 関連ドキュメント
## 人間が検討すべき事項
## 実装プロンプト(Claude Code 用)
## 推奨実行モデル
## 変更履歴
## 仕様書作成プロンプト
### Step 2-2: 前半セクションの追記(Edit または Bash heredoc、~300行)
Phase 1 で確定した情報のみを清書する。以下を記述する:
**## 概要**(テーブル: 案件ID / カテゴリ / Phase / 優先度 / 所要時間 / 対象ファイル / 前提案件)
**## 目的**(1-3 文。「P/L・B/S の既存マートを唯一のデータソースとして月次 T/B を自動生成し、税理士提出用の残高確認作業を効率化する」旨を記述)
**## 現在のコード**(「T/B 生成機能は現状存在しない。データソースとなる `61_pl_monthly` / `71_bs` は `600_report/` 層の既存マートビルダーが生成している」旨を記述。Phase 1-C で確認した既存マートビルダーのシート書き込みロジックのスニペットと行番号を引用する)
**## 修正方針**(以下のアーキテクチャを具体化して記述する):
- **新規ファイル**: `600_report/60X_datamart_tb.js`(X は Phase 1-C で確認した空き番号)
- **新規シート**: `67_tb_monthly`(`Utils.getSheetByKey()` で参照するため `01_sys_config` にシステムキーを登録する。キー名は Phase 1-C の既存命名規則に倣って決定する)
- **データソース**: `61_pl_monthly`(P/L 科目の月次発生額)と `71_bs`(B/S 科目の月初/月末残高)。`42_trn_journal` から直接計算しない(既存の検証済みロジックを再利用して車輪の再発明と不整合リスクを回避する)
- **処理フロー**(Step 分割で記述):
- Step 1 — UI: 対象年月を `SpreadsheetApp.getUi().prompt()` で取得(Phase 1-E で確認した既存パターンに準拠)
- Step 2 — 科目リスト取得: `AccountRepository.findAll()` で全科目 DTO (`{ headers, dtos }`) を取得。`AccountRepository.findAsMap()` で `{ [科目名]: { stmt, cat } }` マップを生成し、`stmt` の値で P/L / B/S を分類する(実際の `stmt` 値は Phase 1-F で確認した値を使用)
- Step 3 — P/L データ取得: `Utils.getSheetByKey(key, '61_pl_monthly')` でシートを取得し、`Contracts.toDtoList(sheet.getDataRange().getValues())` で `{ headers, rows }` に変換する。対象年月列をヘッダー名で特定し、科目ごとの当月発生額を取得する(列番号ハードコード禁止)
- Step 4 — B/S データ取得: `Utils.getSheetByKey(key, '71_bs')` でシートを取得し同様に変換する。「月初残高(=前月末残高)」と「月末残高」を取得し、「当月発生額 = 月末残高 - 月初残高」で計算する
- Step 5 — T/B 組立: 各科目の借方/貸方ホームポジション(資産・費用→借方、負債・純資産・収益→貸方)を `cat`(大分類)から判定し、残高・発生額を借方/貸方列に振り分ける。科目名での行検索は `MATCH` 数式をシートに埋め込まず、GAS 側でヘルパー関数 `findLabelRowIndex_(values, label)` を新規実装して行インデックスを特定する(失敗パターン #24 対策。ラベル正規化は `.replace(/[\s\u3000]+/g, '')` を使用(失敗パターン #22 対策))
- Step 6 — 書き込み: `67_tb_monthly` シートをデータ行クリア後に結果を書き込む(冪等性確保。実行のたびに全クリアしてから再生成する)
- Step 7 — 貸借チェック + 通知: 借方合計 ≠ 貸方合計の場合は差額を「貸借差額」行として最下部に追記し警告ダイアログを表示する。正常時は処理結果サマリーをダイアログで通知し、ユーザーに目視確認を促す(Human-in-the-Loop)
- **メニュー追加**: `100_config/101_sys_config.js` の `onOpen()` に新項目を追加する。文言と挿入位置は Phase 1-E で確認した実在メニュー構造に準拠した文字列のみ使用する
**## 影響範囲**(変更ファイル一覧: 新規 `60X_datamart_tb.js`・変更 `101_sys_config.js`・新規シート `67_tb_monthly`・`01_sys_config` へのシステムキー登録)
**## 注意事項**(番号付きリスト):
1. 列参照はヘッダー名ベース。列番号ハードコード禁止(CLAUDE.md 規約)
2. `getLastColumn()` による動的列範囲取得は使用しない。書式のみの空列が含まれ意図しない列膨張が起きる(失敗パターン #21)。月列は Phase 1-C で確認したヘッダー名で特定する
3. 年月文字列をセルに書き込む場合は `setValue("'YYYY-MM")` または `setNumberFormat('@')` を使用する(失敗パターン #23: `YYYY-MM` をそのまま書くと Sheets が減算式と誤認する)
4. ラベル正規化には `.replace(/[\s\u3000]+/g, '')` を使用する(失敗パターン #22: `String.trim()` は全角スペース U+3000 を除去しない)
5. `61_pl_monthly` / `71_bs` のシート名は `Utils.getSheetByKey(key, fallbackName)` で取得する。シート名のハードコード禁止
6. `AccountRepository._cache` は科目マスタ変更後に `AccountRepository.resetCache()` でリセットが必要
### Step 2-3a: エッジケース〜人間が検討すべき事項の追記
(省略 — 本仕様書の「## エッジケース」以降参照)
### Step 2-3b: 実装プロンプト〜変更履歴の追記
(省略 — 本仕様書の「## 実装プロンプト」以降参照)
### Step 2-4: 仕様書作成プロンプトの記録
末尾の `## 仕様書作成プロンプト` セクションに `<details><summary>展開して表示</summary>` ブロックで、この `<instruction>` 全文を追記する。
---
## Phase 3: 保存・登録・コミット
### 3-A: `_config.json` への追記(必須)
`docs/_config.json` の `nav` 配列 §E.4(データマート・財務諸表)セクションに以下を追記する。
追記後、JSON 構文が壊れていないことを確認する。
### 3-B: `changelog.md` への追記
`docs/_internal/changelog.md` のヘッダー直後に以下を追記する。
### 3-C: コミット&プッシュ
git add → commit → push → PR 作成(main ブランチに対して)。