概要

項目内容
案件IDMAS-087
カテゴリデータ再同期 / UI
PhaseP2
優先度
所要時間3〜4時間
対象ファイル300_ui/301_ui_assist.js(新規関数追加)、templates/operations_sidebar.html(ボタン追加)
前提案件なし(独立着手可能)

目的

ユーザーが 20 番台予算タブ(21_bud_pipeline / 22_bud_headcount / 23_bud_subscription / 24_bud_capex_loan)の元データを修正した際に、対応する 32_wrk_invoiceINV レコードを手動で再生成できる UI を提供する。

  • 現状の運用: 元データ修正後に INV を修正する公式手順が存在せず、ユーザーが手動で INV 行を削除し RPA を再実行する必要がある。
  • 本案件のスコープ: 選択行の元データに紐づく有効 INV を論理削除(有効フラグ=FALSE)し、対象シートの RPA を再実行することで INV を再生成する UI を提供する。
  • スコープ外: 42_trn_journal(自動仕訳)への反映。既発行 JNL の取り消しは監査上の重要操作のため、本案件では対応せず、ユーザーが別途 Action AprocessInvoiceApprovals)を再実行する運用とする。

現在のコード

RPA 公開 API の実シグネチャ(全て「シート全体」対象・行単位呼び出し不可)

Phase 1 で 400_domain/40x_rpa_*.js を確認した結果:

関数名対象シートシグネチャ行単位呼び出し
generatePipelineInvoices(targetOverride, _silent)21_bud_pipelinetargetOverride は対象年月("YYYY-MM")。行単位フィルタ引数なし×
generateHcInvoices(targetOverride, _silent)22_bud_headcount同上×
generateSaasInvoices(targetOverride, _silent)23_bud_subscription同上×
generateCapexInvoices(targetOverride, _silent)24_bud_capex_loan同上×

いずれもシート全体を走査し、RpaCommon による冪等性チェック(既存 INV は二重起票されない)で重複生成を防ぐ。単一行のみを対象とする API は存在しない。

参照経路(予算ID → INV)

000_infra/003_contracts.js 確認結果:

  • OrderDTO参照元区分("SUB"/"HC"/"CAPEX"/"PIPELINE" 等)と 参照元IDPIP_xxxx / EMP_xxxx / SUB_xxxx / CPX_xxxx)を保持する
  • InvoiceDTO親発注ID(ORD) を保持する。予算ID を直接保持するフィールドは持たない

したがって予算ID → INV は 2 ステップ検索 が必要:

予算ID (PIP_xxxx 等)
   ↓ OrderRepository.findAll() で 参照元ID が一致する有効 ORD を取得
ORD_YYYYMMDD_NNNN
   ↓ InvoiceRepository.findAll() で 親発注ID(ORD) が一致する有効 INV を取得
INV_YYYYMMDD_NNNN

InvoiceDTO の主要フィールド(003_contracts.js L40-67)

  • 有効フラグ: boolean(論理削除フィールド)
  • 請求ステータス: "未処理" | "承認済" | "却下" の 3 値のみ。"完了" は存在しない
  • 自動仕訳JNL_ID: 値があれば Action A で仕訳発行済み

