概要

項目
案件IDMAS-123
カテゴリFP&A・レポーティング
Phase
優先度
新規ファイル600_report/610_datamart_commitment.js
新規シート68_ord_commitment
変更ファイル100_config/101_sys_config.js(メニュー追加)
前提案件MAS-122(発注残高計算ロジック修正)

目的

発注済・未請求のコミットメント(将来発生が見込まれる支出)を可視化し、請求書が届く前の段階で予算超過を早期検知できるようにする。31_wrk_order の発注残高と 41_trn_budget の予算を月次・年度累計で並列表示し、FP&A 担当者がキャッシュアウト前に手を打てる状態を作る。

現在のコード

該当機能なし(新規実装)。実装に使用する既存資産として以下を利用する。

資産ファイル戻り値
OrderRepository.findAll()200_data/202_repository.js{ headers: string[], dtos: OrderDTO[] }
InvoiceRepository.findAll()200_data/202_repository.js{ headers: string[], dtos: InvoiceDTO[] }
Contracts.toDtoList(data)000_infra/003_contracts.js{ headers: string[], rows: Object[] }sheet.getDataRange().getValues() を受け取り DTO 配列に変換(戻り値キーは rowsdtos ではない点に注意)
Utils.parseDateToYm(val)000_infra/004_utils.js"YYYY-MM" 形式文字列。Date・"YYYY-MM"・"YYYY/MM"・"YYYY年MM月" に対応
Utils.addMonths(ymStr, months)000_infra/004_utils.js"YYYY-MM" 形式。月按分ループのキー生成に使用
Utils.getSheetByKey(key, fallbackName)000_infra/004_utils.jsSheet オブジェクト。システムキー経由でシート取得
getWebSpreadsheet_()000_infra/004_utils.jsSpreadsheet オブジェクト

修正方針

Step 1: データ取得

var orderResult  = OrderRepository.findAll();   // { headers, dtos: OrderDTO[] }
var invResult    = InvoiceRepository.findAll(); // { headers, dtos: InvoiceDTO[] }
var budgetSheet  = Utils.getSheetByKey('<実際のキー>', '41_trn_budget');
var budgetResult = Contracts.toDtoList(budgetSheet.getDataRange().getValues());
// budgetResult.rows が BudgetDTO の配列 (Contracts.toDtoList は rows キーで返す)
// <実際のキー> は実行前タスクで MCP の 01_sys_config シートを確認して特定する

Step 2: フィルタ・集計マップの構築(Object をキー→値マップとして使用)

InvoiceDTO の集計:

  • 有効フラグ === false をスキップ
  • 請求ステータス === '承認済' のみ対象
  • 集計キー: dto['科目名'] + '_' + Utils.parseDateToYm(dto['発生日(P/L計上日)'])
  • 集計値: dto['税込金額_計画'] の累算

BudgetDTO の集計:

  • 有効フラグ === false をスキップ(BudgetDTO に 有効フラグ プロパティが存在することを 003_contracts.js で確認済み)
  • 集計キー: dto['科目名'] + '_' + dto['対象年月']
  • 集計値: dto['予算金額'] の累算

Step 3: OrderDTO の月割り按分

  • 有効フラグ === false をスキップ
  • 発注ステータス === '発注済' のみ対象(実際の格納値は実行前タスクで MCP 確認)
  • startYm = dto['開始年月']endYm = dto['終了年月'](ともに "YYYY-MM" 形式)
  • 開始月から終了月までの月数 months を算出。Math.max(months, 1) でゼロ除算防止
  • 月次按分額 = Math.floor(dto['税込金額_発注'] / months)
  • 各月のキー: dto['PJ名'] + '_' + Utils.addMonths(startYm, i)
    • OrderDTO科目名 フィールドは存在しない003_contracts.js で確認済み)ため、PJ名 を暫定集計キーとして使用
  • 最終月で端数(税込金額_発注 - 月次額 * months)を加算して累積誤差を解消

