概要

項目内容
案件 IDMAS-130
案件名フィルタービュープリセット配布(行積み上がり対策 A)
カテゴリUX・可視性
優先度P1 ★★★(運用即効・実装規模小・MAS-131 と並列でファーストライン対策)
所要時間約 1 週間(週 10h 前提)
対象ファイル(新規)300_ui/303_filter_presets.jsFilterPresets 名前空間・約 200 行)
対象ファイル(変更)000_infra/002_constants.jsMENU_DEFINITION に 1 項目追加)
新規シートなし
新規 03_sys_params キーなし(プリセット選択状態は PropertiesService.getDocumentProperties() で永続化)
前提案件なし(DDL は既存・GAS Filter API 標準機能のみ)
後続連携MAS-131(条件付き書式・行積み上がり対策 B)と並列で運用効果倍増
吸収・再定義対象なし

目的

行が数千行に達した WRK 系・予算系タブで目視確認が困難になる問題に対し、5-7 種のフィルタービュープリセットをワンクリックで適用できる UI を提供。最後に選択したプリセット名を永続化し、起動時自動復元で運用負荷を最小化する。

  • 経理担当者の月次締めワークフローで「未処理 INV だけ表示」「今月発生分だけ表示」等の頻出フィルタを即時切替可能に
  • DDL 列名の表記揺れ (確認FLG 等) を spec で明示固定し、ヘッダー名ハードコード禁止 (buildHeaderIndex_ 利用) で DDL 変更追従性を担保
  • MAS-131 (条件付き書式) と組み合わせて「フィルタ + 視覚区別」の両軸でファーストライン対策を完成

現在のコード

利用する既存 API

API定義場所シグネチャ用途
Utils.getSheetByKey(key, fallbackName)000_infra/004_utils.js:302(key, fallback) → Sheet | null対象シート取得 (DDL sysKey ベース)
Utils.persistLog(level, funcName, message, detail)000_infra/004_utils.js:577('INFO'|'WARN'|'ERROR', funcName, message, detail)エッジケース発生時のログ
Sheet.getFilter() / setFilter() / removeFilter()GAS 標準フィルタ操作
SpreadsheetApp.newFilterCriteria()GAS 標準() → FilterCriteriaフィルタ条件構築 (whenTextEqualTo / whenDateEqualTo 等)
PropertiesService.getDocumentProperties()GAS 標準() → Propertiesプリセット選択状態の永続化 (シート単位)

既存の DDL 列構造(裏取り確認済・2026-04-30 時点)

100_config/101_sys_config.js:1044-1046 等で確認:

シート主要ステータス列プリセット適用候補
32_wrk_invoice (WRK_INV)確認FLG (FALSE = 未処理 / TRUE = 計上済)「未処理のみ」
33_wrk_bank (WRK_BANK_IMPORT)確認FLG / 処理結果 (MATCHED/UNMATCHED)「未消込 STL
34_wrk_card (WRK_CARD)確認FLG / 処理結果「未消込 STL」
35_wrk_petty (WRK_RCPT)確認FLG / 処理結果「未消込 STL」
21_bud_pipeline26_bud_adhoc確認FLG (TRUE = 確定 / FALSE = 仮計画)「承認待ち」

ヘッダー名は DDL で固定 (確認FLG)。本案件では buildHeaderIndex_ または同等のヘッダーベース参照を強制し、列番号ハードコード禁止。

修正方針

Step 1: 名前空間 FilterPresets の新設

300_ui/303_filter_presets.js を新設し、IIFE + グローバル変数宣言パターン (既存 301_ui_assist.js / 302_spa_bridge.js と並列) で実装:

// 300_ui/303_filter_presets.js
var FilterPresets = (function () {
  // プリセット定義 (v1 はハードコード・v2+ で 03_sys_params 外出し検討)
  var PRESETS = {
    UNPROCESSED: {
      label: '未処理のみ',
      description: '確認FLG が FALSE の行のみ表示',
      condition: function (sheet) { return { column: '確認FLG', criteria: 'whenTextEqualTo', value: 'FALSE' }; }
    },
    CURRENT_MONTH: { /* ... */ },
    THIS_WEEK_DEADLINE: { /* ... */ },
    UNMATCHED_STL: { /* ... */ },
    PENDING_APPROVAL: { /* ... */ },
  };

  function applyFilterPreset(presetKey, sheetKey) { /* ... */ }
  function clearFilter(sheetKey) { /* ... */ }
  function restoreLastPreset() { /* ... */ }

  return { applyFilterPreset, clearFilter, restoreLastPreset, PRESETS };
})();

