概要

項目内容
案件IDMAS-101
カテゴリFP&A・レポーティング / 債権管理
PhaseP2
優先度★★
所要時間3-4時間
対象ファイル600_report/610_datamart_ar_aging.js(新規作成)
templates/operations_sidebar.html(サイドバーへボタン追加)
前提案件なし
関連将来案件MAS-084(貸倒引当金繰入)— 滞留債権データを参照する前提

目的

収入INV32_wrk_invoice収支区分 === '収入')のうち、未決済残高が残っているレコードを 取引先別 × 経過日数別 に集計し、売掛金エイジングレポート (67_ar_aging_report) を自動生成する。

  • 回収リスクの可視化: 発生日から日数が経過するほど回収不能リスクが高まる。滞留を一目で把握して、督促・回収アクションの判断材料を提供する
  • FP&A 連携: 資金繰り予測 (MAS-008) と連動し、売掛金の回収予定精度を高める
  • 会計指針準拠: 中小企業会計指針 第18項で定められた貸倒引当金(法定繰入率 0.6% = Constants.ALLOWANCE_RATE)の計算基礎となる滞留区分を整備する

Human-in-the-Loop の原則に従い、本レポートは「可視化」までを責務とし、回収アクション・貸倒計上の判断は人間が行う。

現在のコード

エイジングレポート機能は未実装(新規開発)。

参考とする既存レポート実装:

ファイルレポートタブ参考になるパターン
600_report/609_datamart_kpi.js93_kpi_dashboardsheet.clear() + 再描画、setConditionalFormatRules() による閾値色分け、列幅設定、Utils.logInfo/logError によるロギング
600_report/602_datamart_main.js71_bs, 61_pl_monthlyss.getSheetByName() → 存在しなければ insertSheet()、ヘッダー行の太字・背景色設定

データアクセス層としては 200_data/202_repository.jsInvoiceRepository.findAll(){ headers: string[], dtos: InvoiceDTO[] } を返すため、シート直接アクセスではなく Repository 経由で読み取る。

InvoiceDTO (000_infra/003_contracts.js L40-67) の主要フィールド:

  • 有効フラグ (boolean, 但し文字列 'FALSE' で格納されている行も存在する)
  • 発生日(P/L計上日) (Date または "YYYY-MM-DD" / "YYYY/MM/DD" 等の文字列)
  • 収支区分 ("収入" | "支出")
  • 取引先名 (string)
  • 未決済残高(自動計算) (number)

修正方針

1. 新規ファイル 600_report/610_datamart_ar_aging.js

600_report/ 配下の既存最大番号は 609_datamart_kpi.js。新規ファイルは 610 で採番する。

関数エクスポート: generateArAgingReport()(公開API、サイドバーから直接呼び出す)

2. 新規シート 67_ar_aging_report

  • 実行のたびに sheet.clearContents() で全クリアして再生成する
  • DDL (setupAllSchemas) の管理対象外(動的生成)。CLAUDE.md 記載の「DDL で管理されないタブ」カテゴリに相当
  • 生成順序: 既存シートなら getSheetByName() で取得、未作成なら ss.insertSheet(name) で作成

3. サイドバー templates/operations_sidebar.html へボタン追加

101_sys_config.jsonOpen()🚀 BizLP メニュー(操作パネルを開く / 自動起動を有効化 / 自動起動を無効化)のみを提供し、実業務操作は templates/operations_sidebar.html のサイドバーに集約されている(CLAUDE.md および operations_sidebar.html の実装を確認)。

実在する h3 セクション(絵文字・名称とも造語禁止):

セクション用途既存ボタン例
📒 経理業務 (RPA / Action)RPA起票・仕訳処理🤖 全RPA一括実行, 📥 SaaS月次請求
🔍 消込・マッチング消込系💳 クレカ消込実行
📝 費用登録費用登録📥 未登録領収書→26タブ
📊 マート更新レポート・マート生成財務3表の更新, 📊 KPIダッシュボード再描画
⚙️ メンテナンスデータ整合性✅ データ整合性チェック
🔧 開発・設定DDL・設定DDL 全更新 (Full)
🔧 マイグレーションマイグレーションD-01〜D-03 科目マスタ追加

