MAS-160: 月次締め前未処理アラート・サマリーUI
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-160 |
| カテゴリ | 新機能(UI・アラート) |
| Phase | P1.5 |
| 優先度 | ★★★(Top 15 昇格済) |
| 所要時間 | 1-2時間 |
| 実装ステータス | 📝 仕様書段階・実装未着手 (2026-04-28 監査時点) |
| 対象ファイル(新規) | 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 にエントリ追加) |
| 前提案件 | MAS-148「ワンクリック月次締め」(runMonthlyClose() の実行前フックとして動作) |
目的
月次締め処理(MAS-148: runMonthlyClose())の実行前に、システムが未処理項目を能動的に横断スキャンし、担当者が内容を確認した上で締めを実行または中止できる Human-in-the-Loop フックを提供する。
従来の MAS-148 preCloseValidation_() は「未承認INV件数」「未消込STL件数」の単純カウントと警告テキストのみを提供するが、MAS-160 では以下を追加する:
- 横断スキャン: 32_wrk_invoice / 33_wrk_bank / 31_wrk_order を対象月でフィルタしてカテゴリ別に集計
- サマリーダイアログ: カテゴリ別件数テーブル + 詳細リストをモーダルで表示
- 強制実行オプション: 未処理項目ありでも内容を確認した担当者が「強制実行」を選択できる(Human-in-the-Loop: 判断を人間が担保)
- 監査ログ記録: 未処理項目ありで強制実行した場合は
Utils.auditLogに記録
現在のコード
該当処理なし(新規実装)。
MAS-148 の実行起点は 400_domain/410_subledger_engine.js の runMonthlyClose() 関数(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 // 全カテゴリの合計件数
}
各チェック処理の実装:
未承認請求書 —
InvoiceRepository.findAll().dtosをフィルタ。if (!dto['発生日(P/L計上日)']) continue;でガード(parseDateToYm(null)=""による誤混入防止)Utils.parseDateToYm(dto['発生日(P/L計上日)']) <= targetYmかつdto['請求ステータス'] !== '承認済' && dto['請求ステータス'] !== '却下'- 有効フラグ
=== false || toUpperCase() === 'FALSE'の行はスキップ
未処理決済レコード —
BankTxRepository.findAll().dtosをフィルタ。if (!dto['決済日_計画']) continue;でガードUtils.parseDateToYm(dto['決済日_計画']) <= targetYmかつdto['決済ステータス'] !== '消込済'- 有効フラグがFALSEの行はスキップ
検収漏れ発注 —
OrderRepository.findAll().dtosをフィルタ。if (!dto['終了年月']) continue;でガードdto['契約形態'] === 'スポット'かつdto['終了年月'] <= targetYmかつdto['発注ステータス'] !== '検収済'- 注意:
003_contracts.jsのOrderDTOJSDoc では発注ステータスの有効値は"見積中" | "発注済" | "検収済"のみ定義。"完納"は実データ確認次第で条件追加(「実データ検証」セクション参照)
未マッチ銀行明細 (
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.jsL685のWRK_BANK_IMPORTヘッダー定義で確認済み
- シートが
未マッチカード明細 (
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.jsL686のWRK_CARDヘッダー定義で確認済み
- シートが
各リストの
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.auditLogは 8引数。引数順序:(operation, targetSheet, targetId, targetCol, funcName, beforeValue, afterValue, note)
新規ファイル③ templates/310_monthly_closing_dialog.html
- ファイル配置:
templates/ディレクトリ(101_sys_config.jsL331 で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.js300_ui/310_monthly_closing_ui.jstemplates/310_monthly_closing_dialog.html
000_infra/002_constants.js:Constants.MENU_DEFINITIONに🔒 月次締めカテゴリを追加- 既存の
400_domain/410_subledger_engine.js(preCloseValidation_()/runMonthlyClose())への変更なし
注意事項
.rowsvs.dtosの混同防止:Contracts.toDtoList(data).rowsを使う(.dtosではない)。一方InvoiceRepository.findAll().dtosは.dtosキーを使う。2つの異なるインターフェースがある。null日付ガード必須:
Utils.parseDateToYm(null)は""を返す。"" <= "2026-03"はtrueになり意図しないレコードが混入するため、日付フィールドは必ずif (!dto['発生日(P/L計上日)']) continue;のようにガードしてからパースする。シートが存在しない場合のガード:
34_wrk_card/36_wrk_bank_importが実装済みでもUtils.getSheetByKey()がnullを返す可能性がある。if (!sheet) return [];でスキップし、エラーにしない。Utils.auditLogの引数順序:(operation, targetSheet, targetId, targetCol, funcName, beforeValue, afterValue, note)— 計8引数。operationに'RUN'を使用する。引数を省略せず全8個を渡す。HTML から GAS 関数を呼び出す場合: 必ず
google.script.run.withSuccessHandler(...).withFailureHandler(...).関数名()を使用する。直接関数呼び出し不可。WRK_BANK_IMPORTの列名:消込結果ではなく処理結果(101_sys_config.jsL685のヘッダー定義で確認済み)。36_wrk_bank_importのマッチ判定は処理結果 !== 'MATCHED'を使用する。HTMLテンプレートファイルのパス:
HtmlService.createTemplateFromFile()に渡す文字列は拡張子.htmlを除いたパス。templates/サブディレクトリの場合は'templates/310_monthly_closing_dialog'と指定する(101_sys_config.jsL331の'templates/operations_sidebar'パターンを踏襲)。
エッジケース
| 条件 | 表示値・動作 | 理由 |
|---|---|---|
| 各シートのデータが0件 | カテゴリ別「0件」として集計し、total === 0 の確認ダイアログ(alert)を表示 | findAll().dtos は空配列を返すためフィルタ結果も空配列になる |
| DTOの日付フィールドが null / undefined / 空文字 | if (!dto['フィールド名']) continue; でスキップ | Utils.parseDateToYm(null) = "" → "" <= targetYm が true になり意図しない件数が積み上がる |
発注ステータス に '完納' が実データに存在する場合 | '検収済' と同様に「完了」として除外対象に加える(&& dto['発注ステータス'] !== '完納' を追加) | 003_contracts.js の OrderDTO 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_order | MCP get_sheet_data で全行の 発注ステータス 列を確認 | なければ '検収済' のみを「完了」とみなす |
34_wrk_card シートが存在するか | スプレッドシート | MCP list_sheets | 未存在の場合はスコープ外(将来対応)とし cards: [] で固定 |
34_wrk_card の 処理結果 列名が正しいか | 34_wrk_card | MCP 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_import | MCP get_sheet_data の1行目ヘッダー確認 | 101_sys_config.js L685のDDL定義で 処理結果 が確定済み |
Utils.getSheetByKey('WRK_BANK_IMPORT', ...) のキーが 01_sys_config に登録されているか | 01_sys_config | MCP get_sheet_data で WRK_BANK_IMPORT キーを検索 | 101_sys_config.js L660に setupAllSchemas でのappendRow確認済み |
関連ドキュメント
| ドキュメント | 関連箇所 |
|---|---|
docs/dev/dev_mas-148_oneclick_monthly_close.md | 月次締め処理のフック点(runMonthlyClose() / preCloseValidation_())・実行フロー |
200_data/202_repository.js | InvoiceRepository / BankTxRepository / OrderRepository の findAll() 戻り値形式 |
000_infra/003_contracts.js | InvoiceDTO / BankTxDTO / OrderDTO のステータス値定義、Contracts.toDtoList() の rows キー |
000_infra/004_utils.js | Utils.parseDateToYm() のnull処理、Utils.getSheetByKey() のnullガード、Utils.auditLog() 8引数シグネチャ |
000_infra/002_constants.js | Constants.MENU_DEFINITION の動的メニュー定義(MAS-214で導入) |
100_config/101_sys_config.js | HtmlService.createTemplateFromFile() + showSidebar/showModalDialog のパターン、WRK_CARD / WRK_BANK_IMPORT シートキー定義 |
人間が検討すべき事項
MAS-148 との統合方針: MAS-148
runMonthlyClose()の冒頭でshowUnprocessedSummaryDialog()を自動呼び出しするか、それとも MAS-160 を独立メニューとして提供するか。統合方式(MAS-148 冒頭呼び出し)にする場合は410_subledger_engine.jsの修正が必要になる。推奨は独立メニューで提供し、担当者が任意で事前確認できる形にする。34_wrk_card/36_wrk_bank_importのスコープ判断: MCP で実データを確認してシートが存在する場合は本仕様スコープ内に含める。存在しない / 列名が異なる場合はcards: []/bankImports: []で固定し、チェック対象から外す(将来対応)。targetYmの入力UI:showUnprocessedSummaryDialogに渡すtargetYmを(a)ダイアログで手入力、(b)01_sys_configの当月値を自動取得、(c)ハードコード(当月)、のいずれとするか。当面は呼び出し側でシステム設定から取得する方式を推奨。「強制実行」の監査ログ保存先:
Utils.auditLogは98_audit_logシートへ書き込む。MAS-213(98_audit_logWORM 保護)と整合するか確認が必要(書き込みは GAS 経由のため保護は読み取り専用設定ではなく編集制限で実装されているはずだが、追加書き込みが禁止されていないか確認)。発注ステータス
'完納'の業務確定:003_contracts.jsのOrderDTOJSDoc には'完納'が未記載。31_wrk_orderの実データを確認し、値が存在する場合は JSDoc も更新すること。存在しない場合は'検収済'のみを「完了」とみなす(仕様書 Step 3 実装時に MCP 確認を必須とする)。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 を参照)