Step 2: プリセット 5-7 種の定義

プリセットキー表示ラベル適用列条件対象シート
UNPROCESSED未処理のみ確認FLG= FALSE32/33/34/35 (WRK 系)
CURRENT_MONTH当月発生発生日(P/L計上日)[当月初日, 当月末日]32-35
THIS_WEEK_DEADLINE今週締め分決済日_計画[当週月曜, 今週末]32 (INV)
UNMATCHED_STL未消込 STL処理結果= UNMATCHED33/34
PENDING_APPROVAL承認待ち確認FLG= FALSE21-26 (予算系)
ALL全表示(既存フィルタ削除)全対象シート
(将来 v2 候補) PJ_SPECIFICPJ別表示プロジェクトID(動的選択)21

Step 3: applyFilterPreset(presetKey, sheetKey) 公開 API

function applyFilterPreset(presetKey, sheetKey) {
  var sheet = Utils.getSheetByKey(sheetKey);
  if (!sheet) {
    Utils.persistLog('WARN', 'FilterPresets.applyFilterPreset', 'Sheet not found: ' + sheetKey, '');
    return { success: false, reason: 'sheet_not_found' };
  }
  var preset = PRESETS[presetKey];
  if (!preset) throw new Error('Unknown preset: ' + presetKey);

  // 既存フィルタを削除
  var existing = sheet.getFilter();
  if (existing) existing.remove();

  // データ範囲取得 (行追加への自動追従)
  var dataRange = sheet.getDataRange();
  if (dataRange.getNumRows() < 2) return { success: false, reason: 'no_data' };

  // 新規フィルタ作成
  var filter = dataRange.createFilter();
  // 条件付与 (列インデックスはヘッダーベース)
  var headers = dataRange.getValues()[0];
  var colIdx = headers.indexOf(preset.column);
  if (colIdx === -1) {
    Utils.persistLog('WARN', 'FilterPresets.applyFilterPreset',
      'Column not found: ' + preset.column + ' in ' + sheetKey, '');
    return { success: false, reason: 'column_not_found' };
  }
  var criteria = SpreadsheetApp.newFilterCriteria()[preset.criteria](preset.value).build();
  filter.setColumnFilterCriteria(colIdx + 1, criteria); // GAS は 1-indexed

  // 永続化
  PropertiesService.getDocumentProperties().setProperty('FILTER_PRESET_' + sheetKey, presetKey);
  return { success: true, presetKey, sheetKey };
}

Step 4: メニュー統合

000_infra/002_constants.jsMENU_DEFINITION📋 サイドバー: 🔧 開発・設定 カテゴリに以下を追加(推奨 = 案 A・既存カテゴリ追加):

{ label: '🔍 表示フィルタープリセット', funcName: 'openFilterPresetsDialog', description: 'WRK/予算タブ向けの定義済みフィルター適用 (MAS-130)' },

openFilterPresetsDialog()301_ui_assist.js (or 新設 entry) でダイアログ UI を表示し、ユーザーが (presetKey, sheetKey) を選択 → FilterPresets.applyFilterPreset() を呼出。

Step 5: 起動時自動復元 (onOpen() フック)

onOpen(e) トリガー (100_config/101_sys_config.js で既設の場合) に以下を追加:

// MAS-130: 前回選択プリセットを起動時自動復元
try {
  FilterPresets.restoreLastPreset();
} catch (err) {
  Utils.persistLog('ERROR', 'onOpen.restoreLastPreset', err.message, err.stack);
}

restoreLastPreset()DocumentProperties.getProperty('FILTER_PRESET_' + sheetKey) で各シートの最終選択を取得し、シートごとに applyFilterPreset を再実行。

影響範囲

対象種別変更内容リスク
300_ui/303_filter_presets.js追加FilterPresets 名前空間 (約 200 行)純粋関数・既存ロジックへの影響なし
000_infra/002_constants.js変更MENU_DEFINITION🔧 開発・設定 カテゴリに 1 行追加既存メニューに影響なし
100_config/101_sys_config.js変更 (任意)onOpen()FilterPresets.restoreLastPreset() 呼出 1 行追加既存トリガーへの影響なし・try-catch で防御
docs/_config.json変更nav §E.2 に E.2.22 追加PR 競合に注意
docs/_internal/changelog.md変更エントリ追加影響なし
appsscript.json変更なしOAuth スコープ追加不要 (spreadsheets 既存)failure_patterns #26 遵守
既存シート DDL変更なしヘッダー名 (確認FLG 等) を spec で参照するのみDDL 影響ゼロ