追加先: 📊 マート更新 セクションbuildKpiDashboard 行のすぐ下)。既存のレポート系ボタンと並ぶ位置が自然。

追加するボタン:

<button class="btn" onclick="run('generateArAgingReport', this)">💰 売掛金エイジングレポート</button>

絵文字 💰 は新規採用になるが、「売掛金」を表す絵文字として分かりやすく、かつ他のボタンと被らない。

4. データ取得・フィルタリング

InvoiceRepository.findAll() の戻り値 { headers, dtos }dtos に対して以下のフィルタを順に適用する:

#条件判定式除外理由
1有効フラグ無効dto['有効フラグ'] === false || String(dto['有効フラグ']).toUpperCase() === 'FALSE'CLAUDE.md コーディング規約: 有効フラグ=FALSE の行は全処理でスキップ
2収支区分dto['収支区分'] === '収入'(完全一致)売掛金は「収入INV」のみが対象
3未決済残高Number(dto['未決済残高(自動計算)']) > 00 は決済済み。マイナスは過入金で別処理
4発生日パース可能Utils.parseDateToYmd(dto['発生日(P/L計上日)']) !== ''パース失敗時は経過日数計算不能のため除外

5. 経過日数の計算

  • 基準日: スクリプト実行日 var today = new Date(); today.setHours(0,0,0,0);(日付比較用に 00:00:00 に正規化)
  • 発生日パース: var ymd = Utils.parseDateToYmd(dto['発生日(P/L計上日)']);var occurrenceDate = new Date(ymd + 'T00:00:00');
  • 経過日数: var daysElapsed = Math.floor((today.getTime() - occurrenceDate.getTime()) / 86400000);
  • 負値の扱い: daysElapsed < 0(発生日が未来)の場合は 0-30日 バケツに含める

6. エイジングバケツ

バケツ名条件 (daysElapsed)
発生後0-30日daysElapsed < 0 || (0 <= daysElapsed && daysElapsed <= 30)
発生後31-60日31 <= daysElapsed && daysElapsed <= 60
発生後61-90日61 <= daysElapsed && daysElapsed <= 90
発生後91日以上daysElapsed >= 91

7. 出力レイアウト

シート 67_ar_aging_report の列構成:

ヘッダー名内容
A取引先名取引先名(グループキー)
B合計未決済残高当該取引先の全バケツ合計
C発生後0-30日バケツ集計額
D発生後31-60日バケツ集計額
E発生後61-90日バケツ集計額
F発生後91日以上バケツ集計額(長期滞留)

行構成:

  • 1行目: ヘッダー(太字 + Constants.COLORS.HEADER_DARK_BLUE 背景 + 白文字)
  • 2行目以降: 取引先ごとの集計行(取引先名の昇順)
  • ヘッダー直下に「基準日: YYYY-MM-DD」を表示する補助行は今回は不要(シンプルさ優先)。必要なら将来拡張

数値フォーマット: Constants.NUMBER_FORMATS.CURRENCY#,##0;[Red]△ #,##0;"-")を B-F 列に適用。

8. 排他制御

冒頭で以下のパターンを必須実装:

var lock = LockService.getScriptLock();
lock.waitLock(30000);
try {
  // ... レポート生成処理 ...
} finally {
  lock.releaseLock();
}

9. 警告色

発生後61-90日(E列)・発生後91日以上(F列)の各セルで値が 0 より大きい場合に Constants.COLORS.WARN_RED_BG'#f4cccc')を背景色として適用する。

実装方法は SpreadsheetApp.newConditionalFormatRule() による条件付き書式 を採用する(参考: 609_datamart_kpi.jsapplyKpiConditionalFormat_()whenNumberGreaterThan(0).setBackground(warnBg).setRanges([range]).build())。

理由: 再描画のたびにセルループで setBackground() を呼び出すより、条件付き書式の方が:

  • 行数が増えても高速
  • 既存ルールを setConditionalFormatRules([...]) でまるごと置換できて冪等性が担保される