UI エントリ(templates/operations_sidebar.html

現行では onOpen() から起動する「🚀 BizLP 操作パネル」サイドバーに全操作が集約されている(101_sys_config.js L299-321)。新規操作ボタンは operations_sidebar.html<button class="btn"> タグで追加する方式。

既存セクション:

  • 📒 経理業務 (RPA / Action)
  • 🔍 消込・マッチング
  • 📝 費用登録
  • 📊 マート更新
  • ⚙️ メンテナンス
  • 🔧 開発・設定
  • 🔧 マイグレーション

本案件の再同期ボタンは ⚙️ メンテナンス セクションに配置する(運用フローとしては「データ整合性チェック」の延長に位置するため)。

Repository の副作用(200_data/202_repository.js L152-207)

  • InvoiceRepository.findAll(){ headers, dtos } を返す
  • InvoiceRepository.save(dtos)全置換。取得した全 DTO 配列を保持したまま対象だけ更新して渡す必要がある
  • OrderRepository.findAll() / .save(dtos) も同パターン

修正方針

処理フロー(7 ステップ)

  1. ユーザー操作: 20 番台予算シート(21_bud_pipeline / 22_bud_headcount / 23_bud_subscription / 24_bud_capex_loan)で再生成したい行を選択した状態で、サイドバー「⚙️ メンテナンス」セクションの 「🔁 INV 再同期(選択行)」 ボタンをクリックする。
  2. ソース種別判定: 現在のアクティブシート名で分岐する。21_bud_pipeline → Pipeline、22_bud_headcount → HC、23_bud_subscription → Subscription、24_bud_capex_loan → Capex。それ以外のシートで実行された場合は Toast「このメニューは 20 番台予算シートから実行してください。」で終了。
  3. 予算ID取得: 選択範囲の各行について、ヘッダー名ベースで 管理ID 列を indexOf で特定し、値(PIP_xxxx / EMP_xxxx / SUB_xxxx / CPX_xxxx)を取得する。空の行はスキップ。
  4. INV検索: OrderRepository.findAll() で全 ORD を取得し、参照元ID が手順3 の予算ID と一致する有効 ORD の 発注ID(ORD) を収集する。次に InvoiceRepository.findAll() で全 INV を取得し、親発注ID(ORD) が手順3 で集めた ORD ID セットに含まれる かつ 有効フラグ === true の INV を対象候補とする。
  5. (Human-in-the-Loop)確認ダイアログ: 対象候補 INV 一覧(INV ID、発生日、金額、請求ステータス)を整形し SpreadsheetApp.getUi().alert(OK_CANCEL) で表示する。件数が 0 件の場合は「既存の請求レコードが見つかりませんでした。新規に生成しますか?」ダイアログに切り替え、「はい」なら論理削除ステップをスキップして手順6 に進む。候補に 請求ステータス !== '未処理'(="承認済" または "却下")または 自動仕訳JNL_ID 非空の INV が 1 件でも含まれる場合はブロックし「決済処理済または承認済の請求レコードが含まれるため再同期できません。経理担当者に確認してください。」ダイアログを表示して終了。
  6. 論理削除→RPA再実行: 承認後、対象 INV の 有効フラグfalse に設定した DTO 配列を InvoiceRepository.save(allDtos) で全置換書き込み。各 INV について Utils.auditLog('DELETE', '32_wrk_invoice', invId, '有効フラグ', 'resyncInvoiceFromSource', true, false, 'S-15再同期') を呼ぶ。続いてソース種別に応じた RPA 関数(手順2 で判定済み)を 引数なし で呼び出す(generatePipelineInvoices() 等)。RPA はシート全体を走査するが RpaCommon の冪等性チェックで既存有効 INV は二重起票されず、論理削除した INV の分だけが新規生成される。
  7. 結果通知: Utils.toastResult('resyncInvoiceFromSource', '論理削除: N件 / 再生成: M件') で処理結果を通知する。

二重実行防止(LockService)

LockService.getScriptLock() でスクリプトレベルの排他ロックを取得する。GASLockService はキーベースロックをサポートしないため、スクリプト全体のシングルロックとして機能する。

var lock = LockService.getScriptLock();
if (!lock.tryLock(10000)) {
  SpreadsheetApp.getActiveSpreadsheet().toast(
    '別の再同期処理が実行中です。しばらく待ってから再試行してください。',
    '⚠️ 再同期', 5);
  return;
}
try {
  // 手順2〜7 の本体処理
} finally {
  lock.releaseLock();
}

waitLock ではなく tryLock(10000) を採用する理由: 再同期は ユーザーが同期的に結果を待つ UI 操作 のため、タイムアウトまで待機されると UX が劣化する。10 秒で取得できなければ即エラーを返す。

影響範囲

  • 変更ファイル:
    • 300_ui/301_ui_assist.js — 新規関数 resyncInvoiceFromSource() を追加(既存のパターン padIdDigitsTo4 / removePartnerAbbreviations と同列)
    • templates/operations_sidebar.html — 「⚙️ メンテナンス」セクションに <button> を 1 行追加
  • 読み取りのみ: 400_domain/401_rpa_hc.js / 402_rpa_subscription.js / 403_rpa_capex.js / 406_rpa_pipeline.js(既存関数を呼び出すのみ。改変しない)
  • スコープ外: 42_trn_journal(本案件では一切変更しない)
  • 影響する下流: 論理削除した INV は 32_wrk_invoice に残る(物理削除ではなく 有効フラグ=FALSE)。マート更新や Action A/B は 有効フラグ=TRUE の行のみ対象のため、論理削除後は下流に波及しない

注意事項

  1. InvoiceRepository.save() は全置換findAll() で取得した全 DTO 配列を保持し、対象 INV のみ 有効フラグ = false に変更してから save() に渡すこと。フィルタした部分配列を渡すと対象外 INV が消失する
  2. 既存 RPA 関数は改変しない。全て「シート全体」対象で行単位呼び出しできないが、RpaCommon の冪等性チェックで既存有効 INV は二重起票されないため、そのまま引数なしで呼び出せば論理削除分のみ再生成される
  3. 予算ID → INV の経路は 2 ステップ必須InvoiceDTO参照元ID を直接保持しない。OrderRepository.findAll() → 該当 ORD ID 収集 → InvoiceRepository.findAll()親発注ID(ORD) でフィルタ の順序
  4. Utils.auditLog() は例外を握りつぶす98_audit_logsetupAllSchemas で作成済みであることが前提(CLAUDE.md / 003_contracts.js に記載)。未作成時は console.error のみで処理継続
  5. サイドバーはグローバル UI。シート限定ボタンは実現不可のため、ボタン実行時にアクティブシートを判定してガードする(Step 2 のソース種別判定がそのガード役を兼ねる)
  6. 請求ステータス の有効値は 3 値のみ"未処理"|"承認済"|"却下")。過去の設計書や Gemini 生成コードで "完了" が登場する場合があるが実装上は存在しないため参照しないこと
  7. ロック取得失敗時は releaseLock() を呼ばないtryLock が false を返した場合はそもそもロックを取得していないため、finally ブロック外で早期 return する

