MAS-113: 株主資本等変動計算書の科目マスタ動的参照化
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-113 |
| カテゴリ | データマート |
| Phase | P2 |
| 優先度 | ★★ |
| 所要時間 | 30分 |
| 対象ファイル | 600_report/604_datamart_bs.js(dmBuildEquityChanges_() のみ) |
| 前提案件 | MAS-112(新規科目3件のマスタ登録:194 開業費・301 資本準備金・217 役員借入金) |
目的
株主資本等変動計算書のレンダリング関数 dmBuildEquityChanges_() で 科目リストがハードコードされている ため、以下の課題が発生している。
- 科目マスタとの乖離リスク —
11_mst_accountに新規純資産科目(例:その他資本剰余金、自己株式、評価・換算差額等)を追加しても株主資本等変動計算書には反映されない。 - 無効化した科目が表示され続ける — MAS-112 で追加した
資本準備金は現時点で残高 0 だが、計算書の列として常時表示されている。逆に、将来「有効フラグ=FALSE」に切り替えても列が消えない。 - 保守コスト — 科目追加のたびに
604_datamart_bs.jsのコードを更新する必要がある。
本案件では AccountRepository.findAll() を用いて科目マスタから純資産の部の有効科目を動的に取得し、ハードコード配列を置き換えることで、科目マスタ単一データソースの原則 を徹底する。
現在のコード
問題箇所: 600_report/604_datamart_bs.js L259-318 dmBuildEquityChanges_()
L296 の出力配列ヘッダーで 科目列がハードコード されている('資本金', '資本準備金', '繰越利益剰余金')。また L297-302 の各行で 資本準備金 の値として常に 0 がハードコードされている。
// 600_report/604_datamart_bs.js L259-
function dmBuildEquityChanges_(ctx, sheet) {
var bsBal = ctx.bsBal;
var plProfitBal = ctx.plProfitBal;
var startYear = ctx.startYear;
// 純資産の各項目: 期首残高(OB=index 0) と 期末残高(index 12)
var capOb = 0, capEnd = 0; // 資本金
if (bsBal['eq_cap']) {
for (var acc in bsBal['eq_cap']) { capOb += bsBal['eq_cap'][acc][0]; capEnd += bsBal['eq_cap'][acc][12]; }
}
// …(中略)…
var out = [
['株主資本等変動計算書', '(' + fy + ')', '', '', ''],
['', '', '', '', ''],
['項目', '資本金', '資本準備金', '繰越利益剰余金', '純資産合計'], // ← L296 ハードコード
['当期首残高', capOb, 0, retOb, eqOb], // ← L297
['当期変動額', '', '', '', ''],
[' 当期純利益', '-', '-', netIncome, netIncome],
[' 増資', capChange, 0, '-', capChange], // ← L300
['当期変動額合計', capChange, 0, retChange, eqChange], // ← L301
['当期末残高', capEnd, 0, retEndTotal, eqEnd], // ← L302
];
// …(以下、レンダリング処理)
}
呼び出し元: 600_report/602_datamart_main.js L328-331
// 株主資本等変動計算書 (75)
var sheetEq = ss.getSheetByName('75_ss_equity_changes');
if (!sheetEq) { try { sheetEq = ss.insertSheet('75_ss_equity_changes'); } catch(e) {} }
if (sheetEq) dmBuildEquityChanges_(ctx, sheetEq);
呼び出し側は変更不要。dmBuildEquityChanges_() 内部のみ修正する。
修正方針
1. AccountRepository.findAll() で科目マスタを取得
200_data/202_repository.js の AccountRepository.findAll() を呼び出す。戻り値は { headers: string[], dtos: Object[] } 形式 のため、必ず .dtos プロパティを参照する(findAsMap() と異なりキャッシュなし・毎回最新読込)。
var acctResult = AccountRepository.findAll(); // { headers, dtos }
2. dtos を .filter() で純資産科目に絞り込み
以下の AND 条件で絞り込む:
| 列名 | 条件 | 備考 |
|---|---|---|
諸表区分 | === 'BS' または === 'B/S' | 11_mst_account の実データは BS(D-02 マイグレーション記録より)。dmProcessAllEvents_() L44 が `"B/S" |
大分類 | === '資本' | D-02 マイグレーション記録(301 資本準備金: 大分類=資本)と整合。dmGetBsSectionId_() L30 フォールバックも c === '資本' → eq_cap で同値 |
有効フラグ | !(flag === false || String(flag).toUpperCase() === 'FALSE') | AccountRepository.findAsMap() L330 の有効フラグ判定パターンと完全一致させる |
3. ソート: 主科目コード 昇順(フォールバック)
11_mst_account の DDL ヘッダー (101_sys_config.js L644) は以下の 11 列で、表示順 列は存在しない:
有効フラグ, 主科目コード, 諸表区分, 大分類, 表示区分,
表示科目, 正式科目名, 固変区分, デフォルト税率, 科目名, 説明
したがって 主科目コード 昇順 をデフォルトのソートキーとする。将来 表示順 列が追加された場合に備えて「列が存在すれば優先、なければ 主科目コード 昇順」のフォールバックロジックを実装する。
4. 科目名配列を生成
var acctResult = AccountRepository.findAll(); // { headers, dtos }
var equityAccounts = acctResult.dtos
.filter(function(dto) {
var flag = dto['有効フラグ'];
var stmt = String(dto['諸表区分'] || '').trim();
var cat = String(dto['大分類'] || '').trim();
return (stmt === 'BS' || stmt === 'B/S')
&& cat === '資本'
&& !(flag === false || String(flag).toUpperCase() === 'FALSE');
})
.sort(function(a, b) {
// 表示順列が存在する場合は昇順、なければ主科目コード昇順
var ka = a['表示順'] != null && a['表示順'] !== '' ? a['表示順'] : a['主科目コード'];
var kb = b['表示順'] != null && b['表示順'] !== '' ? b['表示順'] : b['主科目コード'];
return ka < kb ? -1 : ka > kb ? 1 : 0;
})
.map(function(dto) { return String(dto['科目名'] || '').trim(); });
5. ハードコード配列を動的配列に差し替え
出力配列 out の各行を equityAccounts をベースに動的生成する。繰越利益剰余金 と 純資産合計 列は既存どおり保持(繰越利益剰余金 は P/L 連動ロジックがあるため特別扱い、純資産合計 は全列の合算)。
// 各純資産科目の期首/期末残高を取得 (bsBal['eq_cap'] に科目別で格納されている)
var perAcctOb = {}, perAcctEnd = {};
equityAccounts.forEach(function(accName) {
var ob = 0, end = 0;
if (bsBal['eq_cap'] && bsBal['eq_cap'][accName]) {
ob = bsBal['eq_cap'][accName][0];
end = bsBal['eq_cap'][accName][12];
}
perAcctOb[accName] = ob;
perAcctEnd[accName] = end;
});
// 変動額
var perAcctChange = {};
equityAccounts.forEach(function(accName) {
perAcctChange[accName] = perAcctEnd[accName] - perAcctOb[accName];
});
// 純資産合計 (資本系科目 + 繰越利益剰余金)
var capObTotal = equityAccounts.reduce(function(s, a) { return s + perAcctOb[a]; }, 0);
var capEndTotal = equityAccounts.reduce(function(s, a) { return s + perAcctEnd[a]; }, 0);
var capChangeTotal = capEndTotal - capObTotal;
var eqOb = capObTotal + retOb;
var eqEnd = capEndTotal + retEndTotal;
var eqChange = eqEnd - eqOb;
// 出力配列を動的に組み立て
var headerRow = ['項目'].concat(equityAccounts).concat(['繰越利益剰余金', '純資産合計']);
var obRow = ['当期首残高'].concat(equityAccounts.map(function(a) { return perAcctOb[a]; })).concat([retOb, eqOb]);
var changeHdr = ['当期変動額'].concat(equityAccounts.map(function() { return ''; })).concat(['', '']);
var niRow = [' 当期純利益'].concat(equityAccounts.map(function() { return '-'; })).concat([netIncome, netIncome]);
var issueRow = [' 増資'].concat(equityAccounts.map(function(a) { return perAcctChange[a]; })).concat(['-', capChangeTotal]);
var sumChgRow = ['当期変動額合計'].concat(equityAccounts.map(function(a) { return perAcctChange[a]; })).concat([retChange, eqChange]);
var endRow = ['当期末残高'].concat(equityAccounts.map(function(a) { return perAcctEnd[a]; })).concat([retEndTotal, eqEnd]);
var out = [
['株主資本等変動計算書', '(' + fy + ')'].concat(Array(equityAccounts.length).fill('')),
Array(headerRow.length).fill(''),
headerRow,
obRow,
changeHdr,
niRow,
issueRow,
sumChgRow,
endRow,
];
レンダリング処理 (L305-317) の rows・cols は out から算出しているため列数は自動追従する。
影響範囲
| レイヤー | 変更対象 | 影響 |
|---|---|---|
| データマート | 600_report/604_datamart_bs.js の dmBuildEquityChanges_() のみ | 内部実装の差し替え。シグネチャ((ctx, sheet))は維持 |
| Repository | 200_data/202_repository.js | 変更なし。既存 AccountRepository.findAll() を利用 |
| 呼び出し元 | 600_report/602_datamart_main.js L328-331 | 変更なし |
| 出力タブ | 75_ss_equity_changes | 列数が科目マスタ依存で可変化する。既存の列幅設定(L317 の for (var c = 2; c <= cols; c++))が cols ベースのため追加対応不要 |
| 排他制御 | なし | 親関数 buildBudgetTrendDataMart() の既存ロックで保護される |
注意事項
AccountRepository.findAll()はキャッシュを持たない —findAsMap()と異なり_cacheを経由しないため、マート更新のたびに最新のマスタを読み込む。これは意図した動作(マート更新 = データ最新化の操作)であり問題なし。- 有効フラグ判定の統一 —
flag === false || String(flag).toUpperCase() === 'FALSE'パターンをfindAsMap()L330 と完全一致させる。チェックボックス(boolean)とテキスト("FALSE")の両方を受容する既存慣習を守る。 諸表区分の実格納値はBS— D-02 マイグレーション記録より確定。しかしdmProcessAllEvents_()L44 の既存コードは"B/S" || "BS"を両方受容しているため、本実装でも両方許容して堅牢性を保つ。表示順列は現時点で存在しない —主科目コード昇順をフォールバックとして採用する。「純資産の部に表示する科目の順序ルール」は 人間が検討すべき事項 のトップアジェンダ(後述)。繰越利益剰余金は本改修の対象外 —bsBal['eq_ret']セクション(P/L 連動の当期純利益を含む)で別途処理されており、ハードコードではなく既存のループ処理で動的集計済み。L296-302 の「繰越利益剰余金」列は保持する。- 「資本準備金」の残高は科目マスタに登録されていれば自動反映 — MAS-112 で
301 資本準備金は既に11_mst_accountに登録済み(有効フラグ=TRUE,諸表区分=BS,大分類=資本)。本改修後、仕訳データで資本準備金が発生すれば自動で列表示+金額集計される。
エッジケース
| # | 条件 | 期待動作 | 理由 |
|---|---|---|---|
| E01 | 有効な純資産科目が 0 件(全科目が有効フラグ=FALSE、または 11_mst_account に登録なし) | 計算書のヘッダー列は ['項目', '繰越利益剰余金', '純資産合計'] のみで描画。資本系のデータ行(当期首残高 以下)も 繰越利益剰余金 と 純資産合計 列のみ表示。エラーにしない | 科目未登録はスタートアップ初期等で起こりうる正常系。繰越利益剰余金 は P/L 連動のため常時表示 |
| E02 | 純資産科目は存在するが仕訳データ(32_wrk_invoice / 33_wrk_bank)が 0 件 | 科目列は描画。期首・変動・期末の金額は 0 で表示 | 科目構造と仕訳データは独立して管理。科目マスタ登録済み = 計算書に表示すべき |
| E03 | 純資産のマイナス残高(評価差額金、自己株式控除額 等) | 符号そのままで計算・表示(△表示は setNumberFormat('#,##0;[Red]△ #,##0;"-"') L314 の書式に委ねる) | 意図的なマイナス値を切り捨てない。自己株式は資本控除項目として負値が正常 |
| E04 | 表示順 列が存在しない(現時点の DDL) | 主科目コード 昇順でフォールバックソート。dto['表示順'] の参照は null/undefined を安全に処理 | 本仕様のデフォルト挙動。DDL に 表示順 列が追加された場合は自動で昇順ソートに切り替わる |
| E05 | 主科目コード が文字列と数値で混在(例: '101' と 101) | String() で正規化してから比較するか、</> 演算子で自動比較に委ねる。実害はほぼ無いが安定ソートを担保 | 11_mst_account の実データは文字列格納だが、スプレッドシート編集で数値化するケースを想定 |
| E06 | 同一 科目名 が複数行登録(重複登録事故) | 両行が列として出現する(重複排除は行わない) | データ整合性の問題は 201_data_validator.js の責務。データマート側で隠蔽しない(可視化して問題検知させる) |
| E07 | 大分類='資本' だが 諸表区分='P/L' 等の不整合データ | 諸表区分 フィルタで除外される(AND 条件) | マスタ登録ミスを広げない。整合性エラーは別タブ(データ検証)で検出する責務 |
| E08 | 科目名が空文字列(科目名 列ブランク) | .filter() で除外はしないが、.map() の String(dto['科目名'] || '').trim() で空文字化。スプレッドシート出力時は空列として描画 | 通常ありえないケースだが防御的に処理。空行はバリデーション側で警告される想定 |
実データ検証
11_mst_account の実データパターン(D-02 マイグレーション記録より)
docs/_internal/data_maintenance.md の D-02 レコード(2026-04-14 完了)より、301 資本準備金 の実データは以下:
| 列 | 値 |
|---|---|
| 有効フラグ | TRUE |
| 主科目コード | 301 |
| 諸表区分 | BS |
| 大分類 | 資本 |
| 表示区分 | 純資産 |
| 表示科目 | 資本準備金 |
| 正式科目名 | 資本準備金 |
| 固変区分 | 対象外 |
| デフォルト税率 | 対象外 |
| 科目名 | 資本準備金 |
DDL 定義との整合確認
101_sys_config.jsL644 の DDL ヘッダー定義と D-02 実データは整合する諸表区分の許容値はBSまたはB/S(既存コードdmProcessAllEvents_()L44 で両方受容)大分類の許容値は資本で確定(D-02 より)表示区分は純資産(DDL 辞書 L1040 ではDSP_EQコードに対応)表示順列は現 DDL に未定義 — 本実装では主科目コード昇順フォールバックを採用
変換後の動作検証ポイント
- 改修前後で
75_ss_equity_changesの出力が 同一条件下で同一結果 となること(既存登録済み純資産科目:資本金,資本準備金の 2 件のみの状態で) - MAS-112 で登録済みの
資本準備金が有効フラグ=TRUE 時に列として出現すること - 有効フラグを FALSE に変更すると列から消えること
関連ドキュメント
| ドキュメント | 関連内容 |
|---|---|
| MAS-112 新規科目3件のマスタ登録 | 本改修の前提。301 資本準備金 がマスタ登録済みであること |
| MAS-111 科目マスタ説明列追加 | 11_mst_account DDL の最新構造(説明 列追加済み) |
| MAS-091 月次残高試算表(T/B) | 11_mst_account の 大分類 実格納値パターン確認時の参照先 |
| MAS-095 B/Sスナップ実績専用化 | B/S 系データマートの実装パターン |
| ADR-0002 仕訳エンジンとデータマートの分離 | マート層での Repository 直参照の方針 |
| SME 会計指針 第68-72項 | 株主資本等変動計算書の会計基準 |
600_report/604_datamart_bs.js | 改修対象ファイル |
200_data/202_repository.js | AccountRepository.findAll() インターフェース |
人間が検討すべき事項
1. 純資産の部に表示する科目の順序ルール(TODO_future.md 記載事項)
本仕様では 主科目コード 昇順 をデフォルトのソート規則とした。以下の業務判断が必要:
- これで業務上問題ないか — 現状は
資本金(201?)→資本準備金(301)の順になる見込み。一般的な B/S 表示順(資本金 → 資本剰余金 → 利益剰余金)と整合するか確認 - 将来
表示順列を導入すべきか — DDL 変更を伴うが、会計基準が要求する表示順序(例: 自己株式は最後、評価・換算差額等は最後、等)を柔軟に制御したい場合は検討価値あり - 主科目コードの採番規則 — 純資産科目の採番が会計基準順に沿うよう統制されているかを資本準備金以外の科目登録前に確認
2. 諸表区分 の表記ゆれを統一すべきか
現状 dmProcessAllEvents_() L44 が "B/S" || "BS" を両方受容しているため、本実装もこれに倣った。長期的には BS に統一 するか B/S に統一 するかをデータ整備タスク(D-XX)として切り出すのが望ましい。
3. 動作確認フロー(目視確認手順)
npm run push:dev 後、スプレッドシートの 右側サイドバー「🚀 BizLP」→「操作パネルを開く」 を経由して以下を実施する:
- 現状レンダリング確認 —
📊 マート更新セクションの 「財務3表の更新」ボタン(buildBudgetTrendDataMart)を押下。75_ss_equity_changesシートを開き、ヘッダー行に資本金と資本準備金が両方表示されることを確認。 - 有効フラグ=FALSE で列が消えること —
11_mst_accountで資本準備金の行の有効フラグを FALSE に変更 → 「財務3表の更新」を実行 →75_ss_equity_changesのヘッダーから資本準備金列が消えることを確認。 - 有効フラグ=TRUE に戻す — フラグを TRUE に戻す → 「財務3表の更新」を実行 →
資本準備金列が再表示されることを確認。 - 新規純資産科目の追加動作 — テスト用純資産科目を
11_mst_accountに追加(例:有効フラグ=TRUE,主科目コード=302,諸表区分=BS,大分類=資本,表示区分=純資産,科目名=その他資本剰余金)→ 「財務3表の更新」を実行 → 計算書ヘッダーにその他資本剰余金列が追加され、資本金→資本準備金→その他資本剰余金の順(主科目コード昇順)で表示されることを確認。 - テスト用科目の無効化 — 4. で追加したテスト用科目の
有効フラグを FALSE に変更 → 「財務3表の更新」を実行 → 列が消えることを確認。 - 金額の整合性 — 改修前後で
資本金列の当期首残高/当期末残高が変わらないことを確認(リグレッションチェック)。
4. 会計基準の追加要件の取り込み範囲(将来)
会社法・中小企業会計指針は「評価・換算差額等」「自己株式」「新株予約権」等の区分表示を要求する場合がある。本改修は 純資産の部全体を動的化 するため、将来これらの科目を 11_mst_account に登録するだけで計算書に反映される(コード修正不要)。
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-113「株主資本等変動計算書の科目マスタ動的参照化」を実装してください。
## 実行前タスク
1. 仕様書「現在のコード」セクションに記載された `600_report/604_datamart_bs.js`
の `dmBuildEquityChanges_()` (L259-318) を Read で確認する。
2. `200_data/202_repository.js` の `AccountRepository.findAll()` (L315-317)
を Read し、戻り値が `{ headers: string[], dtos: Object[] }` であることを確認する。
併せて `findAsMap()` (L323-341) の有効フラグ判定パターン
`flag === false || String(flag).toUpperCase() === 'FALSE'` を確認する。
3. `100_config/101_sys_config.js` L644 の MST_ACCT DDL ヘッダー定義で
`表示順` 列が**存在しないこと**を再確認する(フォールバックロジックの正当性)。
4. MCP または目視で `11_mst_account` の実データを参照し、
`諸表区分` の実格納値("BS" か "B/S" か)と、
`大分類` の純資産科目における実格納値("資本" と想定)を確認する。
乖離があれば仕様書「修正方針」セクションのフィルタ条件を更新して実装する。
## 修正対象ファイル
`600_report/604_datamart_bs.js` の `dmBuildEquityChanges_()` 1 関数のみ。
`200_data/202_repository.js` ・`000_infra/002_constants.js` ・
`600_report/602_datamart_main.js` ・他の 600_report/ ファイルは変更しない。
## 実装内容
1. `dmBuildEquityChanges_()` 冒頭の `capOb / capEnd` 集計の直前に、以下を挿入する:
```javascript
var acctResult = AccountRepository.findAll(); // { headers, dtos }
var equityAccounts = acctResult.dtos
.filter(function(dto) {
var flag = dto['有効フラグ'];
var stmt = String(dto['諸表区分'] || '').trim();
var cat = String(dto['大分類'] || '').trim();
return (stmt === 'BS' || stmt === 'B/S')
&& cat === '資本'
&& !(flag === false || String(flag).toUpperCase() === 'FALSE');
})
.sort(function(a, b) {
// 表示順列が存在する場合は昇順、なければ主科目コード昇順
var ka = (a['表示順'] != null && a['表示順'] !== '') ? a['表示順'] : a['主科目コード'];
var kb = (b['表示順'] != null && b['表示順'] !== '') ? b['表示順'] : b['主科目コード'];
return ka < kb ? -1 : ka > kb ? 1 : 0;
})
.map(function(dto) { return String(dto['科目名'] || '').trim(); });
```
2. 既存の `capOb / capEnd` 集計ロジック(`for (var acc in bsBal['eq_cap'])`)を
`equityAccounts` ベースの per-account 集計に置き換える:
```javascript
var perAcctOb = {}, perAcctEnd = {}, perAcctChange = {};
equityAccounts.forEach(function(accName) {
var ob = (bsBal['eq_cap'] && bsBal['eq_cap'][accName]) ? bsBal['eq_cap'][accName][0] : 0;
var end = (bsBal['eq_cap'] && bsBal['eq_cap'][accName]) ? bsBal['eq_cap'][accName][12] : 0;
perAcctOb[accName] = ob;
perAcctEnd[accName] = end;
perAcctChange[accName] = end - ob;
});
var capObTotal = equityAccounts.reduce(function(s, a) { return s + perAcctOb[a]; }, 0);
var capEndTotal = equityAccounts.reduce(function(s, a) { return s + perAcctEnd[a]; }, 0);
var capChangeTotal = equityAccounts.reduce(function(s, a) { return s + perAcctChange[a]; }, 0);
```
従来の `capOb / capEnd / capChange` スカラー変数は `capObTotal / capEndTotal / capChangeTotal`
に統合する(`retOb / retEnd / retEndTotal / retChange / netIncome` はそのまま残す)。
3. `eqOb / eqEnd / eqChange` の再計算を反映する:
```javascript
var eqOb = capObTotal + retOb;
var eqEnd = capEndTotal + retEndTotal;
var eqChange = eqEnd - eqOb;
```
4. 出力配列 `out` を動的に組み立てる(科目列 + `繰越利益剰余金` + `純資産合計`):
```javascript
var headerRow = ['項目'].concat(equityAccounts).concat(['繰越利益剰余金', '純資産合計']);
var obRow = ['当期首残高'].concat(equityAccounts.map(function(a) { return perAcctOb[a]; })).concat([retOb, eqOb]);
var changeHdr = ['当期変動額'].concat(equityAccounts.map(function() { return ''; })).concat(['', '']);
var niRow = [' 当期純利益'].concat(equityAccounts.map(function() { return '-'; })).concat([netIncome, netIncome]);
var issueRow = [' 増資'].concat(equityAccounts.map(function(a) { return perAcctChange[a]; })).concat(['-', capChangeTotal]);
var sumChgRow = ['当期変動額合計'].concat(equityAccounts.map(function(a) { return perAcctChange[a]; })).concat([retChange, eqChange]);
var endRow = ['当期末残高'].concat(equityAccounts.map(function(a) { return perAcctEnd[a]; })).concat([retEndTotal, eqEnd]);
var titleRow = ['株主資本等変動計算書', '(' + fy + ')'].concat(Array(equityAccounts.length + 2).fill(''));
// titleRow の長さを headerRow と揃える(先頭2セル埋め済み、残りは空文字)
// Array(equityAccounts.length + 2) = 科目列数 + 繰越 + 純資産合計 - 既埋2 = len - 2 → 要調整
// 安全策: headerRow.length と同じ長さになるように ''パディングする:
while (titleRow.length < headerRow.length) titleRow.push('');
while (titleRow.length > headerRow.length) titleRow.pop();
var out = [
titleRow,
Array(headerRow.length).fill(''),
headerRow,
obRow,
changeHdr,
niRow,
issueRow,
sumChgRow,
endRow,
];
```
5. `equityAccounts` が空配列の場合もエラーにせず処理続行(エッジケース E01)。
`out` は `['項目', '繰越利益剰余金', '純資産合計']` の 3 列となる。
## 制約
- Repository 層 (`202_repository.js`) ・Constants (`002_constants.js`) ・
他のデータマートファイルは変更しない。
- 列番号ハードコード禁止。`dto['科目名']` 等のヘッダー名ベースで参照する。
- `繰越利益剰余金` 列は本改修の対象外。既存の `bsBal['eq_ret']` 集計ロジックを温存する。
- `_cache` は触らない。`AccountRepository.findAll()` はキャッシュなしが正しい動作。
## エッジケース
仕様書「エッジケース」セクションの E01-E08 を参照。特に:
- E01: 有効な純資産科目が 0 件でもエラーにしない
- E03: マイナス残高は符号そのまま(△表示は既存書式に委ねる)
- E04: `表示順` 列は現 DDL になし → `主科目コード` 昇順フォールバック
## 動作確認
1. `npm run push:dev` で開発環境にデプロイ。
2. スプレッドシートの **🚀 BizLP → 操作パネルを開く → 📊 マート更新 → 「財務3表の更新」**
ボタン(`buildBudgetTrendDataMart`)からマートを更新。
3. `75_ss_equity_changes` シートを開き、科目列が `11_mst_account` の
純資産科目(`諸表区分=BS, 大分類=資本, 有効フラグ=TRUE`)と一致することを目視確認。
4. 仕様書「人間が検討すべき事項」セクション 3. の動作確認手順 1〜6 を実施。
5. 改修前に撮っておいた `75_ss_equity_changes` のスクショと
改修後(同条件)の出力を比較し、リグレッションがないこと(`資本金` の数値不変)を確認。
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| 実行前タスク(調査) | あり | 既存 `dmBuildEquityChanges_()` のロジック理解・MCP による実データ確認 |
| 実装(編集) | なし | 仕様書「修正方針」の書き下しのみ。出力途中で再考しない |
| 動作確認 | 必要に応じて | 目視で差異があった場合のみ原因調査に拡張思考を使う |
推奨実行モデル
| ステップ | 推奨モデル | 理由 |
|---|---|---|
実装(dmBuildEquityChanges_() の差し替え) | Claude Sonnet | 既存パターン(AccountRepository.findAsMap() の有効フラグ判定)の適用 + bsBal['eq_cap'] の per-account 集計への書き換えと、既存コードの挿入位置の特定が必要。仕様書で全コードが定義済みだが「既存変数名を尊重した差し替え」という中程度の判断を伴う |
| 動作確認(目視+スクショ比較) | 人間 | Claude では GUI 操作不可。マート更新ボタン押下 → スプレッドシート目視の手順を人間が実施 |
判断要素が少なく、仕様書に完全なコードサンプルを記載してあるため Sonnet で十分。Opus を使う必要はない。
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-19 | 初版作成。株主資本等変動計算書の科目列をハードコードから科目マスタ動的参照に変更する開発仕様書を作成 |
仕様書作成プロンプト(再現性・監査性のため必ず記録)
展開して表示
<instruction>
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
1. **拡張思考の使い分け**: Phase 1(設計)では拡張思考をフル活用し、対象ファイルパス・関数名・行番号・列名・フィルタ条件を完全確定させる。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 1 で確定した内容を書き下すことに徹し、出力時に設計判断を持ち込まない。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
CLIエージェント「Claude Code」として、案件 S-41「株主資本等変動計算書の科目マスタ動的参照化」の開発仕様書を作成してください。
開発仕様書を新規作成したら、`docs/_config.json` の `nav` 配列の適切なセクションに必ず追記してください。
---
## Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)
以下をすべて完了してから Phase 2 に進む。Phase 2 の清書フェーズで設計判断を行わない。
### 1-A: 案件定義の読み込み
`docs/_internal/TODO_future.md` を検索し、S-41 の案件名・概要・人間が検討すべき事項を取得する。
### 1-B: 株主資本等変動計算書のレンダリング関数を特定する(ハルシネーション防止)
**注意: Gemini が推定した `604_datamart_bs.js` / `dmBuildEquityChanges_()` は実在未確認のため、以下のコマンドで必ず実ファイルを特定すること。**
```bash
grep -rn "75_ss_equity_changes\|EquityChange\|equity_change\|株主資本等変動" 600_report/ --include="*.js" -l
```
ヒットしたファイルを Read し、以下を確定させる:
- **対象ファイルの正確なパス**(例: `600_report/6xx_datamart_xxx.js`)
- **ハードコードされた科目リストを含む関数名**と行番号
- **現在の科目リストの構造**(配列リテラル・Constants 参照・その他)
- **`75_ss_equity_changes` シートへの書き込み箇所**(行番号まで確認)
### 1-C: `AccountRepository` のインターフェース確認
`200_data/202_repository.js` の `AccountRepository` セクションを Read し、以下を確認する:
- `findAll()` の戻り値の型が `{ headers: string[], dtos: Object[] }` であること(`.dtos` プロパティを使う必要がある)
- `findAsMap()` の有効フラグ判定パターン(`flag === false || String(flag).toUpperCase() === 'FALSE'`)を確認し、今回のフィルタに流用する
- `findAll()` にはキャッシュなし、`findAsMap()` にはキャッシュあり(`_cache`)であることを確認する
### 1-D: 科目マスタ `11_mst_account` の列構造確認
MCP またはシートの DDL 定義(`100_config/101_sys_config.js` の setupAllSchemas 相当箇所)を確認し、以下の列が実在するかを確認する:
- `諸表区分`(格納値の実例: "B/S" / "P/L" 等の正確な文字列)
- `大分類`(格納値の実例: "純資産の部" 等の正確な文字列)
- `有効フラグ`
- `表示順`(**存在しない場合はフォールバックとして `主科目コード` 昇順を使用する**ことを Phase 2 に持ち込む)
- `主科目コード`
**「純資産の部」という文字列は推定値。MCP で実データを確認し、DDLコード値と実際の格納値の乖離がないかチェックする。**
### 1-E: テンプレート仕様書の読み込み
`docs/dev/` 配下の既存仕様書から B/S 系データマート関連に最も近いものを 1 件 Read し、フォーマットを把握する。(例: `dev_mas-093_cf_actual_only.md`)
### 1-F: メニュー名の確認(ハルシネーション防止)
`100_config/101_sys_config.js` の `onOpen()` を Read し、マート更新を実行するための**実在するメニュー項目名**を確認する(動作確認手順に記載するため)。
---
## Phase 2: 仕様書の分割作成
出力先: `docs/dev/dev_mas-113_equity_changes_dynamic.md`
**【重要】絶対に 1 回のツール呼び出しで全内容を出力せず、以下の Step に分割して実行すること。**
### Step 2-1: 骨格の作成 (File Write, ~20行)
見出しのみ。本文は空で可。以下のセクション見出しをすべて含める:
`概要 / 目的 / 現在のコード / 修正方針 / 影響範囲 / 注意事項 / エッジケース / 実データ検証 / 関連ドキュメント / 人間が検討すべき事項 / 実装プロンプト(Claude Code 用) / 推奨実行モデル / 変更履歴 / 仕様書作成プロンプト`
### Step 2-2: 概要〜注意事項の追記 (File Edit または Bash heredoc, ~300行)
Phase 1 の調査結果のみを使って以下を書く:
- **概要テーブル**: 案件ID=S-41, カテゴリ=データマート, Phase, 優先度, 対象ファイル=Phase 1-B で特定したファイルパス(正確なパスのみ記載)
- **目的**: 科目ハードコードによる保守コストと科目マスタとの乖離リスクを解消するため
- **現在のコード**: Phase 1-B で特定したハードコード箇所のスニペット(ファイル名 + 行番号を明記)
- **修正方針**:
- `AccountRepository.findAll()` を呼び出す。戻り値は `{ headers: string[], dtos: Object[] }` なので必ず `.dtos` プロパティを参照する
- `.dtos` を `Array.prototype.filter()` で絞り込む条件:
- `dto['諸表区分'] === '(Phase 1-D で確認した実際の文字列)'`
- `dto['大分類'] === '(Phase 1-D で確認した実際の文字列)'`
- 有効フラグ判定: `!(flag === false || String(flag).toUpperCase() === 'FALSE')` (`AccountRepository.findAsMap()` と統一)
- ソート: `表示順` 列が存在する場合は昇順、存在しない場合は `主科目コード` 昇順(Phase 1-D の確認結果を反映)
- `Array.prototype.map(dto => dto['科目名'])` で科目名配列を生成し、ハードコード配列を差し替える
- 具体的なコード例を記載(関数名・変数名はすべて Phase 1 で確定したものを使用)
- **影響範囲**: 変更ファイル(Phase 1-B で特定したファイルのみ)、`11_mst_account` への依存追加、既存マート更新処理の排他制御の範囲内で動作するため追加のロック処理は不要
- **注意事項**:
- `AccountRepository.findAll()` はキャッシュなし(`findAsMap()` と異なる)。マート更新のたびに最新科目マスタを参照することを意図しており、問題なし
- 有効フラグ判定は `flag === false || String(flag).toUpperCase() === 'FALSE'` パターンを使用し、`findAsMap()` の既存実装と統一すること
- `表示順` 列が存在しない場合のフォールバック(`主科目コード` 昇順)は Phase 1-D の確認結果に基づき記述する。推測で書かない
### Step 2-3a: エッジケース〜人間が検討すべき事項の追記 (File Edit または Bash, ~200行)
以下の内容を書く:
- **エッジケーステーブル**:
| 条件 | 期待動作 | 理由 |
|------|---------|------|
| 有効な純資産科目が 0 件 | 株主資本等変動計算書のデータ行を空(またはヘッダー行のみ)で描画。エラーにしない | 科目未登録はスタートアップ初期等で起こりうる正常系 |
| 科目はあるが仕訳データが 0 件 | 科目行は描画し、期首・変動・期末の金額は 0 または `"-"` で表示 | 科目構造とデータは独立して管理 |
| 純資産のマイナス残高(評価差額金等) | 符号そのままで計算・表示(△表示は `Constants.NUMBER_FORMATS.CURRENCY` の書式に委ねる) | 意図的なマイナス値を切り捨てない |
| `表示順` 列が存在しない | `主科目コード` 昇順でフォールバックソート | Phase 1-D で列の有無を確認済み |
- **実データ検証**: Phase 1-D で確認した `11_mst_account` の `諸表区分`・`大分類` の実際の格納値(DDL定義のコード値と実データに乖離がないか確認済みであることを記載)
- **関連ドキュメント**: テーブル形式で関連仕様書リンクを記載
- **人間が検討すべき事項**: `TODO_future.md` の内容 + 以下の追加事項:
- `表示順` 列の有無と運用方針(列が存在しない場合の `主科目コード` フォールバックで業務上問題ないか)は人間が確認・承認すること
- 動作確認(目視確認フロー、番号付きリスト):
1. `11_mst_account` で既存純資産科目の「有効フラグ」を FALSE に変更 → (Phase 1-F で確認した実在するメニュー名)からマートを更新 → `75_ss_equity_changes` シートから当該科目行が消えることを確認
2. フラグを TRUE に戻す → マート更新 → 科目行が再表示されることを確認
3. テスト用純資産科目をマスタに追加(諸表区分・大分類は Phase 1-D で確認した実際の値, 有効フラグ=TRUE)→ マート更新 → 計算書に新科目行が追加されることを確認
4. テスト用科目の有効フラグを FALSE に変更 → マート更新 → 行が消えることを確認
### Step 2-3b: 実装プロンプト〜変更履歴の追記 (File Edit または Bash, ~250行)
実装プロンプトはバッククォートで囲まず、行頭 4 スペースインデントで出力する:
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 S-41「株主資本等変動計算書の科目マスタ動的参照化」を実装してください。
## 実行前タスク
1. 仕様書「現在のコード」セクションに記載されたファイルパスと行番号を Read で確認する。
2. `200_data/202_repository.js` の `AccountRepository.findAll()` を Read し、
戻り値が `{ headers: string[], dtos: Object[] }` であることを確認する。
3. MCP で `11_mst_account` を確認し、`諸表区分`・`大分類`・`表示順`・`主科目コード` の
実際の格納値(特に「純資産の部」に相当する文字列)を確認する。
## 修正対象ファイル
仕様書「現在のコード」セクションに記載されたファイル 1 本のみ。
`200_data/202_repository.js`・`000_infra/002_constants.js`・他の 600_report/ ファイルは変更しない。
## 実装内容
1. 科目リストのハードコード箇所を特定し、以下に置き換える:
```javascript
var acctResult = AccountRepository.findAll(); // { headers, dtos }
var equityAccounts = acctResult.dtos
.filter(function(dto) {
var flag = dto['有効フラグ'];
return dto['諸表区分'] === '(実データで確認した値)'
&& dto['大分類'] === '(実データで確認した値)'
&& !(flag === false || String(flag).toUpperCase() === 'FALSE');
})
.sort(function(a, b) {
// 表示順列が存在する場合は昇順、なければ主科目コード昇順(仕様書参照)
var ka = a['表示順'] != null ? a['表示順'] : a['主科目コード'];
var kb = b['表示順'] != null ? b['表示順'] : b['主科目コード'];
return ka < kb ? -1 : ka > kb ? 1 : 0;
})
.map(function(dto) { return String(dto['科目名']).trim(); });
```
2. 既存のハードコード配列を `equityAccounts` に差し替える。
3. `equityAccounts` が空配列の場合はエラーにせず、科目なしとして後続処理を続行する。
## 制約
- Repository 層(`202_repository.js`)・Constants(`002_constants.js`)・他のデータマートファイルは変更しない。
- 列番号ハードコード禁止。ヘッダー名ベースで参照する(既存コードのパターンに倣う)。
## エッジケース
仕様書「エッジケース」セクションのテーブルを参照。
## 動作確認
1. `npm run push:dev` で開発環境にデプロイ。
2. スプレッドシートのメニュー(仕様書「人間が検討すべき事項」に記載の実在メニュー名)
からマートを更新。
3. `75_ss_equity_changes` シートを開き、科目行が `11_mst_account` の純資産科目と一致することを目視確認。
4. 仕様書「人間が検討すべき事項」の動作確認手順 1〜4 を実施。
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| 実行前タスク(調査) | あり | ファイル特定・行番号確定 |
| 実装(編集) | なし | 調査結果の書き下しのみ |
続けて以下を追記する:
- **推奨実行モデルテーブル**: 挿入位置の特定と既存パターン適用が必要 → **Claude Sonnet**
- **変更履歴テーブル**: `2026-04-19 | 初版作成`
### Step 2-4: 仕様書作成プロンプトの記録 (File Edit または Bash)
仕様書末尾に以下の形式で追記する。この `<instruction>` タグ内の全文をそのまま記録する:
```
## 仕様書作成プロンプト(再現性・監査性のため必ず記録)
<details><summary>展開して表示</summary>
(この <instruction> タグ内の全文をここに貼り付ける)
</details>
```
---
## Phase 3: 保存・登録・コミット
### 3-A: `docs/_config.json` への追記
`docs/_config.json` の §E.4(データマート・財務諸表)セクションに追記:
```json
{ "file": "dev/dev_mas-113_equity_changes_dynamic.md", "title": "E.4.XX MAS-113 株主資本等変動計算書の科目マスタ動的参照化" }
```
追記後に JSON 構文が壊れていないことを `JSON.parse` 相当で確認する。
### 3-B: `docs/_internal/changelog.md` への追記
先頭行(ヘッダー直後)に追記:
```
| 2026-04-19 | [dev_mas-113_equity_changes_dynamic.md](dev_mas-113_equity_changes_dynamic.md) | 初版作成。株主資本等変動計算書の科目列をハードコードから科目マスタ動的参照に変更 |
```
### 3-C: コミット&プッシュ
```bash
git add docs/dev/dev_mas-113_equity_changes_dynamic.md docs/_internal/changelog.md docs/_config.json
git commit -m "docs: MAS-113 株主資本等変動計算書の科目マスタ動的参照化の開発仕様書を作成
株主資本等変動計算書レンダリング関数のハードコード科目リストを
11_mst_accountからの動的参照に切り替える仕様書を作成。
AccountRepository.findAll()を使用しRepositoryパターンを遵守。
https://claude.ai/code/session_XXXXX"
git push -u origin docs/dev-MAS-113
```
</instruction>