10. ログ・結果通知

  • Utils.logInfo('generateArAgingReport', '処理件数: ' + count) で処理件数を記録
  • Utils.toastResult('generateArAgingReport', '売掛金エイジングレポート生成完了 (取引先N件)') で完了通知

影響範囲

変更種別ファイル変更内容
新規追加600_report/610_datamart_ar_aging.jsgenerateArAgingReport() 関数ほか
既存変更templates/operations_sidebar.html📊 マート更新 セクションにボタン1行追加

既存データへの影響: なし(32_wrk_invoice は読み取り専用、67_ar_aging_report は新規シート)。

既存ファイルへの影響: 101_sys_config.js 等の setupAllSchemas スキーマ定義には触らない(動的生成のため DDL 管理外)。

注意事項

  1. 列参照はヘッダー名ベース — CLAUDE.md コーディング規約。列番号ハードコードは禁止。InvoiceRepository.findAll()dtos 各要素はシートヘッダー名と完全一致するキーを持つ。
  2. 有効フラグの両形式対応 — 値が boolean の false と 文字列 'FALSE' の両方を許容する。判定は flag === false || String(flag).toUpperCase() === 'FALSE'
  3. 発生日のパース失敗は除外Utils.parseDateToYmd() は Date / "YYYY-MM-DD" / "YYYY/MM/DD" / "YYYY年MM月DD日" 等に対応するが、それ以外の形式やパース不能値は "" を返す。戻り値が "" のレコードは除外し、例外をスローしない。
  4. 未決済残高の型Number(dto['未決済残高(自動計算)']) で明示的に数値変換してから > 0 判定する(シート側の書式によっては文字列で入ってくる可能性を許容)。
  5. シート直接アクセス禁止 — データ取得は必ず InvoiceRepository.findAll() 経由。ss.getSheetByName('32_wrk_invoice').getDataRange() 等は使わない。
  6. 排他制御LockService.getScriptLock().waitLock(30000) を冒頭で取得し、try ... finallyreleaseLock() を保証。多重実行・サイドバーからの連打によるシート競合を防ぐ。
  7. condition formatルールの冪等性sheet.setConditionalFormatRules([...]) は既存ルールを完全置換するため、再実行しても重複しない。sheet.clearConditionalFormatRules() を明示的に呼ぶ必要はないが、書くなら安全側。
  8. 基準日の正規化new Date() は時分秒を含む。日付比較時は setHours(0,0,0,0) で 00:00:00 に正規化してから new Date(ymd + 'T00:00:00') と差分を取る(タイムゾーンずれ回避のため T00:00:00 付きで Date 化する)。
  9. 取引先名のソート — 集計後、取引先名の昇順(String.prototype.localeCompare('ja'))で並べる。同名の名寄せ処理は本案件のスコープ外(将来 MAS-154 等との連携は MAS-084 実装時に再検討)。

エッジケース

#条件動作理由
1発生日(P/L計上日) が空欄、または Utils.parseDateToYmd()"" を返す当該レコードを集計から除外する経過日数計算不能
2経過日数が負(基準日より未来の発生日)発生後0-30日 バケツに含める将来日付の収入INVも売掛金として管理対象(前受けを除いた正味のリスク残高に集計)
3未決済残高(自動計算) が 0 以下またはマイナス集計対象外(除外)0 は決済済み、マイナスは過入金(過入金の処理は別フローの責務)
4収支区分'収入' 以外('支出' 等)スキップ本レポートは売掛金(受取債権)専用。買掛金エイジングは別案件
5有効フラグfalse または 'FALSE'(文字列)スキップCLAUDE.md コーディング規約
6取引先名 が空欄(取引先名なし) として別グループで集計するデータ不整合の可視化。除外すると金額合計がBS売掛金と乖離する
7フィルタ後の集計対象レコードが 0 件ヘッダー行のみの空シートを生成。例外はスローしないデータなしは正常状態(期首直後・全件回収済み等)
867_ar_aging_report シートが未作成ss.insertSheet('67_ar_aging_report') で新規作成初回実行時の対応
967_ar_aging_report シートが既に存在clearContents() + clearConditionalFormatRules() で全クリアしてから再生成2 回目以降の実行で前回データが残らないようにする
10LockService.waitLock(30000) でタイムアウトGAS の例外をそのまま伝播させ、ユーザーに通知する多重実行防止。30 秒経っても前処理が完了しない場合は異常事態
11InvoiceRepository.findAll() が空配列(dtos.length === 0)を返すヘッダー行のみの空シートを生成。警告ログのみ出力INV シートが空 = 起票前の状態。エラーではない