エッジケース

条件振る舞い理由
対象行が未選択(シート全体が選択状態 / 範囲が1行目のヘッダーを含む)Toast「再同期の対象行を選択してください。」で終了選択範囲の行数が全行と一致する場合などを含め、意図しない全件処理を防ぐ
選択行の 管理ID 列が空その行はスキップ。全行が空なら Toast「管理ID が空の行しかありません。」で終了未発番の予算行を対象にしても参照経路をたどれないため
選択行に紐づく有効 INV レコードが 0 件「既存の請求レコードが見つかりませんでした。新規に生成しますか?」ダイアログ確認 → 「はい」で論理削除ステップをスキップして RPA 再実行のみ実施初回生成失敗後のリカバリ操作を想定
紐づく INV レコードの 請求ステータス"未処理" 以外("承認済" または "却下")が含まれる処理をブロックし「決済処理済または承認済の請求レコードが含まれるため再同期できません。経理担当者に確認してください。」ダイアログを表示InvoiceDTO.請求ステータス の有効値は "未処理"|"承認済"|"却下" の 3 値のみ("完了" は存在しない)。承認済データの誤変更防止
紐づく INV レコードの 自動仕訳JNL_ID が非空(= Action A で仕訳発行済み)上記と同様にブロック仕訳発行済みの INV を論理削除すると 42_trn_journal との整合性が崩れる。本案件スコープ外
20 番台以外のシートでボタンを実行(サイドバーはグローバル)「このメニューは 20 番台予算シート(21/22/23/24)から実行してください。」Toast で終了グローバル UI の誤操作防止
LockService のロック取得タイムアウト(10 秒超)「別の再同期処理が実行中です。しばらく待ってから再試行してください。」Toast で終了。tryLock が false なのでロック解放呼び出しは不要スクリプトレベルロックのため別ユーザーの同時実行を防ぐ
RPA 関数の呼び出し中に例外発生Utils.logError() でスタックトレース記録。「再生成中にエラーが発生しました。ログを確認してください。」Toast。論理削除済みの INV は残ったままになるためその旨をダイアログで警告GAS にはトランザクションが存在しないため、中断時の状態を明示する
対象 ORD が 0 件(予算ID に対応する ORD が未作成)上記「INV 0 件」と同じ扱いで新規生成確認ダイアログに進む予算を作成したが RPA 未実行の状態を想定
複数行選択で一部の行だけ INV が「承認済」を含む一つでも承認済があれば全体をブロック部分的な再同期は整合性判断を複雑化させるため、All-or-Nothing 方針

実データ検証

