MAS-084: 貸倒引当金繰入額を32タブにINVレコード化
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-084 |
| カテゴリ | データマート |
| Phase | P1 |
| 優先度 | ★★ |
| 所要時間 | 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.js の dmCalcBs_()(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.jsL32-63)の末尾(L49adhocCount取得後、L50total計算前)に以下を追加: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 に貸倒引当金繰入額が既存する場合はオンザフライ計算をスキップ」する分岐を入れる必要がある(本案件のスコープ外、「## 人間が検討すべき事項」に記載)。
注意事項
InvoiceRepository.save()は全行置換:findAll().dtosで全件取得してから配列内で該当 dto を差し替え →save(全dtos)の順序を厳守すること。部分更新用 API は存在しない(200_data/202_repository.jsL171-177 /writeDtosToSheet_L38-59 参照)。append()の自動補完に依存: 新規追加時は科目名に'貸倒引当金繰入額'を正確に設定すれば、InvoiceRepository.append()内部でAccountRepository.findAsMap()が呼ばれ諸表区分='P/L'・大分類='販管費'が自動付与される(202_repository.jsL185-207)。11_mst_accountに未登録の場合は自動補完されないため、事前にマスタ登録を確認すること。- 列参照は全てヘッダー名ベース:
71_bsシートの対象月列も、32_wrk_invoiceの各列も、headers.indexOf('列名')で動的に取得すること(CLAUDE.md コーディング規約)。 - 日付正規化: 重複判定時は
Utils.parseDateToYm(dto['発生日(P/L計上日)']) === targetYmのように文字列比較する。Dateオブジェクトの直接比較は禁止(タイムゾーン差異でズレる)。 - 確定済みステータスの保護:
請求ステータスが'承認済'または'却下'の既存 INV は絶対に上書きしないこと(Human-in-the-Loop の原則)。 71_bsシート未更新時の挙動: 対象月列がヘッダーに存在しない場合は、Utils.logError()で停止し、UI に「先にマート更新を実行してください」と表示すること。安全停止を優先。- 仕訳振替フラグ:
決済手段='仕訳振替'をセットすることで、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_bs | B/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 に仕訳化される。
追加で経理担当者の確認が必要な事項
- 戻入益(繰入額マイナス)発生時の収支区分: 本仕様書では
収支区分='支出'(マイナス値)のまま起票する方針としたが、収支区分='収入'に切り替えて科目名='貸倒引当金戻入益'として別科目で起票する方が適切かもしれない。採用するには11_mst_accountに貸倒引当金戻入益を追加する必要がある。 - 二重計上防止の方針: 本 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) のいずれを取るか方針決定が必要。
- (a) 604_datamart_bs.js に「
- 実行タイミング: 月次処理(決算仮締め)の一環として手動実行するか、マート更新時に自動で組み込むか。本仕様書では手動実行ボタン追加 +
generateAllRpaInvoicesへの組込の両方を提供する設計としたが、マート更新トリガーでの自動化は後続案件とする。 - 対象年月のデフォルト: 本仕様書では「現在月」をデフォルトとしたが、日本の月次締めは翌月初に当月分を計上するため「前月」の方が実務に合うか要確認。
- 未収入金の対象化: 604_datamart_bs.js では
recvAccts = ['売掛金']のみだが、将来的に未収入金・受取手形も対象に含めるか検討(コメントでは「未収入金は資本取引等が混在するため除外」とある)。 - 決済手段 =
'仕訳振替'の妥当性: 引当金繰入は(借)貸倒引当金繰入額 / (貸)貸倒引当金の 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 Sonnet | B/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>