実データ検証

実装前に以下を MCP または GAS エディタ(32_wrk_invoice シートのフィルタ)で確認する:

  1. 収入INV の存在確認32_wrk_invoicedtos 中、収支区分 === '収入' かつ 未決済残高(自動計算) > 0 のレコードが実在するか。0 件だとレポートが空になり動作確認にならない
  2. 発生日の格納形式発生日(P/L計上日) 列のセル値が Date オブジェクトで入っているか、文字列("YYYY/MM/DD" 等)で入っているかの混在状況を確認し、Utils.parseDateToYmd() が両形式を正しくパースすることを検証する
  3. 長期滞留の有無 — 実データに 91 日以上経過した収入INV が存在するか。警告色の表示確認のため
  4. 有効フラグの格納形式有効フラグ 列が TRUE/FALSE のチェックボックス(boolean)か、文字列 'FALSE' として入っているかを確認
  5. 取引先名の重複確認 — 同一取引先名が複数行にまたがっているか(グループ集計が効いているかの確認)

関連ドキュメント

仕様書 / コード関連箇所
000_infra/003_contracts.jsInvoiceDTO 型定義(L40-67)。フィールド名 発生日(P/L計上日) 等の完全表記の参照元
200_data/202_repository.jsInvoiceRepository.findAll()(L152-165)。戻り値は { headers: string[], dtos: InvoiceDTO[] }
000_infra/004_utils.jsUtils.parseDateToYmd()(L108-119)。Date / 文字列混在の発生日を "YYYY-MM-DD" 形式に統一
000_infra/002_constants.jsConstants.COLORS.WARN_RED_BG'#f4cccc')、Constants.NUMBER_FORMATS.CURRENCYConstants.ALLOWANCE_RATE(0.006)
600_report/609_datamart_kpi.js既存レポートの実装パターン(setConditionalFormatRules() による閾値色分け、列幅設定、排他制御なしのシンプル構成)
templates/operations_sidebar.htmlサイドバーボタンの追加先。📊 マート更新 セクション
CLAUDE.mdコーディング規約(列参照ルール・有効フラグスキップ)、GAS ファイル番号体系
TODO_future.md #s-29案件定義・期待効果
将来案件 MAS-084貸倒引当金繰入額 INVレコード化。本レポートの 発生後91日以上 バケツを参照する前提
将来案件 MAS-008資金繰り予測(Cash Runway)。売掛金の回収予定精度向上に寄与

