MAS-055: SaaS カタログマスタ(MAS-044 から派生・MAS-011 SAAS_ADD 補完基盤)
概要
| 項目 | 内容 |
|---|---|
| 案件 ID | MAS-055 |
| カテゴリ | FP&A・シミュレーション(基盤整備) |
| 実装ステータス | 📝 仕様書段階・実装未着手 (2026-04-28 監査時点) |
| 対象ファイル(変更あり) | 100_config/101_sys_config.js(schemas に MST_SAAS_TMPL 追加 + existKeys + setVali)200_data/202_repository.js(末尾に SaasTemplateRepository 追記)400_domain/430_what_if_simulator.js(getWhatIfDefaults に 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 の PositionTemplateRepository(AccountRepository パターン準拠)と完全同型の 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_subscription(BUD_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.js の getWhatIfDefaults に 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 の
onchangeでgoogle.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を新設)
運用・デプロイ手順
- MAS-044 実装完了後に着手(Repository パターンを流用)
npm run push:dev→ サイドバー🔧 開発・設定 → DDL 全更新 (Full)→19_tmpl_saas_catalog生成initConfigs実行 →01_sys_configにMST_SAAS_TMPL登録- 既存契約中 SaaS を手動入力 or MCP で
19_tmpl_saas_catalogに投入(ChatGPT Team / GitHub Team 等) - What-if サイドバー起動 → SAAS_ADD 選択 → dropdown が有効化されたことを確認
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 が対称構造になっていることを実装時に確認(
getHcTemplateListvsgetSaasTemplateList、dropdown 配置、エッジケース処理等) - ⚠️ シート番号衝突監査:
19_*が既存他案件で予約されていないことをgrep -rn "19_" docs/で確認済。MAS-046 投資カタログマスタは P2.5 で競合の可能性あり → MAS-055 を先行実装、または MAS-046 側で別番号採用 - ⚠️ Human-in-the-Loop: テンプレート選択は「一括入力」であり確定ではない。選択後もユーザーは各フィールドを手動修正可能
- ⚠️ 契約情報の機密性: 契約単価はベンダーとの交渉結果のため機密情報となる場合がある。
19_tmpl_saas_catalogシートのアクセス権限を経理・経営者のみに制限する運用検討(MAS-200 個人情報保護連携)
エッジケース
| # | 条件 | 検知方法 | 期待される挙動 | ログ出力 |
|---|---|---|---|---|
| 1 | 19_tmpl_saas_catalog シート未作成(DDL 未実行) | Utils.getSheetByKey('MST_SAAS_TMPL', ...) が null | getSaasTemplateList が空配列返却 → サイドバー 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 === false | String(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 | 想定利用者数が 0 | estimatedUsers = 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_withSaasTemplate | templateId 指定時に SaaS テンプレ値が優先される |
test_getSaasTemplateList | 有効行のみが配列で返却される |
実データ検証
実装前に MCP で以下を確認:
19_tmpl_saas_catalogシートの有無(DDL 実行前は存在しない)23_bud_subscriptionの現行ヘッダー構成(BUD_SUBSschema と一致確認)11_mst_accountの費用科目一覧(SaaS カタログの費用科目列で使用する選択肢)01_sys_configにMST_SAAS_TMPLキーが未登録であること- 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.md | SaaS 契約単価は機密性があるため、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 データ層基盤の補完強化 |
人間が検討すべき事項
- マスタ更新頻度: 契約改定のたび手動更新 or
23_bud_subscriptionから自動生成するスクリプトを用意するか。判断基準: 契約数が 20 件超になった時点で自動生成機能を追加 - 利用者数列を持つか: 現案は
想定利用者数列あり(シート ライセンス課金モデル向け)。利用者数が変動する SaaS(従量課金)では別設計が必要か - ベンダー名正規化ルール: 「Anthropic」「Anthropic, PBC」「アンソロピック」の表記揺れを防ぐため、
12_mst_partnerのUI用取引先名を参照する設計を検討(Phase 2) - カテゴリ軸の追加: 現状はフラットな一覧だが、将来的に「開発ツール / コミュニケーション / データ / 請求・会計」等のカテゴリ列を追加するか
- 契約更新日の管理: 現案は
23_bud_subscriptionの次回更新・終了年月に依存。テンプレートマスタ側にも含める or 契約情報と分離するか - マスタ未登録時のフォールバック: 現状どおり手入力可(MAS-011 MVP 挙動を維持)。テンプレ選択は任意機能
- MAS-044 との並行実装: MAS-044 が先行実装の想定だが、MAS-055 を独立に着手する場合の Repository パターン流用元の確認
- 機密性の扱い: 契約単価は機密情報のため、
19_tmpl_saas_catalogシートに読み取り権限制限を付けるか(MAS-200 連携) - 複数契約プランの表現: ChatGPT Team の月額 vs 年額契約等、同一 SaaS で複数プランがある場合の管理 ID 採番ルール
- 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.5 | MAS-044 パターンの単純複製、判断要素なし |
| Step 2: Repository 新設 | Claude Sonnet 4.6 | MAS-044 PositionTemplateRepository の同型複製だがプロパティマッピングの判断が必要 |
Step 3: getWhatIfDefaults 拡張 | Claude Sonnet 4.6 | MAS-044 実装への非侵襲追加(SAAS_ADD 分岐)の判断 |
| Step 4: サイドバー UI 拡張 | Claude Sonnet 4.6 | HTML + JavaScript + google.script.run 連携、MAS-044 パターン流用 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-23 | 初版作成。MAS-044(ポジション別 / SaaS 別テンプレートマスタ)から SaaS カタログ部分を切り出して独立起票。main スペースの採用判断機能群への注力方針を反映し、MAS-044 を HC テンプレ限定に縮小、本案件が SaaS カタログマスタを担当する形に分割。19_tmpl_saas_catalog(MST_SAAS_TMPL / DDL 管理)+ SaasTemplateRepository(PositionTemplateRepository と同型)+ getWhatIfDefaults SAAS_ADD 分岐 + サイドバー dropdown の 4 Step 構成。MAS-044 と並列実装対称性(failure_patterns #25)を維持。エッジケース 12 件・人間検討事項 10 件・推奨実行モデル 4 工程(Haiku×1 / Sonnet×3)を定義 |