MAS-023: 月次経営コメント入力機能
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-023 |
| カテゴリ | UX |
| Phase | P3 |
| 優先度 | ★★ |
| 所要時間 | 3-4時間 |
| 対象ファイル | 000_infra/003_contracts.js(DTO定義追記)000_infra/002_constants.js(MENU_DEFINITION 1項目追加)100_config/101_sys_config.js(setupAllSchemas のスキーマ&システムキー登録追記)200_data/202_repository.js(末尾に MonthlyCommentRepository 新設)300_ui/302_monthly_comments.js(新規・サーバー側関数)templates/monthly_comments_dialog.html(新規・モーダルダイアログUI)webapp_client/src/cockpit/MonthlyCommentPanel.tsx(月次財務諸表画面に統合) |
| 前提案件 | なし(独立機能)→ 設計変更後: MAS-358(Cockpit 左ナビ)、MAS-057(月次財務諸表 Cockpit タブ) |
| 関連案件 | MAS-191(NLPコメント自動生成)の前段階データ基盤 |
⚠️ 設計変更 2026-05-11: GAS 操作パネル廃止方針により、当初の実装アプローチ(
templates/monthly_comments_dialog.htmlによる GAS モーダルダイアログ)は破棄。GAS 永続化バックエンド(94_dat_monthly_commentsDDL・MonthlyCommentRepository・302_monthly_comments.js)は変更なし。UI 起動部分のみ変更:
- 旧: GAS モーダルダイアログ(
openMonthlyCommentsDialog()→showModalDialog())。MENU_DEFINITIONの📋 サイドバー: 📊 マート更新カテゴリに 1 項目追加- 新: 月次財務諸表 Cockpit 画面(
FinancialStatementsView)内の行・月セルにコメントボタンを追加 →MonthlyCommentPanel.tsxとしてインライン表示 →google.script.run.saveMonthlyComment(params)で GAS を直呼びtemplates/monthly_comments_dialog.htmlは作成しないMENU_DEFINITIONへの項目追加は不要(GAS メニュー廃止のため)
目的
月次経営コメント(経営者の所感・意思決定の背景・特殊要因の記録)を、専用永続シート 94_dat_monthly_comments に 対象シート名 × 対象年月 × 対象行ラベル の3軸キーで保存する。
これまで 61_pl_monthly / 71_bs_monthly / 81_cf_indirect 等の表示マートにセルノートで記録する運用は、buildBudgetTrendDataMart 等のマート再生成で シートが clearContents → 全行再書き込み されるため、コメントが消失するリスクがあった(CLAUDE.md の「DDL で管理されないタブ」一覧に列挙されている動的シート群)。
本機能は永続シートを介在させることで:
- マート再生成と独立してコメントが残る
- 同一行ラベル × 同一年月の上書き編集が可能
- 将来的に MAS-191(NLP コメント自動生成)の学習データ+上書き UI として再利用できる
を実現する。
現在のコード
月次経営コメントを保存・表示する仕組みは現時点では存在しない。600_report/602_datamart_main.js 等のマート生成関数は実績/計画の数値のみを書き込み、定性情報の記録口を持たない。98_audit_log は操作監査用であり、経営判断のコメント保管には用途が合わない。
修正方針
実装は以下の 4 Step で進める。各 Step の前に必ず対象ファイルを Read して既存パターンを把握する(推測禁止)。
Step 1: 型定義・スキーマ・ID 発番の追加
1-a. 000_infra/003_contracts.js への DTO 追記
ファイル末尾 // 3. DTO ファクトリ セクションの直前(既存 PartnerDTO の後)に以下を追記する:
/**
* 94_dat_monthly_comments — 月次経営コメントレコード
* @typedef {Object} MonthlyCommentDTO
* @property {string} コメントID - "CMT_YYYYMMDD_NNNN"
* @property {string} 対象シート名 - "61_pl_monthly" | "71_bs" | "81_cf_indirect" 等
* @property {string} 対象年月 - "YYYY-MM"
* @property {string} 対象行ラベル - 該当シートのA列ラベル(例: "売上高"・"営業利益")
* @property {string} コメント本文 - 自由記述(複数行可)
* @property {string} 入力者 - Session.getActiveUser().getEmail()
* @property {Date} 最終更新日時
*/
既存 DTO のスタイル(@property {型} 列名 - 説明、コメント先頭は シート番号 + 物理名 + 1行説明)に完全準拠する。
1-b. 100_config/101_sys_config.js の setupAllSchemas 追記
2箇所への追加が必要:
(1) システムキー登録ブロック(L770-823 の confSheet.appendRow 連続部分)末尾に以下を追加する:
if (!existKeys.includes('DAT_COMM')) confSheet.appendRow(['DAT_COMM', '', '94_dat_monthly_comments', '月次経営コメント']);
(2) schemas オブジェクト(L826 から始まるブロック)の末尾に以下のエントリを追加する。color は監査ログと同じ #434343 を流用(情報系・運用系の同列扱い):
'DAT_COMM': { headers: ["コメントID","対象シート名","対象年月","対象行ラベル","コメント本文","入力者","最終更新日時"], color: "#434343" },
既存スキーマ書式(1行 JS オブジェクトリテラル、headers 配列、color HEX、必要なら validations)に揃える。
1-c. 000_infra/002_constants.js の ID_PREFIX_MAP 追記
L93-112 の ID_PREFIX_MAP 配列末尾に以下を追記する(既存エントリの形式に揃える):
{ pattern: '94_dat_monthly_comments', prefix: 'CMT_', digit: 4, isDate: true },
isDate: true を採用することで CMT_YYYYMMDD_NNNN 形式となり、既存の INV_ / STL_ / TRN_ と同様に発番日が ID から判別できる。
1-d. システムキー追加後の DDL 実行手順
npm run push:devで開発用 GAS にデプロイ- dev スプレッドシートで「📋 サイドバー: 🔧 開発・設定 → DDL 全更新 (Full)」(=
setupAllSchemas)を実行 01_sys_configシートにDAT_COMM行が追加されること、94_dat_monthly_commentsシートが生成されヘッダー 7 列が並んでいることを確認npm run push:prodでも同手順を実施
Step 2: Repository の新設
200_data/202_repository.js の末尾(PartnerRepository の閉じ括弧の後)に MonthlyCommentRepository を追加する。実装パターンは JournalRepository(L259-298)と完全同一とし、内部ヘルパー readSheetAsDtos_() / writeDtosToSheet_() / appendDtosToSheet_() をそのまま再利用する(新規ヘルパーは作らない)。
// =====================================================================
// MonthlyCommentRepository — 94_dat_monthly_comments
// =====================================================================
var MonthlyCommentRepository = {
/** @private */
_getSheet: function() {
return Utils.getSheetByKey('DAT_COMM', '94_dat_monthly_comments');
},
/**
* 全月次コメントレコードを DTO 配列で取得する。
* @returns {{ headers: string[], dtos: MonthlyCommentDTO[] }}
*/
findAll: function() {
return readSheetAsDtos_(MonthlyCommentRepository._getSheet());
},
/**
* DTO 配列でシートを全置換する(編集→保存ユースケース)。
* @param {MonthlyCommentDTO[]} dtos
*/
save: function(dtos) {
var sheet = MonthlyCommentRepository._getSheet();
if (!sheet) return;
var headers = sheet.getRange(1, 1, 1, sheet.getMaxColumns()).getValues()[0]
.map(function(h) { return String(h).trim(); });
writeDtosToSheet_(sheet, headers, dtos);
},
/**
* DTO 配列をシート末尾に追記する(新規行のみ追加するユースケース)。
* @param {MonthlyCommentDTO[]} dtos
* @returns {number} 追記した行数
*/
append: function(dtos) {
var sheet = MonthlyCommentRepository._getSheet();
if (!sheet) return 0;
var headers = sheet.getRange(1, 1, 1, sheet.getMaxColumns()).getValues()[0]
.map(function(h) { return String(h).trim(); });
return appendDtosToSheet_(sheet, headers, dtos, 0);
},
};
最終行判定列は コメントID 列(0始まりで 0 列目) を使う(JournalRepository.append の lastRowCol = 0 パターンに揃える)。
Step 3: サーバーサイド関数の追加
新規ファイル 300_ui/302_monthly_comments.js を作成する。300_ui/ 配下には既存ファイルが 301_ui_assist.js のみで、サーバーサイドUI関連の追加先として番号体系上正しい場所となる(CLAUDE.md「GAS ファイル番号体系」§ 300_ui/ 参照)。
実装する関数は以下 3 本:
// ==========================================
// 📝 月次経営コメント入力機能 (F-23)
// ==========================================
/**
* モーダルダイアログを開く(メニュー / サイドバーからのトリガー関数)。
*/
function showMonthlyCommentDialog() {
var html = HtmlService.createHtmlOutputFromFile('templates/monthly_comments_dialog')
.setWidth(720)
.setHeight(640);
SpreadsheetApp.getUi().showModalDialog(html, '📝 月次経営コメント入力');
}
/**
* 指定シート × 年月のコメント一覧を取得する(ダイアログから google.script.run で呼ばれる)。
* @param {string} sheetName - 対象シート名
* @param {string} yearMonth - "YYYY-MM"
* @returns {MonthlyCommentDTO[]}
*/
function getComments_(sheetName, yearMonth) {
var result = MonthlyCommentRepository.findAll();
var matched = [];
for (var i = 0; i < result.dtos.length; i++) {
var dto = result.dtos[i];
if (String(dto['対象シート名']) === sheetName && String(dto['対象年月']) === yearMonth) {
matched.push(dto);
}
}
return matched;
}
/**
* コメント一覧を保存する。
* - 同一 (対象シート名 × 対象年月 × 対象行ラベル) の既存レコードは更新(コメントID は維持)
* - 該当レコードなしで本文ありは新規追加
* - 本文が空のレコードは削除(ゴミデータ防止)
* - LockService で 5 秒待機の排他ロックを取得(複数ユーザーの同時上書き防止)
* @param {MonthlyCommentDTO[]} comments
* @returns {{ saved: number, deleted: number }}
*/
function saveComments_(comments) {
var lock = LockService.getScriptLock();
try {
lock.waitLock(5000);
var all = MonthlyCommentRepository.findAll();
// ... 既存レコードを (対象シート名+対象年月+対象行ラベル) でマップ化 →
// 入力された comments で更新/追加/削除を反映 →
// MonthlyCommentRepository.save(merged) で全置換
return { saved: /* 保存件数 */, deleted: /* 削除件数 */ };
} finally {
lock.releaseLock();
}
}
LockService.getScriptLock() は GAS 組み込みサービスであり、Utils 名前空間(000_infra/004_utils.js)にラップする必要はない(CLAUDE.md の Utils 構成に追加しない)。
Step 4: メニュー登録と HTML ファイル追加
4-a. MENU_DEFINITION への追加
000_infra/002_constants.js の MENU_DEFINITION には現時点で 「📈 FP&A」相当のメニューカテゴリは存在しない(Phase 1 調査結果)。既存カテゴリのうち、本機能の趣旨(マート再生成と独立した経営情報の永続化)に最も近い 📋 サイドバー: 📊 マート更新 カテゴリ(L229-239、source: 'sidebar')の items 配列末尾に以下を追加する:
{ label: '📝 月次経営コメント入力/編集', funcName: 'showMonthlyCommentDialog', description: '61/71/81 タブの行ラベル別に経営コメントを永続シートに保存・編集' },
onOpen()(100_config/101_sys_config.js L323)は Constants.MENU_DEFINITION をループして動的にメニューを生成するため、onOpen() 本体への手動 addItem 追加は不要(MAS-214 / MAS-217 で確立されたパターン)。
4-b. HTML ダイアログの新設
templates/monthly_comments_dialog.html(新規)を以下のレイアウトで作成する:
| 領域 | 内容 |
|---|---|
| ヘッダー | プルダウン: 対象シート名(候補: 61_pl_monthly / 71_bs / 81_cf_indirect、ハードコード固定)テキスト入力: 対象年月( YYYY-MM 形式、デフォルト= 今月) |
| 中央テーブル | 行ラベル列(左、読み取り専用)+コメント本文列(右、<textarea> で編集可)。行は対象シートの A 列ラベルから動的取得 |
| フッター | 「保存」ボタン(google.script.run.saveComments_(comments)) / 「閉じる」ボタン |
- HTML ファイル配置先は
templates/(既存templates/operations_sidebar.htmlのパターンに準拠。HtmlService.createHtmlOutputFromFile('templates/monthly_comments_dialog')で読み込む) - 行ラベルの取得は新規サーバー関数
getRowLabelsForCommentTarget_(sheetName)を300_ui/302_monthly_comments.jsに同時追加して、対象シートの A 列を Read して返す(行ラベルとして空白・絵文字接頭辞を許容)
影響範囲
| ファイル | 変更種別 | 変更量目安 | 既存動作への影響 |
|---|---|---|---|
000_infra/003_contracts.js | 追記 | +12 行(DTO 1 件) | なし(型定義のみ) |
000_infra/002_constants.js | 追記 | +2 行(ID_PREFIX_MAP 1 件 + MENU_DEFINITION 1 件) | なし(既存 ID 発番・既存メニュー項目に変更なし) |
100_config/101_sys_config.js | 追記 | +2 行(appendRow 1 件 + schemas 1 行) | DDL 実行時に 94_dat_monthly_comments シートが新規生成される。既存シートには無影響 |
200_data/202_repository.js | 追記 | +40 行(MonthlyCommentRepository 新設) | 既存 Repository に変更なし |
300_ui/302_monthly_comments.js | 新規 | +約 100 行 | なし(新規ファイル) |
templates/monthly_comments_dialog.html | 新規 | +約 100 行 | なし(新規ファイル) |
既存マート(61_pl_monthly / 71_bs / 81_cf_indirect 等)への書き込みは行わないため、buildBudgetTrendDataMart 等の既存処理にも影響なし。
注意事項
LockServiceは GAS 組み込みサービスでありUtilsへの追加は不要。saveComments_内で直接LockService.getScriptLock()を呼びtry/finallyでreleaseLock()する。004_utils.jsには何も足さない。getWebSpreadsheet_()を使う。SpreadsheetApp.getActiveSpreadsheet()を直接呼ぶと Web アプリ経由で null になるケースがあるため、新規サーバー関数からシートにアクセスする際は必ずgetWebSpreadsheet_()経由とする(000_infra/004_utils.jsL9-15)。MonthlyCommentRepository内部は既存Utils.getSheetByKey()経由となるため自動的に準拠する。61_pl_monthly/71_bs/81_cf_indirect等のマートシートには絶対に書き込まない。これらはbuildBudgetTrendDataMart等でclearContents → setValuesの全置換が行われるため、コメント情報を直書きすると消失する。本機能の保存先は94_dat_monthly_commentsのみ。94_dat_monthly_commentsは DDL(setupAllSchemas)管理シート。CLAUDE.mdの「DDL で管理されないタブ」一覧(03_sys_params,75_ss_equity_changes,76_notes,77_pj_raw,78_pj_pl,91_fs_bs,92_fs_pl,93_kpi_dashboard,90_test_results)には含めず、schemasオブジェクトで管理する側に置く。動的生成シートと混同しないこと。MENU_DEFINITIONへの追加項目は実在カテゴリのみに加える。Phase 1 調査時点で📋 サイドバー: 📊 マート更新/📋 サイドバー: 🔧 開発・設定/📋 サイドバー: 📒 経理業務 (RPA / Action)等は実在するが、「📈 FP&A」カテゴリは未実装。新規カテゴリを作るのではなく、既存📋 サイドバー: 📊 マート更新に追加する(推測でカテゴリ名を作らない)。MonthlyCommentRepository._getSheet()のシステムキーはDAT_COMMで固定。Phase 1 で確認した既存命名規則(TRN_/WRK_/MST_/BUD_/LOG_等の 4 文字接頭辞 + 4 文字略号)に準拠。COMMENT等の長い名前は使わない。コメント保存時の
入力者フィールドはSession.getActiveUser().getEmail()。空文字になるケース(認可前等)は'SYSTEM'を埋める(Utils.auditLogの L286-287 と同じガード)。行ラベルは絵文字・全角スペースを含む形でそのまま保存する。
61_pl_monthlyの A 列には■ 売上高/売上高/✨ 売上総利益/【売上高 計】等のフォーマットが存在する(docs/dev/dev_mas-001_variance_analysis.mdL36-44)。トリミングや絵文字除去をすると、再描画時に行ラベルがマッチしなくなるため、生のラベル文字列で保存・参照する。
エッジケース
| 条件 | 挙動・表示値 | 理由 |
|---|---|---|
対象行ラベル がマート再生成後に該当シートに存在しなくなった(科目マスタ改定で行ラベルが消滅) | ダイアログのテーブルに (旧科目) {対象行ラベル} と接頭辞付きで表示し、行末に「削除」ボタンを表示。保存時に削除を選択した行は本文を空文字にして saveComments_ に渡す(=既存レコード削除) | マート再生成で行ラベルが変更される可能性。コメント自体は永続シートに残るため、UI 側で目視で削除/更新を促す |
| コメント本文が空文字(または空白のみ)での保存要求 | saveComments_ 側で String(本文).trim() === '' の場合、対応する既存レコードを削除(新規追加はしない) | ゴミデータ防止。空コメントを残すと将来の集計・NLP 処理で空行の扱いが煩雑になる |
saveComments_ への同時アクセス(2 名以上が同時に保存ボタンを押す) | LockService.getScriptLock().waitLock(5000) で最大 5 秒待機。取得不可時は GAS が Could not obtain lock 例外を投げるので、saveComments_ 側で catch して { error: '他のユーザーが保存中です。少し時間を置いて再度お試しください。' } を返却し、ダイアログで alert() 表示 | 競合保存による上書き(最後に保存した人の内容のみ残る現象)防止 |
対象年月(例: 2026-04)に既存コメントが存在しない | getComments_ は空配列を返却。ダイアログは行ラベルだけを表示し、コメント本文の <textarea> は空欄のまま。ユーザーが入力→保存で新規レコードが追加される | 新規入力 UX を保証 |
94_dat_monthly_comments シートが未作成(DDL 未実行環境) | MonthlyCommentRepository._getSheet() が null を返す。findAll() は { headers: [], dtos: [] } を返却するため、getComments_ も空配列。saveComments_ 側は _getSheet() の null チェックで早期 return し、{ error: '94_dat_monthly_comments シートが未作成です。先に DDL 全更新 (Full) を実行してください。' } を返却 | DDL 未実行環境(新規 dev / 環境再構築直後)への対応。例外で落ちずユーザーにアクションを促す |
対象シート名がプルダウン候補(61_pl_monthly / 71_bs / 81_cf_indirect)以外で渡された | saveComments_ 側で許可リストに含まれるか検証し、不一致なら { error: '対象シート名が不正です' } を返却 | クライアント改ざん経由で意図しないシート名が保存されるのを防ぐ |
対象年月が YYYY-MM 形式でない | saveComments_ 側で `/^\d{4}-(0[1-9] | 1[0-2])$/の正規表現マッチ判定。失敗なら{ error: '対象年月は YYYY-MM 形式で入力してください' }` を返却 |
| 同じ (対象シート名, 対象年月, 対象行ラベル) のレコードが複数件存在する(手動編集等で混入) | saveComments_ のマージ時に最初の 1 件のみ採用し、残りは merged 配列から除外(=save() で全置換時に消える)。コンソールに警告ログ出力 | 重複データの自動正規化。手動編集ミスからの自己修復 |
ID(コメントID)の発番タイミング | 新規追加時は 300_ui/302_monthly_comments.js 内の補助関数で CMT_YYYYMMDD_NNNN を生成(既存 generateNextIdForAssist_ 等のパターンを踏襲。Constants.ID_PREFIX_MAP の 94_dat_monthly_comments エントリを参照) | ID 発番ルールを ID_PREFIX_MAP 集約原則(CLAUDE.md 参照)に揃える |
実データ検証
本機能は新規シートのため DDL コード値と実データの乖離は基本なし。ただし以下を MCP で確認すること:
61_pl_monthlyの A 列ラベル形式(全角スペース・絵文字■✨【】の有無、MAS-001 仕様書 L36-44 の表示例と実データの一致)71_bs/81_cf_indirectの A 列ラベル形式(同上)- 上記いずれも、
getRowLabelsForCommentTarget_(sheetName)で取得したラベルが94_dat_monthly_commentsの対象行ラベル列に保存される文字列と完全一致すること
特に、絵文字や全角スペースを「整形のために trim」してしまうと、再描画時にマッチング不能となる。ラベルは生のまま保存・参照することを徹底する(注意事項 #8)。
関連ドキュメント
| ドキュメント | 関連性 |
|---|---|
| MAS-001 予実差異分析 | 同じく 61_pl_monthly 系の行ラベルを参照する。本機能で追加するコメントは MAS-001 出力 65_pl_variance の差異要因の質的説明として読まれる想定 |
| MAS-022 予実差異ドリルダウン(INV単位) | 差異の要因 INV を一覧化する機能。本機能のコメントは「なぜその差異を許容したか/対応策の要約」として補完的に活用 |
| MAS-025 予算 vs 実績差異分析 | 同上。経営者コメント+数値差異+INV 明細 の 3 点セットが揃うことで月次レビューの完成度が上がる |
| MAS-191 NLP コメント自動生成(未着手・将来案件) | 本機能の 94_dat_monthly_comments 蓄積データを LLM の Few-shot プロンプトの実例として再利用する想定 |
| CLAUDE.md コーディング規約 | 「データアクセス」「DDL で管理されないタブ」セクションの規約に準拠 |
| MAS-192 Repository 完全移行 | MonthlyCommentRepository は本リファクタリングで確立された Repository パターンに準拠して実装する |
人間が検討すべき事項
TODO_future.md の MAS-023 説明文「コメントの保存先(専用シート or セルノート)」については、本仕様書で 専用シート方式(94_dat_monthly_comments) を採用済み(マート再生成耐性が決め手)。以下は実装着手前または運用開始後に判断すべき残項目:
- 対象シート選択肢の管理方式:
- 案 A: HTML / GAS 両方にハードコード(
['61_pl_monthly', '71_bs', '81_cf_indirect']) - 案 B:
03_sys_paramsにCFG_MONTHLY_COMMENT_TARGETSキーで CSV 保存 - 案 C:
01_sys_configでcomment_target=trueフラグ列を追加して動的取得 - 推奨: 案 A(初期実装をシンプルに保つ)。将来的に対象シートを増やしたくなったタイミングで案 B に移行
- 案 A: HTML / GAS 両方にハードコード(
- コメント編集権限:
- 全ユーザーが全コメント編集可とするか、特権ユーザーのみ編集可とするか(
isPrivilegedUser_()で制御)。経営判断系の情報のため、特権限定が安全だが、現場担当者が事実情報を入れたいケースでは緩い方が運用コストが低い
- 全ユーザーが全コメント編集可とするか、特権ユーザーのみ編集可とするか(
- 複数行コメントの上限:
<textarea>の上限を何文字にするか。Google Sheets のセル文字数上限(50,000 文字)を超えない範囲で UX 上適切な制限(例: 1,000 文字)を設けるか
- コメントの履歴管理(バージョニング):
- 上書き保存だけで良いか、過去版を別シート(
94_dat_monthly_comments_history)に追記するか。経営判断の根拠記録としては履歴が残るべきだが、初期スコープでは上書きのみとする提案
- 上書き保存だけで良いか、過去版を別シート(
- エクスポート連携(MAS-021)との統合:
- MAS-021(PDF/Excel エクスポート)にコメントを含めるか。取締役会用フォーマットにコメント欄を追加する案(同じ行ラベルの右側にコメントを差し込む)の優先度
- MAS-191(NLP 自動生成)連携時の上書きポリシー:
- 自動生成コメントとユーザー入力コメントが衝突した場合、どちらを優先するか。
入力者列で'AI'/ メールアドレス を区別して上書き判定する設計が必要
- 自動生成コメントとユーザー入力コメントが衝突した場合、どちらを優先するか。
- シート未作成時の自動 DDL 実行:
- エッジケース「シート未作成」時に、エラーメッセージではなく自動で
setupAllSchemasを裏で実行する案。ただしsetupAllSchemasは特権ユーザー限定 +isFull確認ダイアログがあるため、自動実行は副作用が大きく非推奨
- エッジケース「シート未作成」時に、エラーメッセージではなく自動で
MENU_DEFINITION配置先カテゴリ:- 現状は
📋 サイドバー: 📊 マート更新に配置する方針だが、もし将来「📈 FP&A」相当の独立カテゴリを新設するなら、本機能はそちらに移動する。新カテゴリ新設の判断は MAS-025 / MAS-022 / MAS-001 等の FP&A 系機能の数が増えた段階で別途検討
- 現状は
実装プロンプト(Claude Code 用)
Step 1: 型定義・スキーマ・ID 発番の追加
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-023「月次経営コメント入力機能」の Step 1(型定義・スキーマ・ID 発番の追加)を実装してください。
## 実行前タスク
- `100_config/101_sys_config.js` を Read して、`setupAllSchemas` の既存
`confSheet.appendRow([...])` 呼び出し(L770-823 付近)と `schemas`
オブジェクト(L826 から始まるブロック)の正確な型・行フォーマットを確認する(推測禁止)
- `000_infra/003_contracts.js` を Read して既存 @typedef の記述スタイル
(`@property {型} 列名 - 説明`、コメント先頭は `シート番号 + 物理名 + 1行説明`)を確認する
- `000_infra/002_constants.js` を Read して `ID_PREFIX_MAP` のエントリ形式
(`{ pattern: '...', prefix: '...', digit: 4, isDate: true|false }`)を確認する
## 修正対象ファイル
- `000_infra/003_contracts.js` — `MonthlyCommentDTO` の @typedef 追記のみ
(既存 `PartnerDTO` の後、`// 3. DTO ファクトリ` セクションの直前)
- `100_config/101_sys_config.js` — `setupAllSchemas` 内の以下 2 箇所のみ追記:
(1) confSheet.appendRow ブロックの末尾に
`if (!existKeys.includes('DAT_COMM')) confSheet.appendRow(['DAT_COMM', '', '94_dat_monthly_comments', '月次経営コメント']);`
(2) `schemas` オブジェクト末尾に
`'DAT_COMM': { headers: ["コメントID","対象シート名","対象年月","対象行ラベル","コメント本文","入力者","最終更新日時"], color: "#434343" },`
- `000_infra/002_constants.js` — `ID_PREFIX_MAP` 配列末尾に
`{ pattern: '94_dat_monthly_comments', prefix: 'CMT_', digit: 4, isDate: true },`
を追記のみ
## 実装内容
### MonthlyCommentDTO の列定義(7 列固定)
- コメントID (string, "CMT_YYYYMMDD_NNNN")
- 対象シート名 (string, "61_pl_monthly" | "71_bs" | "81_cf_indirect" 等)
- 対象年月 (string, "YYYY-MM")
- 対象行ラベル (string, 例: "売上高", "営業利益")
- コメント本文 (string, 自由記述・複数行可)
- 入力者 (string, Session.getActiveUser().getEmail())
- 最終更新日時 (Date)
## 制約
- 既存スキーマ・既存 ID_PREFIX_MAP エントリ・既存 DTO は一切変更しない(追記のみ)
- schemas エントリの color は `#434343`(LOG_AUDIT と同じ)固定
- `91_fs_bs` / `92_fs_pl` / `93_kpi_dashboard` 等の既存シート番号と重複しない
`94_dat_monthly_comments` を使用(Phase 1 で確認済み)
- 推測でカテゴリ名・キー名を作らない。Phase 1 の Read 結果に基づく文字列のみ使用
## 動作確認
1. `npm run push:dev` でデプロイ
2. dev スプレッドシートで「📋 サイドバー: 🔧 開発・設定 → DDL 全更新 (Full)」
(= `setupAllSchemas`) を実行
3. `01_sys_config` シートに `DAT_COMM` 行が追加されたこと、
`94_dat_monthly_comments` シートが生成されヘッダー 7 列が並んでいることを確認
4. `Constants.ID_PREFIX_MAP.find(e => e.pattern === '94_dat_monthly_comments')`
が `{prefix: 'CMT_', digit: 4, isDate: true}` を返すことをエディタコンソールで確認
Step 2: Repository の新設
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-023「月次経営コメント入力機能」の Step 2(MonthlyCommentRepository の新設)を実装してください。
## 実行前タスク
- `200_data/202_repository.js` を Read して、`JournalRepository` (L259-298) の
`_getSheet()` / `findAll()` / `save()` / `append()` の実装パターンを把握する
- 内部ヘルパー `readSheetAsDtos_()` / `writeDtosToSheet_()` / `appendDtosToSheet_()`
の引数・戻り値(L19-101)を確認し、新規ヘルパーは作らずに再利用する方針を理解する
- `000_infra/004_utils.js` を Read して `Utils.getSheetByKey(key, fallbackName)` の
シグネチャを確認する
## 修正対象ファイル
- `200_data/202_repository.js` — ファイル末尾に `MonthlyCommentRepository` を追記のみ
(既存 Repository は一切変更しない)
## 実装内容
### MonthlyCommentRepository の実装方針
- _getSheet(): `Utils.getSheetByKey('DAT_COMM', '94_dat_monthly_comments')` を使用
(キー名は Step 1 で `setupAllSchemas` に追加した実在する文字列)
- findAll(): `readSheetAsDtos_(MonthlyCommentRepository._getSheet())` を返す
(JournalRepository.findAll() と同一パターン)
- save(dtos): `writeDtosToSheet_(sheet, headers, dtos)` を呼ぶ
(JournalRepository.save() と同一パターン)
- append(dtos): `appendDtosToSheet_(sheet, headers, dtos, 0)` を呼ぶ
(末尾追記、最終行判定列はコメントID 列 = 0、JournalRepository.append() と同一)
## 制約
- 既存 Repository(OrderRepository / InvoiceRepository / BankTxRepository /
JournalRepository / AccountRepository / PartnerRepository)は変更しない
- 内部ヘルパー(readSheetAsDtos_ / writeDtosToSheet_ / appendDtosToSheet_)は
新規追加せず既存のものを再利用
- findAll() 戻り値は `{ headers: string[], dtos: MonthlyCommentDTO[] }` オブジェクト
(配列ではない。MAS-012 仕様書で実装ミス事例があったため特に注意)
## 動作確認
1. `npm run push:dev` でデプロイ
2. GAS エディタで以下を実行:
```
function testMcr_() {
var r = MonthlyCommentRepository.findAll();
console.log('headers:', r.headers);
console.log('dtos.length:', r.dtos.length);
MonthlyCommentRepository.append([{
コメントID: 'CMT_TEST_0001',
対象シート名: '61_pl_monthly',
対象年月: '2026-04',
対象行ラベル: '売上高',
コメント本文: 'テストコメント',
入力者: '[email protected]',
最終更新日時: new Date()
}]);
console.log('after append:', MonthlyCommentRepository.findAll().dtos.length);
}
```
3. 上記実行後、シート上に 1 行追加されていることを確認、その後手動で削除して環境クリーン
Step 3: サーバーサイド関数の追加
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-023「月次経営コメント入力機能」の Step 3(サーバーサイド関数の追加)を実装してください。
## 実行前タスク
- `300_ui/301_ui_assist.js` を Read して既存 UI 関連関数の宣言スタイル
(var 名前空間か裸関数か、コメント形式)を確認する
- `100_config/101_sys_config.js` の `openOperationsSidebar()` (L356-364) を Read して
`HtmlService.createTemplateFromFile('templates/operations_sidebar')` のパターンを確認する
- `000_infra/004_utils.js` の `Utils.auditLog` 内 `Session.getActiveUser().getEmail()`
フォールバック処理(L286-287、空時 'SYSTEM' 埋め)を確認する
## 修正対象ファイル
- `300_ui/302_monthly_comments.js` — 新規作成のみ
## 実装内容
### 追加する関数(裸関数で宣言。既存 301_ui_assist.js のスタイルに準拠)
1. `showMonthlyCommentDialog()`:
- `HtmlService.createHtmlOutputFromFile('templates/monthly_comments_dialog')`
で 720x640 のモーダルダイアログを表示
- `SpreadsheetApp.getUi().showModalDialog(html, '📝 月次経営コメント入力')`
2. `getComments_(sheetName, yearMonth)`:
- `MonthlyCommentRepository.findAll()` を呼び、対象シート名と対象年月でフィルタ
- 戻り値: `MonthlyCommentDTO[]`
3. `saveComments_(comments)`:
- `LockService.getScriptLock().waitLock(5000)` で排他ロック
- 既存全レコードを `MonthlyCommentRepository.findAll()` で取得し、
`(対象シート名 + '|' + 対象年月 + '|' + 対象行ラベル)` をキーにマップ化
- 入力された `comments` を逐次処理:
- 本文が空(trim 後)→ 既存マップから削除
- 既存キーあり → 本文・最終更新日時・入力者を更新
- 既存キーなし & 本文あり → 新規追加(コメントID は
`'CMT_' + Utilities.formatDate(new Date(), 'JST', 'yyyyMMdd') + '_' + 連番`)
- マージ後の全レコードを `MonthlyCommentRepository.save(merged)` で全置換
- 戻り値: `{ saved: 件数, deleted: 件数 }`
- finally で `lock.releaseLock()`
4. `getRowLabelsForCommentTarget_(sheetName)`:
- 許可リスト(`['61_pl_monthly', '71_bs', '81_cf_indirect']`)に含まれることを検証
- `getWebSpreadsheet_().getSheetByName(sheetName)` の A 列(2 行目以降)を Read
- 空白行は除外、絵文字・全角スペース等の整形は一切しない(生のまま返す)
- 戻り値: `string[]`
5. 補助: 内部 ID 発番関数(`generateMonthlyCommentId_()`)を private で実装
(`Constants.ID_PREFIX_MAP` から `94_dat_monthly_comments` のエントリを取得し、
`prefix + YYYYMMDD + '_' + 連番(4桁)` を返す)
## 制約
- LockService は GAS 組み込みサービスをそのまま使う(`Utils` には追加しない)
- 既存の Repository(JournalRepository 等)は変更しない
- `getWebSpreadsheet_()` を使う(`SpreadsheetApp.getActiveSpreadsheet()` 直接呼び出し禁止)
- `61_pl_monthly` / `71_bs` / `81_cf_indirect` 等のマートシートへは絶対に書き込まない
- 行ラベルは絵文字・全角スペースを保ったまま保存・参照(trim 禁止)
- 入力者の取得は `Session.getActiveUser().getEmail() || 'SYSTEM'` でフォールバック
### saveComments_ での LockService 使用例
var lock = LockService.getScriptLock();
try {
lock.waitLock(5000);
// ... 既存マップ化 → 入力反映 → save(merged)
MonthlyCommentRepository.save(merged);
} catch (e) {
if (String(e.message || e).indexOf('Could not obtain lock') !== -1) {
return { error: '他のユーザーが保存中です。少し時間を置いて再度お試しください。' };
}
throw e;
} finally {
lock.releaseLock();
}
## 動作確認
1. `npm run push:dev` でデプロイ
2. GAS エディタで `getComments_('61_pl_monthly', '2026-04')` を実行し空配列が返ることを確認
3. `saveComments_([{対象シート名:'61_pl_monthly', 対象年月:'2026-04', 対象行ラベル:'売上高',
コメント本文:'テスト', 入力者:'', 最終更新日時:new Date()}])` を実行し
`{saved: 1, deleted: 0}` が返り、シートに 1 行追加されることを確認
4. 同じキーで本文を空文字で再保存し `{saved: 0, deleted: 1}` が返り、シートから消えることを確認
5. テスト後、テストデータをすべて削除
Step 4: メニュー登録と HTML ファイル追加
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-023「月次経営コメント入力機能」の Step 4(メニュー登録と HTML ダイアログ)を実装してください。
## 実行前タスク
- `000_infra/002_constants.js` の `MENU_DEFINITION` (L206-324) を Read して
`📋 サイドバー: 📊 マート更新` カテゴリ (L229-239) の `items` 配列構造を確認する
- `templates/operations_sidebar.html` を Read して既存ダイアログの記述スタイル
(`<?!= include('...') ?>` の使用有無、`google.script.run` の呼び出しパターン)を確認する
- `100_config/101_sys_config.js` の `onOpen()` (L323) が `MENU_DEFINITION` ループで
動的にメニューを生成する仕組みを再確認する(手動 `addItem` の追加は不要)
## 修正対象ファイル
- `000_infra/002_constants.js` — `MENU_DEFINITION` の
`📋 サイドバー: 📊 マート更新` カテゴリの `items` 配列末尾に 1 項目追記のみ
- `templates/monthly_comments_dialog.html` — 新規作成のみ
## 実装内容
### MENU_DEFINITION への追加項目(`📋 サイドバー: 📊 マート更新` の items 末尾)
{ label: '📝 月次経営コメント入力/編集', funcName: 'showMonthlyCommentDialog', description: '61/71/81 タブの行ラベル別に経営コメントを永続シートに保存・編集' },
### HTML ダイアログ (`templates/monthly_comments_dialog.html`) の構成
- <head> に Apps Script の標準 CSS (https://www.gstatic.com/script/css/script.css 等) は使わず、
シンプルな <style> ブロックで完結
- body 上部:
- <select id="sheetName"> プルダウン: `61_pl_monthly` / `71_bs` / `81_cf_indirect` (3 項目固定)
- <input id="yearMonth" type="text" placeholder="YYYY-MM"> (デフォルト値=今月)
- 「読込」ボタン: クリックで `google.script.run.withSuccessHandler(renderTable).getRowLabelsForCommentTarget_(sheetName)`
→ 続けて `getComments_(sheetName, yearMonth)` を呼び、テーブルに展開
- body 中央:
- <table>: 1 行 = (左セル: 行ラベル、右セル: <textarea name=\"comment\" data-label=\"...\">)
- 既存コメントは <textarea> の初期値として埋め込み
- 既存レコードがあるが行ラベルがマートに存在しなくなった場合、左セルに `(旧科目) ラベル` と表示し、
右に「削除」ボタンを表示
- body 下部:
- 「💾 保存」ボタン: クリックで全 <textarea> の値を集約 → `MonthlyCommentDTO[]` 化 →
`google.script.run.withSuccessHandler(...).withFailureHandler(...).saveComments_(comments)`
- 結果ハンドラで `{saved, deleted}` を alert 表示し、エラー時は `{error}` を alert 表示
- 「✖ 閉じる」ボタン: `google.script.host.close()`
## 制約
- `onOpen()` 本体には何も追加しない(MENU_DEFINITION ループで自動的に追加される)
- 新カテゴリ `📈 FP&A` は作らない(Phase 1 調査時点で未実装、本案件のスコープ外)
- HTML ファイルは `templates/` ディレクトリに配置(既存 `operations_sidebar.html` と同階層)
- `google.script.run` の関数名は末尾アンダースコア付き
(`getComments_` / `saveComments_` / `getRowLabelsForCommentTarget_`)でも GAS 側で呼び出し可能
- 文字列はすべて UTF-8 で保存(絵文字・日本語含む)
## 動作確認
1. `npm run push:dev` でデプロイ
2. dev スプレッドシートを再読み込み → `📋 サイドバー: 📊 マート更新` カテゴリに
「📝 月次経営コメント入力/編集」が表示されることを確認
3. メニューをクリックしてダイアログが開くこと、対象シート・対象年月を選んで「読込」で
行ラベル一覧が表示されることを確認
4. コメントを入力 → 保存 → ダイアログを閉じて再度開く → コメントが永続化されていることを確認
5. コメントを空文字に編集 → 保存 → 該当行が `94_dat_monthly_comments` から削除されていることを確認
6. `901_test_runner.js` に関連テスト(追加した場合のみ)を実行して回帰がないことを確認
推奨実行モデル
| Step | 推奨モデル | 理由 |
|---|---|---|
| Step 1(型定義・スキーマ・ID 発番) | Sonnet | 既存パターンに沿った追記。schemas オブジェクト末尾の挿入位置・appendRow の挿入位置の特定に中程度の判断が必要 |
| Step 2(Repository 新設) | Sonnet | 既存 JournalRepository のコピー+シートキー差し替えのみ。判断要素は最小だが、findAll() 戻り値が {headers, dtos} 形式である点の認識が必要 |
| Step 3(サーバーサイド関数) | Opus | 複数ファイル横断の設計判断(LockService のエラーハンドリング・既存マップとの差分マージロジック・ID 発番ヘルパーの再利用判断)が必要 |
| Step 4(メニュー登録 + HTML) | Opus | HTML ダイアログの UX 設計(行ラベル列が動的・既存/新規の差分表示・削除確認 UX)と GAS 側 google.script.run の双方向通信設計に多面的な判断が必要 |
変更履歴
| 日時 | 変更内容 |
|---|---|
| 2026-04-21 | 初版作成。月次経営コメントを 94_dat_monthly_comments 専用シートに永続保存する方針、MonthlyCommentRepository の新設(既存 JournalRepository パターン踏襲)、LockService 排他ロック、MENU_DEFINITION の 📋 サイドバー: 📊 マート更新 カテゴリ追加、HTML モーダルダイアログ(templates/monthly_comments_dialog.html)等の設計方針を定義 |
| 2026-05-11 | 設計変更: GAS モーダルダイアログ → 月次財務諸表 Cockpit 画面内 MonthlyCommentPanel.tsx。GAS 永続化バックエンドは変更なし。templates/monthly_comments_dialog.html は作成しない |
仕様書作成プロンプト
展開して表示
<instruction>
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
1. **拡張思考の使い分け**: Phase 1(設計)では拡張思考をフル活用し、ファイル名形式・エッジケース一覧・Step 分割粒度・固有名詞(関数名/シート名/列名/行番号)を完全に確定させる。Phase 2(清書)の各 Step 内では拡張思考を最小限に抑え、Phase 1 で確定済みの内容の書き下しに徹する。出力途中で再考しない。
2. **テキスト報告の禁止**: 「〜を作成します」等の text のみで tool_use なしに turn を終了しない。説明は 1 文以内。直ちに tool を呼ぶ。
3. **4-5 分割の Write/Edit 実行**: 2-1(骨格)/2-2(概要〜注意事項)/2-3a(エッジケース〜人間検討事項)/2-3b(実装プロンプト〜変更履歴)/2-4(`<details>`プロンプト記録) に分割する。
4. **各 Step で何を書くかを具体指示**: 設計判断を Phase 2 実行時に持ち込まないよう、Phase 1 で固有名詞(関数名/シート名/行番号)を完全確定させてから清書に入る。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 F-23「月次経営コメント入力機能」の開発仕様書を作成してください。
開発仕様書を新規作成した後は、`docs/_config.json` の `nav` 配列の適切なセクションにも必ず追記してください。
---
## Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)
以下を **全て Read/Grep で調査**してから Phase 2 に進むこと。推測で固有名詞を書かない(失敗パターン #18-#20)。
### 1-A: 案件定義の読み込み
- `docs/_internal/TODO_future.md` を Grep して案件 F-23 の行を特定し、案件名・概要・人間が検討すべき事項を取得する。
### 1-B: 関連コードの調査(Grep は発見まで。「どう書くか」の判断は必ず Read)
1. **`100_config/101_sys_config.js`** を Read して以下を確認する:
- `setupAllSchemas` 関数内の既存シートエントリを **1 件 Read して**、配列エントリの正確な型(フィールド名・形式)を把握する。名前から推測しない。
- `onOpen()` 関数内の実在するメニュー名を **そのまま引用**する。「📈 FP&A」相当のメニューが存在するか、実在する文字列のみ確認する。存在しない場合は「要調査(onOpen() に該当メニューなし)」と仕様書に記載する。
- `setupAllSchemas` で登録されている既存シート番号の一覧を確認し、`94_dat_monthly_comments` と重複しないことを確認する。
2. **`200_data/202_repository.js`** を Read して以下を確認する:
- `JournalRepository` の `_getSheet()` / `findAll()` / `save()` / `append()` の実装を読み、`MonthlyCommentRepository` の雛形として使用するパターンを特定する。
- 内部ヘルパー `readSheetAsDtos_()` / `writeDtosToSheet_()` / `appendDtosToSheet_()` は既存のまま再利用する(新規ヘルパーは不要)。
- `Utils.getSheetByKey(key, fallbackName)` の呼び出し形式を確認する(`_getSheet()` で使う引数パターン)。
3. **`000_infra/003_contracts.js`** を Read して以下を確認する:
- 既存 `@typedef` の記述スタイル(`MonthlyCommentDTO` の型定義を同ファイルに追記する方針のため、フォーマットを把握する)。
- `toDtoList` / `toRow` / `toRows` の役割(Repository 実装で利用するか判断する)。
4. **`000_infra/002_constants.js`** を Read して以下を確認する:
- `ID_PREFIX_MAP` の配列エントリ形式(`94_dat_monthly_comments` のコメントID発番ルール追加時の正確な型・フィールド名)。
- `SHEET_DEFAULTS` の配列エントリ形式(新シートのデフォルト値追加が必要か判断する)。
5. **`000_infra/004_utils.js`** を Read して以下を確認する:
- `getSheetByKey(key, fallbackName)` の使い方を確認する。
- ⚠️ `LockService` は **GAS 組み込みサービスであり `004_utils.js` には存在しない**。`saveComments_` の排他ロックは `LockService.getScriptLock()` を GAS API として直接使用する(`Utils` に追加しない)。
6. **参考仕様書**: `docs/dev/dev_mas-001_variance_analysis.md` を Read し、新機能仕様書のセクション構成・記述粒度を確認する。
### 1-C: Phase 1 で確定させる事項(Phase 2 では清書のみ行う)
- 新シート `94_dat_monthly_comments` のスキーマ登録エントリの正確な形式(`setupAllSchemas` の Read 結果に基づく)
- `MonthlyCommentRepository._getSheet()` で使うシステムキー文字列(例: `DAT_COMM`。既存キーのパターンに合わせる)とフォールバック名
- `onOpen()` の実在するメニューグループ名(Read で取得した文字列のみ)
- `ID_PREFIX_MAP` に追加するエントリ(プレフィックス例: `CMT_`。Read で確認したフィールド形式に準拠)
---
## Phase 2: 仕様書の分割作成
**出力先**: `docs/dev/dev_mas-023_monthly_comments.md`
(⚠️ ファイル名は大文字 `MAS-023`。`dev_f-23_*` は誤り)
**1 回の Write/Edit は 300 行以内。1 回のツール呼び出しで全内容を出力しない。**
### Step 2-1: 骨格の作成 (Write / ~20行)
全セクション見出しのみのファイルを作成する(本文空で可):
`# F-23: 月次経営コメント入力機能` 以降、`## 概要` / `## 目的` / `## 現在のコード` / `## 修正方針` / `## 影響範囲` / `## 注意事項` / `## エッジケース` / `## 実データ検証` / `## 関連ドキュメント` / `## 人間が検討すべき事項` / `## 実装プロンプト(Claude Code 用)` / `## 推奨実行モデル` / `## 変更履歴` / `## 仕様書作成プロンプト`
### Step 2-2: 概要〜注意事項の追記 (Edit or Bash / ~300行)
(中略 — Phase 1 で確定した固有名詞のみ使う)
### Step 2-3a: エッジケース〜人間検討事項の追記 (Edit or Bash / ~200行)
(中略)
### Step 2-3b: 実装プロンプト〜変更履歴の追記 (Edit or Bash / ~250行)
(中略 — 行頭 4 スペースインデントで Claude Code 用プロンプトを記載)
### Step 2-4: 仕様書作成プロンプトの記録 (Edit or Bash)
仕様書末尾の `## 仕様書作成プロンプト` セクションに `<details><summary>展開して表示</summary>` ブロックを追加し、この `<instruction>` 全文を記録する。
---
## Phase 3: 保存・登録・コミット
### 3-A: `docs/_config.json` への追記
`docs/_config.json` の `nav` 配列「§E.5 FP&A・レポーティング」セクションに以下を追記する:
```json
{ "file": "dev/dev_mas-023_monthly_comments.md", "title": "E.5.X MAS-023 月次経営コメント入力機能" }
3-B: docs/_internal/changelog.md への追記
ヘッダー直後に以下を追記する:
| 2026-04-20 | [dev_mas-023_monthly_comments.md](dev_mas-023_monthly_comments.md) | 初版作成。月次経営コメント入力機能(専用シート永続保存方針) |
3-C: コミット&プッシュ
git add → git commit → git push を実行する。
</details>