人間が検討すべき事項

  1. Human-in-the-Loop の適用範囲 本レポートは回収リスクの可視化が目的。回収アクション(督促・入金確認)、貸倒処理の判断は人間が行う。MAS-101 のスコープは「レポート生成」までとし、督促メール自動送信・貸倒 INV 自動起票は含めない。

  2. 貸倒引当金(MAS-084)との連携設計 発生後91日以上 の滞留債権データは、将来案件 MAS-084 「貸倒引当金繰入額 INVレコード化」実装時に参照される前提。Constants.ALLOWANCE_RATE = 0.006(サービス業の法定繰入率、中小企業会計指針 第18項)との連携ポイントを本案件で設計上意識しておく:

    • エイジング集計結果(67_ar_aging_report)は MAS-084 から参照可能なシート構造とする
    • 長期滞留分に対して個別評価(取立不能見込額)を適用するか、一括評価(期末債権残高 × 法定繰入率)にするかは MAS-084 実装時に決定
    • MAS-101 の実装では 発生後91日以上 列を独立して出力し、MAS-084 からその合計が取得できるようにする
  3. DDL 管理対象に含めるかどうか 67_ar_aging_report シートを setupAllSchemas(DDL)の管理対象に含めると:

    • ヘッダー書式・列幅・データバリデーションが DDL で一元管理できる
    • 他のタブ(71_bs 等)と同じパターンで運用できる

    含めない場合(今回の方針):

    • 実行のたびに動的生成されるため、DDL 定義との二重管理が不要
    • レポート構成を柔軟に変更可能(列追加・列名変更が DDL 再実行なしで済む)

    今回は動的生成・DDL 管理外とする。CLAUDE.md 記載の「DDL で管理されないタブ」カテゴリ(77_pj_raw, 78_pj_pl, 91_fs_bs 等と同じ扱い)に準じる。

  4. エイジング基準日の指定方式 今回は「スクリプト実行日」固定とし、月末基準・期末基準の指定は将来拡張ポイントとして記録する。要望に応じて:

    • 03_sys_paramsCFG_AR_AGING_BASE_DATE を追加し、未設定時は実行日、設定時は指定日を基準とする
    • または buildArAgingReportWithCustomDate() 関数を追加してプロンプト入力で基準日を受け取る いずれも本案件のスコープ外。
  5. エイジング区分の閾値調整 現状は 0-30 / 31-60 / 61-90 / 91以上 の固定 4 区分。取引先ごとの支払条件(net30 / net60 等)を反映するには 12_mst_partner に支払条件列を追加する必要があるが、これは MAS-120(取引先マスタ拡張)と連動する別スコープ。本案件では固定閾値で実装する。

  6. 買掛金エイジング(支出INV)の別案件化 本案件は売掛金(収入INV)専用。買掛金(支出INV)のエイジングは同じロジックで実装可能だが、支払義務管理の観点で意味が異なるため別案件とする(将来的に対応)。

  7. ボタン絵文字 💰 の採用可否 既存ボタンで 💰 は未使用だが、「売掛金」を表す絵文字として妥当か要確認。他の候補: 📈(トレンド)、🧾(請求書)、(経過日数)。実装担当者が既存との視認性バランスを見て最終決定する。

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

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-101「債権管理・売掛金エイジング」を実装してください。

## 実行前タスク(Read で実コードを確認してから着手すること)

1. `000_infra/003_contracts.js` を Read し、InvoiceDTO のフィールド名(特に `発生日(P/L計上日)`・`未決済残高(自動計算)`・`収支区分`・`取引先名`・`有効フラグ` の完全表記)を確認する
2. `200_data/202_repository.js` を Read し、`InvoiceRepository.findAll()` の戻り値が `{ headers: string[], dtos: InvoiceDTO[] }` であることを確認する
3. `000_infra/004_utils.js` を Read し、`Utils.parseDateToYmd()` のパース失敗時の戻り値が `""` であることを確認する
4. `000_infra/002_constants.js` を Read し、`Constants.COLORS.WARN_RED_BG`(`'#f4cccc'`)・`Constants.NUMBER_FORMATS.CURRENCY` の定義を確認する
5. `600_report/` ディレクトリを `ls` で一覧し、既存最大番号を確認する(本案件では `609_datamart_kpi.js` が既存最大のため **610** を使う)
6. `600_report/609_datamart_kpi.js` を Read し、`SpreadsheetApp.newConditionalFormatRule()` を用いた条件付き書式の適用パターンを確認する
7. `templates/operations_sidebar.html` を Read し、`📊 マート更新` セクションの実在ボタン構造を確認してから追加位置を決定する(`101_sys_config.js` の `onOpen()` は BizLP メニュー 3項目のみを提供し、実業務ボタンは全てこのサイドバーに集約されている)

## 修正対象ファイル

- `600_report/610_datamart_ar_aging.js` — 新規作成
- `templates/operations_sidebar.html` — `📊 マート更新` セクションにボタン1行追加のみ

## 実装内容

### 1. `600_report/610_datamart_ar_aging.js`(新規)

公開関数 `generateArAgingReport()` を作成する:

