最終更新: 2026/06/22 18:56
MAS-148: ワンクリック月次締め
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-148 |
| カテゴリ | 自動入力パイプライン(即効) |
| Phase | P1.5 |
| 優先度 | ★★★ |
| 所要時間 | 2-3時間 |
| 実装ステータス | 📝 仕様書段階・実装未着手 (2026-04-28 監査時点) |
| 対象ファイル | 100_config/101_sys_config.js(メニュー追加), 400_domain/410_subledger_engine.js(統合関数追加) |
| 前提案件 | なし(Action A/B/マート更新は全て実装済み) |
目的
月次締めに必要な3つの操作(Action A → Action B → マート更新)を1つのメニュー項目で連続実行できるようにし、締め作業のUXを改善する。事前バリデーション(未承認INV・未消込STLの警告)を付与することで、差戻し漏れや処理忘れも防止する。
現在のコード
月次締めに必要な3つの操作(手動で順次実行)
現在、月次締めは以下の3つのメニュー操作を順番に手動実行する必要がある:
| 順番 | メニュー | 関数 | ファイル:行番号 |
|---|---|---|---|
| 1 | 📒 経理業務 → 承認済INVを仕訳台帳へ転記 (Action A) | processInvoiceApprovals() | 410_subledger_engine.js:106 |
| 2 | 📒 経理業務 → 消込済STLを仕訳台帳へ転記 (Action B) | processSettlementClearings() | 410_subledger_engine.js:352 |
| 3 | 📊 マート更新 → 財務3表の更新 | buildBudgetTrendDataMart() | 602_datamart_main.js:157 |
Action A のエントリーポイント(410_subledger_engine.js L106)
function processInvoiceApprovals() {
const FUNC = 'processInvoiceApprovals';
try {
const invSheet = Utils.getSheetByKey('WRK_INVC', '32_wrk_invoice');
// ...
// 完了時:
SpreadsheetApp.getActiveSpreadsheet().toast(
`🎉 ${newTrnRows.length}件のINV承認を仕訳台帳へ転記しました${stlMsg}`, '処理完了', 5
);
} catch (e) {
console.error(FUNC, e.message, e.stack);
SpreadsheetApp.getUi().alert(`🚨 ${FUNC} でエラーが発生しました`, e.message, SpreadsheetApp.getUi().ButtonSet.OK);
}
}
Action B のエントリーポイント(410_subledger_engine.js L352)
function processSettlementClearings() {
const FUNC = 'processSettlementClearings';
try {
const bankSheet = Utils.getSheetByKey('WRK_BANK', '33_wrk_bank');
// ...
// 完了時:
SpreadsheetApp.getActiveSpreadsheet().toast(
`🎉 ${processCount}件のSTL消込を仕訳台帳へ転記しました`, '処理完了', 5
);
} catch (e) {
console.error(FUNC, e.message, e.stack);
SpreadsheetApp.getUi().alert(`🚨 ${FUNC} でエラーが発生しました`, e.message, SpreadsheetApp.getUi().ButtonSet.OK);
}
}
マート更新のエントリーポイント(602_datamart_main.js L157)
function buildBudgetTrendDataMart(overrideBoundary) {
const FUNC = 'buildBudgetTrendDataMart';
try {
// ... 約200行のマート構築ロジック
ss.toast('🎉 マート更新完了 ...', '完了', 5);
} catch (e) {
Utils.logError(FUNC, e);
SpreadsheetApp.getUi().alert(`🚨 ${FUNC} でエラーが発生しました`, e.message, SpreadsheetApp.getUi().ButtonSet.OK);
}
}
現在のメニュー構造(101_sys_config.js L299-377)
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('📒 経理業務')
// ... RPA 6種 ...
.addItem('📋 承認済INVを仕訳台帳へ転記 (Action A)', 'processInvoiceApprovals') // L311
.addItem('✅ 消込済STLを仕訳台帳へ転記 (Action B)', 'processSettlementClearings') // L312
.addToUi();
// ...
ui.createMenu('📊 マート更新')
.addItem('財務3表(P/L・B/S・C/F)の更新', 'buildBudgetTrendDataMart') // L329
// ...
修正方針
設計思想
既存の processInvoiceApprovals() / processSettlementClearings() / buildBudgetTrendDataMart() はそのまま維持し、これらを順次呼び出すオーケストレーター関数 runMonthlyClose() を新設する。各関数の内部ロジックには一切手を加えない。
ただし、既存関数は内部で try-catch してエラー時に ui.alert を出す設計のため、オーケストレーターからはエラーの発生を直接検知できない。そこで、各関数の実行前後でデータ状態(TRN行数等)を比較し、処理の進行を確認する。
Step 1: 事前バリデーション関数 preCloseValidation_() の追加
410_subledger_engine.js の末尾にプライベート関数として追加。32_wrk_invoice / 33_wrk_bank を走査し、月次締め前に確認すべき状態を集計する。
/**
* I-04: 月次締め事前バリデーション
* @return {{ warnings: string[], pendingActionA: number, pendingActionB: number }}
*/
function preCloseValidation_() {
var ss = getWebSpreadsheet_();
var warnings = [];
// 32_wrk_invoice チェック
var invSheet = Utils.getSheetByKey('WRK_INVC', '32_wrk_invoice');
var pendingActionA = 0;
var unapprovedCount = 0;
if (invSheet) {
var invData = invSheet.getDataRange().getValues();
var invIdx = buildHeaderIndex_(invData[0]);
var iFlag = invIdx['有効フラグ'];
var iStatus = invIdx['請求ステータス'];
var iJnl = invIdx['自動仕訳JNL_ID'];
for (var i = 1; i < invData.length; i++) {
if (iFlag !== undefined && (invData[i][iFlag] === false ||
String(invData[i][iFlag]).toUpperCase() === 'FALSE')) continue;
var status = String(invData[i][iStatus] || '').trim();
var jnlId = String(invData[i][iJnl] || '').trim();
if (status === '承認済' && !jnlId) pendingActionA++;
if (status === '未処理' || status === '未承認') unapprovedCount++;
}
}
// 33_wrk_bank チェック
var bankSheet = Utils.getSheetByKey('WRK_BANK', '33_wrk_bank');
var pendingActionB = 0;
var unsettledCount = 0;
if (bankSheet) {
var bankData = bankSheet.getDataRange().getValues();
var bankIdx = buildHeaderIndex_(bankData[0]);
var bFlag = bankIdx['有効フラグ'];
var bStatus = bankIdx['決済ステータス'];
var bJnl = bankIdx['自動仕訳JNL_ID'];
for (var j = 1; j < bankData.length; j++) {
if (bFlag !== undefined && (bankData[j][bFlag] === false ||
String(bankData[j][bFlag]).toUpperCase() === 'FALSE')) continue;
var bStat = String(bankData[j][bStatus] || '').trim();
var bJnlId = String(bankData[j][bJnl] || '').trim();
if (bStat === '消込済' && !bJnlId) pendingActionB++;
if (bStat === '未処理') unsettledCount++;
}
}
if (unapprovedCount > 0)
warnings.push('⚠️ 未承認INV: ' + unapprovedCount + '件(承認を確認してください)');
if (unsettledCount > 0)
warnings.push('⚠️ 未消込STL: ' + unsettledCount + '件(消込を確認してください)');
return {
warnings: warnings,
pendingActionA: pendingActionA,
pendingActionB: pendingActionB
};
}
Step 2: オーケストレーター関数 runMonthlyClose() の追加
410_subledger_engine.js の末尾(preCloseValidation_() の後)に追加。
/**
* I-04: ワンクリック月次締め
* Action A → Action B → マート更新 を連続実行する
*/
function runMonthlyClose() {
var FUNC = 'runMonthlyClose';
var ui = SpreadsheetApp.getUi();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var summary = [];
try {
// Phase 0: 事前バリデーション
Utils.logInfo(FUNC, '月次締め開始 — 事前チェック');
var validation = preCloseValidation_();
// 警告がある場合はユーザーに確認
if (validation.warnings.length > 0) {
var warningMsg = '以下の未処理データがあります:\n\n'
+ validation.warnings.join('\n')
+ '\n\nこのまま月次締めを続行しますか?\n'
+ '(未承認・未消込のデータは締め処理の対象外です)';
var confirm = ui.alert('📋 月次締め — 事前チェック', warningMsg, ui.ButtonSet.YES_NO);
if (confirm !== ui.Button.YES) {
Utils.logInfo(FUNC, 'ユーザーによりキャンセル');
return;
}
}
// Phase 1: Action A(承認済INV→仕訳台帳)
if (validation.pendingActionA > 0) {
ss.toast('📋 Step 1/3: Action A 実行中...', '月次締め', 30);
processInvoiceApprovals();
summary.push('✅ Action A: ' + validation.pendingActionA + '件のINVを転記');
} else {
summary.push('⏭️ Action A: 対象INVなし(スキップ)');
}
// Phase 2: Action B(消込済STL→仕訳台帳)
if (validation.pendingActionB > 0) {
ss.toast('✅ Step 2/3: Action B 実行中...', '月次締め', 30);
processSettlementClearings();
summary.push('✅ Action B: ' + validation.pendingActionB + '件のSTLを転記');
} else {
summary.push('⏭️ Action B: 対象STLなし(スキップ)');
}
// Phase 3: マート更新
ss.toast('📊 Step 3/3: マート更新中...', '月次締め', 60);
buildBudgetTrendDataMart();
summary.push('✅ マート更新: 財務3表(P/L・B/S・C/F)を再構築');
// 完了サマリー
Utils.logInfo(FUNC, '月次締め完了');
ui.alert('🎉 月次締め完了',
summary.join('\n') + '\n\n'
+ '環境: ' + Env.name().toUpperCase(),
ui.ButtonSet.OK);
} catch (e) {
Utils.logError(FUNC, e);
ui.alert('🚨 月次締めでエラーが発生しました',
'処理済み:\n' + (summary.length > 0 ? summary.join('\n') : '(なし)')
+ '\n\nエラー: ' + e.message
+ '\n\n途中で停止しました。各処理を個別に実行してください。',
ui.ButtonSet.OK);
}
}
Step 3: メニューへの追加
101_sys_config.js の onOpen() に新メニュー項目を追加。「📒 経理業務」メニューの先頭に配置する。
ui.createMenu('📒 経理業務')
.addItem('📊 月次締め実行 (A→B→マート)', 'runMonthlyClose') // I-04: 追加
.addSeparator() // I-04: 追加
.addItem('🤖 全RPA一括実行(SaaS+HC+CAPEX+FIN+Adhoc)', 'generateAllRpaInvoices')
// ... 以下変更なし
影響範囲
| 変更対象 | 変更内容 | 変更量 |
|---|---|---|
400_domain/410_subledger_engine.js | preCloseValidation_() + runMonthlyClose() 追加 | ~100行 |
100_config/101_sys_config.js | メニュー項目2行追加(.addItem + .addSeparator) | 2行 |
- 既存動作への影響: なし。既存の
processInvoiceApprovals()/processSettlementClearings()/buildBudgetTrendDataMart()は一切変更しない。個別実行も従来通り可能 - 新規関数はグローバル1つ + プライベート1つ:
runMonthlyClose()はメニューから呼び出す公開関数。preCloseValidation_()は末尾アンダースコア付きのプライベート関数
注意事項
- GAS 6分実行制限: Action A + Action B + マート更新の合計実行時間が6分を超えるとGASがタイムアウトする。現状の実データ量(INV 数百件、STL 数百件)では問題ないが、データ量が増加した場合は MAS-194(長時間処理の分割実行)と連携が必要
- 既存関数の
try-catch: Action A/B は内部でtry-catchしてエラー時にui.alertを出す。オーケストレーターのcatchでは捕捉できないが、ui.alertがユーザーに表示されるため実用上問題ない。ただしその場合、後続の処理(Action B やマート更新)は引き続き実行される - 冪等性: Action A/B は JNL_ID の有無で処理済み判定するため、2回実行しても安全(0件処理になるだけ)。マート更新も全データを再構築するため冪等
- toast の上書き: 各 Phase で
ss.toast()を使って進捗表示するが、既存関数内の toast と上書きし合う。表示は最後の toast が残る(機能上問題なし) - Action A/B の「対象なし」判定:
preCloseValidation_()で事前に件数を確認し、0件ならスキップ。ただしバリデーション後にユーザーが別タブでデータを変更する可能性はゼロではない(実用上無視可能)
エッジケース
| 条件 | 動作 | 理由 |
|---|---|---|
| 未承認INV・未消込STLが存在 | 警告ダイアログ表示 → YES で続行、NO でキャンセル | 差戻し漏れの防止。未承認・未消込は処理対象外であることを明示 |
| Action A/B の対象が0件 | スキップしてサマリーに「⏭️ 対象なし」と表示 | 不要な処理を避けてマート更新に進む |
| Action A 実行中にエラー | Action A の ui.alert でエラー表示後、Action B・マート更新が続行 | Action A の内部 catch がエラーを吸収するため。部分的な処理結果はサマリーに反映 |
| 全処理が正常完了 | サマリーダイアログに各 Phase の結果を一覧表示 | ユーザーが処理結果を一目で確認できる |
| GAS 6分タイムアウト | GAS ランタイムが強制終了。catch に到達しない | 現状のデータ量では発生しない。将来的に MAS-194 で対応 |
| 事前チェックでキャンセル | 何も実行しない | ユーザーが未承認INV等を先に処理するため |
| dev 環境で実行 | 正常動作。サマリーに「環境: DEV」と表示 | Env.name() で判定 |
実データ検証(MCP でのデータ確認が必要な場合)
| 確認項目 | 確認方法 | 理由 |
|---|---|---|
| 32_wrk_invoice の請求ステータス値 | MCP で 請求ステータス 列のユニーク値を確認 | バリデーションの条件(「承認済」「未処理」「未承認」)が実データと一致するか |
| 33_wrk_bank の決済ステータス値 | MCP で 決済ステータス 列のユニーク値を確認 | バリデーションの条件(「消込済」「未処理」)が実データと一致するか |
| 3処理の合計実行時間 | Action A/B/マート更新をそれぞれ計測 | GAS 6分制限の余裕度を把握 |
関連ドキュメント
| 仕様書 | 関連箇所 |
|---|---|
| CLAUDE.md | 変更時の動作確認テスト: 400_domain/401_bat_rpa.js → HC RPA テスト, 403_subledger_engine.js → Action A → Action B テスト |
| spec_pl.md | P/L計上のフィルタ条件(承認済・決済完了のみ) |
| spec_bs.md | B/S計上のフィルタ条件 |
| 410_subledger_engine.js | L106: Action A, L352: Action B |
| 602_datamart_main.js | L157: マート更新メインエントリーポイント |
| dev_mas-094_boundary_month_selector.md | 基準年月の手動指定(月次締めでは自動計算を使用) |
人間が検討すべき事項
- GAS 6分実行制限への対策: 現状のデータ量では問題ないが、INV/STL が数千件に増加した場合、3処理の合計が6分を超える可能性がある。MAS-194(長時間処理の分割実行)と連携して、タイムアウト時に残りの処理を自動スケジュールする仕組みが将来的に必要(TODO_future.md から転記)
- 途中エラー時のリカバリー方針: Action A でエラーが発生した場合、Action B やマート更新を続行すべきか中断すべきか。現設計では Action A/B の内部 catch がエラーを吸収するため、実質的に続行される。これが望ましいかはデータの整合性リスクに依存する
- 事前バリデーションの項目拡張: 将来的に MAS-149(取込進捗ダッシュボード)や MAS-183(月次決算チェックリスト)と統合し、「CC未マッチ件数」「領収書未処理件数」などのチェック項目を追加できる設計になっている
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-148「ワンクリック月次締め」を実装してください。
## 実行前タスク
以下のファイルを読み込んでください:
1. `400_domain/410_subledger_engine.js` — `processInvoiceApprovals()` (L106) と `processSettlementClearings()` (L352) の全体構造。エラーハンドリング(try-catch + ui.alert)のパターンを確認
2. `600_report/602_datamart_main.js` — `buildBudgetTrendDataMart()` (L157) の関数シグネチャと完了通知(L368)
3. `100_config/101_sys_config.js` — `onOpen()` (L299) のメニュー構築。「📒 経理業務」メニュー (L301-313) の先頭に追加する
4. `000_infra/004_utils.js` — `Utils.logInfo()` / `Utils.logError()` / `Utils.getSheetByKey()` のシグネチャ
5. `000_infra/001_env.js` — `Env.name()` のシグネチャ
6. `CLAUDE.md`
7. `docs/dev/dev_mas-148_oneclick_monthly_close.md`
## 修正対象ファイル
- `400_domain/410_subledger_engine.js` — 末尾に2関数追加
- `100_config/101_sys_config.js` — メニュー項目追加(2行)
## 実装内容
### A: preCloseValidation_() の追加(410_subledger_engine.js 末尾)
32_wrk_invoice と 33_wrk_bank を走査し、以下を集計するプライベート関数:
- `pendingActionA`: 承認済 かつ JNL_ID 空の INV 件数(Action A 対象)
- `pendingActionB`: 消込済 かつ JNL_ID 空の STL 件数(Action B 対象)
- `warnings`: 未承認INV件数・未消込STL件数の警告メッセージ配列
ヘッダー参照は `buildHeaderIndex_()` を使用(列番号ハードコード禁止)。
有効フラグ=FALSE の行はスキップ。
### B: runMonthlyClose() の追加(410_subledger_engine.js 末尾、preCloseValidation_ の後)
1. `preCloseValidation_()` で事前チェック
2. warnings がある場合は `ui.alert(YES_NO)` で続行確認。NO ならリターン
3. pendingActionA > 0 なら `processInvoiceApprovals()` を実行。0件ならスキップ
4. pendingActionB > 0 なら `processSettlementClearings()` を実行。0件ならスキップ
5. `buildBudgetTrendDataMart()` を引数なしで実行(常に実行)
6. 完了後にサマリーダイアログ(`ui.alert`)で各 Phase の結果を表示
各 Phase の前に `ss.toast()` で進捗を表示(「Step 1/3: Action A 実行中...」等)。
全体を try-catch で囲み、エラー時は処理済み内容 + エラーメッセージを表示。
### C: メニュー追加(101_sys_config.js L301-302)
「📒 経理業務」メニューの先頭(`generateAllRpaInvoices` の前)に追加:
```js
.addItem('📊 月次締め実行 (A→B→マート)', 'runMonthlyClose')
.addSeparator()
```
## 制約
- 既存の `processInvoiceApprovals()` / `processSettlementClearings()` / `buildBudgetTrendDataMart()` は一切変更しない
- `preCloseValidation_()` の列参照は必ずヘッダー名ベース(`buildHeaderIndex_` 使用)
- 有効フラグ=FALSE の行は全処理でスキップ
- 新規関数は `410_subledger_engine.js` の末尾(`processSettlementClearings` 関数の後)に追加
## 動作確認
`npm run push:dev` 後:
1. スプレッドシートをリロードし、「📒 経理業務」メニューの先頭に「📊 月次締め実行 (A→B→マート)」が表示されること
2. 未承認INVまたは未消込STLがある状態で実行 → 警告ダイアログが表示されること
3. 警告ダイアログで「いいえ」 → 何も実行されないこと
4. 警告ダイアログで「はい」(または警告なし) → Action A → Action B → マート更新が順次実行されること
5. 完了後にサマリーダイアログが表示され、各 Phase の結果(処理件数 or スキップ)が確認できること
6. Action A/B の対象が0件の場合 → スキップされてマート更新のみ実行されること
7. 2回連続実行 → 2回目は Action A/B とも「対象なし(スキップ)」になること(冪等性)
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|:--------:|------|
| preCloseValidation_ 実装 | なし | 32/33タブの走査 + 条件カウントの単純ロジック |
| runMonthlyClose 実装 | なし | 既存3関数の順次呼び出し + UI制御 |
| メニュー追加 | なし | addItem + addSeparator の2行追加 |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| 仕様書作成(本ドキュメント) | Claude Opus 4.6 | 既存3関数のエラーハンドリングパターン分析、オーケストレーター設計の判断が必要 |
| 実装 | Claude Haiku 4.5 | 仕様書でコードが完全に定義済み。2関数追加 + メニュー2行の機械的作業 |
| 動作確認 | ユーザー手動 | GASエディタでのメニュー操作・ダイアログ確認が必要 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-16 | 初版作成 |
| 2026-04-16 | テンプレート準拠で全面改訂。コード調査に基づき行番号・エラーハンドリングパターンを反映。事前バリデーション・エッジケース・実データ検証セクション追加 |