概要

項目内容
案件IDMAS-199
カテゴリDevOps
PhaseP1
優先度★★★
所要時間1時間
対象ファイル800_ops/803_sync_tool.js(拡張)
関連ファイル000_infra/001_env.js, 100_config/101_sys_config.js
前提案件なし

目的

Google Sheets のフィルター適用中に手動コピペすると非表示行がコピーされない問題(フィルター罠)を根本回避するため、GASgetValues() による全行取得ベースの同期ツールを提供する。

背景

2026-04-15 に発生した実障害。prod → dev へのデータ同期時に、32_wrk_invoice / 33_wrk_bank タブにフィルターが適用された状態で手動コピペを行ったため、非表示行がコピーされず dev 環境の B/S 残高が prod と乖離した。

GAS の getValues() はフィルター状態に関係なく全行を取得する。この特性を活用し、手動コピペに依存しない同期ツールを提供する。

現在のコード

803_sync_tool.js(既存)

既に syncProdToDev() 関数が実装済み。以下の機能を持つ:

  • SYNC_TARGET_SHEETS_ 配列で同期対象シート(23タブ)を定義
  • Env.isDev() ガードで dev 環境のみ実行可能
  • スクリプトプロパティから PROD_SPREADSHEET_ID を取得
  • 同一ID チェック(prod = dev の誤操作防止)
  • 確認ダイアログ → 全タブ一括同期 → 結果表示

101_sys_config.js(既存メニュー)

L357-364: 「🔄 開発用」メニューに syncProdToDev が登録済み(dev 環境のみ表示)。

現状の問題点

既存の syncProdToDev() は全23タブを一括同期するため:

  1. 実行時間が長い: 全タブ同期はデータ量によっては GAS 6分制限に抵触するリスクがある
  2. 選択同期ができない: 特定タブ(32/33 のみ)だけ同期したい場合にも全タブが対象
  3. フィルター罠の注意喚起がない: 手動コピペを防止する明示的な警告や案内がない

修正方針

803_sync_tool.jsタブ選択式の同期関数を追加し、メニューに登録する。既存の syncProdToDev()(全タブ一括)はそのまま残す。

変更1: syncProdToDev() の改修(統合UI)

既存の syncProdToDev() に「全タブ / タブ選択」の選択ダイアログを追加する。新規関数は作らず、既存関数を拡張する。

function syncProdToDev() {
  // ... 既存の環境チェック・ID取得 ...

  var ui = SpreadsheetApp.getUi();

  // 同期モード選択
  var mode = ui.alert(
    '本番データ同期',
    '同期モードを選択してください。\n\n'
    + '【OK】全タブ一括同期(' + SYNC_TARGET_SHEETS_.length + 'タブ)\n'
    + '【キャンセル】タブを選択して同期',
    ui.ButtonSet.OK_CANCEL
  );

  var targetSheets;
  if (mode === ui.Button.OK) {
    targetSheets = SYNC_TARGET_SHEETS_;
  } else {
    // タブ選択モード
    var response = ui.prompt(
      'タブ選択同期',
      '同期するタブ名をカンマ区切りで入力してください。\n\n'
      + '例: 32_wrk_invoice, 33_wrk_bank\n\n'
      + '利用可能なタブ:\n' + SYNC_TARGET_SHEETS_.join(', '),
      ui.ButtonSet.OK_CANCEL
    );
    if (response.getSelectedButton() !== ui.Button.OK) return;
    var input = response.getResponseText().trim();
    if (!input) return;

    targetSheets = input.split(',').map(function(s) { return s.trim(); });

    // 入力検証
    var invalidTabs = targetSheets.filter(function(t) {
      return SYNC_TARGET_SHEETS_.indexOf(t) === -1;
    });
    if (invalidTabs.length > 0) {
      ui.alert('エラー', '以下のタブは同期対象外です:\n' + invalidTabs.join(', '), ui.ButtonSet.OK);
      return;
    }
  }

  // 確認ダイアログ(対象タブ数を表示)
  // ... 既存の確認ダイアログを targetSheets.length に変更 ...

  // 同期実行(既存ループを targetSheets で回す)
  // ...
}

変更2: メニュー登録

101_sys_config.js のメニュー項目名を変更(関数名は同じ):

ui.createMenu('🔄 開発用')
  .addItem('本番データ同期', 'syncProdToDev')
  .addToUi();

影響範囲

変更ファイル変更量内容
800_ops/803_sync_tool.js約20行追加syncProdToDev() にモード選択UIを追加
100_config/101_sys_config.js1行変更メニュー項目名を「本番データ同期」に変更

既存の syncProdToDev() を拡張。新規関数は追加しない。メニューも1つのまま統合。

注意事項

  1. dev 環境限定: Env.isDev() ガードを必ず含める。prod で実行すると本番データが上書きされる
  2. 同一ID チェック: PROD_SPREADSHEET_ID === SPREADSHEET_ID の場合はエラーで中止(既存ロジックを維持)
  3. prod スプレッドシートの閲覧権限: 実行者に prod スプレッドシートの閲覧権限が必要
  4. GAS 6分制限: 全タブ一括同期は大量データ時にリスクあり。タブ選択式で必要なタブのみ同期を推奨
  5. dev テストデータの上書き: 同期実行前の確認ダイアログで軽減するが、完全な防止ではない
  6. GCP移行後の廃止: Phase 3(GCP移行)後はDB間同期に置き換え前提の暫定ツール
  7. prod スプレッドシートID の管理: スクリプトプロパティ PROD_SPREADSHEET_ID で管理(既存パターンを踏襲)