1. 冒頭で `var lock = LockService.getScriptLock(); lock.waitLock(30000);` を取得し、`try...finally` で `lock.releaseLock()` を保証する
2. `InvoiceRepository.findAll()` で `{ headers, dtos }` を取得する
3. 基準日 `var today = new Date(); today.setHours(0, 0, 0, 0);` を設定
4. `dtos` を以下の条件で順にフィルタリング:
   - `dto['有効フラグ'] === false || String(dto['有効フラグ']).toUpperCase() === 'FALSE'` → スキップ
   - `dto['収支区分'] !== '収入'` → スキップ
   - `Number(dto['未決済残高(自動計算)']) <= 0` → スキップ
   - `Utils.parseDateToYmd(dto['発生日(P/L計上日)'])` が `""` → スキップ
5. 経過日数 `var daysElapsed = Math.floor((today.getTime() - new Date(ymd + 'T00:00:00').getTime()) / 86400000)` を算出。負の場合は 0 として扱い `0-30日` バケツに分類
6. `取引先名` でグループ集計し、バケツ別合計(`0-30日 / 31-60日 / 61-90日 / 91日以上`)と合計未決済残高を算出。空欄取引先は `(取引先名なし)` グループに集約
7. `ss.getSheetByName('67_ar_aging_report')` で取得、未作成なら `ss.insertSheet('67_ar_aging_report')`。`clearContents()` + `clearConditionalFormatRules()` で全クリア
8. ヘッダー行 `['取引先名', '合計未決済残高', '発生後0-30日', '発生後31-60日', '発生後61-90日', '発生後91日以上']` を書き込む。太字 + `Constants.COLORS.HEADER_DARK_BLUE` 背景 + 白文字
9. 取引先名 `localeCompare('ja')` 昇順でデータ行を `setValues` で一括書き込み
10. B-F 列に `Constants.NUMBER_FORMATS.CURRENCY` を適用
11. `SpreadsheetApp.newConditionalFormatRule().whenNumberGreaterThan(0).setBackground(Constants.COLORS.WARN_RED_BG).setRanges([E列範囲, F列範囲]).build()` で警告色を適用
12. 列幅を設定(A=200, B=150, C-F=120)
13. `sheet.setFrozenRows(1)` で 1 行目を固定
14. `Utils.logInfo` + `Utils.toastResult` で完了通知

### 2. `templates/operations_sidebar.html`(ボタン追加)

`📊 マート更新` セクションの `buildKpiDashboard` 行の直後に以下を追加する(他のコードは変更禁止):

    <button class="btn" onclick="run('generateArAgingReport', this)">💰 売掛金エイジングレポート</button>

## 制約

- 列番号ハードコード禁止。列参照はヘッダー名ベースで行うこと(`dto['発生日(P/L計上日)']` の形式)
- シートへの直接アクセス禁止。データ取得は `InvoiceRepository.findAll()` のみ使用
- サイドバー以外の既存コード(`onOpen()` など)は変更禁止
- ボタン追加箇所は `📊 マート更新` セクション内のみ
- `SHEET_DEFAULTS` や `setupAllSchemas` のスキーマ定義には触らない(`67_ar_aging_report` は動的生成で DDL 管理外)

## エッジケース

| # | 条件 | 動作 | 理由 |
|---|------|------|------|
| 1 | 発生日が空欄・パース失敗 | 除外 | 経過日数計算不能 |
| 2 | 経過日数が負(未来日付) | `0-30日` バケツに含める | 将来日付の収入INVも管理対象 |
| 3 | 未決済残高 ≤ 0 | 除外 | 決済済み or 過入金 |
| 4 | 収支区分が `'収入'` 以外 | スキップ | 売掛金専用 |
| 5 | 有効フラグ FALSE(boolean / 文字列両対応) | スキップ | CLAUDE.md コーディング規約 |
| 6 | 取引先名が空欄 | `(取引先名なし)` で別グループ集計 | BS 売掛金との金額整合 |
| 7 | 集計対象 0 件 | ヘッダーのみの空シート生成。例外スローしない | 正常状態 |
| 8 | シート未作成 | `insertSheet()` で新規作成 | 初回実行時対応 |
| 9 | シート既存 | `clearContents()` + `clearConditionalFormatRules()` で全クリア | 2回目以降の再生成 |
| 10 | `LockService.waitLock` タイムアウト | GAS 例外を伝播 | 多重実行防止 |
| 11 | `InvoiceRepository.findAll()` が空 | ヘッダーのみの空シート生成 | 起票前の正常状態 |

