概要

項目内容
案件IDMAS-160
カテゴリ新機能(UI・アラート)
PhaseP1.5
優先度★★★(Top 15 昇格済)
所要時間1-2時間
実装ステータス📝 仕様書段階・実装未着手 (2026-04-28 監査時点)
対象ファイル(新規)400_domain/430_monthly_closing_validator.js300_ui/310_monthly_closing_ui.jstemplates/310_monthly_closing_dialog.html
修正ファイル000_infra/002_constants.jsConstants.MENU_DEFINITION にエントリ追加)
前提案件MAS-148「ワンクリック月次締め」(runMonthlyClose() の実行前フックとして動作)

目的

月次締め処理(MAS-148: runMonthlyClose())の実行前に、システムが未処理項目を能動的に横断スキャンし、担当者が内容を確認した上で締めを実行または中止できる Human-in-the-Loop フックを提供する。

従来の MAS-148 preCloseValidation_() は「未承認INV件数」「未消込STL件数」の単純カウントと警告テキストのみを提供するが、MAS-160 では以下を追加する:

  1. 横断スキャン: 32_wrk_invoice / 33_wrk_bank / 31_wrk_order を対象月でフィルタしてカテゴリ別に集計
  2. サマリーダイアログ: カテゴリ別件数テーブル + 詳細リストをモーダルで表示
  3. 強制実行オプション: 未処理項目ありでも内容を確認した担当者が「強制実行」を選択できる(Human-in-the-Loop: 判断を人間が担保)
  4. 監査ログ記録: 未処理項目ありで強制実行した場合は Utils.auditLog に記録

現在のコード

該当処理なし(新規実装)。

MAS-148 の実行起点は 400_domain/410_subledger_engine.jsrunMonthlyClose() 関数(preCloseValidation_() を内部呼び出し)。MAS-160 は runMonthlyClose()Phase 0 として呼び出されるフックとして設計するか、独立したメニュー項目として提供する(後述「人間が検討すべき事項」を参照)。

MAS-148 preCloseValidation_() との関係:

  • MAS-148 の preCloseValidation_()warnings[] と pendingActionA/B カウントを返すシンプルな実装
  • MAS-160 の MonthlyClosingValidator.checkUnprocessedItems(targetYm) は対象年月ベースのフィルタと詳細DTOリストを返す、より詳細な実装
  • 両者はスコープが異なるため競合しない(MAS-160 を MAS-148 の前段フックとして配置することで事前確認を二層化できる)

修正方針

新規ファイル① 400_domain/430_monthly_closing_validator.js

名前空間オブジェクト MonthlyClosingValidator を定義し、パブリック関数 checkUnprocessedItems(targetYm) を実装する。

