概要

項目内容
案件IDMAS-084
カテゴリデータマート
PhaseP1
優先度★★
所要時間90〜120分
対象ファイル400_domain/405_rpa_adhoc.js(新関数追加) / 400_domain/407_rpa_orchestrator.js(公開API追加・一括実行への組込) / templates/operations_sidebar.html(ボタン追加)
前提案件なし(600_report/604_datamart_bs.js の既存 dmCalcBs_() 計算式を INV レコード化する)

目的

中小企業会計指針 第18項に基づく差額補充法 × 法定繰入率(Constants.ALLOWANCE_RATE = 0.006)による貸倒引当金繰入額を、データマート内のオンザフライ計算値から 32_wrk_invoice の INV レコードとして実体化する。現状はマート更新時に 600_report/604_datamart_bs.jsdmCalcBs_()(L53-102)でメモリ上でのみ計算され、91_fs_bs / 71_bs に表示されるだけで、サブ元帳(INV → Action A → 42_trn_journal の仕訳経路)には流れていない。本案件で月末に自動 INV を起票し、Human-in-the-Loop の承認を経て仕訳化することで 費用計上の漏れを防止する。

現在のコード

法定繰入率の定数定義(000_infra/002_constants.js L12-13)

/** 貸倒引当金 法定繰入率 (サービス業=0.6%) — 中小企業会計指針 第18項 */
ALLOWANCE_RATE: 0.006,

Constants.getParam('CFG_ALLOWANCE_RATE', 0.006) 経由でも参照可能(03_sys_params で上書き可能)。

既存のオンザフライ計算(600_report/604_datamart_bs.js L53-71)

// 貸倒引当金の自動計算 (中小企業会計指針 第18項: 差額補充法 × 法定繰入率)
// 期末の売掛金+未収入金残高 × ALLOWANCE_RATE = あるべき引当金残高
var allowanceRate = Constants.getParam('CFG_ALLOWANCE_RATE', 0.006);
var allowanceMonthly = Array(13).fill(0);  // 月次のP/L繰入額
var recvAccts = ['売掛金'];
var recvBal = Array(13).fill(0);
for (var ra = 0; ra < recvAccts.length; ra++) {
  var arr = martBs['asset_ca'][recvAccts[ra]];
  if (arr) { var cum = arr[0]; for (var ri = 0; ri <= 12; ri++) { if (ri > 0) cum += arr[ri]; recvBal[ri] += cum; } }
}
var prevAllowance = 0;  // OB (初年度は0)
for (var ai = 1; ai <= 12; ai++) {
  var targetAllowance = Math.round(Math.max(0, recvBal[ai]) * allowanceRate);
  allowanceMonthly[ai] = Math.max(0, targetAllowance - prevAllowance);  // 差額補充 (戻入は0以上にクランプ)
  prevAllowance = targetAllowance;
}

この計算は 32_wrk_invoice に INV レコードを生成していないため、42_trn_journal への仕訳化・承認フローに載らない(マートから直接 91_fs_bs へ反映されるだけ)。

B/S 実績シート(600_report/602_datamart_main.js L178)

let sheetBs = Utils.getSheetByKey('BS_ACT', '71_bs');

71_bsマート更新時に動的生成されるシートで、A 列に   売掛金 /   貸倒引当金 等のインデント付き科目ラベル、ヘッダー行に 期首残高(OB)\nYYYY年7月末 + 12 ヶ月分の年月ラベル(YYYY-MM 形式、targetMonthsWithActBgt)が並ぶ。

修正方針

新規関数 createAllowanceForDoubtfulAccountsInv_(targetYm)400_domain/405_rpa_adhoc.js に追加する(新規ファイル作成は避け、単発経費RPAと同居させる。targetYm"YYYY-MM" 形式)。また公開 API を RPAService.generateAllowance として 400_domain/407_rpa_orchestrator.js に追加する。