## 動作確認

1. `npm run push:dev` で開発環境にデプロイ
2. GAS エディタで `generateArAgingReport` を直接実行し、例外なく終了することを確認
3. `67_ar_aging_report` シートが生成され、ヘッダー 6 列が太字で表示されていることを確認
4. 取引先ごとに集計行が生成され、合計未決済残高 = 4 バケツの合計になっていることを確認(Excel 関数で検算)
5. `発生後61-90日` と `発生後91日以上` 列で 0 超のセルに背景色 `#f4cccc` が適用されていることを確認
6. 2 回目実行で前回のデータが残らず、重複・警告色の重複適用が発生しないことを確認
7. 対象データ 0 件の場合にヘッダーのみの空シートが生成され、例外が発生しないことを確認
8. サイドバー `📊 マート更新` から `💰 売掛金エイジングレポート` ボタンで実行できることを確認
9. `npm run push:prod` 本番デプロイ後、prod 環境でも同様に動作することを確認

### 拡張思考の使用状況

| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| 実行前タスク(Read 調査) | あり | 既存コードの固有名詞・パターンの正確な把握 |
| 実装(コード記述) | なし | 調査内容の書き下しに徹する |
| 動作確認 | なし | 仕様書の確認リストを順に実行 |

推奨実行モデル

工程推奨モデル理由
仕様書作成(本ドキュメント)Claude Opus 4.6新規レポート設計、既存パターン調査、貸倒引当金との連携設計を要する
実装(610_datamart_ar_aging.js 新規作成 + サイドバー追加)Claude Sonnet 4.6新規ファイル作成・既存パターン(609_datamart_kpi.js の条件付き書式適用)の踏襲。サイドバーの追記位置特定など中程度の判断が必要
動作確認(dev / prod 両環境)Claude Haiku 4.5仕様書の確認リストを順に実行するのみで判断要素なし

変更履歴

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

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