注意事項

  1. #18-#20(命名造語禁止): applyFilterPreset / clearFilter / restoreLastPreset / FilterPresets は GAS API 標準命名 (Sheet.getFilter() / removeFilter() 等) と整合する命名。プリセットキー (UNPROCESSED 等) は SCREAMING_SNAKE_CASE で予約語と衝突しないことを確認。
  2. #21(getLastColumn() 列範囲膨張): フィルタ範囲は getDataRange() を使い、列範囲ハードコード禁止。getDataRange() は実際のデータが入った範囲のみ取得するため、空列を含まない。
  3. #25(並列実装対称性): 既存 300_ui/301_ui_assist.js / 302_spa_bridge.js と命名・構造対称性を保つ (IIFE + グローバル変数宣言パターン)。
  4. #26(oauthScopes 部分宣言禁止): 確認済 — 本案件は SpreadsheetApp / PropertiesService のみ使用。appsscript.json を一切編集しない。
  5. #31(採番衝突): 元 TODO の 300_ui/302_filter_presets.js302 が spa_bridge.js で占有済のため、本 spec で 303_filter_presets.js に変更。新規案件起票時の Phase 1-A-pre 番号衝突チェックの再発防止事例として記録。
  6. DDL 列名依存: ハードコード列名 (確認FLG / 処理結果 / 発生日(P/L計上日) 等) は DDL 変更時に同期更新が必要。列名は spec § 現在のコードに集約し、列番号ハードコードは禁止。
  7. 複数ユーザー共有制約: GAS の Sheet.setFilter()シート単位の共有フィルタのみサポート。各ユーザー個別の FilterView API は GAS から制御不可。本案件は共有フィルタ前提で実装し、個別フィルタが必要な場合は人間が手動で FilterView を作成する旨を運用ガイドに明記。
  8. 行追加時のフィルタ範囲自動追従: getDataRange() 利用で実現するが、行追加直後にフィルタが自動拡張されない場合があるため、行追加トリガー (onEdit) で再適用する選択肢を将来検討 (v2 候補・人間検討事項)。
  9. シート保護中のフィルタ操作失敗: 編集権限がないシートで setFilter() は例外。try-catch でハンドリングしユーザーに「権限不足」を表示。
  10. ヘッダー名の表記揺れ防止: 例えば 確認FLG vs 確認フラグ 等の混在を防ぐため、Step 1-4 (DDL 確認) で全シートのヘッダー名を網羅。本案件で扱う列名は setupAllSchemas で正規化されている前提。
  11. 永続化の競合: 複数人同時編集時の DocumentProperties 競合は最終書込み優先で OK (UX への影響は最小・各人が再選択すればよい)。
  12. Disclaimer: 本機能は表示の便宜のためのフィルタリングのみで、データの整合性に影響なし。仕訳データ自体は変更しない。

エッジケース

実装時に必ず以下 10 件を単体テストでカバーする。

#条件検知方法期待される挙動ログ出力
1シートが存在しない (DDL 未実行)Utils.getSheetByKey() が nullエラーログ + {success: false, reason: 'sheet_not_found'} 返却Utils.persistLog('WARN', ...)
2対象列がヘッダーに存在しないheaders.indexOf(preset.column) === -1WARN ログ + プリセットスキップWARN
3既存フィルタありsheet.getFilter() !== nullexisting.remove() で削除してから新規 setFilter()
4データ 0 行dataRange.getNumRows() < 2フィルタ作成スキップ + {success: false, reason: 'no_data'}
5プリセット名が DocumentProperties に未保存getProperty() が nullデフォルト「全表示」(ALL) を適用INFO
6列インデックス範囲外colIdx < 0 または colIdx > 列数エラー throw + ログERROR
7当月判定のタイムゾーン依存JST 固定 (Utilities.formatDate(d, 'JST', 'yyyy-MM-dd') 等)JST 0 時を境界として [当月初日 00:00:00 JST, 当月末日 23:59:59 JST] で判定
8シート保護中で setFilter() 例外try-catch で捕捉エラーハンドリング + ユーザーガイド表示 (「保護中のシートにはフィルタ適用不可」)ERROR
9複数シートで一括適用中の例外ループ内で個別 try-catch1 シート失敗で他シートに影響しない・最後にサマリレポートWARN
10getDataRange() が空dataRange.getNumRows() === 0スキップ + {success: false, reason: 'no_data'}

実データ検証

実装完了後に以下を GAS 実行で確認し、期待挙動と一致することを仕様書完成の必須条件とする。

1. 32_wrk_invoice (典型的 WRK タブ・数千行想定)

