MAS-124: 31タブ発注残高エイジング&超過発注アラート
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-124 |
| カテゴリ | データ整合性 |
| Phase | P2 |
| 優先度 | ★★ |
| 所要時間 | 3-4時間 |
| 対象ファイル | 400_domain/411_order_aging.js(新規作成) / 200_data/201_data_validator.js(超過発注ハイライト組込) / templates/operations_sidebar.html(メニュー追加) / 900_test/901_test_runner.js(テスト追加) |
| 前提案件 | MAS-122(ORD↔INV 整合性チェック)— 発注残高の健全性が担保されていること |
目的
31_wrk_order の発注残高の滞留・異常値を自動検知する監視機構を整備し、発注統制を強化する。具体的には次の 3 つの機能を提供する。
- 超過発注検知:
発注残高(自動計算) < 0(INV 過剰紐付け)を 🔴 ハイライト。 - 長期未消化エイジング:
終了年月を過ぎても発注残高(自動計算) > 0の ORD を滞留日数別(30日 / 60日 / 90日 / 90日超)に分類し、🟠 ハイライト。 - 発注ステータス自動更新候補の抽出:
発注残高(自動計算) = 0かつ紐づく INV が全て承認済の ORD を「完納」相当のステータスへ遷移する候補として収集し、人間の確認のうえOrderRepository.save()で一括更新する(Human-in-the-Loop)。
現在のコード
エイジング管理・超過検知・ステータス自動遷移の専用ロジックは未実装。現状は以下の部分的チェックのみ存在する。
200_data/201_data_validator.jsL447-462validateOrder_:発注残高(自動計算)がマイナスのセルにVAL_COLORS.WARNING(#FCE5CD)を付与し、メモ[VAL] 発注残高がマイナスです(INV過剰紐付けの可能性)を設定するのみ。docs/spec/spec_data_validation.mdL155:O01 発注残高(自動計算) マイナスはNG (INV過剰紐付け) 警告として仕様に明記済だが、色分けは警告色のみで 🔴 強調なし。400_domain/410_subledger_engine.jsL106processInvoiceApprovals(Action A)が発注残高をordSheet.getRange(ordMap[ordId].rowIndex, iOrdBalance + 1).setValue(newBal)で直接書き込む(= GAS 管理、Sheets 数式ではない)。OrderDTOの発注ステータス列挙値は"見積中" | "発注済" | "検収済"のみ(000_infra/003_contracts.jsL32)。「完納」ステータスは JSDoc・MST_DICT ともに未登録(Phase 1 実データ検証参照)。
再利用する既存関数・データ:
| モジュール | 関数/オブジェクト | 用途 |
|---|---|---|
200_data/202_repository.js | OrderRepository.findAll() | { headers, dtos: OrderDTO[] } |
200_data/202_repository.js | OrderRepository.save(dtos) | 全行置換(writeDtosToSheet_ で clear→setValues) |
200_data/202_repository.js | InvoiceRepository.findAll() | { headers, dtos: InvoiceDTO[] } |
000_infra/004_utils.js | Utils.parseAmt(val) (L191) | 数値パース(全角・カンマ対応、失敗時 0) |
000_infra/004_utils.js | Utils.parseDateToYm(val) (L92) | "YYYY-MM" 文字列を返す |
000_infra/004_utils.js | Utils.auditLog(operation, targetSheet, targetId, targetCol, funcName, beforeValue, afterValue, note) (L273) | 98_audit_log への記録 |
000_infra/004_utils.js | Utils.toastResult(funcName, message, duration) (L254) | 完了トースト |
000_infra/004_utils.js | Utils.logInfo(funcName, message) / Utils.logError(funcName, error, context) (L232/L242) | ログ出力 |
000_infra/002_constants.js | Constants.COLORS.WARN_RED_BG / WARN_RED_FC | #f4cccc / #cc0000 |
修正方針
新規ファイルの配置
400_domain/411_order_aging.js(新規作成) にメインロジックを置く。理由: (a) 発注残高エイジング判定・ステータス自動遷移は業務ロジック(会計ロジックの理解が必要)であり、400_domain/が適切。(b)201_data_validator.jsは「受動的なデータ不整合の検出」専用で、本案件のようなステータス遷移(副作用あり)は置かない。(c) 400_domain/ の既存番号の空き番(410_subledger_engine.js の次、420_project_profitability.js の前)として 411 を採用。200_data/201_data_validator.jsにはvalidateOrder_に超過発注(発注残高 < 0)の色分けを 🔴Constants.COLORS.WARN_RED_BGに格上げするマイナー改修のみ行う(エイジング本体は置かない)。既存のメモ文言は維持。
新設公開 API
400_domain/411_order_aging.js:
var OrderAgingService = {
/** メニュー公開: エイジング計算 + 超過発注ハイライト + ステータス自動更新候補の確認・適用 */
runOrderAging: function() { return runOrderAging(); }
};
function runOrderAging() { /* ...データフロー 1〜7 */ }
データフロー(番号付き手順)
LockService.getScriptLock()を取得しlock.waitLock(30000)で多重実行を防止(try { ... } finally { lock.releaseLock(); })。OrderRepository.findAll()で全発注 DTO とヘッダーを取得({ headers, dtos })。InvoiceRepository.findAll()で全請求 DTO を取得し、親発注ID(ORD)をキーとするMap<string, InvoiceDTO[]>を構築する(有効フラグ=TRUE かつ 請求ステータス !== "却下" の INV のみ採用)。Mapを使うことで発注ループ内 O(1) 検索になる。- 各発注 DTO を走査し、次の 3 判定を同時に行う(
有効フラグ=FALSEおよび発注ステータス = "検収済"はスキップ):- ① 超過発注判定:
Utils.parseAmt(dto['発注残高(自動計算)']) < 0→31_wrk_orderの発注残高(自動計算)列をConstants.COLORS.WARN_RED_BGでハイライト。 - ② エイジング判定:
Utils.parseDateToYm(dto['終了年月'])が空文字なら対象外(継続契約・無期限)。空でない場合、当月"YYYY-MM"と比較し、滞留月数(当月 − 終了月 の月差)を算出。残高 > 0 かつ滞留月数 > 0 の場合、区分に応じた背景色を終了年月セルに適用(エッジケーステーブルの「色分けルール」を参照)。 - ③ ステータス自動更新候補抽出:
Utils.parseAmt(dto['発注残高(自動計算)']) === 0かつ紐づく有効 INV が 1 件以上あり、かつ全て請求ステータス = "承認済"の ORD を候補リストに追加({ ordId, beforeStatus, afterStatus })。afterStatusは Phase 1-E「実データ検証」で業務確定した値(MCP 未確認なら空のまま候補リストを UI に表示して人間に判断させる)。
- ① 超過発注判定:
- ハイライト書き込み:
bg/notes2 次元配列を構築してsheet.getRange(1,1,rowCount,colCount).setBackgrounds(bg)/setNotes(notes)で一括適用する(Repository パターン外の直接操作だが、セルの見た目の装飾のみでデータは書き換えないため例外的に許可)。既存のapplyValidationResults_パターン(201_data_validator.jsL156-199)と同様に、Constants.COLORS.WARN_RED_BGと同型エイジング色のみをクリア対象にすることで他の書式との干渉を避ける。 - ステータス自動更新候補がある場合、
SpreadsheetApp.getUi().alert(..., ButtonSet.YES_NO)で候補一覧(ORD_ID と遷移先ステータス)を提示し、YES の場合のみ以下を実行:- 候補 DTO の
発注ステータスフィールドを書き換え、OrderRepository.save(dtos)で全行置換。 Utils.auditLog('UPDATE', '31_wrk_order', ordId, '発注ステータス', 'runOrderAging', before, after, 'S-52 エイジングによる自動遷移')を各候補について記録。
- 候補 DTO の
Utils.toastResult('runOrderAging', '🔎 超過X件・長期滞留Y件・ステータス自動更新Z件を処理しました', 8)で完了通知。Utils.logInfoでもログ出力。
フック挿入先
Action A / Action B の末尾フック: Phase 1 調査の結果、
runActionA_/runActionB_という関数は実在しない。実体はprocessInvoiceApprovals()(410_subledger_engine.jsL106) とprocessSettlementClearings()(L365) であり、SubledgerService.processApprovals/processClearingsとして公開されている。本案件では Action A/B の末尾にフックを差し込む代わりに、独立したメニュー呼び出し(後述)として実装する。理由: (a) Action A/B はSpreadsheetApp.getUi().alertでエラー通知するフローのため、末尾でさらに alert を出すと UX が悪化する。(b) エイジング判定は日次で独立に行えば十分で、Action A/B の度に実行する必要はない。メニュー追加先:
templates/operations_sidebar.htmlL71-76 の<div class="section"><h3>⚙️ メンテナンス</h3>ブロック内、既存のrunDataValidation(L72)の直後に以下を 1 行追加する。<button class="btn" onclick="run('runOrderAging', this)">🗂️ 発注エイジング&超過検知</button>造語を避け、既存セクション見出し「⚙️ メンテナンス」に合わせる(
101_sys_config.jsのonOpen()は単一メニュー「🚀 BizLP → 操作パネルを開く」のみで、実機能はサイドバーtemplates/operations_sidebar.html側に集約されているため、101_sys_config.jsの修正は不要)。
LockService
LockService.getScriptLock() を使用して Action A/B との競合を避ける。try { lock.waitLock(30000); ... } finally { lock.releaseLock(); } のパターンを厳守(既存の 410_subledger_engine.js は LockService 未使用だが、本案件は OrderRepository.save(dtos) による全行置換を行うため明示的に排他制御を入れる)。
影響範囲
| ファイル | 変更内容 | 変更量 |
|---|---|---|
400_domain/411_order_aging.js | 新規作成。OrderAgingService と runOrderAging() を実装 | 約180-220行 |
200_data/201_data_validator.js | validateOrder_ の iBal < 0 時の色を VAL_COLORS.WARNING → Constants.COLORS.WARN_RED_BG に変更 | 1行 |
templates/operations_sidebar.html | 「⚙️ メンテナンス」に 🗂️ 発注エイジング&超過検知 ボタンを追加 | 1行 |
900_test/901_test_runner.js | エイジング判定・候補抽出のユニットテストケース追加 | 約60-80行 |
docs/spec/spec_data_validation.md | 必要に応じて O01 の色仕様変更を追記(任意) | 1-2行 |
既存ロジックへの影響:
OrderRepository.save(dtos)は既存writeDtosToSheet_を通じてclearContent→setValuesを行うが、発注残高(自動計算)は GAS 管理の数値のため、DTO の値をそのまま書き戻しても数式は消えない(Sheets 数式ではない)。セル背景色はsetValuesでクリアされないため、手順 5 のハイライト設定と順序は独立。validateOrder_の色変更は runDataValidation の既存フローに影響するのみで、他バリデータへは影響しない。- Action A/B とは明示的に排他ロックで分離されるため、実行中の書き込み競合は起こらない。
注意事項
- 「完納」ステータス値は JSDoc・MST_DICT 未登録。Phase 1-E の実データ検証結果を踏まえて「afterStatus」を確定するまで、ステータス自動更新はバックエンド実装のみ行い、UI からは事前確認ダイアログで必ず人間が承認する(Human-in-the-Loop)。MCP 未確認で
"完納"が実在しない場合は、候補リストを表示するのみでOrderRepository.save()は呼ばない(= ドライラン表示)。 発注残高(自動計算)は GAS 管理(410_subledger_engine.jsL332-339 がsetValueで書き込む)であり、Sheets 数式ではない。OrderRepository.save(dtos)で上書きしても数式は消えない。ただしdto['発注残高(自動計算)']自体が有効な数値であることを事前にUtils.parseAmtで正規化すること(空文字が紛れると""が書かれ、以降の再計算で NaN 化するリスク)。- セル背景色設定 (
setBackgrounds) は Repository パターン外の直接操作だが、セル装飾のみでデータは変えないため本案件では例外的に許可する。実装ではapplyValidationResults_型のパターン(既存色を保持し、エイジング色・警告色のみをクリア → 新規適用)を採用し、他バリデータや条件付き書式との干渉を避ける。 LockServiceロック: Action A/B と競合しないようlock.waitLock(30000)を使う。タイムアウト時はSpreadsheetApp.getUi().alertでエラー表示し終了(無理に実行しない)。- 孤立 INV の扱い:
親発注ID(ORD)が ORD に存在しない(= 孤立 INV)の場合は、本案件のエイジング・超過判定では無視する(MAS-122 の責務)。本案件は ORD ループに閉じており、INV→ORD の逆引きは行わない。 - 有効フラグ=FALSE の扱い: ORD・INV ともに
有効フラグ=FALSEの行は全処理でスキップ(CLAUDE.md コーディング規約準拠)。 - 既存バリデーション色とのクリア順序:
validateOrder_のiBal < 0色をWARN_RED_BG(#f4cccc)に変更する場合、applyValidationResults_のerrColorsリスト(L163)にも#f4ccccを追加しないと、既存のVALクリアパスで消されない点に注意。ただしConstants.COLORS.WARN_RED_BGはVAL_COLORS.ERRORと同値(#f4cccc)のため、結果的に既存ロジックと整合する(= リストへの追加不要)。念のため実装時に再確認すること。
エッジケース
| 条件 | 挙動 | 理由 |
|---|---|---|
終了年月 が空欄 | エイジング計算対象外・警告なし | 継続契約など無期限の ORD が該当。終了年月の自動推定は行わない |
終了年月 あり、契約形態="継続" | 終了年月 を基準に滞留月数を算出 | 契約終了予定を超えても残高が残っている場合は異常 |
終了年月 あり、契約形態="スポット" | 終了年月 を基準に滞留月数を算出 | 単発発注は期日管理がより厳格で、滞留を即警告する |
| 発注に紐づく INV が 0 件 | 超過判定はスキップ、ステータス自動更新候補からも除外 | 消化行動の実績が無ければ「完納」判定は不能 |
発注ステータス "検収済" | 全チェック対象外(エイジング・超過・候補抽出すべてスキップ) | 検収済は既に業務完了扱い。再アラート不要 |
紐づく INV に 請求ステータス="却下" が含まれる | その INV は Map 構築時点で除外、合計・残高判定の母数に入れない | 却下は計上対象外 |
紐づく INV に 請求ステータス="未処理" が含まれる | 候補抽出(③)では「全て承認済」条件を満たさないので除外。エイジング(②)は通常通り判定 | 未承認 INV を抱えたまま「完納」へ遷移させない |
発注残高(自動計算) が空セル | Utils.parseAmt が 0 を返すため、残高 0 扱いとなる。ただし紐づく INV が 0 件なら候補抽出対象外 | 空セル= Action A 未実行の新規 ORD も誤判定されない |
| 複数の INV が同一 ORD に紐づく | Map 経由で合算して判定。全て承認済なら候補抽出可 | 分割請求の正常ケース |
発注残高(自動計算) < 0(超過発注) | 発注残高(自動計算) セルを WARN_RED_BG でハイライト + メモ [S-52] 超過発注(INV過剰紐付けの可能性) | MAS-122 の (b) 超過紐付けと相補的に働く |
当月 < 終了年月 | エイジング未発生(滞留月数 ≦ 0)として警告なし | 契約期間内は正常 |
| 滞留月数の色分け | 1〜1ヶ月(30日相当)=#FFF2CC(薄黄)、2ヶ月以内=#FCE5CD(薄オレンジ)、3ヶ月以内=#F4CCCC(薄赤)、3ヶ月超=#EA9999(濃赤) | 既存 VAL_COLORS および Constants.COLORS 体系との整合。閾値の業務確定は「人間が検討すべき事項」参照 |
終了年月 が "YYYY/MM" や Date 型 | Utils.parseDateToYm が "YYYY-MM" に正規化して比較するため問題なし | 入力バリエーションの吸収 |
実データ検証
Phase 1 のコード・スキーマ調査で確認した事項(MCP ライブ検証は別途実施して本セクションに追記する):
| 項目 | 静的確認結果 | MCP 確認で追加検証すべき内容 |
|---|---|---|
| 発注ステータス 実在値 | JSDoc: "見積中" | "発注済" | "検収済" のみ(003_contracts.js L32) | 31_wrk_order の実データで "完納" が既に運用中か確認。無い場合は MST_DICT にカテゴリ "発注ステータス" のコード値を追加するかどうかを人間確定 |
| 請求ステータス 実在値 | JSDoc: "未処理" | "承認済" | "却下"(003_contracts.js L49) | 32_wrk_invoice で実際に格納されている値が JSDoc と一致するか確認 |
| 発注残高(自動計算) の管理方式 | GAS 管理(410_subledger_engine.js L332-339 が setValue)。Sheets 数式ではない | 実シートで当該列に数式が紛れていないか確認(sheet.getRange(2, col).getFormula() が空文字であること) |
| 終了年月 のデータ形式 | JSDoc: "YYYY-MM"。Utils.parseDateToYm は Date / "YYYY-MM" / "YYYY/MM" / "YYYY年MM月" に対応 | 空欄・"未定" 等の非日付文字列の混入有無を確認し、混入が多ければ Utils.parseDateToYm の戻り値 "" を「対象外」として扱う仕様を再確認 |
関連ドキュメント
| 仕様書 | 関連箇所 |
|---|---|
| CLAUDE.md | コーディング規約(列参照ルール・有効フラグ・シート書き込み位置)、会計ロジック、ファイル番号体系 |
| dev_mas-122 ORD↔INV 整合性チェック | 前提案件。発注残高の健全性を担保する監視機構 |
| spec_data_validation | O01(発注残高マイナス)の仕様。本案件で色分けを🔴に格上げ |
| spec_engine | Action A / Action B と発注残高の関係 |
| failure_patterns.md #16 | 合算マッチ時の matched=true ロック = 本案件では「ステータス更新済み ORD の二重更新防止」に適用 |
| failure_patterns.md #18-#20 | 仕様書固有名詞の誤記防止:データ構造・フィールド名は Read で裏取り済みのもののみ使用 |
| failure_patterns.md #21 | getLastColumn() 使用禁止・月列は固定 |
| B.3 統合テスト手順 | メニューからの単体実行手順 |
人間が検討すべき事項
| # | 項目 | 内容 |
|---|---|---|
| 1 | "完納" ステータス値の業務確定 | OrderDTO JSDoc には "見積中" | "発注済" | "検収済" しか無い。「完納」相当の状態名称を確定し、MST_DICT の "発注ステータス" カテゴリに追加するかを決める。MAS-125(発注ステータス遷移ワークフローの標準化)と合わせて設計すべき |
| 2 | エイジング警告の閾値(滞留月数) | 30日 / 60日 / 90日 / 90日超 の 4 段階で提案中。事業部門と合意して確定。閾値は 03_sys_params に格納し Constants.getParam('ORD_AGING_THRESHOLDS', '30,60,90') で読み取る設計も検討(本仕様では定数直書きを暫定提案) |
| 3 | 色分けルール(何ヶ月で何色か) | 薄黄→薄オレンジ→薄赤→濃赤の案を提示。既存 VAL_COLORS と重複しない配色か UI レビューで確定 |
| 4 | 契約形態("継続" / "スポット")別の滞留判定ルール | 継続契約は 終了年月 到来後も残業務があり得る運用を許容するか、スポット同様に即警告するか。TODO_future.md の MAS-124「人間が検討すべき事項」からの継承 |
| 5 | ステータス自動更新の対象条件 | 「残高=0 かつ全 INV 承認済」で十分か、それとも「全 INV 決済完了(42_trn_journal に計上済)」まで要件化するか。MAS-125(ステータス遷移ワークフロー)との一貫性 |
| 6 | 自動更新のトリガー | メニュー手動実行のみで良いか、日次トリガーで自動実行すべきか。月次締めバッチ(MAS-148)に組み込むかを検討 |
| 7 | MAS-123(発注残高ダッシュボード)との表示連動 | 本案件のエイジング区分を MAS-123 のマートでも集計するかどうか。共通キー(滞留区分の enum)を先に合意しておくと後工程が楽 |
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-124「31タブ発注残高エイジング&超過発注アラート」を実装してください。
## 実行前タスク(必読)
- `000_infra/003_contracts.js`: OrderDTO の `発注ステータス` 列挙値("見積中"|"発注済"|"検収済")・`終了年月` フォーマット("YYYY-MM")・InvoiceDTO の `請求ステータス` 列挙値("未処理"|"承認済"|"却下")を確認
- `200_data/202_repository.js`: `OrderRepository.save()` が `writeDtosToSheet_` による全行置換であり、`発注残高(自動計算)` は GAS 管理のため DTO 経由で書き戻しても数式は消えないことを再確認
- `200_data/201_data_validator.js` L447-462 `validateOrder_`: 既存の `iBal < 0` 警告色を `VAL_COLORS.WARNING` から `Constants.COLORS.WARN_RED_BG` に変更
- `400_domain/410_subledger_engine.js`: `processInvoiceApprovals`(L106)/ `processSettlementClearings`(L365)が実体。`runActionA_/runActionB_` は**実在しない**ため、末尾フックではなく独立メニュー呼び出しを採用
- `templates/operations_sidebar.html` L71-76 の `<div class="section"><h3>⚙️ メンテナンス</h3>` を Read し、`runDataValidation` ボタン直後にメニュー追加
- 仕様書の「実データ検証」セクションで確定した `発注ステータス` 実在値のみを使用。"完納" が MCP で未確認の場合はステータス自動更新を**ドライラン(候補表示のみ)**に留める
## 修正対象ファイル
1. `400_domain/411_order_aging.js` — **新規作成**。`OrderAgingService` と `runOrderAging()` を実装
2. `200_data/201_data_validator.js` — `validateOrder_` の `iBal < 0` の色を `Constants.COLORS.WARN_RED_BG` に変更(1行)
3. `templates/operations_sidebar.html` — 「⚙️ メンテナンス」セクションに発注エイジングボタンを1行追加
4. `900_test/901_test_runner.js` — エイジング計算・候補抽出のユニットテストを追加
## 実装内容
仕様書「修正方針」のデータフロー手順 1〜7 に厳密に従って実装する:
1. `LockService.getScriptLock()` + `lock.waitLock(30000)` を `try/finally` で取得
2. `OrderRepository.findAll()` で全 ORD DTO 取得
3. `InvoiceRepository.findAll()` から `Map<ORD_ID, InvoiceDTO[]>` を構築(有効フラグ=TRUE かつ `請求ステータス !== "却下"` のみ)
4. 全 ORD を走査し、①超過発注 / ②エイジング / ③ステータス自動更新候補 を同時判定(`有効フラグ=FALSE` と `発注ステータス="検収済"` はスキップ)
5. `bg` / `notes` 2 次元配列を構築して `sheet.getRange(1,1,rowCount,colCount).setBackgrounds(bg)` / `setNotes(notes)` で一括適用。既存色は `applyValidationResults_` 型のパターンで保持
6. ステータス自動更新候補があれば `ui.alert(..., ButtonSet.YES_NO)` で確認 → YES のみ DTO 書き換え → `OrderRepository.save(dtos)` → `Utils.auditLog` で各 ORD を記録
7. `Utils.toastResult('runOrderAging', ..., 8)` で完了通知
## 制約
- `発注ステータス` の更新値は仕様書「実データ検証」で確定した実在値のみ使用。MCP 未確認で "完納" が実在しない場合は候補表示のみ(save 呼ばない)
- `LockService.getScriptLock()` は `try { lock.waitLock(30000); ... } finally { lock.releaseLock(); }` パターンを厳守
- データ書き込みは `OrderRepository.save()` 経由のみ。セル直接書き込みは `setBackgrounds()` / `setNotes()` による装飾のみ許可(データ変更は不可)
- 列インデックスのハードコード禁止。`headers.indexOf('フィールド名')` または DTO プロパティアクセス(`dto['税込金額_発注']`)を使用
- 金額フィールドは必ず `Utils.parseAmt()` 経由で数値化
- 日付フィールドは `Utils.parseDateToYm()` で `"YYYY-MM"` に正規化して比較
- `getLastColumn()` 使用禁止(failure_patterns.md #21)。列数は headers.length または DDL 定数から取得
- `発注残高(自動計算)` は GAS 管理のため、save() 前に DTO 内の該当値を `Utils.parseAmt` で正規化(空文字のまま書くと NaN 化するリスク)
## エッジケース
仕様書「エッジケース」テーブルを全件実装で担保すること。特に:
- `終了年月` 空欄 → エイジング対象外(警告なし)
- `発注ステータス="検収済"` → 全チェック対象外
- `請求ステータス="却下"` の INV → 集計母数から除外
- `請求ステータス="未処理"` の INV を含む ORD → 候補抽出(③)から除外、エイジング(②)は通常通り
- 超過発注(`発注残高 < 0`)→ `WARN_RED_BG` でハイライト
- 滞留月数の色分け: 1ヶ月以内=`#FFF2CC`、2ヶ月以内=`#FCE5CD`、3ヶ月以内=`#F4CCCC`、3ヶ月超=`#EA9999`
## 動作確認
1. `npm run push:dev` でデプロイ
2. 開発用スプレッドシートのサイドバー「⚙️ メンテナンス」→「🗂️ 発注エイジング&超過検知」を実行
3. `発注残高(自動計算) < 0` の ORD が 🔴 `#f4cccc` でハイライトされることを確認
4. `終了年月` を 3 ヶ月前に設定した ORD が濃赤でハイライトされることを確認
5. `終了年月` 空欄の ORD がエイジング警告対象外であることを確認
6. `発注残高=0` かつ全 INV 承認済の ORD が候補ダイアログに列挙されることを確認
7. YES 選択後、`31_wrk_order` の `発注ステータス` が確定値に更新されていることを確認
8. `98_audit_log` に各 ORD の遷移記録が追記されていることを確認
9. `Utils.toastResult` の完了通知が表示されることを確認
10. 処理中に Action A (`processInvoiceApprovals`) を別タブから実行 → `LockService` で待機されることを確認
11. `OrderRepository.save()` 実行後に `発注残高(自動計算)` の値が保持されていることを確認
12. `900_test/901_test_runner.js` の新規テストケースが全て PASS すること
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|:--------:|------|
| Phase 1(設計) | あり | ファイル調査・固有名詞確定・配置レイヤー判断 |
| Phase 2(清書) | なし | Phase 1 確定内容の書き下し |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
バックエンドロジック実装(411_order_aging.js 新規作成) | Sonnet | 複数ファイル横断の参照・既存 Repository パターンの適用・Map 構築・LockService 使用・色分けロジックなど中程度の判断が必要 |
validateOrder_ の色変更(201_data_validator.js) | Haiku | 1行の変更で仕様書に色コードまで明記済。判断要素なし |
| サイドバー HTML へのボタン追加 | Haiku | 挿入位置・ボタン HTML ともに仕様書で確定済。判断要素なし |
| 900_test/901_test_runner.js のテスト追加 | Sonnet | 既存テストケースの構造把握と新規ケースの追加が必要 |
| 動作確認 | ユーザー手動 | スプレッドシート UI でのメニュー実行・ダイアログ承認・エイジング色の目視確認が必要 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 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 実行**: Step 2-1(骨格 ~20行) / Step 2-2(概要〜注意事項 ~300行) / Step 2-3a(エッジケース〜人間検討事項 ~200行) / Step 2-3b(実装プロンプト〜変更履歴 ~250行) / Step 2-4(`<details>` プロンプト全文記録) に分割。1 回の Write/Edit は約 300 行以内。
4. **各 Step で何を書くかを具体指示**: Phase 1 で設計を完全に確定させ、Phase 2 実行時に設計判断を再考しない。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 S-52「31タブ発注残高エイジング&超過発注アラート」の開発仕様書を作成してください。
作成した仕様書は `docs/_config.json` の `nav` 配列の適切なセクションにも必ず追記してください。
---
## Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)
以下を **全て Read/Grep ツールで調査** してから Phase 2 に進む。推測・記憶で記述しない。
**Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず Read。**
### 1-A: 案件定義の読み込み
- `docs/_internal/TODO_future.md` — S-52 の「案件名」「概要」「期待される効果」「人間が検討すべき事項」を取得
### 1-B: プロジェクト規約の読み込み
- `CLAUDE.md` — ファイル番号体系・コーディング規約(列参照ルール、シート書き込み位置など)・会計ロジックルールを確認
### 1-C: 既存仕様書テンプレートの読み込み
- `docs/dev/dev_mas-085_consistency_check.md` — 整合性チェック・アラート系の実装パターンを把握する
### 1-D: 関連コードの調査(以下を順番に Read し、仕様書に書く全固有名詞を Read した内容のみから引用すること)
1. **`000_infra/003_contracts.js`**
- `OrderDTO` の全プロパティ名・型・JSDoc コメントを確認
- `発注ステータス` の列挙値("見積中" / "発注済" / "検収済" 以外の値が実在するか)
- `発注残高(自動計算)` フィールドの扱い(GAS 書き込みか Sheets 数式か後述 MCP で確認)
- `開始年月` / `終了年月` のフォーマット("YYYY-MM" であること)
- `税抜金額_発注` / `税込金額_発注` のどちらを超過判定に使うか
- ⚠️ **「完納」ステータスは JSDoc に記載なし**。Read で実定義を確認し、存在しない場合は後述 1-E の MCP 確認結果に委ねる
- `InvoiceDTO` の全プロパティ名・型・JSDoc コメントを確認
- `請求ステータス` の列挙値("承認済" の表記が正確か)
- `親発注ID(ORD)` のプロパティ名(Map キーに使用する)
- 計上済金額の算出に使うフィールド(`税抜金額_計画` / `税込金額_計画` のどちらか)
- ⚠️ `契約終了年月` という名称は OrderDTO に存在しない。正しくは `終了年月` であることを Read で確認
2. **`000_infra/002_constants.js`**
- `SHEET_DEFAULTS` の `31_wrk_order` エントリの `defaults` オブジェクトでフィールド名の正確な文字列を確認
- `ID_PREFIX_MAP` の `31_wrk_order` エントリ(`ORD_` プレフィックス)を確認
3. **`000_infra/004_utils.js`**
- `Utils.parseDateToYm(val)` の引数型と戻り値を確認
- `Utils.auditLog(operation, targetSheet, targetId, targetCol, funcName, beforeValue, afterValue, note)` の引数シグネチャを正確に把握
- `Utils.toastResult(funcName, message, duration)` の引数シグネチャを確認
- `Utils.logInfo` / `Utils.logError` の使用パターンを確認
4. **`200_data/202_repository.js`**
- `OrderRepository.findAll()` の戻り値型 `{ headers: string[], dtos: OrderDTO[] }` を確認
- `OrderRepository.save(dtos)` が `writeDtosToSheet_()` を呼ぶ**全行置換**であることを確認
- ⚠️ `発注残高(自動計算)` が Sheets 数式管理の場合、`save()` で上書きすると数式が消える。1-E の MCP 確認が必須
- `InvoiceRepository.findAll()` の戻り値型を確認
5. **`200_data/201_data_validator.js`**
- 既存バリデータの関数構造・呼び出し方・配置パターンを把握
- 今回の機能(エイジング計算・超過判定・ステータス更新)がデータアクセス層(`200_data/`)に属するか、ドメイン層(`400_domain/`)に属するかを判断するための根拠として使う
6. **`400_domain/410_subledger_engine.js`**
- `SubledgerService` の公開 API と内部プライベート関数の全一覧を確認
- `runActionA_` / `runActionB_` が**実在するか**と、実在する場合の末尾フック挿入に適した行番号を特定
- ⚠️ 実在しない場合は、代替フック方法(メニュー直接呼び出し関数として独立実装など)を仕様書に記載する
- `LockService` の利用有無と、既存ロックとの競合リスクを確認
7. **`100_config/101_sys_config.js`**
- `onOpen()` のメニュー構造を Read し、今回の機能をどのメニュー区分に追加すべきかを判断
- ⚠️ メニュー名・区分名は Read した実在する文字列のみ引用。造語禁止
8. **`docs/_internal/failure_patterns.md`**
- #16(合算マッチ時の matched=true ロック = 今回はステータス更新済み発注の二重更新防止に適用)
- #18-#20(仕様書固有名詞の誤記防止:データ構造の型・フィールド名は Read で裏取り済みのもののみ)
- #21(`getLastColumn()` 使用禁止・月列は固定)
### 1-E: 実データ検証(MCP で確認)
- `31_wrk_order` の「発注ステータス」列の実格納値一覧("完納" が存在するか確認)
- `32_wrk_invoice` の「請求ステータス」列の実格納値("承認済" の表記確認)
- `31_wrk_order` の「発注残高(自動計算)」列が Sheets 数式管理か GAS 書き込みかを確認(`save()` で上書きしてよいか判断)
- `31_wrk_order` の「終了年月」列の実際のデータ形式(空欄・YYYY-MM・その他)を確認
---
## Phase 2: 仕様書の分割作成
**出力先**: `docs/dev/dev_mas-124_order_aging_alert.md`
### Step 2-1: 骨格の作成 (Write, ~20行)
(セクション見出しのみ。本文は空で可)
### Step 2-2: 概要〜注意事項の追記 (Edit, ~300行)
(概要テーブル / 目的 / 現在のコード / 修正方針 / 影響範囲 / 注意事項)
### Step 2-3a: エッジケース〜人間が検討すべき事項の追記 (Edit, ~200行)
(エッジケーステーブル / 実データ検証 / 関連ドキュメント / 人間が検討すべき事項)
### Step 2-3b: 実装プロンプト〜変更履歴の追記 (Edit, ~250行)
(実装プロンプト / 推奨実行モデル / 変更履歴)
### Step 2-4: 仕様書作成プロンプトの記録 (Edit)
(末尾に `<details>` で本プロンプト全文を転記)
---
## Phase 3: `_config.json` 追記・changelog 追記・コミット
### 3-1: `_config.json` に登録
`docs/_config.json` を Read して `nav` 配列の既存エントリを確認してから、適切なセクションに追記:
セクション判定: バリデーション・アラート系 → §E.2。
### 3-2: changelog 追記
`docs/_internal/changelog.md` のヘッダー直後に追記
### 3-3: コミット&プッシュ
```bash
git add docs/dev/dev_mas-124_order_aging_alert.md docs/_internal/changelog.md docs/_config.json
git commit -m "docs: MAS-124 31タブ発注残高エイジング&超過発注アラートの開発仕様書を作成"
git push -u origin <現在のブランチ>
```
</instruction>