実装前に MCP または GAS コンソールで以下を確認する:

確認項目確認方法理由
32_wrk_invoice のヘッダーに 有効フラグ / 親発注ID(ORD) / 請求ステータス / 自動仕訳JNL_ID の実列名と位置GAS エディタで =INDIRECT("32_wrk_invoice!1:1") または MCP read_range でヘッダー行を取得列名ハードコード禁止のため indexOf で動的取得することを前提とするが、そもそも実在するかの事前確認
31_wrk_order参照元区分 / 参照元ID カラムに PIP_xxxx / EMP_xxxx / SUB_xxxx / CPX_xxxx 形式のデータが実際に格納されているかMCP read_range で 31 タブの該当列をサンプリング2 ステップ検索の前提となるデータ形式の確認
20 番台予算タブの 管理ID 列名が全 4 シートで同一(管理ID)か4 シートの 1 行目をそれぞれ確認ソース種別ごとに列名が異なるとロジックが複雑化する

関連ドキュメント

仕様書 / ファイル関連箇所
CLAUDE.mdコーディング規約(列参照はヘッダー名ベース、有効フラグ=FALSE 行は処理スキップ、Human-in-the-Loop 原則)
003_contracts.jsL15-36: OrderDTO参照元区分参照元ID)、L40-67: InvoiceDTO親発注ID(ORD)有効フラグ請求ステータス
202_repository.jsL107-147: OrderRepository、L152-207: InvoiceRepositoryfindAll/save は全置換)
407_rpa_orchestrator.jsRPAService の公開 API(本案件は既存関数を直接呼ぶ)
406_rpa_pipeline.jsL10: generatePipelineInvoices(targetOverride, _silent) シグネチャ
401_rpa_hc.jsL10: generateHcInvoices(targetOverride, _silent) シグネチャ
templates/operations_sidebar.html⚙️ メンテナンスセクション(新規ボタンの追加先)
dev_mas-179_audit_trail.mdUtils.auditLog() の仕様(98_audit_log 未作成時は握りつぶす)

人間が検討すべき事項

  • 再同期の範囲(TODO_future.md から転記): 「全件再生成 vs 差分更新」の方針決定。本仕様書では 「論理削除 + 全件再生成(RPA 冪等性に依拠)」 を採用する。理由:
    1. 差分更新はフィールド単位の比較ロジックが複雑化し、比較もれ・不整合リスクが高い
    2. 全件再生成は既存 RPA 関数(RpaCommon の冪等性チェック付き)をそのまま再利用でき、追加の実装コストが最小
    3. 20 番台予算行 1 件あたりの INV 生成件数は月数×科目数程度(多くても数十件)でパフォーマンス問題が生じにくい
  • 42_trn_journal(自動仕訳)をスコープに含めるか: 含めない。既発行 JNL の取り消しは監査上の重要操作で Human-in-the-Loop 承認フローを別途設計する必要がある。本案件ではユーザーが「INV 再同期 → Action A 再実行」を順次手動で行う運用で割り切る。将来的に JNL 再同期を自動化する場合は別案件化
  • 複数行選択時の All-or-Nothing 方針の妥当性: 「一部承認済」の場合に「承認済以外だけ再同期する」オプションをユーザーが求める可能性がある。初版は安全側に倒し全体ブロックとするが、運用してニーズが出たら再検討
  • サイドバーの配置場所: 「⚙️ メンテナンス」セクションに配置。別案として「🔧 開発・設定」や新規セクション「🔁 データ再同期」も検討したが、運用者(非エンジニア)が日常的に触る操作のため目立つ位置が望ましく「メンテナンス」下とした

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

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-087「元データ修正→下流タブ再同期 (RPA再生成)」を実装してください。

