最終更新: 2026/06/22 18:56
MAS-199: prod → dev データ同期ツール(フィルター罠回避)
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-199 |
| カテゴリ | DevOps |
| Phase | P1 |
| 優先度 | ★★★ |
| 所要時間 | 1時間 |
| 対象ファイル | 800_ops/803_sync_tool.js(拡張) |
| 関連ファイル | 000_infra/001_env.js, 100_config/101_sys_config.js |
| 前提案件 | なし |
目的
Google Sheets のフィルター適用中に手動コピペすると非表示行がコピーされない問題(フィルター罠)を根本回避するため、GAS の getValues() による全行取得ベースの同期ツールを提供する。
背景
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タブを一括同期するため:
- 実行時間が長い: 全タブ同期はデータ量によっては GAS 6分制限に抵触するリスクがある
- 選択同期ができない: 特定タブ(32/33 のみ)だけ同期したい場合にも全タブが対象
- フィルター罠の注意喚起がない: 手動コピペを防止する明示的な警告や案内がない
修正方針
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.js | 1行変更 | メニュー項目名を「本番データ同期」に変更 |
既存の syncProdToDev() を拡張。新規関数は追加しない。メニューも1つのまま統合。
注意事項
- dev 環境限定:
Env.isDev()ガードを必ず含める。prod で実行すると本番データが上書きされる - 同一ID チェック:
PROD_SPREADSHEET_ID === SPREADSHEET_IDの場合はエラーで中止(既存ロジックを維持) - prod スプレッドシートの閲覧権限: 実行者に prod スプレッドシートの閲覧権限が必要
- GAS 6分制限: 全タブ一括同期は大量データ時にリスクあり。タブ選択式で必要なタブのみ同期を推奨
- dev テストデータの上書き: 同期実行前の確認ダイアログで軽減するが、完全な防止ではない
- GCP移行後の廃止: Phase 3(GCP移行)後はDB間同期に置き換え前提の暫定ツール
- prod スプレッドシートID の管理: スクリプトプロパティ
PROD_SPREADSHEET_IDで管理(既存パターンを踏襲)
デメリット
| デメリット | 軽減策 |
|---|---|
| dev GAS に prod スプレッドシートID を保持する必要がある | スクリプトプロパティで管理(コード外) |
| 実行者に prod スプレッドシートの閲覧権限が必要 | 権限管理は Google Workspace 側で制御 |
| 大量データ時に GAS 6分制限に抵触するリスク | タブ選択式で必要なタブのみ同期 |
| dev 側のテストデータが上書きされる | 確認ダイアログで注意喚起 |
| GCP移行後は別の仕組みに置き換え前提 | 暫定ツールとして割り切り |
関連ドキュメント
| 仕様書 | 関連箇所 |
|---|---|
| CLAUDE.md | GAS環境分離(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 | 初版作成。実障害(フィルター罠によるデータ欠落)を受けて新規案件として起票 |