概要

項目内容
案件 IDMAS-055
カテゴリFP&A・シミュレーション(基盤整備)
実装ステータス📝 仕様書段階・実装未着手 (2026-04-28 監査時点)
対象ファイル(変更あり)100_config/101_sys_config.jsschemasMST_SAAS_TMPL 追加 + existKeys + setVali
200_data/202_repository.js(末尾に SaasTemplateRepository 追記)
400_domain/430_what_if_simulator.jsgetWhatIfDefaults に SaaS テンプレ参照ロジック追加)
templates/what_if_sidebar.html(SAAS_ADD ブロックにテンプレート選択 dropdown 追加・onChange で全フィールド自動入力)
新規シート19_tmpl_saas_catalog(SaaS カタログ・DDL 管理)
前提案件MAS-011 MVP(PR #315 + #320 polish、完了済)
姉妹案件MAS-044(HC テンプレートマスタ・2026-04-23 に本案件と分離独立)

MAS-011 What-if シミュレーションで「SaaS 追加(SAAS_ADD)」を検討する際、毎回手入力していた月額費用・決済ラグ・想定利用者数等を サービス単位でカタログ化 する。MAS-011 polish で追加された「実績平均プリロード」(23_bud_subscription の有効行平均から自動補完)を補完する位置付け — 実績ゼロ期(新規 SaaS 導入検討時)でもデフォルト値が出る状態を実現する。

SaaS 例: ChatGPT Team / Notion Plus / GitHub Team / AWS 基本枠 / Slack Pro / Figma / Zoom 等。

2026-04-23 独立起票の経緯

MAS-044(ポジション別 / SaaS 別テンプレートマスタ)で HC + SaaS を一括対象としていたが、main スペースの採用判断機能群への注力方針を反映し、SaaS 部分を本案件として切り出し独立起票。MAS-044 は HC テンプレに限定された。実装順序は MAS-044 を先行、MAS-055 は後続(両者のパターンは同型のため、MAS-044 完成後に流用する形で着手する)。

目的

  • 毎回手入力する手間を削減(複数 SaaS 導入パターンを比較する時に効果大)
  • 契約中 SaaS の一元管理(契約情報 = マスタ化することで棚卸し・コスト見直しを容易化)
  • 実績ゼロ時のフォールバック(新規 SaaS 導入検討時でもベンダー標準月額・決済ラグのデフォルト値を提供)
  • MAS-011 SAAS_ADD シミュレーションの精度向上(ユーザー入力ミスによる計算結果のブレを低減)

現在のコード

100_config/101_sys_config.js(DDL スキーマ)

MAS-044 と同じく setupAllSchemas()schemas 配列に 'MST_SAAS_TMPL' を追加。existKeys への MST_SAAS_TMPL 登録も同時に行う。

200_data/202_repository.js(Repository パターン)

MAS-044 の PositionTemplateRepositoryAccountRepository パターン準拠)と完全同型の SaasTemplateRepository を実装。readSheetAsDtos_(sheet) を流用し、findAsMap() では有効フラグスキップ + 重複キー先着採用 + _cache キャッシュ。

400_domain/430_what_if_simulator.js(MAS-011 MVP 実装)

getWhatIfDefaults(driver, templateId) は MAS-044 で driver === 'HC_ADD' 分岐が実装済の想定。本案件では driver === 'SAAS_ADD' 分岐を追加し、SaasTemplateRepository.findAsMap() から取得した値を返す。

templates/what_if_sidebar.html(MAS-011 MVP 実装)

MAS-044 で HC_ADD ブロックに dropdown が追加されている想定。本案件は SAAS_ADD ブロックに同型の dropdown を追加する。

23_bud_subscriptionBUD_SUBS schema L956、24 列)

既存ヘッダー 24 列。本案件は サービス・ツール名 / 費用科目 / 契約形態 / 税抜金額_計画 / 決済ラグ(月) / 取引先名 等を参照して既存契約の実績平均を算出し、シミュレーション入力のプリロードに使う(MAS-011 polish 既実装)。MAS-055 テンプレ値がプリロードを上書きする優先度構造。