## 実行前タスク
- `100_config/101_sys_config.js` を Read し、`onOpen()` とサイドバー起動の仕組み(`openOperationsSidebar()`)を確認する
- `templates/operations_sidebar.html` を Read し、「⚙️ メンテナンス」セクションのボタン追加パターンを確認する
- `400_domain/407_rpa_orchestrator.js` を Read し、RPAService の公開 API シグネチャ(本案件では `RPAService.generatePipeline()` 等ではなく、各実関数 `generatePipelineInvoices()` / `generateHcInvoices()` / `generateSaasInvoices()` / `generateCapexInvoices()` を直接呼ぶ)を確認する
- `400_domain/406_rpa_pipeline.js` / `401_rpa_hc.js` / `402_rpa_subscription.js` / `403_rpa_capex.js` を Read し、いずれも `(targetOverride, _silent)` シグネチャで「シート全体」を対象とすることを確認する
- `000_infra/003_contracts.js` を Read し、`OrderDTO.参照元ID` / `OrderDTO.参照元区分` / `OrderDTO.発注ID(ORD)` / `InvoiceDTO.親発注ID(ORD)` / `InvoiceDTO.有効フラグ` / `InvoiceDTO.請求ステータス`(`"未処理"|"承認済"|"却下"`)/ `InvoiceDTO.自動仕訳JNL_ID` のフィールド名を確認する
- `200_data/202_repository.js` を Read し、`OrderRepository.findAll()` / `InvoiceRepository.findAll()` が `{headers, dtos}` を返し、`save(dtos)` が全置換であることを確認する
- `300_ui/301_ui_assist.js` を Read し、`padIdDigitsTo4()` / `removePartnerAbbreviations()` など既存 UI エントリポイントの実装パターンを確認する

## 修正対象ファイル
- `300_ui/301_ui_assist.js` — 新規関数 `resyncInvoiceFromSource()` をファイル末尾に追加
- `templates/operations_sidebar.html` — 「⚙️ メンテナンス」セクションに `<button class="btn" onclick="run('resyncInvoiceFromSource', this)">🔁 INV 再同期(選択行)</button>` を追加

## 実装内容
エントリポイント関数: `function resyncInvoiceFromSource()`(サイドバーから google.script.run 経由で呼ばれる)

1. `LockService.getScriptLock()` で排他ロックを取得。`lock.tryLock(10000)` が false なら Toast 通知して return(`releaseLock` は呼ばない)
2. `try { ... } finally { lock.releaseLock(); }` で本体をラップ
3. アクティブシート名を取得。`21_bud_pipeline` / `22_bud_headcount` / `23_bud_subscription` / `24_bud_capex_loan` 以外なら Toast「このメニューは 20 番台予算シート(21/22/23/24)から実行してください。」で return
4. シート名から RPA 関数を選択: pipeline → `generatePipelineInvoices`, headcount → `generateHcInvoices`, subscription → `generateSaasInvoices`, capex_loan → `generateCapexInvoices`
5. `SpreadsheetApp.getActiveRange()` で選択範囲を取得。範囲の行番号が 1(ヘッダー行のみ)または行数がシート最終行と一致する場合は「再同期の対象行を選択してください。」Toast で return
6. 選択範囲の各行について、ヘッダー名 `管理ID` を `indexOf` で特定し値を収集。空なら個別にスキップ。全行空なら Toast で return
7. `OrderRepository.findAll()` で全 ORD を取得し、`有効フラグ === true` かつ `参照元ID` が手順6 の予算 ID セットに含まれる ORD の `発注ID(ORD)` を集める
8. `InvoiceRepository.findAll()` で全 INV を取得し、`有効フラグ === true` かつ `親発注ID(ORD)` が手順7 の ORD ID セットに含まれる INV を対象候補とする
9. 対象候補が 0 件の場合、`ui.alert('再同期確認', '既存の請求レコードが見つかりませんでした。新規に生成しますか?', OK_CANCEL)` で確認。OK なら手順11 へ、Cancel なら return
10. 対象候補に `請求ステータス !== '未処理'` または `自動仕訳JNL_ID` 非空の INV が 1 件でもあれば `ui.alert('❌ 再同期不可', '決済処理済または承認済の請求レコードが含まれるため再同期できません。経理担当者に確認してください。', OK)` を表示して return
11. 対象候補を整形した一覧(INV ID / 発生日 / 税込金額_計画 / 請求ステータス)を文字列化し `ui.alert('🔁 INV再同期確認', '以下のINVを論理削除し再生成します:\\n\\n' + list, OK_CANCEL)` で確認。Cancel なら return
12. 対象 INV の `有効フラグ` を `false` に設定した DTO 配列全体を `InvoiceRepository.save(allDtos)` で書き戻す。**対象外を含む全 DTO を保持し、対象のみ変更すること**
13. 各論理削除 INV について `Utils.auditLog('DELETE', '32_wrk_invoice', invId, '有効フラグ', 'resyncInvoiceFromSource', true, false, 'S-15再同期')` を呼ぶ
14. 手順4 で選択した RPA 関数を **引数なし** で呼び出す(例: `generatePipelineInvoices()`)
15. RPA 呼び出し後の有効 INV 件数と論理削除件数を比較し、`Utils.toastResult('resyncInvoiceFromSource', '論理削除: N件 / 再生成: M件')` で通知