入力: 32_wrk_invoice (3,000 行・確認FLG = TRUE が 80%、FALSE が 20%)

プリセット期待行数確認方法
UNPROCESSED (未処理のみ)約 600 行getRange().createFilter().setColumnFilterCriteria() 適用後に sheet.getFilter().getRange().getValues() で検証
CURRENT_MONTH (当月発生)月により変動 (例: 4 月分 200 行)発生日が 2026-04-01〜2026-04-30 の行のみ表示
ALL (全表示)3,000 行フィルタ削除後の表示行数

2. 21_bud_pipeline で「承認待ち」適用

プリセット期待挙動
PENDING_APPROVAL確認FLG = FALSE の行のみ表示 (仮計画行)
ALL全行表示

3. プリセット切替パフォーマンス

「未処理のみ」 → 「承認待ち」を 5 シート一括切替 → 1 秒以内に応答完了することを基準とする (3,000 行 × 5 シート想定)。

4. 起動時自動復元

セッション 1 で「未処理のみ」を選択 → ブラウザ再起動 → セッション 2 でスプレッドシート開く → onOpen()restoreLastPreset() 実行 → 「未処理のみ」が自動適用されていること。

関連ドキュメント

  • MAS-131: dev_mas-131_conditional_formatting.md — 条件付き書式 (行積み上がり対策 B)・並列運用
  • failure_patterns: docs/_internal/failure_patterns.md — #18-20 (命名造語禁止) / #21 (getLastColumn()) / #25 (並列実装対称性) / #26 (oauthScopes) / #31 (採番衝突)
  • dev_spec_prompt_template.md v1.10: 仕様書作成標準テンプレート
  • CLAUDE.md: ファイル番号体系・コーディング規約
  • PRD: プロダクトポリシー・Human-in-the-Loop
  • DDL setupAllSchemas (100_config/101_sys_config.js): シート定義・列名

人間が検討すべき事項

実装着手前に決定が必要な論点を列挙。

  1. プリセット定義の 03_sys_params 外出し時期: v1 ハードコード → v2 動的定義 (税理士・経理担当が UI から追加可能) の移行タイミング
  2. 個別ユーザー向け FilterView API サポート可否: GAS API 制約あり (共有フィルタのみ)・将来 Apps Script Advanced Service 拡張で個別 FilterView 制御可能になった際の再評価
  3. プリセット数の上限・カテゴリ分け: 10 種以上に達した場合の UX (グループ化・検索・お気に入り)
  4. メニュー配置: 案 A (🔧 開発・設定 既存カテゴリ追加・推奨) vs 案 B (新規カテゴリ 🔍 表示フィルター 作成)
  5. プリセット復元タイミング: onOpen() 自動 / 手動切替時のみ / 初回開時のみ — UX テストで確定
  6. 一括適用 vs シート個別適用: 「未処理のみ」を 5 シート同時適用 vs 1 シートずつ — UI ラジオボタンで選択可能にする等
  7. 複合プリセット: 「未処理 + 当月」のような AND 条件 — GAS Filter API 制約 (列ごとに 1 条件) を考慮
  8. 操作ログの粒度: Utils.persistLog で各プリセット適用を記録するか・パフォーマンス影響と監査価値のバランス
  9. 行追加時の自動拡張: onEdit で行追加検知 → フィルタ範囲拡張するか (パフォーマンス影響大・v2 候補)
  10. MAS-131 (条件付き書式) との連動運用: 「フィルタ + 視覚区別」のセット運用ガイドラインを docs/ops/ 配下に新設するか

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

Claude Sonnet 4.6 推奨。GAS Filter API パターン適用 + 既存 301_ui_assist.js / 302_spa_bridge.js の構造踏襲で純粋関数中心の実装。

## 案件
MAS-130 — フィルタービュープリセット配布(行積み上がり対策 A)

## 事前調査(必ず Read する)
1. `100_config/101_sys_config.js` (L1044-1100): WRK 系 + 予算系の DDL ヘッダー (確認FLG / 処理結果 / 発生日 等の列名)
2. `300_ui/301_ui_assist.js`: IIFE + グローバル変数宣言パターンの既存構造
3. `300_ui/302_spa_bridge.js`: 同上
4. `000_infra/002_constants.js` の `MENU_DEFINITION`: 既存メニュー構造
5. `000_infra/004_utils.js`: `getSheetByKey` / `persistLog` シグネチャ
6. GAS 公式ドキュメント: `Sheet.getFilter()` / `setFilter()` / `removeFilter()` / `FilterCriteria` API