Step 4: シート出力

  • var ss = getWebSpreadsheet_();
  • var outSheet = ss.getSheetByName('68_ord_commitment') || ss.insertSheet('68_ord_commitment');
  • ヘッダー行を固定列数配列で定義(例: ['グループキー', '年月', '予算金額', '請求済金額', '発注コミットメント(按分)', '予算執行率']
  • clearContent() でデータ行をクリア後、setValues() で上書き
  • 予算執行率の計算:
    • 予算金額 === 0 の場合 → '-'(文字列、ゼロ除算回避)
    • それ以外 → (請求済金額 + 発注コミットメント) / 予算金額
  • 年度合計行の予算執行率: 月次値の合計ではなく、年度合計請求額 + 年度合計コミットメント ÷ 年度合計予算で別途再計算(failure_patterns.md #1: 非加算指標の Total 破壊防止)

影響範囲

変更種別ファイル / シート変更量
新規作成600_report/610_datamart_commitment.js~150行
新規シート68_ord_commitment
追記100_config/101_sys_config.jsメニュー登録数行(MENU_DEFINITION の items 配列に追加)

既存の RPA仕訳エンジン・他レポートへの影響なし(読み取り専用の新規レポートのため)。

注意事項

  1. OrderRepository / InvoiceRepository / Contracts.toDtoList / Utils.parseDateToYm / Utils.addMonths 等の既存ユーティリティを必ず再利用すること。車輪の再発明は厳禁。
  2. 金額集計は全て税込を基準とする。OrderDTO: 税込金額_発注、InvoiceDTO: 税込金額_計画、BudgetDTO: 予算金額
  3. OrderDTO['発注残高(自動計算)'] は参照値として扱い、残高再計算ロジック自体の修正は本案件スコープ外(MAS-122 スコープ)。
  4. 68_ord_commitment は読み取り専用レポートシートとし、ユーザーによる直接編集は想定しない。
  5. sheet.getLastColumn() による動的列範囲取得は禁止。月列は固定列数でハードコードする(failure_patterns.md #21)。
  6. Sheets 数式内でのラベル検索(ARRAYFORMULA + MATCH + SUBSTITUTE の組合せ)は禁止(failure_patterns.md #24)。行番号は GAS 側で特定してリテラル埋め込みとする。
  7. OrderDTO に存在しないフィールド名(例: 科目名)を参照してはならない。使用前に 003_contracts.jsOrderDTO 定義で実在を必ず確認すること。
  8. Contracts.toDtoList() の戻り値は { headers, rows } であり dtos ではない。予算データは budgetResult.rows でアクセスする。
  9. 新規ファイル番号: 609_datamart_kpi.js が既存のため、610_datamart_commitment.js を使用する。

エッジケース

条件表示値理由
予算金額 === 0 で予算執行率を計算"-"(文字列)ゼロ除算回避。varRate = 0 フォールバックは誤り(failure_patterns.md #2)
発注残高(自動計算) が負値(過剰請求)そのまま負の値を出力過剰請求の可視化が目的。マスクしない
予算・発注・請求のいずれかが 0 件空のダッシュボードを正常生成各データソースは独立してゼロ件を許容する。エラーを発生させない
開始年月終了年月 が同一月按分月数 = 1 として全額をその月に計上ゼロ除算防止。Math.max(months, 1) でガード
年度合計列の 予算執行率年度合計執行額 ÷ 年度合計予算で別途再計算非加算指標のため月次値の単純合計は不可(failure_patterns.md #1)。年度合計の金額を合算してから除算する

実データ検証

実装前に MCP 等で以下を確認すること。

確認項目確認方法目的
31_wrk_order発注ステータス 実際の格納値MCP でシートを確認DDL 定義(「見積中」「発注済」「検収済」)との乖離チェック
32_wrk_invoice請求ステータス 実際の格納値MCP でシートを確認DDL 定義(「未処理」「承認済」「却下」)との乖離チェック
41_trn_budget のシステムキー01_sys_config シートの B 列を MCP で確認Utils.getSheetByKey() の第一引数に使用
41_trn_budget有効フラグ 列が存在するかMCP でシートを確認BudgetDTO のフィルタ処理に影響(003_contracts.js では定義済みだが実データとの乖離確認)

関連ドキュメント

ドキュメント関連箇所
docs/dev/dev_mas-001_variance_analysis.mdFP&A 系マートビルダーの仕様書構成参考
docs/_internal/failure_patterns.md#1(非加算 Total)/ #2(ゼロ除算)/ #17(Date ソート)/ #18-#20(固有名詞誤記)/ #21(getLastColumn 禁止)/ #24(数式ラベル検索禁止)

人間が検討すべき事項

  1. [要決定] OrderDTO の集計キー: OrderDTO科目名 フィールドが存在しないため、未請求コミットメントの集計粒度を確定する必要がある。候補:

    • (a) PJ名 を暫定キーとして科目混在のまま可視化(仮実装はこれを採用)
    • (b) 取引先名 を集計キーとする
    • (c) InvoiceDTO['親発注ID(ORD)'] で JOIN し 科目名 を引く
    • (d) OrderDTO科目名 列を追加(別案件として起票)
    • 現在の仮実装は (a) を採用。科目粒度でのコミットメント把握が必要になった場合は (c) または (d) を検討すること。
  2. [要確認] 発注ステータスフィルタの範囲: ダッシュボードに含める発注ステータスを「発注済」のみとするか、「見積中」も含めるか(コミットメントの定義による)。見積中を含める場合は確度の概念が必要になる可能性がある。

  3. [決定済] 金額基準: 税込金額を正とする(OrderDTO: 税込金額_発注、InvoiceDTO: 税込金額_計画、BudgetDTO: 予算金額)。

  4. [決定済] 長期契約の按分: 契約期間(開始年月終了年月)で月割り按分する方式を採用する。

  5. [決定済] 集計粒度: 月次と年度累計を並列表示。OrderDTO の科目粒度欠如は事項 1 で別途決定。

実装プロンプト(Claude Code 用)

あなたはGAS会計システムのシニア開発者です。
案件 MAS-123「31タブ発注残高・予算執行ダッシュボード」を実装してください。

## 実行前タスク
以下を順番に Read して実装着手前に構造を把握すること(Grep の部分ヒットで推測しない。
固有名詞は Read した実在文字列のみ使用: failure_patterns.md #18-#20):

1. `000_infra/003_contracts.js` — OrderDTO / InvoiceDTO / BudgetDTO の全フィールド名を確認。
   特に OrderDTO に `科目名` が存在しないことを確認する。
2. `200_data/202_repository.js` — OrderRepository.findAll() / InvoiceRepository.findAll() の
   戻り値形式 { headers: string[], dtos: DTO[] } を確認する。
3. `000_infra/004_utils.js` — parseDateToYm() / addMonths() / getSheetByKey() の
   引数・戻り値を確認する。
4. `100_config/101_sys_config.js` — MENU_DEFINITION の既存 items 配列パターンを Read で確認する。
   メニュー名・関数名は実在文字列のみ使用すること。
5. MCP で `01_sys_config` シートを確認し、`41_trn_budget` に対応するシステムキーを特定する。
6. MCP で `31_wrk_order` / `32_wrk_invoice` の実際のステータス格納値を確認し、
   フィルタ条件の文字列と一致させる。

## 修正対象ファイル
- 新規作成: `600_report/610_datamart_commitment.js`
  (609_datamart_kpi.js が既存のため 610 を使用する)
- 変更(追記のみ): `100_config/101_sys_config.js`

## 実装内容

### 610_datamart_commitment.js の新規作成

function buildCommitmentDatamart() を以下の 4 Step で実装する。

#### Step 1: データ取得
- var orderResult = OrderRepository.findAll();  // { headers, dtos: OrderDTO[] }
- var invResult   = InvoiceRepository.findAll(); // { headers, dtos: InvoiceDTO[] }
- var budgetSheet = Utils.getSheetByKey('<実際のキー>', '41_trn_budget');
- var budgetResult = Contracts.toDtoList(budgetSheet.getDataRange().getValues());
  // 注意: Contracts.toDtoList の戻り値キーは `rows` であり `dtos` ではない
  // → budgetResult.rows が BudgetDTO の配列
  // <実際のキー> は実行前タスク 5 で確認した文字列を使用する

#### Step 2: フィルタ・集計マップの構築(Object をキー→値マップとして使用)

InvoiceDTO の集計:
- 有効フラグ === false をスキップ
- 請求ステータス === '承認済' のみ対象
- キー: dto['科目名'] + '_' + Utils.parseDateToYm(dto['発生日(P/L計上日)'])
- 値: dto['税込金額_計画'] の累算

BudgetDTO の集計:
- 有効フラグ === false をスキップ(有効フラグが実シートに存在するかを MCP で確認)
- キー: dto['科目名'] + '_' + dto['対象年月']
- 値: dto['予算金額'] の累算

#### Step 3: OrderDTO の月割り按分

- 有効フラグ === false をスキップ
- 発注ステータス === '発注済' のみ対象(実際の格納値は実行前タスク 6 で確認)
- startYm = dto['開始年月'], endYm = dto['終了年月'](ともに "YYYY-MM" 形式)
- 開始月から終了月までの月数 months を算出。Math.max(months, 1) でゼロ除算防止
- 月次按分額 = Math.floor(dto['税込金額_発注'] / months)
- 各月のキー: dto['PJ名'] + '_' + Utils.addMonths(startYm, i)
  ※ OrderDTO に科目名フィールドは存在しないため PJ名 を暫定集計キーとして使用
- 最終月で端数(税込金額_発注 - 月次額 * months)を加算して累積誤差を解消

#### Step 4: シート出力

- var ss = getWebSpreadsheet_();
- var outSheet = ss.getSheetByName('68_ord_commitment') || ss.insertSheet('68_ord_commitment');
- ヘッダー行を固定列数配列で定義(例: ['グループキー', '年月', '予算金額', '請求済金額',
  '発注コミットメント(按分)', '予算執行率'])
- データ行が存在する場合は clearContent でクリア後に setValues で上書き
- 予算執行率の計算:
    予算金額 === 0 の場合 → '-'(文字列)
    それ以外 → (請求済金額 + 発注コミットメント) / 予算金額
- 年度合計行の予算執行率: 月次値の合計ではなく、年度合計請求額+コミットメント ÷ 年度合計予算
  で別途再計算すること(failure_patterns.md #1: 非加算指標の Total 破壊を防ぐ)

### 101_sys_config.js のメニュー追加

Read で確認した MENU_DEFINITION の既存 items 配列と同一形式で buildCommitmentDatamart を追加する。
例: { label: 'コミットメントダッシュボード更新', funcName: 'buildCommitmentDatamart',
      description: '発注残高・予算執行ダッシュボード (68_ord_commitment) を更新' }
メニュー名・関数名は Read した実在パターンに合わせること。造語厳禁(failure_patterns.md #20)。

## 制約
- OrderDTO に存在しないフィールド名(例: '科目名')を参照してはならない
- sheet.getLastColumn() による動的列範囲取得は禁止(failure_patterns.md #21)
- Sheets 数式での ARRAYFORMULA + MATCH + SUBSTITUTE の組合せは禁止(failure_patterns.md #24)
- 年度合計行の予算執行率を月次値の単純合計で計算してはならない(failure_patterns.md #1)
- 101_sys_config.js はメニュー追加のみ。他の既存ロジックは変更しない
- Contracts.toDtoList の戻り値キーは rows であり dtos ではない(混同しない)

## エッジケース
| 条件 | 処理 |
|---|---|
| 予算金額 === 0 | 予算執行率を '-' で表示。varRate = 0 フォールバックは誤り |
| 発注残高(自動計算) が負値 | そのまま出力(過剰請求の可視化) |
| 各データソースが 0 件 | エラーを発生させず空ダッシュボードを正常出力 |
| 開始年月 === 終了年月 | months = 1 として全額をその月に計上 |
| 年度合計の予算執行率 | 年度合計金額ベースで再計算(月次値の合計は不可) |

## 実データ検証(実装前に MCP で確認)
- 31_wrk_order の発注ステータス実際の格納値(DDL 定義との乖離確認)
- 32_wrk_invoice の請求ステータス実際の格納値(同上)
- 41_trn_budget のシステムキー(01_sys_config シートで確認)
- 41_trn_budget に有効フラグ列が存在するか

## 動作確認
1. npm run push:dev でデプロイ
2. GAS エディタから buildCommitmentDatamart() を直接実行
3. 68_ord_commitment シートが生成されることを確認
4. 予算金額 = 0 の行の執行率が '-' になることを確認
5. 年度合計行の執行率が月次値の合計でなく再計算値になることを確認
6. 各データソース 0 件の状態で実行し、エラーが発生しないことを確認

### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---|---|---|
| Phase 1(ファイル調査・設計確定) | あり | OrderDTO 科目名問題の設計判断を含む |
| Phase 2(実装) | なし | 確定済み仕様の書き下しに徹する |

推奨実行モデル

工程推奨モデル理由
仕様書作成(本プロンプト)Claude SonnetOrderDTO 科目名問題の設計分岐など中程度の判断が必要
実装(610_datamart_commitment.js)Claude Sonnet複数ファイル横断だが仕様書でロジックを定義済み

変更履歴

日付変更内容
2026-04-21初版作成

仕様書作成プロンプト(再現性・監査性のため必ず記録)

展開して表示
<instruction>
【タイムアウト回避・実行原則(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(骨格 ~20行)/ 2-2(概要〜注意事項 ~300行)/ 2-3a(エッジケース〜人間検討事項 ~200行)/ 2-3b(実装プロンプト〜変更履歴 ~250行)/ 2-4(`<details>` プロンプト全文記録)に厳密に分割する。
4. **各 Step で何を書くかを具体指示**: Phase 2 実行時に設計判断を持ち込まず、Phase 1 で確定した内容の書き下しに徹する。

======================================================================
あなたはGAS会計システムのシニア開発者兼仕様書ライターです。
案件 S-51「31タブ発注残高・予算執行ダッシュボード」の開発仕様書を作成してください。
作成完了後、`docs/_config.json` の `nav` 配列 §E.5 と `docs/_internal/changelog.md` への追記も必ず実施してください。

---

## Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)

以下をすべてツールで読み込み、Phase 2 着手前に設計を完全に確定させること。**Grep は「どこにあるか」の発見まで、「どう書くか」の判断は必ず Read で行う。**

1. `docs/_internal/TODO_future.md` — S-51 の要件・人間が検討すべき事項を把握する。
2. `000_infra/003_contracts.js` — `OrderDTO` / `InvoiceDTO` / `BudgetDTO` の**全プロパティ名**を正確に把握する。特に `OrderDTO` に `科目名` フィールドが**存在しない**ことを確認し、集計キー設計に反映させる。
3. `200_data/202_repository.js` — `OrderRepository.findAll()` / `InvoiceRepository.findAll()` の戻り値型(`{ headers: string[], dtos: DTO[] }`)を確認する。なお予算シート(`41_trn_budget`)に対応する Repository クラスは存在しないため、`Contracts.toDtoList(sheet.getDataRange().getValues())` で直接変換する方式を採用する。
4. `000_infra/004_utils.js` — `parseDateToYm(val)` / `addMonths(ymStr, months)` / `getSheetByKey(key, fallbackName)` の引数・戻り値を確認する。
5. `100_config/101_sys_config.js` — 既存マートビルダーのメニュー登録パターン(`onOpen()` 内の `buildXxxDatamart` 系関数の登録箇所)と `setupAllSchemas` でのシートスキーマ登録パターンを Read で把握する。固有名詞(メニュー名・関数名)はここで確認した実在文字列のみ使用すること(failure_patterns.md #18-#20)。
6. `600_report/608_datamart_render.js`(または番号が最も近い既存レポートファイル)— シート出力・フォーマット適用の実装パターンを把握する。
7. `docs/_internal/failure_patterns.md` — #1(非加算指標の Total 破壊)・#2(ゼロ除算)・#18-#20(固有名詞の誤記)・#21(`getLastColumn()` の落とし穴)・#24(`ARRAYFORMULA + MATCH + SUBSTITUTE` 禁止)を把握する。
8. `docs/dev/dev_mas-001_variance_analysis.md` — FP&A 系仕様書のセクション構成・記述粒度を参考として把握する。

### Phase 1 での確定事項(ここで決定し、Phase 2 では再考しない)

- **出力ファイル名**: `docs/dev/dev_mas-123_commitment_dashboard.md`(S は大文字)
- **新規 GAS ファイル**: `600_report/609_datamart_commitment.js`
- **公開関数名**: `buildCommitmentDatamart()`(命名は `101_sys_config.js` の既存パターンに合わせること)
- **新規シート名**: `68_ord_commitment`(要調査: 既存シート番号体系・命名規則を `101_sys_config.js` の `setupAllSchemas` で確認)
- **予算シートの取得方法**: `Utils.getSheetByKey('<実際のキー>', '41_trn_budget')` で取得後、`Contracts.toDtoList(sheet.getDataRange().getValues())` で BudgetDTO 配列に変換。`<実際のキー>` は `01_sys_config` シートを MCP で確認して特定する(要調査)
- **日付→YYYY-MM 変換**: `Utils.parseDateToYm(dto['発生日(P/L計上日)'])` を使用(Date オブジェクトの文字列ソート禁止: failure_patterns.md #17)
- **月按分ループ**: `Utils.addMonths(dto['開始年月'], i)` で各月のキーを生成
- **集計キー設計上の重要な制約**(`OrderDTO` に `科目名` フィールドが存在しないため):
  - 請求済分(InvoiceDTO): `dto['科目名'] + '_' + Utils.parseDateToYm(dto['発生日(P/L計上日)'])` でグループ化
  - 未請求コミットメント(OrderDTO): `dto['PJ名'] + '_' + ym` でグループ化(科目粒度への対応は「人間が検討すべき事項」に記載)
  - 予算(BudgetDTO): `dto['科目名'] + '_' + dto['対象年月']` でグループ化
- **フィルタ条件**:
  - OrderDTO: `有効フラグ === false` をスキップ、`発注ステータス === '発注済'` のみ集計(要調査: TODO_future.md の要件で範囲を確認)
  - InvoiceDTO: `有効フラグ === false` をスキップ、`請求ステータス === '承認済'` のみ集計
  - BudgetDTO: `有効フラグ === false` をスキップ(BudgetDTO に `有効フラグ` が存在するか `003_contracts.js` で確認済みであること)
- **金額フィールド**: OrderDTO → `税込金額_発注`、InvoiceDTO → `税込金額_計画`、BudgetDTO → `予算金額`(すべて税込基準)

---

## Phase 2: 仕様書の分割作成

出力先: `docs/dev/dev_mas-123_commitment_dashboard.md`
**絶対に 1 回のツール呼び出しで全内容を出力しない。以下の Step に厳密に分割して実行すること。**

(以下省略 — 本プロンプト全文は /tmp/prompt_S-51.md を参照)
</instruction>