## 制約
- 既存の RPA 関数(`400_domain/40x_rpa_*.js`)のロジックは改変しない。呼び出しのみ
- `InvoiceRepository.save()` は全置換のため、`findAll()` で取得した全 DTO を保持して部分更新する
- 列番号ハードコード禁止。ヘッダー名ベースで列を参照する(CLAUDE.md 規約)
- `LockService.getScriptLock()` + `tryLock(10000)` + `try { ... } finally { lock.releaseLock() }` パターンを必ず使用する
- ブランチは `feat/S-15-invoice-resync` を切り PR → main マージのフローに従う

## エッジケース
仕様書「エッジケース」テーブルの全条件を実装に反映すること。特に:
- 選択行が未選択 / ヘッダー行のみ → Toast で return
- 全行の `管理ID` が空 → Toast で return
- 対象候補 0 件 → 新規生成確認ダイアログ
- 承認済・却下・仕訳発行済 INV を含む → ブロックダイアログ
- 20 番台以外のシート → Toast で return
- ロック取得タイムアウト → Toast で return(releaseLock 不要)
- RPA 例外 → `Utils.logError()` + 警告ダイアログ

## 動作確認
1. `npm run push:dev` で開発環境にデプロイ
2. スプレッドシートを再読み込みし、サイドバー「⚙️ メンテナンス」に「🔁 INV 再同期(選択行)」ボタンが表示されていることを確認
3. `21_bud_pipeline` を開き、有効な PIP_xxxx 行を 1 行選択してボタン実行 → 確認ダイアログに対象 INV が列挙されることを確認
4. OK 承認後、`32_wrk_invoice` で対象 INV の `有効フラグ` が FALSE になり、末尾に新規 INV が追加されていることを確認
5. `98_audit_log` に `DELETE / 32_wrk_invoice / INV_xxxx / 有効フラグ / resyncInvoiceFromSource` のレコードが記録されていることを確認
6. ヘッダー行のみ選択した状態でボタンを実行し、Toast「再同期の対象行を選択してください。」が表示されて処理が終了することを確認
7. `請求ステータス = 承認済` の INV に紐づく行を選択してボタン実行し、ブロックダイアログが表示されることを確認
8. `自動仕訳JNL_ID` 非空の INV に紐づく行でも同様にブロックされることを確認
9. `22_bud_headcount` / `23_bud_subscription` / `24_bud_capex_loan` でも同じ挙動になることを確認
10. `21_bud_pipeline` 以外の一般シート(例: `91_fs_bs`)でボタンを実行し、Toast「このメニューは 20 番台予算シート(21/22/23/24)から実行してください。」が表示されることを確認

### 拡張思考の使用状況

| フェーズ | 拡張思考 | 備考 |
|---------|:--------:|------|
| 実行前タスク(ファイル調査・関数名確認) | あり | ORD 経由の 2 ステップ参照経路の確定に使用 |
| 実装(コード記述) | なし | 仕様書でフロー・エッジケースが確定済み |

推奨実行モデル

工程推奨モデル理由
仕様書作成(本ドキュメント)Claude Opus 4.7複数 RPA ファイルの関数シグネチャ確認、ORD 経由の参照経路の設計判断が必要
実装(コード記述)Claude Sonnet 4.6既存パターン(301_ui_assist.js の UI エントリ、LockService 定型)の適用と複数ファイル変更が必要
動作確認ユーザー手動GAS エディタ / スプレッドシートでの UI 操作、承認済 INV のモック準備が必要

変更履歴

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

仕様書作成プロンプト