デメリット

デメリット軽減策
dev GAS に prod スプレッドシートID を保持する必要があるスクリプトプロパティで管理(コード外)
実行者に prod スプレッドシートの閲覧権限が必要権限管理は Google Workspace 側で制御
大量データ時に GAS 6分制限に抵触するリスクタブ選択式で必要なタブのみ同期
dev 側のテストデータが上書きされる確認ダイアログで注意喚起
GCP移行後は別の仕組みに置き換え前提暫定ツールとして割り切り

関連ドキュメント

仕様書関連箇所
CLAUDE.mdGAS環境分離(Prod/Dev)、Env モジュール、ファイル番号体系
D.8 MAS-116 マイグレーションスクリプト基盤800_ops/ の配置ルール・命名規則
環境構築ガイドdev/prod 環境の切り替え手順

人間が検討すべき事項

  • prod スプレッドシートID の管理方法(スクリプトプロパティ vs ハードコード)→ 既存パターン通りスクリプトプロパティを推奨
  • 同期対象タブの範囲(32/33 のみ or 他タブも含めるか)→ 既存の SYNC_TARGET_SHEETS_(23タブ)全体を対象とし、タブ選択式で絞り込む設計を推奨

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

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-199「prod → dev データ同期ツール(フィルター罠回避)」を実装してください。

## 背景

2026-04-15 に prod → dev へのデータ同期時に、32_wrk_invoice / 33_wrk_bank に
フィルターが適用された状態で手動コピペを行い、非表示行がコピーされず dev の
B/S 残高が prod と乖離した実障害がある。
既存の 803_sync_tool.js の syncProdToDev() に「全タブ/タブ選択」の統合UIを追加する。

## 実行前タスク

以下のファイルを読み込み、既存実装を把握すること:
- `800_ops/803_sync_tool.js` — syncProdToDev() の全体構造、SYNC_TARGET_SHEETS_
- `000_infra/001_env.js` — Env.isDev() のガードパターン
- `100_config/101_sys_config.js` — L357-364 の「🔄 開発用」メニュー登録
- `docs/dev/dev_mas-199_prod_dev_sync.md` — 修正方針の詳細

## 修正対象ファイル

1. `800_ops/803_sync_tool.js` — syncProdToDev() の改修
2. `100_config/101_sys_config.js` — メニュー項目名の変更(1行)

## 実装内容

### Step 1: syncProdToDev() にモード選択UIを追加

既存の確認ダイアログの前に、同期モード選択を挿入:
- OK → 全タブ一括(SYNC_TARGET_SHEETS_ をそのまま使用)
- キャンセル → タブ選択モード(ui.prompt でカンマ区切り入力)

タブ選択モードの処理:
- 入力をカンマ分割して配列化
- SYNC_TARGET_SHEETS_ に含まれないタブ名はエラーダイアログで中止
- 有効なタブ名のみで同期実行

既存の同期ループは変更不要。ループ対象を SYNC_TARGET_SHEETS_ から
targetSheets(選択結果)に差し替えるだけ。

### Step 2: メニュー項目名の変更

101_sys_config.js L361:
変更前: `'本番データ同期 (syncProdToDev)'`
変更後: `'本番データ同期'`

## 制約

- 新規関数は追加しない(syncProdToDev() の改修のみ)
- Env.isDev() ガード・同一IDチェックは既存のまま維持
- GAS コード(000_infra/ 〜 600_report/)は一切変更しない

## 動作確認

1. `npm run push:dev` で開発 GAS にデプロイ
2. GAS エディタでスプレッドシートを開き直し、「🔄 開発用」メニューを確認
3. 「本番データ同期」を実行 → モード選択ダイアログが表示されること
4. OK(全タブ)→ 全23タブが同期されること
5. キャンセル → タブ選択プロンプトが表示されること
6. `32_wrk_invoice, 33_wrk_bank` を入力 → 2タブのみ同期されること
7. dev の 32/33 タブの行数が prod と一致すること(フィルター非表示行も含む)
8. 存在しないタブ名を入力 → エラーダイアログが表示されること

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

| フェーズ | 拡張思考 | 備考 |
|---------|:--------:|------|
| モード選択UI追加 | なし | ui.alert() のボタン分岐のみ |
| タブ選択バリデーション | なし | 配列フィルタの単純なロジック |
| メニュー項目名変更 | なし | 文字列変更のみ |

### 推奨実行モデル

| 工程 | 推奨モデル | 理由 |
|------|----------|------|
| 全工程 | **Claude Sonnet 4.6** | 既存関数の改修。パターン確立済みだが挿入位置の判断が必要 |

推奨実行モデル

工程推奨モデル理由
仕様書作成(本ドキュメント)Claude Opus 4.6実障害分析、既存コードの拡張方針設計、デメリット整理に高い推論力が必要
実装Claude Sonnet 4.6既存パターンの踏襲が中心。リファクタリング+新関数追加

変更履歴

日付変更内容
2026-04-15初版作成。実障害(フィルター罠によるデータ欠落)を受けて新規案件として起票