展開して表示
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
1. **拡張思考の使い分け**: Phase 1(設計)では拡張思考をフル活用し、ファイル名形式・エッジケース一覧・Step分割粒度・固有名詞(関数名/シート名/列名/行番号)を完全に確定させる。Phase 2(清書)の各Step内では拡張思考を最小限に抑え、Phase 1で確定済みの内容の書き下しに徹する。出力途中で再考しない。
2. **テキスト報告の禁止**: 「〜を作成します」等のテキストのみで 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>`記録)に分割。1回の Write/Edit は約300行以内を目安にする。
4. **各Stepで何を書くかを具体指示**: 設計判断をPhase 2実行時に持ち込まないよう、各Stepの内容をPhase 1で完全に確定させること。

======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 S-29「債権管理・売掛金エイジング」の開発仕様書を作成してください。
開発仕様書を新規作成した後は、`docs/_config.json` の `nav` 配列の適切なセクションにも必ず追記してください。

---

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

**Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず Read。推測した瞬間に手を止めて Read する。**

### 1-A: 案件定義の読み込み
`docs/_internal/TODO_future.md` を検索し、S-29 の案件名・概要・期待される効果・人間が検討すべき事項を取得する。

### 1-B: プロジェクト規約の読み込み
`CLAUDE.md` を読み込み、ファイル番号体系・コーディング規約(列参照ルール・有効フラグスキップ等)・会計ロジックのルールを把握する。

### 1-C: 既存仕様書テンプレートの読み込み
`docs/dev/dev_mas-001_variance_analysis.md`(新機能・レポート系の参考)を1件読み込み、フォーマットを把握する。

### 1-D: 関連コードの調査(Read 必須・推測禁止)

以下をすべて Read で確認し、固有名詞(変数名・関数名・シート名・列名・メニュー名)を実コードから引用する:

1. **`000_infra/003_contracts.js`** — `InvoiceDTO` の全フィールド名を確認する。特に以下の正確な列名を取得する:
   - 発生日フィールド名(`発生日(P/L計上日)` 等、括弧付きの完全表記)
   - 未決済残高フィールド名(`未決済残高(自動計算)` 等)
   - `収支区分`・`有効フラグ`・`取引先名` の正確な表記

2. **`200_data/202_repository.js`** — `InvoiceRepository.findAll()` の戻り値型(`{ headers: string[], dtos: InvoiceDTO[] }` の形式)を確認する。

3. **`000_infra/004_utils.js`** — `Utils.parseDateToYmd()` のシグネチャ・戻り値仕様(パース失敗時は `""` を返す)を確認する。

4. **`000_infra/002_constants.js`** — `Constants.COLORS` の全キー名と値を確認する(特に `WARN_RED_BG`・`WARN_RED_FC` の実値)。

5. **`100_config/101_sys_config.js`** — `onOpen()` の実在するメニュー構造(絵文字・メニュー名・サブメニュー名)を Read して確認する。仕様書に書くメニュー名は実在する文字列のみ引用する。推測・造語禁止。

6. **`600_report/` 配下のファイル一覧** — ディレクトリを一覧して最大番号を確認する(CLAUDE.md には `608_datamart_render` まで記載されているため、**新規ファイルは 609 以降で採番**。ただし実際のファイル一覧で最大番号を必ず確認して決定すること)。次に既存レポートファイルを1件 Read し、レポートシートの生成パターン(`clearContents()` の使い方・書式設定方法・条件付き書式 vs 直接 `setBackground()` の選択等)を把握する。

7. **`docs/_config.json`** — `nav` 配列を読み込み、§E.4「データマート・財務諸表」と §E.5「FP&A・レポーティング」の既存エントリのフォーマットと連番を確認し、追加先セクションと番号を確定する。

8. **`docs/_internal/failure_patterns.md`** — 特に #18-#24(仕様書記述・数式設計の失敗パターン)を確認し、ハルシネーション防止に役立てる。

---

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

出力先: `docs/dev/dev_mas-101_ar_aging_report.md`
**【重要】絶対に1回のツール呼び出しで全内容を出力せず、以下の Step に分割して実行すること。**

### Step 2-1: 骨格の作成(File Write、~20行)
(概要・目的・現在のコード・修正方針・影響範囲・注意事項・エッジケース・実データ検証・関連ドキュメント・人間が検討すべき事項・実装プロンプト・推奨実行モデル・変更履歴・仕様書作成プロンプトの見出しのみ)

### Step 2-2: 前半セクションの追記(概要〜注意事項)
(S-29 案件概要、新規ファイル番号の決定、メニュー追加先、フィルタ条件、経過日数計算、排他制御、警告色などの具体的な設計方針)

### Step 2-3a: エッジケース〜人間が検討すべき事項の追記
(11 パターンのエッジケーステーブル、実データ検証リスト、関連ドキュメント、貸倒引当金 S-12 との連携・DDL 管理範囲・基準日指定など)

### Step 2-3b: 実装プロンプト〜変更履歴の追記
(Claude Code への実装指示、Step-by-step の実装内容、制約事項、動作確認手順、推奨実行モデル、変更履歴)

### Step 2-4: 仕様書作成プロンプトの記録
(`<details>` ブロックで展開可能に本プロンプトを添付)

---

## Phase 3: 保存・登録・コミット

### 3-A: `docs/_config.json` への追記(必須)
Phase 1で確認したセクション(§E.4 または §E.5)の既存エントリ最大番号+1 で以下を追加する:
{ "file": "dev/dev_mas-101_ar_aging_report.md", "title": "E.X.X S-29 債権管理・売掛金エイジング" }
追記後、`JSON.parse()` 等で構文エラーがないことを確認する。

### 3-B: `docs/_internal/changelog.md` への追記

### 3-C: コミット&プッシュ