修正方針

Step 1: DDL 追加(101_sys_config.js

schemas 配列に 1 エントリ追加:

'MST_SAAS_TMPL': {
  headers: ["有効フラグ", "管理ID", "サービス名", "費用科目", "標準月額", "決済ラグ(月)", "想定利用者数", "ベンダー", "備考"],
  color: "#674ea7",  // BUD_SUBS と同系統色
  validations: {
    "決済ラグ(月)": { type: 'range', min: -1, max: 12 },
    "標準月額": { type: 'range', min: 0, max: 1000000 },
    "想定利用者数": { type: 'range', min: 0, max: 1000 }
  }
},

existKeys 追加(L830 付近):

if (!existKeys.includes('MST_SAAS_TMPL')) confSheet.appendRow(['MST_SAAS_TMPL', '', '19_tmpl_saas_catalog', 'SaaS カタログ']);

setVali プルダウン設定(L1491 付近):

  • MST_SAAS_TMPL費用科目 列: 11_mst_account の科目名プルダウン参照
  • MST_SAAS_TMPLサービス名 列 / ベンダー 列: フリーテキスト

Step 2: Repository 新設(202_repository.js 末尾)

MAS-044 の PositionTemplateRepository と完全同型:

// =====================================================================
// SaasTemplateRepository — 19_tmpl_saas_catalog (読み取り専用マスタ)
// =====================================================================

var SaasTemplateRepository = {
  _getSheet: function() {
    return Utils.getSheetByKey('MST_SAAS_TMPL', '19_tmpl_saas_catalog');
  },
  findAll: function() {
    return readSheetAsDtos_(SaasTemplateRepository._getSheet());
  },
  findAsMap: function() {
    if (SaasTemplateRepository._cache) return SaasTemplateRepository._cache;
    var result = SaasTemplateRepository.findAll();
    var map = {};
    for (var i = 0; i < result.dtos.length; i++) {
      var dto = result.dtos[i];
      var flag = dto['有効フラグ'];
      if (flag === false || String(flag).toUpperCase() === 'FALSE') continue;
      var id = String(dto['管理ID'] || '').trim();
      if (!id || map[id]) continue;  // 重複は先着採用
      map[id] = {
        serviceName: String(dto['サービス名'] || '').trim(),
        accountName: String(dto['費用科目'] || '').trim(),
        monthlyAmount: Number(dto['標準月額']) || 0,
        paymentLagMonths: Number(dto['決済ラグ(月)']) || 1,
        estimatedUsers: Number(dto['想定利用者数']) || 1,
        vendor: String(dto['ベンダー'] || '').trim(),
        note: String(dto['備考'] || '').trim(),
      };
    }
    SaasTemplateRepository._cache = map;
    return map;
  },
  _cache: null,
  resetCache: function() { SaasTemplateRepository._cache = null; },
};

Step 3: 430_what_if_simulator.jsgetWhatIfDefaults に SAAS_ADD 分岐追加

MAS-044 で実装済の getWhatIfDefaults(driver, templateId) に SAAS_ADD 分岐を追加:

function getWhatIfDefaults(driver, templateId) {
  if (driver === 'HC_ADD' && templateId) {
    // F-44 で実装済の HC_ADD 分岐
  } else if (driver === 'SAAS_ADD' && templateId) {
    // F-55 で追加する SAAS_ADD 分岐
    var tmpl = SaasTemplateRepository.findAsMap()[templateId];
    if (tmpl) {
      return {
        serviceName: tmpl.serviceName,
        accountName: tmpl.accountName,
        monthlyAmount: tmpl.monthlyAmount,
        paymentLagMonths: tmpl.paymentLagMonths,
        estimatedUsers: tmpl.estimatedUsers,
        vendor: tmpl.vendor,
        source: 'TEMPLATE',
        templateName: tmpl.serviceName,
      };
    }
  }
  // (既存 F-11 polish の実績平均プリロードロジックにフォールバック)
}

新規関数 getSaasTemplateList() を公開:

function getSaasTemplateList() {
  var map = SaasTemplateRepository.findAsMap();
  return Object.keys(map).map(function(id) {
    return { id: id, name: map[id].serviceName };
  });
}

Step 4: サイドバー UI 拡張(what_if_sidebar.html

SAAS_ADD ドライバーフォーム冒頭に dropdown を挿入(MAS-044 の HC_ADD 実装を完全同型で流用):

<div class="driver-form" id="form-saas-add">
  <label for="saas-template">SaaS カタログ(任意・選択すると自動入力)</label>
  <select id="saas-template">
    <option value="">-- 手動入力 --</option>
    <!-- onload で getSaasTemplateList() の結果を動的挿入 -->
  </select>
  <!-- 既存フォーム項目 -->
</div>

JavaScript:

  • ページロード時に google.script.run.getSaasTemplateList()<option> を動的挿入
  • dropdown の onchangegoogle.script.run.getWhatIfDefaults('SAAS_ADD', templateId) を呼び、返り値で全フィールドを更新
  • source: 'TEMPLATE' のときはフォーム上部に「📋 テンプレート「{{templateName}}」を適用」のヒント表示

影響範囲

ファイル変更種別内容
100_config/101_sys_config.js追加のみschemas に 1 エントリ / existKeys に 1 行 / setVali に 1-2 行
200_data/202_repository.js追加のみ末尾に SaasTemplateRepository(約 40 行)
400_domain/430_what_if_simulator.js変更getWhatIfDefaults に SAAS_ADD 分岐追加 + getSaasTemplateList 新設(約 20 行)
templates/what_if_sidebar.html変更SAAS_ADD ブロックに dropdown 追加 + JS 修正(約 30 行)
docs/_config.json追加のみnav に仕様書登録

既存動作への影響

  • MAS-011 MVP 動作(#315 / #320): templateId 未指定時は既存の実績平均プリロードにフォールバック → 完全後方互換
  • MAS-044 HC テンプレ: 本案件とは完全独立。共存 OK
  • 23_bud_subscription: 変更なし(本案件は別シート 19_tmpl_saas_catalog を新設)

運用・デプロイ手順

  1. MAS-044 実装完了後に着手(Repository パターンを流用)
  2. npm run push:dev → サイドバー 🔧 開発・設定 → DDL 全更新 (Full)19_tmpl_saas_catalog 生成
  3. initConfigs 実行 → 01_sys_configMST_SAAS_TMPL 登録
  4. 既存契約中 SaaS を手動入力 or MCP で 19_tmpl_saas_catalog に投入(ChatGPT Team / GitHub Team 等)
  5. What-if サイドバー起動 → SAAS_ADD 選択 → dropdown が有効化されたことを確認
  6. npm run push:prod → 同手順

注意事項

  • ⚠️ failure_patterns #18-#20(命名造語禁止): システムキー MST_SAAS_TMPL・シート名 19_tmpl_saas_catalog・関数名 getSaasTemplateList・Repository 名 SaasTemplateRepository を記述前に必ず Read で実在確認 or 新設予定として明示
  • ⚠️ failure_patterns #3(DDL コード値 vs 実データ乖離): 費用科目 プルダウンは 11_mst_account の科目名を参照するが、新規 SaaS 導入時に科目マスタに存在しない科目を選択できないため、先に 11_mst_account 拡張が必要な場合がある
  • ⚠️ failure_patterns #25(並列実装対称性): MAS-044 の HC_ADD / MAS-055 の SAAS_ADD が対称構造になっていることを実装時に確認(getHcTemplateList vs getSaasTemplateList、dropdown 配置、エッジケース処理等)
  • ⚠️ シート番号衝突監査: 19_* が既存他案件で予約されていないことを grep -rn "19_" docs/ で確認済。MAS-046 投資カタログマスタは P2.5 で競合の可能性あり → MAS-055 を先行実装、または MAS-046 側で別番号採用
  • ⚠️ Human-in-the-Loop: テンプレート選択は「一括入力」であり確定ではない。選択後もユーザーは各フィールドを手動修正可能
  • ⚠️ 契約情報の機密性: 契約単価はベンダーとの交渉結果のため機密情報となる場合がある。19_tmpl_saas_catalog シートのアクセス権限を経理・経営者のみに制限する運用検討(MAS-200 個人情報保護連携)

エッジケース

#条件検知方法期待される挙動ログ出力
119_tmpl_saas_catalog シート未作成(DDL 未実行)Utils.getSheetByKey('MST_SAAS_TMPL', ...)nullgetSaasTemplateList が空配列返却 → サイドバー dropdown に「-- 手動入力 --」のみ表示Utils.logInfo('getSaasTemplateList', 'MST_SAAS_TMPL 未作成')
2マスタ空(ヘッダー行のみ)findAsMap(){} 返却空配列返却(dropdown 選択肢なし)ログ出力なし(正常系)
3管理 ID が空欄`id = String(dto['管理ID']'').trim()` が空
4同一管理 ID の重複map[id] 既存検知先着行採用Utils.persistLog('WARN', 'findAsMap', '重複ID: ' + id, '先着採用')
5有効フラグ FALSE`flag === falseString(flag).toUpperCase() === 'FALSE'`
6標準月額が負値Number(dto[...]) が負数そのまま格納(計算時にシミュレーター側でエラー or 警告)格納時はログなし、使用時に警告
7決済ラグ(月) が範囲外(-1〜12 超)バリデーション側でブロックDDL 未適用時はシミュレーター側で Math.max(-1, Math.min(12, lag)) で防御Utils.persistLog('WARN', ...)
8費用科目が 11_mst_account に存在しないサイドバー UI 側で検知不可(マスタ参照プルダウン未設定時)シミュレーター側で未登録科目をログ出力 + 計算続行Utils.persistLog('WARN', 'getWhatIfDefaults', '未登録科目: ' + accountName)
9想定利用者数が 0estimatedUsers = 0利用者あたり単価の計算でゼロ除算 → シミュレーター側で防御(Math.max(1, users)ログ出力なし
10テンプレート適用後にユーザーが手動で値を変更フォーム側の input / change イベントsource: 'TEMPLATE' ヒントを非表示化し source: 'MANUAL' に切替ログ出力なし(UX の一部)
11サービス名に絵文字・全角記号プルダウン表示そのまま表示(サニタイズ不要)。管理 ID で参照するため表示崩れでも動作影響なしログ出力なし
12契約終了 SaaS の有効フラグ FALSE 運用findAsMap でスキップ履歴として残すが dropdown には表示されないログ出力なし(正常系)

冪等性・再実行の設計

本案件は読み取り専用マスタ + UI 拡張。シート書き込みは setupAllSchemas 実行時のみ。サイドバー起動時の getSaasTemplateList / getWhatIfDefaults は副作用なし(読み取りのみ)で何度呼んでも安全。

Human-in-the-Loop

テンプレート選択は一括入力の補助であり確定ではない。ユーザーは選択後にすべてのフィールドを自由に修正可能。MAS-045(予算転記)実行時点で最終確定する位置付け。

テスト要件(900_test/901_test_runner.js への追加)

テスト関数合格基準
test_SaasTemplateRepository_findAsMap_emptyMaster空マスタで {} 返却
test_SaasTemplateRepository_findAsMap_disabledFlag有効フラグ=FALSE 行が除外される
test_SaasTemplateRepository_findAsMap_duplicateId重複 ID で先着行採用
test_getWhatIfDefaults_withSaasTemplatetemplateId 指定時に SaaS テンプレ値が優先される
test_getSaasTemplateList有効行のみが配列で返却される

実データ検証

実装前に MCP で以下を確認:

  1. 19_tmpl_saas_catalog シートの有無(DDL 実行前は存在しない)
  2. 23_bud_subscription の現行ヘッダー構成BUD_SUBS schema と一致確認)
  3. 11_mst_account の費用科目一覧(SaaS カタログの 費用科目 列で使用する選択肢)
  4. 01_sys_configMST_SAAS_TMPL キーが未登録であること
  5. MAS-044 の HC_ADD dropdown 実装状況(MAS-044 完成後の実装を参照してパターン流用)

関連ドキュメント

仕様書・ドキュメント関連箇所
dev_mas-011_what_if_simulation.md本案件の UI 拡張対象(MAS-011 MVP 完成済、polish で実績平均プリロード実装済)。MAS-055 は新規 SaaS 導入時の補完として機能
dev_mas-044_hc_template_master.md本案件の姉妹案件。HC 側のテンプレートマスタを担当。2026-04-23 に MAS-044 から MAS-055 が独立起票された
dev_mas-045_whatif_to_budget_transfer.md後続案件。MAS-045 の予算転記行に templateId 列を追加する拡張余地あり
dev_mas-200_personal_info_protection.mdSaaS 契約単価は機密性があるため、19_tmpl_saas_catalog のアクセス権限制限を連携運用
CLAUDE.mdコーディング規約(ヘッダー名ベース列参照・有効フラグスキップ・キャッシュ付きマスタ)
failure_patterns.md#18-#20 命名造語禁止 / #25 並列実装対称性 / #3 DDL コード値 vs 実データ乖離
dev_mas-046_investment_catalog_master.md同パターンマスタの兄弟案件。投資カタログマスタ (Investment Catalog) は本仕様 (SaaS Catalog) と同パターン実装で実装コスト削減・テストパターン共通化・FP&A データ層基盤の補完強化

人間が検討すべき事項

  1. マスタ更新頻度: 契約改定のたび手動更新 or 23_bud_subscription から自動生成するスクリプトを用意するか。判断基準: 契約数が 20 件超になった時点で自動生成機能を追加
  2. 利用者数列を持つか: 現案は 想定利用者数 列あり(シート ライセンス課金モデル向け)。利用者数が変動する SaaS(従量課金)では別設計が必要か
  3. ベンダー名正規化ルール: 「Anthropic」「Anthropic, PBC」「アンソロピック」の表記揺れを防ぐため、12_mst_partnerUI用取引先名 を参照する設計を検討(Phase 2)
  4. カテゴリ軸の追加: 現状はフラットな一覧だが、将来的に「開発ツール / コミュニケーション / データ / 請求・会計」等のカテゴリ列を追加するか
  5. 契約更新日の管理: 現案は 23_bud_subscription次回更新・終了年月 に依存。テンプレートマスタ側にも含める or 契約情報と分離するか
  6. マスタ未登録時のフォールバック: 現状どおり手入力可(MAS-011 MVP 挙動を維持)。テンプレ選択は任意機能
  7. MAS-044 との並行実装: MAS-044 が先行実装の想定だが、MAS-055 を独立に着手する場合の Repository パターン流用元の確認
  8. 機密性の扱い: 契約単価は機密情報のため、19_tmpl_saas_catalog シートに読み取り権限制限を付けるか(MAS-200 連携)
  9. 複数契約プランの表現: ChatGPT Team の月額 vs 年額契約等、同一 SaaS で複数プランがある場合の管理 ID 採番ルール
  10. MAS-044 との運用統合: HC と SaaS のテンプレートを同じ UI から探索できるよう、MAS-044 / MAS-055 双方完成後に統合ダッシュボード(📋 全テンプレート一覧)を検討

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

あなたは GAS 会計システム (bizlp-gas-accounting) のシニア開発者です。
案件 MAS-055「SaaS カタログマスタ」を以下の 4 Step で実装してください。MAS-044(HC テンプレートマスタ)が実装済の前提。

## 実行前タスク(必須・4 件)
1. `200_data/202_repository.js` の MAS-044 で実装された `PositionTemplateRepository` を Read し、流用元パターンを確認
2. `100_config/101_sys_config.js` の `schemas`(L879 起点)で `MST_HC_TMPL`(MAS-044 で追加済)の記述位置を確認
3. `400_domain/430_what_if_simulator.js` の MAS-044 で拡張された `getWhatIfDefaults` の HC_ADD 分岐を確認
4. `templates/what_if_sidebar.html` の MAS-044 で追加された HC_ADD dropdown 実装を確認

## Step 1: DDL 追加(`101_sys_config.js`、推奨モデル: Haiku)

- `schemas` に `MST_SAAS_TMPL` エントリ追加(MAS-044 の `MST_HC_TMPL` の直下に配置推奨)
- `existKeys` に 1 行追加
- `setVali` で `費用科目` → `11_mst_account` 科目名プルダウンを設定
- `npm run push:dev` → `setupAllSchemas` 実行 → `19_tmpl_saas_catalog` 生成確認

## Step 2: Repository 新設(`202_repository.js`、推奨モデル: Sonnet)

- 末尾に `SaasTemplateRepository` を追加(MAS-044 の `PositionTemplateRepository` 完全同型)
- `findAsMap()` キャッシュ + 有効フラグスキップ + 重複キー先着採用

## Step 3: `430_what_if_simulator.js` に SAAS_ADD 分岐追加(推奨モデル: Sonnet)

- `getWhatIfDefaults(driver, templateId)` の `else if (driver === 'SAAS_ADD' && templateId)` 分岐追加
- `SaasTemplateRepository.findAsMap()[templateId]` から取得
- 新規公開関数 `getSaasTemplateList()`

## Step 4: サイドバー UI 拡張(`what_if_sidebar.html`、推奨モデル: Sonnet)

- SAAS_ADD ドライバーフォーム冒頭に dropdown 追加(MAS-044 HC_ADD の UI パターン流用)
- ページロード時 `google.script.run.getSaasTemplateList()` で選択肢挿入
- dropdown `onchange` で `getWhatIfDefaults('SAAS_ADD', templateId)` 呼出

## 制約
- `appsscript.json` の `oauthScopes` を変更しない
- 列番号ハードコード禁止
- MAS-044 との並列実装対称性を維持(failure_patterns #25)
- MAS-011 MVP の既存動作を破壊しない(`templateId` 未指定時は実績平均プリロードにフォールバック)

## 動作確認
1. サイドバー起動 → SAAS_ADD ドライバー選択 → dropdown に登録済 SaaS が表示
2. テンプレート選択 → 全フィールドが自動入力される
3. 「📋 テンプレート「ChatGPT Team」を適用」ヒント表示
4. フィールドを手動変更 → ヒント非表示化
5. `01_sys_config` に `MST_SAAS_TMPL` が登録されている

推奨実行モデル

工程推奨モデル理由
Step 1: DDL 追加Claude Haiku 4.5MAS-044 パターンの単純複製、判断要素なし
Step 2: Repository 新設Claude Sonnet 4.6MAS-044 PositionTemplateRepository の同型複製だがプロパティマッピングの判断が必要
Step 3: getWhatIfDefaults 拡張Claude Sonnet 4.6MAS-044 実装への非侵襲追加(SAAS_ADD 分岐)の判断
Step 4: サイドバー UI 拡張Claude Sonnet 4.6HTML + JavaScript + google.script.run 連携、MAS-044 パターン流用

変更履歴

日付変更内容
2026-04-23初版作成。MAS-044(ポジション別 / SaaS 別テンプレートマスタ)から SaaS カタログ部分を切り出して独立起票。main スペースの採用判断機能群への注力方針を反映し、MAS-044 を HC テンプレ限定に縮小、本案件が SaaS カタログマスタを担当する形に分割。19_tmpl_saas_catalogMST_SAAS_TMPL / DDL 管理)+ SaasTemplateRepositoryPositionTemplateRepository と同型)+ getWhatIfDefaults SAAS_ADD 分岐 + サイドバー dropdown の 4 Step 構成。MAS-044 と並列実装対称性(failure_patterns #25)を維持。エッジケース 12 件・人間検討事項 10 件・推奨実行モデル 4 工程(Haiku×1 / Sonnet×3)を定義