引数: targetYm"YYYY-MM" 形式の文字列(例: "2026-03"

戻り値:

{
  invoices:    Object[],  // 未承認請求書のDTOリスト
  bankTxs:     Object[],  // 未処理決済レコードのDTOリスト
  orders:      Object[],  // 検収漏れ発注のDTOリスト
  cards:       Object[],  // 未マッチカード明細(34_wrk_card)
  bankImports: Object[],  // 未マッチ銀行明細(36_wrk_bank_import)
  total:       number     // 全カテゴリの合計件数
}

各チェック処理の実装:

  1. 未承認請求書InvoiceRepository.findAll().dtos をフィルタ。

    • if (!dto['発生日(P/L計上日)']) continue; でガード(parseDateToYm(null) = "" による誤混入防止)
    • Utils.parseDateToYm(dto['発生日(P/L計上日)']) <= targetYm かつ
    • dto['請求ステータス'] !== '承認済' && dto['請求ステータス'] !== '却下'
    • 有効フラグ === false || toUpperCase() === 'FALSE' の行はスキップ
  2. 未処理決済レコードBankTxRepository.findAll().dtos をフィルタ。

    • if (!dto['決済日_計画']) continue; でガード
    • Utils.parseDateToYm(dto['決済日_計画']) <= targetYm かつ
    • dto['決済ステータス'] !== '消込済'
    • 有効フラグがFALSEの行はスキップ
  3. 検収漏れ発注OrderRepository.findAll().dtos をフィルタ。

    • if (!dto['終了年月']) continue; でガード
    • dto['契約形態'] === 'スポット' かつ
    • dto['終了年月'] <= targetYm かつ
    • dto['発注ステータス'] !== '検収済'
    • 注意: 003_contracts.jsOrderDTO JSDoc では 発注ステータス の有効値は "見積中" | "発注済" | "検収済" のみ定義。"完納" は実データ確認次第で条件追加(「実データ検証」セクション参照)
  4. 未マッチ銀行明細 (36_wrk_bank_import) — Utils.getSheetByKey('WRK_BANK_IMPORT', '36_wrk_bank_import') でシートを取得。

    • シートが null の場合は [] を返してスキップ(エラーにしない)
    • 存在する場合は Contracts.toDtoList(sheet.getDataRange().getValues()).rows でDTOリスト化(.rows キーを使う。.dtos ではない
    • dto['処理結果'] !== 'MATCHED' かつ dto['確認FLG'] !== true のレコードを返す
    • 列名 処理結果101_sys_config.js L685の WRK_BANK_IMPORT ヘッダー定義で確認済み
  5. 未マッチカード明細 (34_wrk_card) — Utils.getSheetByKey('WRK_CARD', '34_wrk_card') でシートを取得。

    • シートが null の場合は [] を返してスキップ
    • Contracts.toDtoList(sheet.getDataRange().getValues()).rows でDTOリスト化
    • dto['処理結果'] !== '消込済' かつ dto['確認FLG'] !== true のレコードを返す
    • 列名 処理結果101_sys_config.js L686の WRK_CARD ヘッダー定義で確認済み
  6. 各リストの length を合計して total を算出する。


新規ファイル② 300_ui/310_monthly_closing_ui.js

showUnprocessedSummaryDialog(targetYm) 関数の実装:

  • MonthlyClosingValidator.checkUnprocessedItems(targetYm) を呼び出す
  • total === 0 の場合:
    var result = SpreadsheetApp.getUi().alert(
      '月次締め前チェック',
      '未処理項目はありません。月次締めを実行しますか?',
      SpreadsheetApp.getUi().ButtonSet.OK_CANCEL
    );
    if (result === SpreadsheetApp.getUi().Button.OK) {
      runMonthlyClose(); // I-04 オーケストレーター呼び出し
    }
    
  • total > 0 の場合:
    • HtmlService.createTemplateFromFile('templates/310_monthly_closing_dialog') でテンプレートを生成
    • テンプレート変数にデータをセット: template.targetYm = targetYm; template.summary = JSON.stringify({...}); template.details = JSON.stringify({...});
    • SpreadsheetApp.getUi().showModalDialog(template.evaluate().setWidth(640).setHeight(480), '月次締め前チェック — 未処理項目あり');

forceClosingMonth(targetYm, summaryCounts) 関数の実装:

  • summaryCounts 形式: { invoices: n, bankTxs: n, orders: n, cards: n, bankImports: n }
  • 監査ログを記録してから締め処理を呼び出す:
    Utils.auditLog(
      'RUN', '', '', '', 'forceClosingMonth',
      null, JSON.stringify(summaryCounts),
      '未処理項目あり・強制月次締め実行: ' + targetYm
    );
    runMonthlyClose(); // I-04 オーケストレーター
    
  • Utils.auditLog8引数。引数順序: (operation, targetSheet, targetId, targetCol, funcName, beforeValue, afterValue, note)

新規ファイル③ templates/310_monthly_closing_dialog.html

  • ファイル配置: templates/ ディレクトリ(101_sys_config.js L331 で HtmlService.createTemplateFromFile('templates/operations_sidebar') のパターンを踏襲)
  • テンプレート変数: <?= targetYm ?>, <?= summary ?>, <?= details ?>(scriptlets 形式)
  • カテゴリ別件数サマリーテーブル(請求書 / 決済 / 発注 / カード明細 / 銀行明細)
  • 詳細リスト: <div style="overflow-y: auto; max-height: 300px;"> でラップ(件数多数対応)
  • 「締め処理を強制実行」ボタン: HTML から GAS 関数を呼び出す
    google.script.run
      .withSuccessHandler(function() { google.script.host.close(); })
      .withFailureHandler(function(err) { alert('エラー: ' + err.message); })
      .forceClosingMonth(targetYm, summaryCounts);
    
  • 「キャンセル」ボタン: google.script.host.close() でダイアログを閉じる

Constants.MENU_DEFINITION への追加(000_infra/002_constants.js

現在の Constants.MENU_DEFINITION🚀 BizLP💾 バックアップ の2カテゴリのみ(002_constants.js L206-228)。MAS-148 はまだ MENU_DEFINITION 未登録(MAS-148 仕様書は古い onOpen() 直書き形式を前提にしているが、MAS-214 で MENU_DEFINITION 動的生成に移行済み)。

MAS-160 は 🔒 月次締め カテゴリを新設して登録する(MAS-148 が未登録のため MAS-148 エントリも同時追加を推奨):

{
  category: '🔒 月次締め',
  items: [
    { label: '📋 月次締め前チェック (I-16)', funcName: 'showUnprocessedSummaryDialog', description: '月次締め前に未処理INV/STL/ORDを横断スキャンしてサマリーを表示' },
    { label: '🚀 月次締め実行 (I-04)', funcName: 'runMonthlyClose', description: 'Action A → Action B → マート更新を連続実行' },
  ]
}

影響範囲

  • 新規ファイル3件(既存コードへの変更なし):
    • 400_domain/430_monthly_closing_validator.js
    • 300_ui/310_monthly_closing_ui.js
    • templates/310_monthly_closing_dialog.html
  • 000_infra/002_constants.js: Constants.MENU_DEFINITION🔒 月次締め カテゴリを追加
  • 既存の 400_domain/410_subledger_engine.jspreCloseValidation_() / runMonthlyClose())への変更なし

注意事項

  1. .rows vs .dtos の混同防止: Contracts.toDtoList(data).rows を使う(.dtos ではない)。一方 InvoiceRepository.findAll().dtos.dtos キーを使う。2つの異なるインターフェースがある。

  2. null日付ガード必須: Utils.parseDateToYm(null)"" を返す。"" <= "2026-03"true になり意図しないレコードが混入するため、日付フィールドは必ず if (!dto['発生日(P/L計上日)']) continue; のようにガードしてからパースする。

  3. シートが存在しない場合のガード: 34_wrk_card / 36_wrk_bank_import が実装済みでも Utils.getSheetByKey()null を返す可能性がある。if (!sheet) return []; でスキップし、エラーにしない。

  4. Utils.auditLog の引数順序: (operation, targetSheet, targetId, targetCol, funcName, beforeValue, afterValue, note) — 計8引数。operation'RUN' を使用する。引数を省略せず全8個を渡す。

  5. HTML から GAS 関数を呼び出す場合: 必ず google.script.run.withSuccessHandler(...).withFailureHandler(...).関数名() を使用する。直接関数呼び出し不可。

  6. WRK_BANK_IMPORT の列名: 消込結果 ではなく 処理結果101_sys_config.js L685のヘッダー定義で確認済み)。36_wrk_bank_import のマッチ判定は 処理結果 !== 'MATCHED' を使用する。

  7. HTMLテンプレートファイルのパス: HtmlService.createTemplateFromFile() に渡す文字列は拡張子 .html を除いたパス。templates/ サブディレクトリの場合は 'templates/310_monthly_closing_dialog' と指定する(101_sys_config.js L331の 'templates/operations_sidebar' パターンを踏襲)。

エッジケース

条件表示値・動作理由
各シートのデータが0件カテゴリ別「0件」として集計し、total === 0 の確認ダイアログ(alert)を表示findAll().dtos は空配列を返すためフィルタ結果も空配列になる
DTOの日付フィールドが null / undefined / 空文字if (!dto['フィールド名']) continue; でスキップUtils.parseDateToYm(null) = """" <= targetYmtrue になり意図しない件数が積み上がる
発注ステータス'完納' が実データに存在する場合'検収済' と同様に「完了」として除外対象に加える(&& dto['発注ステータス'] !== '完納' を追加)003_contracts.jsOrderDTO JSDoc には '完納' が未記載だが、実データにあれば除外しないとアラートノイズになる
締め対象年月より未来の日付データフィルタで除外(parseDateToYm(date) > targetYm の場合はスキップ)未来月の未処理は今月締めのスコープ外
36_wrk_bank_import / 34_wrk_card がシートに存在しないUtils.getSheetByKey()null を返す → [] を返してスキップシートが未作成の環境でもエラーにしない
未処理件数が非常に多い場合(100件超)詳細リストは max-height: 300px のスクロール div 内に表示GAS モーダルダイアログの高さ上限を超えないよう HTML 側で制御
有効フラグ が FALSE の行全チェック処理でスキップ(コーディング規約「有効フラグ=FALSE の行は全処理でスキップ」)フラグが false (boolean) または 'FALSE' (string) の両方を判定する
契約形態'継続' の発注検収漏れチェック対象外(dto['契約形態'] === 'スポット' でフィルタ)継続契約は終了年月到達でも自動検収とならず毎月存在する可能性があるためノイズになる
forceClosingMonth 呼び出し後の成功ハンドラgoogle.script.host.close() でダイアログを閉じるGASモーダルダイアログは自動クローズしないため明示的にclose呼び出しが必要

実データ検証

MCP で確認すべき項目(実装前に実施推奨):

確認項目シート確認方法未確認時のデフォルト対応
発注ステータス'完納' という値が格納されているか31_wrk_orderMCP get_sheet_data で全行の 発注ステータス 列を確認なければ '検収済' のみを「完了」とみなす
34_wrk_card シートが存在するかスプレッドシートMCP list_sheets未存在の場合はスコープ外(将来対応)とし cards: [] で固定
34_wrk_card処理結果 列名が正しいか34_wrk_cardMCP get_sheet_data の1行目ヘッダー確認101_sys_config.js L686のDDL定義と一致(処理結果 で確定)
36_wrk_bank_import シートが存在するかスプレッドシートMCP list_sheets未存在の場合はスコープ外(将来対応)とし bankImports: [] で固定
36_wrk_bank_import の列名確認(処理結果 vs 消込結果36_wrk_bank_importMCP get_sheet_data の1行目ヘッダー確認101_sys_config.js L685のDDL定義で 処理結果 が確定済み
Utils.getSheetByKey('WRK_BANK_IMPORT', ...) のキーが 01_sys_config に登録されているか01_sys_configMCP get_sheet_dataWRK_BANK_IMPORT キーを検索101_sys_config.js L660に setupAllSchemas でのappendRow確認済み

関連ドキュメント

ドキュメント関連箇所
docs/dev/dev_mas-148_oneclick_monthly_close.md月次締め処理のフック点(runMonthlyClose() / preCloseValidation_())・実行フロー
200_data/202_repository.jsInvoiceRepository / BankTxRepository / OrderRepositoryfindAll() 戻り値形式
000_infra/003_contracts.jsInvoiceDTO / BankTxDTO / OrderDTO のステータス値定義、Contracts.toDtoList()rows キー
000_infra/004_utils.jsUtils.parseDateToYm() のnull処理、Utils.getSheetByKey() のnullガード、Utils.auditLog() 8引数シグネチャ
000_infra/002_constants.jsConstants.MENU_DEFINITION の動的メニュー定義(MAS-214で導入)
100_config/101_sys_config.jsHtmlService.createTemplateFromFile() + showSidebar/showModalDialog のパターン、WRK_CARD / WRK_BANK_IMPORT シートキー定義

人間が検討すべき事項

  1. MAS-148 との統合方針: MAS-148 runMonthlyClose() の冒頭で showUnprocessedSummaryDialog() を自動呼び出しするか、それとも MAS-160 を独立メニューとして提供するか。統合方式(MAS-148 冒頭呼び出し)にする場合は 410_subledger_engine.js の修正が必要になる。推奨は独立メニューで提供し、担当者が任意で事前確認できる形にする。

  2. 34_wrk_card / 36_wrk_bank_import のスコープ判断: MCP で実データを確認してシートが存在する場合は本仕様スコープ内に含める。存在しない / 列名が異なる場合は cards: [] / bankImports: [] で固定し、チェック対象から外す(将来対応)。

  3. targetYm の入力UI: showUnprocessedSummaryDialog に渡す targetYm を(a)ダイアログで手入力、(b)01_sys_config の当月値を自動取得、(c)ハードコード(当月)、のいずれとするか。当面は呼び出し側でシステム設定から取得する方式を推奨。

  4. 「強制実行」の監査ログ保存先: Utils.auditLog98_audit_log シートへ書き込む。MAS-213(98_audit_log WORM 保護)と整合するか確認が必要(書き込みは GAS 経由のため保護は読み取り専用設定ではなく編集制限で実装されているはずだが、追加書き込みが禁止されていないか確認)。

  5. 発注ステータス '完納' の業務確定: 003_contracts.jsOrderDTO JSDoc には '完納' が未記載。31_wrk_order の実データを確認し、値が存在する場合は JSDoc も更新すること。存在しない場合は '検収済' のみを「完了」とみなす(仕様書 Step 3 実装時に MCP 確認を必須とする)。

  6. 34_wrk_card処理結果 判定値: カード明細がマッチ済かどうかの判定値が '消込済' であることを実データで確認すること(DDL 定義には値の選択肢が "AH" と記載されており、実際の値セットが不明)。

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

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-160「月次締め前未処理アラート・サマリーUI」を実装してください。

## 実行前タスク
1. `300_ui/301_ui_assist.js` を Read し、既存UIパターンを確認する(本件では使用しないが参考)
2. `100_config/101_sys_config.js` を Read し、`HtmlService.createTemplateFromFile()` + `showModalDialog()` の呼び出しパターン(L331付近)を確認する
3. `400_domain/410_subledger_engine.js` の `runMonthlyClose()` 関数の定義(フック点)を確認する
4. `000_infra/003_contracts.js` を Read し、`OrderDTO` の `発注ステータス` 有効値('見積中' | '発注済' | '検収済')を確認する
5. `200_data/202_repository.js` を Read し、`InvoiceRepository.findAll()` の戻り値が `{ headers, dtos }` 形式であることを確認する
6. `000_infra/003_contracts.js` を Read し、`Contracts.toDtoList()` の戻り値が `{ headers, rows }` 形式(`dtos` ではなく `rows`)であることを確認する
7. MCP で `31_wrk_order` の `発注ステータス` 実データを確認し、`'完納'` が存在するかチェックする
8. MCP で `34_wrk_card` / `36_wrk_bank_import` の存在・列名(`処理結果`)を確認する

## 新規作成ファイル
- `400_domain/430_monthly_closing_validator.js`(ドメインロジック)
- `300_ui/310_monthly_closing_ui.js`(UIハンドラ)
- `templates/310_monthly_closing_dialog.html`(ダイアログHTML。`templates/` 配下に配置)

## 修正ファイル
- `000_infra/002_constants.js` — `Constants.MENU_DEFINITION` の末尾に `🔒 月次締め` カテゴリを追加

## 実装内容

### 430_monthly_closing_validator.js の実装
`var MonthlyClosingValidator = { ... };` の名前空間オブジェクトを定義し、パブリック関数 `checkUnprocessedItems(targetYm)` を実装する。
- 戻り値: `{ invoices: [], bankTxs: [], orders: [], cards: [], bankImports: [], total: 0 }`
- 各フィルタで日付フィールドを使う前に `if (!dto['フィールド名']) continue;` でガードする
- Repository 系(INV/STL/ORD): `findAll().dtos` を使う(`.rows` ではない)
- 非 Repository 系(WRK_CARD/WRK_BANK_IMPORT): `Contracts.toDtoList(sheet.getDataRange().getValues()).rows` を使う(`.dtos` ではない)
- シートが null の場合: `if (!sheet) return [];` でスキップする
- `34_wrk_card` のマッチ判定: `dto['処理結果'] !== '消込済' && dto['確認FLG'] !== true`
- `36_wrk_bank_import` のマッチ判定: `dto['処理結果'] !== 'MATCHED' && dto['確認FLG'] !== true`

### 310_monthly_closing_ui.js の実装
`showUnprocessedSummaryDialog(targetYm)` と `forceClosingMonth(targetYm, summaryCounts)` を実装する。
- `total === 0` の場合は `SpreadsheetApp.getUi().alert()` を使用(HTMLダイアログ不要)
- `total > 0` の場合は `HtmlService.createTemplateFromFile('templates/310_monthly_closing_dialog')` でテンプレートを生成し、`showModalDialog` で表示
- `forceClosingMonth` では以下の8引数で `Utils.auditLog` を呼び出す:
  `Utils.auditLog('RUN', '', '', '', 'forceClosingMonth', null, JSON.stringify(summaryCounts), '未処理項目あり・強制月次締め実行: ' + targetYm)`
- `forceClosingMonth` の後で `runMonthlyClose()` を呼び出す

### templates/310_monthly_closing_dialog.html の実装
- テンプレート変数: `<?= targetYm ?>`, `<?= summary ?>`, `<?= details ?>`(GAS scriptlets 形式)
- カテゴリ別件数サマリーテーブル(請求書 / 決済 / 発注 / カード明細 / 銀行明細)
- 詳細リスト: `<div style="overflow-y: auto; max-height: 300px;">` でラップ
- 「締め処理を強制実行」: `google.script.run.withSuccessHandler(cb).withFailureHandler(onErr).forceClosingMonth(targetYm, summaryCounts)` を呼び出す
- 「キャンセル」: `google.script.host.close()`

### 002_constants.js の修正
`Constants.MENU_DEFINITION` の末尾(`💾 バックアップ` エントリの直後、`]` 閉じる前)に以下を追加:
```js
{
  category: '🔒 月次締め',
  items: [
    { label: '📋 月次締め前チェック (I-16)', funcName: 'showUnprocessedSummaryDialog', description: '月次締め前に未処理INV/STL/ORDを横断スキャンしてサマリーを表示' },
    { label: '🚀 月次締め実行 (I-04)', funcName: 'runMonthlyClose', description: 'Action A → Action B → マート更新を連続実行' },
  ]
},
```

## 制約
- 既存の Repository・ユーティリティ・contracts を変更しない
- 列参照はヘッダー名ベースで行う(列番号ハードコード禁止)
- `Utils.auditLog` の引数は必ず8個: `(operation, targetSheet, targetId, targetCol, funcName, beforeValue, afterValue, note)`
- HTML から GAS 関数を呼び出す場合は必ず `google.script.run.withSuccessHandler(...).withFailureHandler(...)` を使用する

## 動作確認
1. `npm run push:dev` でデプロイする
2. GAS エディタで `showUnprocessedSummaryDialog('2026-03')` を直接実行し、ダイアログが表示されることを確認する
3. `32_wrk_invoice` に `請求ステータス = '未処理'`、`発生日(P/L計上日) = '2026-03-01'` のレコードを1件入れて再実行し、請求書カテゴリが1件検出されることを確認する
4. 未処理0件のケースで確認ダイアログ(alert)が表示されることを確認する
5. 「締め処理を強制実行」後に `98_audit_log` シートに `operation = 'RUN'` のログが記録されることを確認する

推奨実行モデル

工程推奨モデル理由
Phase 1 調査(実行前タスク)Sonnet複数ファイル横断調査だが設計判断は少ない
ドメインロジック実装(430_monthly_closing_validator.js)Sonnet複数 Repository 横断・DTO フィルタロジックの理解が必要
UIハンドラ実装(310_monthly_closing_ui.js)Haiku既存パターン(Phase 1-B で確認済み)への準拠が主体
HTML ダイアログ実装(310_monthly_closing_dialog.html)Haikuテンプレート変数の展開のみ、設計判断なし
002_constants.js 修正Haiku既存エントリへの定型追加

変更履歴

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

仕様書作成プロンプト

展開して表示
<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>`記録) に分割。1回のWrite/Editは約300行以内。
4. **各Stepで何を書くかを具体指示**: 設計判断をPhase 2実行時に持ち込まない。

======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 I-16「月次締め前未処理アラート・サマリーUI」の開発仕様書を作成してください。
仕様書を新規作成後は `docs/_config.json` の `nav` 配列の §E.6(パイプライン・RPA・外部連携)セクションにも必ず追記してください。

---

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

(以下略 — 完全な instruction は tasks/prompts/task_I-16.md を参照)