MAS-130: フィルタービュープリセット配布(行積み上がり対策 A)
概要
| 項目 | 内容 |
|---|---|
| 案件 ID | MAS-130 |
| 案件名 | フィルタービュープリセット配布(行積み上がり対策 A) |
| カテゴリ | UX・可視性 |
| 優先度 | P1 ★★★(運用即効・実装規模小・MAS-131 と並列でファーストライン対策) |
| 所要時間 | 約 1 週間(週 10h 前提) |
| 対象ファイル(新規) | 300_ui/303_filter_presets.js(FilterPresets 名前空間・約 200 行) |
| 対象ファイル(変更) | 000_infra/002_constants.js(MENU_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_pipeline 〜 26_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 | = FALSE | 32/33/34/35 (WRK 系) |
CURRENT_MONTH | 当月発生 | 発生日(P/L計上日) | [当月初日, 当月末日] | 32-35 |
THIS_WEEK_DEADLINE | 今週締め分 | 決済日_計画 | [当週月曜, 今週末] | 32 (INV) |
UNMATCHED_STL | 未消込 STL | 処理結果 | = UNMATCHED | 33/34 |
PENDING_APPROVAL | 承認待ち | 確認FLG | = FALSE | 21-26 (予算系) |
ALL | 全表示 | — | (既存フィルタ削除) | 全対象シート |
(将来 v2 候補) PJ_SPECIFIC | PJ別表示 | プロジェクト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.js の MENU_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 影響ゼロ |
注意事項
- #18-#20(命名造語禁止):
applyFilterPreset/clearFilter/restoreLastPreset/FilterPresetsは GAS API 標準命名 (Sheet.getFilter()/removeFilter()等) と整合する命名。プリセットキー (UNPROCESSED等) は SCREAMING_SNAKE_CASE で予約語と衝突しないことを確認。 - #21(
getLastColumn()列範囲膨張): フィルタ範囲はgetDataRange()を使い、列範囲ハードコード禁止。getDataRange()は実際のデータが入った範囲のみ取得するため、空列を含まない。 - #25(並列実装対称性): 既存
300_ui/301_ui_assist.js/302_spa_bridge.jsと命名・構造対称性を保つ (IIFE + グローバル変数宣言パターン)。 - #26(oauthScopes 部分宣言禁止): 確認済 — 本案件は
SpreadsheetApp/PropertiesServiceのみ使用。appsscript.jsonを一切編集しない。 - #31(採番衝突): 元 TODO の
300_ui/302_filter_presets.jsは 302 がspa_bridge.jsで占有済のため、本 spec で303_filter_presets.jsに変更。新規案件起票時の Phase 1-A-pre 番号衝突チェックの再発防止事例として記録。 - DDL 列名依存: ハードコード列名 (
確認FLG/処理結果/発生日(P/L計上日)等) は DDL 変更時に同期更新が必要。列名は spec § 現在のコードに集約し、列番号ハードコードは禁止。 - 複数ユーザー共有制約: GAS の
Sheet.setFilter()はシート単位の共有フィルタのみサポート。各ユーザー個別の FilterView API は GAS から制御不可。本案件は共有フィルタ前提で実装し、個別フィルタが必要な場合は人間が手動で FilterView を作成する旨を運用ガイドに明記。 - 行追加時のフィルタ範囲自動追従:
getDataRange()利用で実現するが、行追加直後にフィルタが自動拡張されない場合があるため、行追加トリガー (onEdit) で再適用する選択肢を将来検討 (v2 候補・人間検討事項)。 - シート保護中のフィルタ操作失敗: 編集権限がないシートで
setFilter()は例外。try-catch でハンドリングしユーザーに「権限不足」を表示。 - ヘッダー名の表記揺れ防止: 例えば
確認FLGvs確認フラグ等の混在を防ぐため、Step 1-4 (DDL 確認) で全シートのヘッダー名を網羅。本案件で扱う列名はsetupAllSchemasで正規化されている前提。 - 永続化の競合: 複数人同時編集時の
DocumentProperties競合は最終書込み優先で OK (UX への影響は最小・各人が再選択すればよい)。 - Disclaimer: 本機能は表示の便宜のためのフィルタリングのみで、データの整合性に影響なし。仕訳データ自体は変更しない。
エッジケース
実装時に必ず以下 10 件を単体テストでカバーする。
| # | 条件 | 検知方法 | 期待される挙動 | ログ出力 |
|---|---|---|---|---|
| 1 | シートが存在しない (DDL 未実行) | Utils.getSheetByKey() が null | エラーログ + {success: false, reason: 'sheet_not_found'} 返却 | Utils.persistLog('WARN', ...) |
| 2 | 対象列がヘッダーに存在しない | headers.indexOf(preset.column) === -1 | WARN ログ + プリセットスキップ | WARN |
| 3 | 既存フィルタあり | sheet.getFilter() !== null | existing.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-catch | 1 シート失敗で他シートに影響しない・最後にサマリレポート | 各 WARN |
| 10 | getDataRange() が空 | 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): シート定義・列名
人間が検討すべき事項
実装着手前に決定が必要な論点を列挙。
- プリセット定義の
03_sys_params外出し時期: v1 ハードコード → v2 動的定義 (税理士・経理担当が UI から追加可能) の移行タイミング - 個別ユーザー向け FilterView API サポート可否: GAS API 制約あり (共有フィルタのみ)・将来 Apps Script Advanced Service 拡張で個別 FilterView 制御可能になった際の再評価
- プリセット数の上限・カテゴリ分け: 10 種以上に達した場合の UX (グループ化・検索・お気に入り)
- メニュー配置: 案 A (
🔧 開発・設定既存カテゴリ追加・推奨) vs 案 B (新規カテゴリ🔍 表示フィルター作成) - プリセット復元タイミング:
onOpen()自動 / 手動切替時のみ / 初回開時のみ — UX テストで確定 - 一括適用 vs シート個別適用: 「未処理のみ」を 5 シート同時適用 vs 1 シートずつ — UI ラジオボタンで選択可能にする等
- 複合プリセット: 「未処理 + 当月」のような AND 条件 — GAS Filter API 制約 (列ごとに 1 条件) を考慮
- 操作ログの粒度:
Utils.persistLogで各プリセット適用を記録するか・パフォーマンス影響と監査価値のバランス - 行追加時の自動拡張:
onEditで行追加検知 → フィルタ範囲拡張するか (パフォーマンス影響大・v2 候補) - 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.6 | Filter 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-30 | v0.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