## 実装対象
1. `300_ui/303_filter_presets.js` 新設:
   - `FilterPresets` IIFE 名前空間
   - `PRESETS` 定数定義 (5-7 種)
   - `applyFilterPreset(presetKey, sheetKey)` 公開 API
   - `clearFilter(sheetKey)` 公開 API
   - `restoreLastPreset()` 公開 API
   - `_buildHeaderIndex_(headers)` private ヘルパ (or `buildHeaderIndex_` 既存を再利用)
2. `000_infra/002_constants.js` 編集:
   - `MENU_DEFINITION` の `🔧 開発・設定` カテゴリに 1 行追加
3. `100_config/101_sys_config.js` 編集 (任意):
   - `onOpen(e)` 末尾に `FilterPresets.restoreLastPreset()` 呼出 + try-catch
4. `900_test/901_test_runner.js` 編集:
   - `runMAS130Tests()` でエッジケース 10 件のテストケース追加
5. `docs/_config.json`:
   - nav §E.2 に E.2.22 追加

## デプロイ手順
- dev 環境で `npm run push:dev` → `applyFilterPreset` 単体テスト → メニュー実行 → `runMAS130Tests` 実行
- 問題なければ `npm run push:prod`
- コミットメッセージ: `feat(MAS-130): フィルタービュープリセット 5 種を 300_ui/303_filter_presets.js で実装`

## failure_patterns チェック
- #18-#20: 関数名・名前空間は GAS API 標準命名と整合 (`applyFilterPreset` / `getFilter` 等)
- #21: フィルタ範囲は `getDataRange()` を使い列範囲ハードコード禁止
- #25: 既存 `301/302_*.js` と命名・構造対称性
- #26: `appsscript.json` を一切編集しない (cloud-platform 既存スコープのみ使用)
- #31: 採番衝突対策で `302 → 303` に変更済 (本 spec で根拠記録)

推奨実行モデル

Phase推奨モデル根拠
ドメイン実装 (303_filter_presets.js 200 行)Claude Sonnet 4.6Filter API パターン適用・既存 IIFE 構造踏襲・判断要素少
メニュー統合 + onOpen 改修Claude Sonnet 4.6既存 MENU_DEFINITION 拡張・パターン化
単体テスト (901_test_runner.js への追加)Claude Haiku 4.5期待値テーブル既定義・パターン化
仕様書レビュー (scripts/4_review_specs_by_gemini.js)Gemini 3 Pro Preview + Deep Think第三者視点での GAS API 制約検証

変更履歴

日時バージョン変更内容
2026-04-30v0.1 (仕様書完了)初版作成。tasks/prompts/task_MAS-130.md (PR #435・手動骨格 221 行) + tasks/prompts/task_MAS-130.gemini.md (PR #436・Gemini 3 Pro Preview Deep Think 79 行) を統合 input として Claude Opus 4.7 (1M context) で本体起草。14 セクション全網羅: 概要 + 目的 + 現在のコード (利用 API 5 件 + DDL 列構造表) + 修正方針 5 Step (名前空間新設 / プリセット 5-7 種定義 / applyFilterPreset 公開 API / メニュー統合 / onOpen 自動復元) + 影響範囲 (新規 1 / 変更 4 / 変更なし 2) + 注意事項 12 件 (#18-20/#21/#25/#26/#31 + DDL 列名依存 + 共有制約等) + エッジケース 10 件 + 実データ検証 4 ケース + 関連ドキュメント 6 件 + 人間検討事項 10 件 + 実装プロンプト (Sonnet 推奨) + 推奨実行モデル + 変更履歴。採番衝突対応: 元 TODO の 300_ui/302_filter_presets.js は 302 が spa_bridge.js で占有済 → 303 に変更し failure_patterns #31 防止事例として注意事項に記録。実装着手可能状態として v0.1 で起票。

仕様書作成プロンプト

task_MAS-130.md (手動骨格・PR #435) を展開して表示

本仕様書の作成指示プロンプト全文は tasks/prompts/task_MAS-130.md を参照。Gemini Deep Think 比較資料は tasks/prompts/task_MAS-130.gemini.md (PR #436) を参照。パイプラインの再現性確保のため、仕様書末尾への転載は省略し、Git 履歴上の元ファイルを参照する方針とする。

関連コマンド:

# Gemini Deep Think で代替案再生成する場合
TARGET_ID=MAS-130 node scripts/gen_gemini_alt_oneshot.js

# Gemini レビュー実行
SPEC_FILES=docs/dev/dev_mas-130_filter_view_presets.md node scripts/4_review_specs_by_gemini.js