1. B/S 残高取得

  • Utils.getSheetByKey('BS_ACT', '71_bs') でシートを取得
  • ヘッダー行から targetYm"YYYY-MM")に一致する列インデックスを indexOf で検索(列番号ハードコード禁止)
  • 前月年月(prevYm)も計算し、同様にヘッダー列インデックスを取得
  • 列A で String(label).replace(/[\s ]/g, '').trim() === '売掛金' となる行を検索 → 当月列の値を 売上債権合計 として取得
  • 列A で String(label).replace(/[\s ]/g, '').trim() === '貸倒引当金' となる行を検索 → 前月列の値を 前月末貸倒引当金残高 として取得(B/S では貸倒引当金はマイナス表示のため絶対値化 = Math.abs(rawVal)
  • targetYm 列が存在しない(未来年月・マート未更新)場合は Utils.logError() で停止

2. 繰入額計算

var targetAllowance = Math.round(Math.max(0, 売上債権合計) * Constants.getParam('CFG_ALLOWANCE_RATE', 0.006));
var 繰入額 = targetAllowance - 前月末貸倒引当金残高;  // マイナス(戻入益)も許容

※ 604_datamart_bs.js では Math.max(0, targetAllowance - prevAllowance)(戻入を0クランプ)だが、INV 化に際しては 戻入益発生時も正直に記録する方針に切り替える(経理担当者要確認。「## 人間が検討すべき事項」参照)。

3. upsert 処理フロー(InvoiceRepository

InvoiceRepository.findAll() → { headers, dtos } で全件取得
  ↓
dtos 中から以下の条件で既存レコードを検索:
  Utils.parseDateToYm(dto['発生日(P/L計上日)']) === targetYm
  && dto['科目名'] === '貸倒引当金繰入額'
  && dto['申請種別'] === '財務仕訳(振替等)'   ← APL_JE の Japanese label
  ↓
Case A: 既存あり かつ dto['請求ステータス'] === '未処理'
  → 金額3列(税抜金額_計画 / 消費税額_計画 / 税込金額_計画)と摘要を差し替え
  → InvoiceRepository.save(dtos)  ※全行置換
  ↓
Case B: 既存あり かつ dto['請求ステータス'] ∈ { '承認済', '却下' }
  → スキップ、Utils.logInfo() で警告出力(確定済み上書き防止)
  ↓
Case C: 既存なし
  → newDto を作成し、InvoiceRepository.append([newDto]) を呼ぶ
    (内部で AccountRepository.findAsMap() が諸表区分='P/L'・大分類='販管費'を自動付与、
     PJ名未設定時に '指定なし_共通費など' を自動設定)

4. 新規 InvoiceDTO 設定値

プロパティ
有効フラグtrue
請求ID(INV)空文字(onEdit トリガーまたは ID_PREFIX_MAP の採番ロジックに委譲)
親発注ID(ORD)空文字
起票日時new Date()
起票者`Session.getActiveUser().getEmail()
申請種別'財務仕訳(振替等)'(APL_JE の Japanese label。100_config/101_sys_config.js L1036 参照)
発生日(P/L計上日)対象月末日(YYYY-MM-DD 形式の文字列。例: targetYm='2026-03''2026-03-31'
決済日_計画発生日(P/L計上日) と同日(引当金繰入は仕訳振替で即時解消)
請求ステータス'未処理'(Human-in-the-Loop 承認フロー)
収支区分'支出'(戻入益マイナス時も '支出' のまま。方針は要確認)
取引先名'(自己)'
PJ名空文字(append で '指定なし_共通費など' が自動設定される)
組織名空文字
諸表区分空文字(append で自動設定)
大分類空文字(append で自動設定)
科目名'貸倒引当金繰入額'11_mst_account 登録表記そのまま)
税区分'対象外'(引当金繰入は消費税対象外)
通貨'JPY'
税抜金額_計画繰入額(マイナス可)
消費税額_計画0
税込金額_計画繰入額(税抜と同額、税区分=対象外)
未決済残高(自動計算)空文字(シートの式に委ねる)
決済手段'仕訳振替'(CLAUDE.md「仕訳振替の判定は === "仕訳振替" の完全一致」に準拠)
摘要'【自動計上】貸倒引当金繰入額_' + targetYm
証憑URL空文字
自動仕訳JNL_ID空文字

5. 呼び出し元

  • 手動実行: templates/operations_sidebar.html📒 経理業務 (RPA / Action) セクション(L32-43)に新ボタンを追加:
    <button class="btn" onclick="run('generateAllowanceInvoice', this)">📥 貸倒引当金繰入</button>
    
    併せて 400_domain/407_rpa_orchestrator.js に以下の公開グローバル関数を追加:
    function generateAllowanceInvoice() {
      // 対象年月プロンプト → createAllowanceForDoubtfulAccountsInv_(ym) 呼び出し
    }
    
  • 一括実行への組込: generateAllRpaInvoices()407_rpa_orchestrator.js L32-63)の末尾(L49 adhocCount 取得後、L50 total 計算前)に以下を追加:
    const allowanceCount = createAllowanceForDoubtfulAccountsInv_(Utils.currentYm_()) || 0;
    
    total / alert メッセージ / auditLog にも allowance を追加する。
  • 対象年月のデフォルト: 現在月(Utils.parseDateToYm(new Date()))。手動実行時はプロンプトで変更可能。

影響範囲

変更ファイル変更量(概算)変更内容
400_domain/405_rpa_adhoc.js+120 行新関数 createAllowanceForDoubtfulAccountsInv_(targetYm) を追加
400_domain/407_rpa_orchestrator.js+25 行RPAService.generateAllowance / 公開関数 generateAllowanceInvoice を追加、generateAllRpaInvoices() に allowance を組み込み
templates/operations_sidebar.html+1 行📒 経理業務セクションにボタン追加
600_report/604_datamart_bs.js変更なし既存のオンザフライ計算は温存(マート更新時の B/S 表示に必要)

既存動作への影響:

  • 91_fs_bs / 71_bs 上の貸倒引当金表示は変化しない(604_datamart_bs.js は従来通り動作)
  • 二重計上のリスク: 本案件で 32_wrk_invoice に INV を起票 → Action A で仕訳化 → 42_trn_journal に貸倒引当金繰入額の TRN が入る。その後マート更新で 604_datamart_bs.js のオンザフライ計算が再実行されると 同月の繰入額が重複するリスクがある。これを防ぐため、Phase 2 以降の改修で「TRN に 貸倒引当金繰入額 が既存する場合はオンザフライ計算をスキップ」する分岐を入れる必要がある(本案件のスコープ外、「## 人間が検討すべき事項」に記載)。

注意事項

  1. InvoiceRepository.save() は全行置換: findAll().dtos で全件取得してから配列内で該当 dto を差し替え → save(全dtos) の順序を厳守すること。部分更新用 API は存在しない(200_data/202_repository.js L171-177 / writeDtosToSheet_ L38-59 参照)。
  2. append() の自動補完に依存: 新規追加時は 科目名'貸倒引当金繰入額' を正確に設定すれば、InvoiceRepository.append() 内部で AccountRepository.findAsMap() が呼ばれ 諸表区分='P/L'大分類='販管費' が自動付与される(202_repository.js L185-207)。11_mst_account に未登録の場合は自動補完されないため、事前にマスタ登録を確認すること。
  3. 列参照は全てヘッダー名ベース: 71_bs シートの対象月列も、32_wrk_invoice の各列も、headers.indexOf('列名') で動的に取得すること(CLAUDE.md コーディング規約)。
  4. 日付正規化: 重複判定時は Utils.parseDateToYm(dto['発生日(P/L計上日)']) === targetYm のように文字列比較する。Date オブジェクトの直接比較は禁止(タイムゾーン差異でズレる)。
  5. 確定済みステータスの保護: 請求ステータス'承認済' または '却下' の既存 INV は絶対に上書きしないこと(Human-in-the-Loop の原則)。
  6. 71_bs シート未更新時の挙動: 対象月列がヘッダーに存在しない場合は、Utils.logError() で停止し、UI に「先にマート更新を実行してください」と表示すること。安全停止を優先。
  7. 仕訳振替フラグ: 決済手段='仕訳振替' をセットすることで、33_wrk_bank に STL を作成せずに Action A→仕訳振替経由で解消される(CLAUDE.md「決済手段「資産計上」はB/S即時計上+CF除外」に類似したフロー)。

エッジケース

条件動作・表示値理由
売上債権合計 ≤ 0処理スキップ・Utils.logInfo() で記録残高ゼロ以下は異常系。不要な INV 生成を防ぐ
計算後の繰入額が負値(戻入益)税抜金額_計画 にマイナス値をセット。収支区分'支出' のまま差額補充法では戻入益も発生しうる(経理担当者要確認)
Math.abs(繰入額) < 1処理スキップ・Utils.logInfo() で記録丸め誤差回避。1 円未満のレコードは作成しない
貸倒引当金繰入額 が科目マスタ(11_mst_account)に未登録Utils.logError() でエラー出力し処理中断未登録科目への仕訳はシステムポリシー違反(CLAUDE.md「科目マスタに未登録の科目名はエラー。キーワード推測による自動分類禁止」)
対象年月の INV が既に 承認済 または 却下upsert をスキップ・Utils.logInfo() で警告出力Human-in-the-Loop で確定済みの記録を自動処理が上書きすることを禁止
71_bs シートに対象月列が存在しない(マート未更新)Utils.logError() で停止・UI に「先にマート更新を実行してください」と表示残高取得不能時は安全停止。誤った前月値での計算を防ぐ
71_bs シートに 売掛金 行が見つからないUtils.logError() で停止B/S 科目構成の変更を検知するセーフティネット
対象月が期首(OB=期首残高(OB) 列)で前月列が存在しない前月末貸倒引当金残高 = 0(初年度想定)604_datamart_bs.js の prevAllowance = 0 初期化に揃える
targetYm の書式が "YYYY-MM" でないUtils.logError() で停止正規化失敗時の誤動作防止
同月に '未処理' の既存 INV が 2 件以上存在先頭 1 件のみ更新し、残りは Utils.logInfo() で警告手動追加された重複を検知する (通常は 1 件のみ)

実データ検証

実装前に MCP で以下の実データを確認すること。

確認項目対象期待値
科目マスタ登録11_mst_account貸倒引当金繰入額(諸表区分=P/L・大分類=販管費)・貸倒引当金(諸表区分=B/S・大分類=資産 表示区分=流動資産 or 売上債権 系)が有効フラグ=TRUE で登録されているか
B/S シート生成状況71_bsマート更新後に 売掛金 / 貸倒引当金 の行が存在し、対象月列に数値が入っているか
貸倒引当金 残高の符号71_bsB/S の慣例に従い 貸倒引当金 行はマイナス表示のはず(資産の控除項目)。コード側で Math.abs() が必要か確認
申請種別 のプルダウン値17_sys_dropdown / 15_mst_dictionary財務仕訳(振替等) が登録されているか(未登録なら DDL 再実行 or マスタ追加)
32_wrk_invoice の既存データ32_wrk_invoiceテスト対象月に 貸倒引当金繰入額 の INV が既存しないことを確認(冪等性テスト用)

関連ドキュメント

ドキュメント関連性
中小企業の会計に関する指針 第18項差額補充法・法定繰入率の根拠
PRD プロダクトポリシーHuman-in-the-Loop の適用方針(請求ステータス='未処理' で起票・手動承認)
CLAUDE.md コーディング規約列参照・有効フラグ・Repository 経由アクセスの義務
MAS-074 給与INV仕訳振替仮想消込決済手段='仕訳振替' の既存実装例
MAS-073 未払法人税等引当金繰入と同様の「計算値を INV 化する」パターンの先行事例
MAS-089 消費税 税抜方式への切替えマート計算結果を INV 化する類似構造(仮受/仮払 → 期末精算 INV)

人間が検討すべき事項

TODO_future.md 記載事項: 繰入INVの承認ステータス(自動承認 or 要手動承認)

本仕様書で採用した方針

  • 自動承認は採用しない請求ステータス='未処理' で起票し、経理担当者が Human-in-the-Loop で承認する運用とする(PRD プロダクトポリシーに準拠)。
  • 承認後は既存の Action A (processInvoiceApprovals) で 42_trn_journal に仕訳化される。

追加で経理担当者の確認が必要な事項

  1. 戻入益(繰入額マイナス)発生時の収支区分: 本仕様書では 収支区分='支出'(マイナス値)のまま起票する方針としたが、収支区分='収入' に切り替えて 科目名='貸倒引当金戻入益' として別科目で起票する方が適切かもしれない。採用するには 11_mst_account貸倒引当金戻入益 を追加する必要がある。
  2. 二重計上防止の方針: 本 INV が仕訳化された後、マート更新時の 604_datamart_bs.js dmCalcBs_() が同月の繰入額を再計算すると P/L で二重計上される。以下のいずれかで対応が必要:
    • (a) 604_datamart_bs.js に「42_trn_journal に当月の 貸倒引当金繰入額 TRN が既存する場合はオンザフライ計算をスキップ」する分岐を追加
    • (b) オンザフライ計算を完全削除し、INV → 仕訳経由の実計上のみを信頼する(MAS-084 完了を前提)
    • (c) 601_datamart_ingest.js で 貸倒引当金繰入額 の仕訳を読み込み時に除外する 本 MAS-084 スコープ外。実装前に (a)-(c) のいずれを取るか方針決定が必要
  3. 実行タイミング: 月次処理(決算仮締め)の一環として手動実行するか、マート更新時に自動で組み込むか。本仕様書では手動実行ボタン追加 + generateAllRpaInvoices への組込の両方を提供する設計としたが、マート更新トリガーでの自動化は後続案件とする。
  4. 対象年月のデフォルト: 本仕様書では「現在月」をデフォルトとしたが、日本の月次締めは翌月初に当月分を計上するため「前月」の方が実務に合うか要確認。
  5. 未収入金の対象化: 604_datamart_bs.js では recvAccts = ['売掛金'] のみだが、将来的に未収入金・受取手形も対象に含めるか検討(コメントでは「未収入金は資本取引等が混在するため除外」とある)。
  6. 決済手段 = '仕訳振替' の妥当性: 引当金繰入は (借)貸倒引当金繰入額 / (貸)貸倒引当金 の B/S 内振替的な仕訳だが、現金移動はない。決済手段='仕訳振替' + 即日決済日計画で Action A→仕訳振替経由で 42_trn_journal に登録される想定。Action B (消込) は不要。

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

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-084「貸倒引当金繰入額を32タブにINVレコード化」を実装してください。

## 実行前タスク
- `000_infra/002_constants.js`: `Constants.ALLOWANCE_RATE`(L13)の値と `Constants.getParam('CFG_ALLOWANCE_RATE', 0.006)` 経由の参照方法を確認する
- `000_infra/003_contracts.js`: `InvoiceDTO` の全プロパティ名(特に `発生日(P/L計上日)`・`申請種別`・`収支区分`・`請求ステータス`・`摘要`・`決済手段`・`税抜金額_計画`・`消費税額_計画`・`税込金額_計画`)を確認する
- `200_data/202_repository.js`: `InvoiceRepository.findAll()` の戻り値が `{ headers, dtos }` であること、`save(dtos)` が全行置換であること、`append(dtos)` が内部で `AccountRepository.findAsMap()` を呼び出して `諸表区分`・`大分類` を自動付与し `PJ名` 未設定時に `'指定なし_共通費など'` をセットすることを確認する
- `100_config/101_sys_config.js` L1036: `申請種別` のプルダウン値として `['APL_JE','財務仕訳(振替等)']` が登録されていることを確認する。**シートに書き込むのは D 列の Japanese label = `'財務仕訳(振替等)'`** を使用すること
- `600_report/604_datamart_bs.js` L53-102: 既存のオンザフライ計算ロジック(差額補充法)を温存することを確認する
- `600_report/602_datamart_main.js` L178: B/S 実績シートが `Utils.getSheetByKey('BS_ACT', '71_bs')` で取得されることを確認する
- `templates/operations_sidebar.html` L32-43: 📒 経理業務 (RPA / Action) セクションにボタンを追加する位置を確認する
- `400_domain/407_rpa_orchestrator.js` L32-63: `generateAllRpaInvoices()` への組込位置を確認する

## 修正対象ファイル
- `400_domain/405_rpa_adhoc.js`: 新関数 `createAllowanceForDoubtfulAccountsInv_(targetYm)` を追加する(targetYm は "YYYY-MM" 形式)
- `400_domain/407_rpa_orchestrator.js`: 公開ラッパー関数 `generateAllowanceInvoice()` を追加し、`RPAService.generateAllowance` もエクスポート。`generateAllRpaInvoices()` に `allowanceCount` を組み込む
- `templates/operations_sidebar.html`: 📒 経理業務セクションに `<button class="btn" onclick="run('generateAllowanceInvoice', this)">📥 貸倒引当金繰入</button>` を追加

## 実装内容

### ① `createAllowanceForDoubtfulAccountsInv_(targetYm)` の実装

1. **入力検証**: `targetYm` が `/^\d{4}-\d{2}$/` に一致しない場合は `Utils.logError()` で停止
2. **B/S 残高取得**:
   - `Utils.getSheetByKey('BS_ACT', '71_bs')` でシート取得。シート不在時は `Utils.logError()` で停止
   - ヘッダー行全列を取得し、`indexOf(targetYm)` で対象月列のインデックスを取得。見つからなければ `Utils.logError()` で停止し UI に「先にマート更新を実行してください」と表示
   - 前月の `prevYm` を計算し、同様にインデックス取得(見つからなければ `prevAllowance = 0`)
   - 列 A 全行を走査し、`String(cellA).replace(/[\s ]/g, '').trim()` が `'売掛金'` に一致する行から対象月列の数値を `receivables` として取得
   - 同様に `'貸倒引当金'` 行から前月列の数値を取得し `prevAllowance = Math.abs(rawVal)` で絶対値化
   - `売掛金` 行が見つからない場合は `Utils.logError()` で停止
3. **繰入額計算**:
   - `rate = Constants.getParam('CFG_ALLOWANCE_RATE', 0.006)`
   - `targetAllowance = Math.round(Math.max(0, receivables) * rate)`
   - `繰入額 = targetAllowance - prevAllowance`(マイナス許容)
4. **エッジケース**:
   - `receivables <= 0` → `Utils.logInfo()` で記録しスキップ
   - `Math.abs(繰入額) < 1` → `Utils.logInfo()` で記録しスキップ
   - 科目マスタに `'貸倒引当金繰入額'` が未登録 → `Utils.logError()` で停止(`AccountRepository.findAsMap()` を事前呼出しして判定)
5. **upsert 処理**:
   - `{ headers, dtos } = InvoiceRepository.findAll()` で全件取得
   - 既存検索: `Utils.parseDateToYm(dto['発生日(P/L計上日)']) === targetYm && String(dto['科目名']).trim() === '貸倒引当金繰入額' && String(dto['申請種別']).trim() === '財務仕訳(振替等)'`
   - 既存あり かつ `請求ステータス` が `'承認済'` / `'却下'` → スキップ + `Utils.logInfo()` で警告
   - 既存あり かつ `請求ステータス === '未処理'` → 該当 dto の金額3列・摘要を差し替え → `InvoiceRepository.save(dtos)` で全件保存
   - 既存なし → `newDto` を「## 修正方針 > 4. 新規 InvoiceDTO 設定値」テーブル通りに構築し、`InvoiceRepository.append([newDto])` を呼ぶ
6. **戻り値**: 1(起票)/ 0(スキップ)を返す。`generateAllRpaInvoices()` の `allowanceCount` 加算に使用

### ② `generateAllowanceInvoice()` の実装(407_rpa_orchestrator.js)

```js
function generateAllowanceInvoice() {
  const FUNC = 'generateAllowanceInvoice';
  try {
    const ui = SpreadsheetApp.getUi();
    const defaultYm = Utils.parseDateToYm(new Date());
    const res = ui.prompt('📥 貸倒引当金繰入の自動起票', '対象年月 (YYYY-MM):', ui.ButtonSet.OK_CANCEL);
    if (res.getSelectedButton() !== ui.Button.OK) return;
    const ym = (res.getResponseText() || defaultYm).trim();
    const n = createAllowanceForDoubtfulAccountsInv_(ym) || 0;
    ui.alert('✅ 貸倒引当金繰入', ym + ' の処理完了(' + n + ' 件)', ui.ButtonSet.OK);
  } catch (e) {
    Utils.logError(FUNC, e);
    SpreadsheetApp.getUi().alert('🚨 ' + FUNC + ' でエラー', e.message, SpreadsheetApp.getUi().ButtonSet.OK);
  }
}
```

また `RPAService` に `generateAllowance: function(ym) { return createAllowanceForDoubtfulAccountsInv_(ym); }` を追加し、
`generateAllRpaInvoices()` の `adhocCount` 取得直後に `const allowanceCount = createAllowanceForDoubtfulAccountsInv_(Utils.parseDateToYm(new Date())) || 0;` を追加、`total` / alert 文言 / `auditLog` の context にも `allowance` を加える。

### ③ サイドバーボタン追加

`templates/operations_sidebar.html` L42(`processInvoiceApprovals` の 1 行上)に以下を挿入:
```html
<button class="btn" onclick="run('generateAllowanceInvoice', this)">📥 貸倒引当金繰入</button>
```

## 制約
- `32_wrk_invoice` シートへの直接アクセス禁止。必ず `InvoiceRepository` 経由(CLAUDE.md コーディング規約)
- 列番号ハードコード禁止。全シートのヘッダー名ベースで列インデックスを取得すること
- `InvoiceRepository.save()` は全行置換のため `findAll().dtos` を取得してから配列内で差し替え → `save(全件)` の順序を守ること
- 未登録科目名の自動推測禁止。`11_mst_account` に `貸倒引当金繰入額` が未登録の場合は `Utils.logError()` で停止
- `承認済` / `却下` 済みの INV は絶対に上書きしないこと
- `決済手段='仕訳振替'` をセットすること(`=== "仕訳振替"` の完全一致で判定されるため、全角・スペース混入に注意)

## エッジケース
仕様書「## エッジケース」テーブルをそのまま参照すること(10 ケース)。

## 実データ検証
実装前に MCP で以下を確認する:
1. `11_mst_account` に `貸倒引当金繰入額` と `貸倒引当金` が有効フラグ=TRUE で登録されているか
2. `71_bs` シートに `売掛金` / `貸倒引当金` の行が存在し、対象月列に数値が入っているか
3. `17_sys_dropdown` に `UI申請種別` として `財務仕訳(振替等)` が含まれているか
4. `32_wrk_invoice` のテスト対象月に `貸倒引当金繰入額` の既存 INV がないこと

未登録・未生成の場合は経理担当者にマスタ登録またはマート更新を依頼してから実装を開始する。

## 動作確認
1. `npm run push:dev` で開発環境にデプロイする
2. `setupAllSchemas` 実行で DDL 反映(`17_sys_dropdown` の `UI申請種別` に `財務仕訳(振替等)` が入ることを確認)
3. サイドバーの「財務3表の更新」を実行し、`71_bs` に対象月のデータを生成する
4. サイドバー「📥 貸倒引当金繰入」を実行(対象年月: `Utils.parseDateToYm(new Date())` デフォルト)
5. `32_wrk_invoice` に `科目名='貸倒引当金繰入額'`・`申請種別='財務仕訳(振替等)'`・`請求ステータス='未処理'`・`決済手段='仕訳振替'` の INV が 1 件生成され、税抜金額_計画 = 税込金額_計画 = 繰入額(Math.round 結果)、消費税額_計画 = 0 であることを確認
6. 同一年月で再実行し、INV が重複せず金額のみ更新されることを確認(冪等性テスト)
7. 該当 INV を `承認済` に変更してから再実行し、`Utils.logInfo()` で警告出力され上書きスキップされることを確認
8. 対象月を未来月(`71_bs` に列がない月)で実行し、`Utils.logError()` + UI アラートで安全停止することを確認
9. 売掛金残高がゼロの月(設立直後等)を選んで実行し、INV が作成されないことを確認
10. Action A (`processInvoiceApprovals`) を実行し、承認済み INV が `42_trn_journal` に仕訳振替ステータスで計上されることを確認(`(借)貸倒引当金繰入額 / (貸)貸倒引当金` の対仕訳になること)

### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| 実行前調査 | あり | B/S シート構造・APL コード Japanese label の確定・604_datamart_bs.js 既存計算式の把握 |
| 実装 | なし | 仕様書定義済みの内容を書き下し |

推奨実行モデル

工程推奨モデル理由
Phase 1 調査・仕様書作成Claude SonnetB/S シート特定・APL コード確認・会計ロジック理解が必要
実装Claude Sonnet複数ファイル横断・Repository upsert パターンの判断が必要

変更履歴

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

仕様書作成プロンプト

展開して表示
<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(骨格)/ 2-2(概要〜注意事項)/ 2-3a(エッジケース〜人間検討事項)/ 2-3b(実装プロンプト〜変更履歴)/ 2-4(`<details>` 記録)の最低 5 Step に分割する。1 回の Write/Edit は約 300 行以内を目安にする。
4. **各 Step で何を書くかを具体指示**: Phase 2 実行時に設計判断を持ち込まず、Phase 1 で確定した固有名詞・値のみを書き出す。

======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 S-12「貸倒引当金繰入額を32タブにINVレコード化」の開発仕様書を作成してください。
開発仕様書を新規作成した後、`docs/_config.json` の `nav` 配列の適切なセクションにも必ず追記してください。

---

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

Phase 1 で全設計判断を確定させる。**Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず Read で裏取りする。** 名前から推測した瞬間に手を止めて Read すること(失敗パターン #18-#20 の直接対策)。

### 1-A: 案件定義の読み込み
- `docs/_internal/TODO_future.md` で S-12 の概要・カテゴリ・Phase・優先度・人間が検討すべき事項を取得する。

### 1-B: プロジェクト規約の確認
- `CLAUDE.md` を読み込み、申請種別コード(`APL_xxx` 体系: APL_TBD/HC/TR/DD/AP/EX/JE)・コーディング規約(列参照ルール、有効フラグ処理、Repository 利用義務)を把握する。

### 1-C: 既存仕様書テンプレートの参照
- `docs/dev/` 配下の既存仕様書を 1 件 Read し、フォーマットを把握する(案件性質が近いもの: `dev_mas-077_settlement_date_sync.md` または `dev_mas-080_pipeline_early_id.md` を選択)。

### 1-D: 関連コードの調査(Read で裏取り必須)

以下のファイルを Read し、仕様書で使う固有名詞・型・値・呼び出し経路を全て確定する。

1. **`000_infra/002_constants.js`**:
   - `Constants.ALLOWANCE_RATE` の値(期待値: `0.006`)と行番号を確認する。
   - `SHEET_DEFAULTS` の `pattern: '32_wrk_invoice'` エントリの `申請種別` デフォルト値を確認する。

2. **`000_infra/003_contracts.js`**:
   - `InvoiceDTO` の全プロパティ名を確認する。特に `発生日(P/L計上日)`・`申請種別`・`収支区分`・`請求ステータス`・`摘要`・`税抜金額_計画`・`税込金額_計画` の正確な列名を特定する。
   - **`対象年月` という列名が InvoiceDTO に存在しないことを確認する**(存在しない場合、重複判定には `発生日(P/L計上日)` を `Utils.parseDateToYm()` で YYYY-MM 形式に正規化して使用する)。

3. **`200_data/202_repository.js`**:
   - `InvoiceRepository.findAll()` の戻り値が `{ headers, dtos }` であることを確認する(`dtos` プロパティを参照する)。
   - `InvoiceRepository.save(dtos)` が**シート全行置換**であることを確認する(upsert パターンでは `findAll().dtos` で全件取得 → 配列内で該当レコードを差し替え → `save(全件)` の順序が必須)。
   - `InvoiceRepository.append(dtos)` が `AccountRepository.findAsMap()` を内部で呼び出し `諸表区分`・`大分類` を自動付与すること、および `PJ名` 未設定時に `'指定なし_共通費など'` をセットすることを確認する。
   - `AccountRepository.findAsMap()` の戻り値型 `{ [科目名]: { stmt, cat } }` を確認する。

4. **`000_infra/004_utils.js`**:
   - `Utils.parseDateToYm(val)` の引数・戻り値(`"YYYY-MM"` 形式)を確認する。
   - `Utils.logInfo(funcName, message)` / `Utils.logError(funcName, error, context)` のシグネチャを確認する。

5. **`100_config/101_sys_config.js`**:
   - `onOpen()` のメニュー構造を Read し、新関数を追加する既存のメニュー項目と行番号を特定する。
   - 申請種別コード `APL_JE`(または他の `APL_xxx`)が実際のコードでどのように参照されているかを確認し、自動計上 INV に使うべき正しいコード値を確定する。

6. **`400_domain/407_rpa_orchestrator.js`**(または類似のオーケストレーションファイル):
   - 新関数 `createAllowanceForDoubtfulAccountsInv_(targetYm)` をどこから呼び出すかを特定する(既存のマート更新・月次処理トリガーがあればその行番号を確認する)。

7. **`600_report/` ディレクトリの B/S 関連ファイル**:
   - Grep で `売掛金` / `貸倒引当金` / `売上債権` をキーに `600_report/` 配下を検索し、B/S 実績データが格納されているシート名とアクセスコードを Read で特定する。
   - **`71_bs` / `72_bs_snap` という名称が実際のコードに存在するかを確認する**(CLAUDE.md の動的生成タブ一覧(`91_fs_bs` 等)と照合し、存在しない名称はそのまま使わず正しいシート名に置き換える)。
   - 当月末売上債権合計・前月末貸倒引当金残高を取得するための列名・行構造を確認する。

### 1-E: マスタ実データの確認(MCP で実データを参照)
- `11_mst_account` シートに以下の科目が登録されているかを MCP で確認する:
  - `貸倒引当金繰入額`(P/L 費用科目)
  - `貸倒引当金`(B/S 評価勘定)
  - 売上債権に分類される科目(売掛金等)の正確な科目名と `大分類` の値
- 未登録の場合は「人間が検討すべき事項」に追記する。

---

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

出力先: `docs/dev/dev_mas-084_allowance_inv_creation.md`

**【重要】1 回のツール呼び出しで全内容を出力しない。以下の 5 Step に必ず分割すること。**

### Step 2-1: 骨格の作成(Write / 〜20 行)
`docs/_internal/dev_spec_prompt_template.md` の必須セクション構成に準拠した見出しのみの骨格を作成する。以下の順序で出力する:
`# S-12: 貸倒引当金繰入額を32タブにINVレコード化` から始まり、`## 概要` / `## 目的` / `## 現在のコード` / `## 修正方針` / `## 影響範囲` / `## 注意事項` / `## エッジケース` / `## 実データ検証` / `## 関連ドキュメント` / `## 人間が検討すべき事項` / `## 実装プロンプト(Claude Code 用)` / `## 推奨実行モデル` / `## 変更履歴` / `## 仕様書作成プロンプト` の順。

### Step 2-2: 前半セクションの追記(Edit または Bash heredoc / 〜300 行)
Phase 1 で確定した固有名詞・値のみを使用し、推測で書かない:

- **`## 概要`**: テーブル(案件ID, カテゴリ, Phase, 優先度, 所要時間, 対象ファイル, 前提案件)
- **`## 目的`**: 差額補充法で貸倒引当金繰入額を計算し `32_wrk_invoice` に INV レコードとして自動起票する目的(1〜3 文)
- **`## 現在のコード`**: 現状では自動起票機能がない旨、`Constants.ALLOWANCE_RATE = 0.006` の定義箇所(`000_infra/002_constants.js` Phase 1 で確認した行番号)を示す
- **`## 修正方針`**: 新規関数 `createAllowanceForDoubtfulAccountsInv_(targetYm)` の設計を記載する:
  - **B/S 残高取得**: Phase 1 で特定した正しいシート名から、ヘッダー名ベース(列番号ハードコード禁止)で当月末売上債権合計・前月末貸倒引当金残高を取得する方法
  - **繰入額計算式**: `繰入額 = Math.round(売上債権合計 × Constants.ALLOWANCE_RATE) - 前月末貸倒引当金残高`
  - **upsert 処理フロー**: `InvoiceRepository.findAll().dtos` で全件取得 → `Utils.parseDateToYm(dto['発生日(P/L計上日)']) === targetYm` かつ `dto['科目名'] === '貸倒引当金繰入額'` かつ `dto['申請種別'] === [Phase 1 で確認した APL_xxx]` で既存レコードを検索 → 既存あり(かつ `請求ステータス` が `'未処理'`)の場合は金額フィールドを差し替えて `InvoiceRepository.save(全dtos)` → 既存なしの場合は `InvoiceRepository.append([newDto])`
  - **InvoiceDTO 設定値**: `発生日(P/L計上日)` = 対象月末日(`YYYY-MM-DD` 形式)、`申請種別` = Phase 1 で確認した APL_xxx コード、`請求ステータス` = `'未処理'`、`収支区分` = `'支出'`、`科目名` = `'貸倒引当金繰入額'`(マスタ登録表記そのまま)、`税抜金額_計画` = 繰入額(マイナスも可)、`消費税額_計画` = 0、`税込金額_計画` = 繰入額、`摘要` = `'【自動計上】貸倒引当金繰入額_' + targetYm`、`有効フラグ` = true
  - **呼び出し元**: Phase 1 で確認したメニュー項目・オーケストレーション関数と追加行番号
- **`## 影響範囲`**: 変更ファイル・変更量・既存動作への影響
- **`## 注意事項`**: 番号付きリストで以下を記載:
  1. `InvoiceRepository.save()` は全行置換のため、`findAll().dtos` で全件取得してから配列内で差し替え → `save(全件)` の順序を厳守すること
  2. `InvoiceRepository.append()` は内部で `AccountRepository.findAsMap()` を呼び `諸表区分`・`大分類` を自動付与するため、`科目名` は `11_mst_account` に登録された正確な表記を使うこと
  3. B/S 残高シートの列参照は列番号ハードコード禁止。ヘッダー名で `indexOf` して取得すること(コーディング規約)
  4. `Utils.parseDateToYm()` で日付を YYYY-MM 形式に正規化してから重複チェックを行うこと(生の Date オブジェクト比較禁止)
  5. `承認済` または `却下` ステータスの既存 INV は上書きしないこと

### Step 2-3a: エッジケース〜人間検討事項の追記(Edit または Bash / 〜200 行)

- **`## エッジケース`**: テーブルを作成する(売上債権合計≤0・戻入益・1円未満・未登録科目・確定ステータス保護・B/S未生成・対象月列なし 等)
- **`## 実データ検証`**: Phase 1-E で確認した科目マスタの状態を記載。実装前に MCP で確認する項目を列挙
- **`## 関連ドキュメント`**: テーブルで関連仕様書リンクを記載
- **`## 人間が検討すべき事項`**: TODO_future.md 記載事項を転記し、自動承認vs手動承認、戻入益の収支区分、二重計上防止の方針、実行タイミング等を追加

### Step 2-3b: 実装プロンプト〜変更履歴の追記(Edit または Bash / 〜250 行)
仕様書末尾の実装プロンプトを 4 スペースインデントで記述(バッククォート3つのコードブロック回避)。続けて推奨実行モデル・変更履歴テーブルを追記。

### Step 2-4: 仕様書作成プロンプトの記録(Edit または Bash / プロンプトサイズ依存)
仕様書末尾の `## 仕様書作成プロンプト` セクションに、`<details><summary>展開して表示</summary>` ブロックで囲み、この `<instruction>` 全文を記録する。

---

## Phase 3: `_config.json` への追記・changelog 記録・コミット

### 3-B: _config.json にナビゲーション登録(必須)
- `docs/_config.json` を Read し、Phase 1-A で確認した案件カテゴリに基づいて適切なセクション(§E.2 バグ修正・バリデーション / §E.4 データマート・財務諸表 / §E.6 パイプライン・RPA・外部連携のいずれか)に追加する。

### 3-C: changelog 追記 + コミット&プッシュ
`docs/_internal/changelog.md` のヘッダー直後に追記し、docs/dev-S-12 ブランチで commit & push。
</instruction>