仕様書作成プロンプト(展開して表示)
【タイムアウト回避・実行原則(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〜2-4 に分けて実行する(詳細は各 Step 参照)。1 回の Write/Edit は約 300 行以内を目安にする。
4. **各 Step で何を書くかを具体指示**: 設計判断を Phase 2 実行時に持ち込まない。Phase 1 で固有名詞・行番号・関数名をすべて確定させてから清書に入る。

======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 S-15「元データ修正→下流タブ再同期 (RPA再生成)」の開発仕様書を作成してください。
仕様書新規作成後は `docs/_config.json` の `nav` 配列の適切なセクションに必ず追記してください。

---

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

以下のファイルを順に Read し、確認すべきポイントを全て Phase 1 中に解決してから Phase 2 に進むこと。**Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず Read で行う。**

### 1-A: 案件要件の把握
- `docs/_internal/TODO_future.md` — S-15 の案件名・概要・人間が検討すべき事項を取得する

### 1-B: 既存仕様書テンプレートの把握
- `docs/dev/dev_mas-094_boundary_month_selector.md` などの UI/メニュー系仕様書を 1 件 Read し、フォーマットを把握する

### 1-C: メニュー定義の確認(固有名詞の裏取り)
- `100_config/101_sys_config.js` を Read し、`onOpen()` の `ui.createMenu()` 呼び出し部分で**実在するメニュー名・サブメニュー名の文字列**を確認する。動作確認手順に書く固有名詞はここから引用する。仕様書に「🔧 ○○」と書く場合は必ずこのファイルで実在を確認すること

### 1-D: RPA 公開 API・実関数名の確認(ハルシネーション防止)
以下のファイルを Read し、各予算シートに対応する**実際の関数名**を確認する。Gemini が生成した `createInvoicesFromPipeline_` 等の関数名は未検証のため、実在する関数名に差し替えること。
- `400_domain/407_rpa_orchestrator.js` — `RPAService` の公開 API 一覧(`runHC`/`runPipeline`/`runSubscription`/`runCapex` 等の実際のシグネチャ)
- `400_domain/406_rpa_pipeline.js` — PIP_ 対応の内部 RPA 関数名と引数形式
- `400_domain/401_rpa_hc.js` — EMP_ 対応の内部 RPA 関数名と引数形式
- `400_domain/402_rpa_subscription.js` — SUB_ 対応の内部 RPA 関数名と引数形式
- `400_domain/403_rpa_capex.js` — CPX_ 対応の内部 RPA 関数名と引数形式
- 確認すべき点: 各関数は「1行分のデータ」を引数に取るか、「シート全体」を対象とするか。行単位で呼び出せない場合は設計変更が必要

### 1-E: InvoiceDTO・OrderDTO のフィールド定義確認(参照関係の正確な把握)
- `000_infra/003_contracts.js` を Read し、以下を確認する:
  - `InvoiceDTO` に `有効フラグ` フィールドが存在するか(typedef に明示されていない。実シートに列があるかは `101_sys_config.js` の DDL 定義も参照して確認)
  - `InvoiceDTO` に `参照元ID` フィールドが存在するか(`OrderDTO` には `参照元ID` があるが `InvoiceDTO` には記載なし。予算ID→INV の連鎖は「予算ID → `OrderDTO.参照元ID` → `InvoiceDTO.親発注ID(ORD)`」の2ステップ経路が想定されるが、実際の列構造を確認する)
  - `InvoiceDTO.請求ステータス` の有効な値: `003_contracts.js` の typedef より `"未処理" | "承認済" | "却下"` の3値のみ(「完了」は存在しない)

### 1-F: Repository インターフェース確認
- `200_data/202_repository.js` を Read し、`InvoiceRepository.findAll()` / `InvoiceRepository.save()` の返却型と副作用(`save` は全置換であることを確認)を把握する

### 1-G: 新規関数の配置先を決定
- `300_ui/301_ui_assist.js` を Read し、UI トリガー関数の配置パターンを確認する。S-15 の新規関数(メニュー実行エントリポイント)をこのファイルに追加するか、新規ファイルを起こすかを Phase 1 中に決定する

---

## Phase 2: 仕様書の分割作成

出力先: `docs/dev/dev_mas-087_data_resync.md`
(Step 2-1〜2-4 の詳細は原プロンプト参照)

---

## Phase 3: `_config.json` への追記と構文チェック

1. `docs/_config.json` の `nav` 配列の **§E.2(バグ修正・バリデーション系)** セクションに以下を追記する
2. `docs/_config.json` の JSON 構文が壊れていないことを確認する
3. `docs/_internal/changelog.md` の先頭行に追記する
4. コミット→ push