MAS-032: プロダクト別P/L
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-032 |
| カテゴリ | FP&Aレポーティング / BizDev |
| Phase | P2 |
| 優先度 | ★★ |
| 対象ファイル(新規) | 600_report/610_datamart_product_pl.js |
| 対象ファイル(追記) | 200_data/202_repository.js・100_config/101_sys_config.js・000_infra/002_constants.js |
| 対象シート(新規) | 67_pl_by_product(プロダクト別 P/L マート) |
| 対象シート(追記DDL) | 14_mst_project(「事業区分」列の追加) |
目的
ハイブリッド型(受託事業+プロダクト事業)の事業ポートフォリオに対して、セグメント(プロダクト事業 / 受託事業 / 全社共通)別に損益を可視化し、以下を支援する:
- 受託事業の利益でプロダクト投資を賄っている構造を定量的に把握
- プロダクト事業の売上原価(開発人件費の按分)・粗利率・営業利益率を個別に算出
- 事業ポートフォリオの意思決定材料(Rule of 40・ユニットエコノミクス等、F-33・F-29 の前提データ)を提供
現在のコード
既存の月別 P/L 実績は 600_report/602_datamart_main.js:185 で PL_M_ACT キー経由で 61_pl_monthly に出力されており、配賦・按分を経ない全社合算ベースのみ表示している。
600_report/602_datamart_main.js:185—Utils.getSheetByKey('PL_M_ACT', '61_pl_monthly')で取得600_report/603_datamart_pl.js:196—dmBuildPlOutput_(ctx)が科目×月の2次元マートを構築600_report/603_datamart_pl.js:232-256— セクション(売上高・売上原価・販管費・営業外損益・特別損益)× 科目 × 月の行出力
PJ単位の採算は 400_domain/420_project_profitability.js:7(buildProjectProfitability)で 78_pj_pl(PJ横並び P/L)・79_pj_monthly(PJ別月次採算)として生成済みだが、事業セグメント(プロダクト / 受託 / 共通)での集約は未実装。14_mst_project のスキーマ(100_config/101_sys_config.js:836)にも事業区分を保持する列が存在せず、セグメント判定のマスタが欠落している。
修正方針
前提 DDL 変更(本仕様で同時対応)
14_mst_project(システムキー MST_PROJ)の DDL スキーマに「事業区分」列を追加する。現在のスキーマ(100_config/101_sys_config.js:836):
["有効フラグ","PJコード","プロジェクト名","PJ小区分","PJ大区分","社内外","資産化","顧客・取引先名","PJ区分","契約形態","ステータス","資産化対象","PM・責任者名","配賦区分"]
末尾(配賦区分の後)に "事業区分" を追加し、値として プロダクト / 受託 / 共通 の3値を想定する。既存 PJ レコードへの値入力は人間による作業とする(本仕様はスキーマ拡張と空値時のフォールバックまでを担当)。
新規ファイル
600_report/610_datamart_product_pl.jsを新設する(既存最大番号609_datamart_kpi.jsの直後)。公開関数buildProductPl()をエントリーポイントとする。
新規シート
67_pl_by_product(プロダクト別 P/L マート)を出力先とする。既存シート番号(65_pl_variance・71_bs等)と衝突しない 67 番台を確保。- レイアウトは
61_pl_monthlyの科目×月形式を踏襲し、各月に対してプロダクト事業 / 受託事業 / 全社共通 / 合計の 4 列をネストする「科目 × (セグメント × 月)」の多列構成とする。 - 書き込みは
600_report/608_datamart_render.jsのdmApplyDwhFormat_パターンに倣い、独自レンダラを 610 ファイル内に持つ(DWH フォーマットのセクション色分け・フィルター設定を踏襲)。 - シートキー登録(
100_config/101_sys_config.js:791周辺のconfSheet.appendRow群)に'PL_PROD'キーを追加する:confSheet.appendRow(['PL_PROD', '', '67_pl_by_product', 'P/Lプロダクト別 計上']); 61-65と同様にschemasオブジェクト(100_config/101_sys_config.js:826)には DDL ヘッダー定義を持たない(動的生成のため)。
ProjectRepository の新設(200_data/202_repository.js)
AccountRepository(200_data/202_repository.js:304-350)の実装を完全に踏襲し、AccountRepository 定義の直後に以下の構造で追加する:
_getSheet():Utils.getSheetByKey('MST_PROJ', '14_mst_project')を返す(AccountRepository._getSheetと同パターン)。findAll():readSheetAsDtos_(ProjectRepository._getSheet())を返す(AccountRepository.findAllと同一)。findAsMap(): キー =プロジェクト名(=JournalEntryDTO.PJ名と突合するカラム)、値 ={ category: String(dto['事業区分'] || '').trim() }のマップを返す。有効フラグがfalseまたは'FALSE'の行はスキップ。_cacheにキャッシュする(AccountRepository.findAsMapのキャッシュパターンと同一)。_cache: null/resetCache()を設ける(テスト干渉防止)。
備考:
400_domain/420_project_profitability.js:811のloadPjMaster_が示す通り、シート列名はプロジェクト名、JournalEntryDTO側の参照キーはPJ名で、両者は同一文字列(プロジェクト正式名)で突合する運用。ProjectRepository.findAsMap()のキーはプロジェクト名列の値をそのまま採用する。
計算ロジック(buildProductPl() メイン関数)
JournalRepository.findAll()で全仕訳データを取得({ headers, dtos })。ProjectRepository.findAsMap()でプロジェクト名 → { category }マップを取得。AccountRepository.findAsMap()で科目名 → { stmt, cat }マップを取得。- 対象期間(
420_project_profitability.js:22に倣い、当期 8 月 ~ 翌年 7 月の 12 ヶ月)を生成。 - 仕訳ループ: 各 dto に対し
Utils.parseDateToYm(dto['発生日(P/L計上日)'])でYYYY-MMを正規化。対象期間外はスキップ。 - セグメント判定:
dto['PJ名']をProjectRepository.findAsMap()で引き、categoryがプロダクト/受託/共通のいずれか一致したもののみ採用。それ以外(空 / 未登録 / マスタに値なし / 異常値)は全社共通にフォールバック(「共通」マスタ値と同一セグメントに集約)。 - 3 次元集計マップを構築:
{ 科目名: { 月: { セグメント: 金額 } } }。収支区分に応じて符号付きで加算(収入→ 正、支出→ 負 → 絶対値化)。B/S 科目(stmt === 'BS')はスキップ。 - 開発人件費の按分:
Constants.getParam('PRODUCT_PL_DEV_ALLOC_BASIS', '売上高比')で按分基準を取得(デフォルト売上高比)。- 対象科目は
03_sys_paramsのPRODUCT_PL_DEV_ACCOUNTSキー(カンマ区切り、デフォルト空 → 按分スキップ)で指定する。コード内に科目名をハードコードしない。 - 月別総売上がゼロの場合はゼロ除算を回避し、全額を
全社共通セグメントに計上する(failure_patterns.md #2参照)。
- その他共通費の配賦:
Constants.getParam('PRODUCT_PL_COMMON_ALLOC_BASIS', '売上高比')で配賦基準を取得。420_project_profitability.js:498-534の配賦ロジック(配賦元 → 配賦先への比例配分)を参照モデルとし、セグメント単位(プロダクト / 受託)への 2 分配に適用する。全社共通セグメントに残った共通費を対象とし、配賦基準(売上高比等)でプロダクト/受託に振り替える。- ゼロ除算ガード(基準がゼロの月は配賦せず
全社共通に据え置き)を適用。
- 二重計上チェック: 各科目・各月で「プロダクト + 受託 + 共通」のセグメント合計 vs 全社合計を比較し、絶対値 1 円超の差異があれば
Utils.logInfo('buildProductPl', '二重計上チェック警告: ' + 科目名 + '/' + 月 + ' 差異=' + diff)で警告出力(処理は継続)。 Utils.getSheetByKey('PL_PROD', '67_pl_by_product')でシート取得し、608_datamart_render.jsのdmApplyDwhFormat_に準じた独自レンダラでセクション見出し → 科目 → 小計 → 利益行の構造を書き込む。各月に対してネスト 4 列(プロダクト / 受託 / 共通 / 合計)を展開する。SpreadsheetApp.getUi().toast('💡 プロダクト別 P/L を再構築しました', '完了', 5);で通知。エラー時はUtils.logError(FUNC, e)+ui.alertで報告。
メニュー登録
000_infra/002_constants.js:230-239 の MENU_DEFINITION 内、📋 サイドバー: 📊 マート更新 カテゴリの items 配列に以下を追加する(プロジェクト別 採算 の直後・📊 KPIダッシュボード再描画 の直前に配置推奨):
{ label: 'プロダクト別 P/L', funcName: 'buildProductPl', description: 'プロダクト事業 / 受託事業 / 全社共通 のセグメント別 P/L を再構築' },
備考: 旧仕様テンプレートでは
101_sys_config.jsのonOpen()内に直接メニュー項目を書く想定だったが、現在はonOpen()(100_config/101_sys_config.js:323-350)がConstants.MENU_DEFINITIONを動的ループしてメニュー生成する構造(N-38 で移行済)のため、実体の追記先は000_infra/002_constants.js側となる。
影響範囲
- 新規作成:
600_report/610_datamart_product_pl.js(buildProductPl本体・独自レンダラ)
- 追記:
200_data/202_repository.js—ProjectRepositoryをAccountRepository定義の直後に追加100_config/101_sys_config.js—schemas.MST_PROJ.headersに"事業区分"列を追加 +confSheet.appendRow(['PL_PROD', ...])を追加000_infra/002_constants.js—MENU_DEFINITIONのマート更新カテゴリにbuildProductPl項目を追加
- 変更なし:
000_infra/001_env.js/003_contracts.js/004_utils.js(新規 DTO は不要。既存のJournalEntryDTOで十分)600_report/602-609既存ファイル(独立マートとして実装)400_domain/420_project_profitability.js(PJ単位の既存採算ロジックには干渉しない)
注意事項
03_sys_paramsへの初期登録手順を明記: 本仕様は以下 3 キーをConstants.getParam()経由で参照する。未登録でもデフォルト値で動作するが、初回setupAllSchemas実行後に人間が登録することを推奨:PRODUCT_PL_DEV_ALLOC_BASIS(デフォルト売上高比)— 開発人件費の按分基準PRODUCT_PL_COMMON_ALLOC_BASIS(デフォルト売上高比)— その他共通費の配賦基準PRODUCT_PL_DEV_ACCOUNTS(デフォルト"")— 開発人件費に該当する科目名のカンマ区切りリスト。空の場合は按分処理自体をスキップ
- 日付型の正規化:
JournalEntryDTO.発生日(P/L計上日)はDate型と文字列型が混在しうる(000_infra/003_contracts.js:99の型定義参照)。月集計前に必ずUtils.parseDateToYm()でYYYY-MMに正規化する(failure_patterns.md #17参照)。 - 科目名のハードコード禁止: 開発人件費の対象科目は
03_sys_paramsのPRODUCT_PL_DEV_ACCOUNTSで設定可能とし、コード内にハードコードしない。科目マスタ未登録の科目が指定されていた場合はUtils.logInfoで警告出力し処理を継続(エラーにはしない)。 - キャッシュの初期化:
ProjectRepository.findAsMap()は_cacheにキャッシュする。テストやリエントラント呼び出しで干渉を避けるためresetCache()を必ず公開する(AccountRepository.resetCacheと対称)。 - 二重計上チェックの許容範囲: 「1 円」許容は浮動小数点演算の丸め誤差を考慮したもの。セグメント合計と全社合計の差異が 1 円超でも処理は中断せず、警告ログのみ出力する(業務判断を阻害しないため)。
- B/S 科目の扱い:
stmt === 'BS'の科目は本マートの対象外(P/L 専用)。資産計上(決済手段 === '資産計上')の扱いは420_project_profitability.js:124, 340と整合させ、本仕様では一律スキップ(プロダクト別 P/L は期間損益のみ対象)。 - 仕訳振替の扱い:
決済手段 === '仕訳振替'の INV は 60 番台と同じく除外する(実損益として二重計上を避ける)。JournalRepositoryベースで集計するため、仕訳振替 TRN も同様に除外判定を入れる。
エッジケース
| 条件 | 表示値 / 動作 | 理由 |
|---|---|---|
仕訳の PJ名 が空 | 「全社共通」セグメントに計上 | デフォルトフォールバック |
仕訳の PJ名 が ProjectRepository のマスタに未登録 | 「全社共通」セグメントに計上 | マスタ未整備時の保守的処理 |
14_mst_project.事業区分 列の値が プロダクト/受託/共通 以外(空含む) | 「全社共通」セグメントに計上 | 異常値は保守的に共通扱い(誤セグメント計上の回避) |
| 配賦基準(月別総売上)がゼロの月 | 按分・配賦せず全額「全社共通」計上。P/L は 0 表示("-" にしない) | ゼロ除算回避(failure_patterns.md #2 参照) |
| 対象期間に仕訳データが 0 件 | 全セグメント全科目ゼロで正常出力 | データなし月も行構造は維持(空シートにはしない) |
| セグメント合計 ≠ 全社合計(差異 > 1 円) | Utils.logInfo で警告出力。出力処理は継続 | 実装バグの早期検出(浮動小数点丸め誤差は許容) |
14_mst_project シートが存在しない | Utils.logError('buildProductPl', e, '14_mst_project シートが見つかりません') でエラー記録し処理中断 | マスタ未整備を明示的に報告 |
67_pl_by_product シートが存在しない | setupAllSchemas 未実行と判定し、ui.alert で DDL 実行を促して処理中断 | 前提 DDL 未実行時の誘導 |
JournalEntryDTO.発生日(P/L計上日) が Date 型でなく文字列("2025-08" / "2025/08/15" 等) | Utils.parseDateToYm() で吸収して処理継続 | 型混在は想定内(failure_patterns.md #17) |
PRODUCT_PL_DEV_ACCOUNTS が空文字列 | 開発人件費按分処理自体をスキップ。各仕訳は素の PJ セグメントに計上 | 未設定時は按分ロジックを無効化(誤按分を避ける) |
PRODUCT_PL_DEV_ACCOUNTS に科目マスタ未登録の科目名が含まれる | Utils.logInfo で警告出力し該当科目のみスキップ | 設定ミスを許容し処理は継続 |
科目が B/S 区分(stmt === 'BS') | 本マート対象外(全セグメント計上しない) | P/L 専用マートのため |
決済手段 = 仕訳振替 の仕訳 | スキップ | 実損益を二重計上しない(420_project_profitability.js:114 と整合) |
実データ検証(実装前に MCP 等で確認すべき項目)
14_mst_projectシートに「事業区分」列が存在するか(列名・列位置を確認。存在しない場合は本仕様の DDL 変更で追加される)。14_mst_projectの既存レコードに「事業区分」値(プロダクト/受託/共通)が設定済みか(空行の割合を確認し、空の場合は人間へマスタ整備を依頼)。42_trn_journalの仕訳データにPJ名が設定されている割合(設定率が低い場合、「全社共通」への流入が多くなるためプロダクトオーナーへ事前確認が必要)。03_sys_paramsにPRODUCT_PL_DEV_ALLOC_BASIS/PRODUCT_PL_COMMON_ALLOC_BASIS/PRODUCT_PL_DEV_ACCOUNTSキーが登録されているか(未登録ならデフォルト値で動作)。11_mst_accountで「開発人件費」に相当する候補科目名を洗い出す(例:給料手当・役員報酬・業務委託費等のうち、開発業務に紐づくもの)。洗い出した候補をPRODUCT_PL_DEV_ACCOUNTSに人間が登録する。61_pl_monthlyの月別合計と本マートの「合計」列(全社合算)が各科目・各月で一致することを検証(二重計上チェックの妥当性確認)。
関連ドキュメント
| 仕様書 | 関連箇所 |
|---|---|
| dev_F-06_project_overhead_allocation.md | PJ別共通費配賦の丸め誤差解消統合。配賦ロジックの参照元(420_project_profitability.js) |
| dev_F-01_variance_analysis.md | P/L 予実差異分析。P/L 生成パイプラインの参照元 |
| dev_F-28_arr_mrr_tracking.md | ARR/MRR トラッキング。プロダクト事業セグメントの売上定義を共有 |
| dev_F-29_unit_economics.md | ユニットエコノミクス(LTV/CAC)。本マートをプロダクト売上・コスト入力として利用 |
| (未作成)dev_F-33_rule_of_40.md | Rule of 40 スコアボード。プロダクト事業の成長率・利益率算出に本マートを利用 |
人間が検討すべき事項
- 開発者の工数按分方法の最終決定(TODO_future.md F-32 より転記): 暫定は
PRODUCT_PL_DEV_ALLOC_BASIS = 売上高比とするが、将来的には S-33 工数入力データ(27_bud_resource)との連携で「工数比」への切り替えを検討する。本仕様はConstants.getParamによる設定可能な設計としているため、切り替えは03_sys_params編集のみで完了する。 - 共通費の配賦ルールの確定(TODO_future.md F-32 より転記): F-06 の PJ 別配賦ロジック(
420_project_profitability.js)とは別次元(セグメント次元)の配賦が必要。売上高比 / 工数比 / 労務費比 / 均等割の 4 方式のうちどれを標準とするかはプロダクトオーナーの判断。 14_mst_projectへの「事業区分」列追加とデータ整備: DDL 変更後、既存 PJ レコードへの値入力(プロダクト / 受託 / 共通の振り分け)は人間による作業。入力完了までは全 PJ が「全社共通」に集約される。- 「プロダクト事業」の範囲定義: どの PJ をプロダクトと見なすかの業務定義(例: SaaS サブスク売上のみ / プロダクト関連の受託開発も含む / R&D 段階のプロダクト PJ の扱い)を明文化し、マスタ入力ガイドラインに反映する。
- R&D セグメントの扱い: F-34(R&D 費用の自動分類・集計)では「受託 / プロダクト / R&D」の三分類が想定されている。F-32 では暫定で
プロダクト/受託/共通の 3 セグメントとするが、R&D を第 4 セグメントとして追加するかは F-34 実装時に再検討する。 - 開発人件費の識別ルール:
PRODUCT_PL_DEV_ACCOUNTSに登録する科目名の粒度(科目名レベル / 補助科目レベル / 人員名レベル)を決定する。現行仕様では科目名レベルのみサポート。
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 F-32「プロダクト別P/L」を実装してください。
## 実行前タスク
以下のファイルを Read し、記述内容を実装の直接参照とすること(推測・記憶からの実装禁止):
- `200_data/202_repository.js`: `AccountRepository` の `_getSheet`・`findAll`・`findAsMap`・`_cache`・`resetCache` の実装全体(`ProjectRepository` の完全な参照モデル)
- `100_config/101_sys_config.js`: `14_mst_project` のシステムキー (`MST_PROJ`)・`setupAllSchemas` 内の `schemas.MST_PROJ.headers`・`confSheet.appendRow` のシートキー登録群
- `000_infra/002_constants.js`: `MENU_DEFINITION` 配列の `📋 サイドバー: 📊 マート更新` カテゴリ(メニュー項目の追加先)と `getParam(key, defaultVal)` の実装
- `400_domain/420_project_profitability.js`: 共通費配賦のゼロ除算ガードと計算パターン(特に `498-534` 行の売上高比・工数比・労務費比・均等割の実装)
- `600_report/603_datamart_pl.js` と `600_report/608_datamart_render.js`: シートへの書き込みパターン(`dmApplyDwhFormat_` の `setValues` の呼び出し方・行構成の組み立て方・色分け)
- `000_infra/003_contracts.js`: `JournalEntryDTO` の型定義(特に `発生日(P/L計上日)` が `Date|string` 型であること)
- `000_infra/004_utils.js`: `Utils.parseDateToYm`・`Utils.getSheetByKey`・`Utils.logInfo`・`Utils.logError` の引数と戻り値
## 修正対象ファイル
1. `200_data/202_repository.js` — `ProjectRepository` を `AccountRepository` 定義の直後(`PartnerRepository` の前)に追記のみ(既存コード変更禁止)
2. `100_config/101_sys_config.js` — `schemas.MST_PROJ.headers` の末尾に `"事業区分"` を追加 + `confSheet.appendRow` のシートキー登録群に `PL_PROD` 行を追記(既存コード変更禁止。既存行の順序も変えない)
3. `000_infra/002_constants.js` — `MENU_DEFINITION` の `📋 サイドバー: 📊 マート更新` カテゴリ items に `buildProductPl` メニュー項目を追記(既存コード変更禁止)
4. `600_report/610_datamart_product_pl.js` — 新規作成(ファイル番号は必ず 610、既存最大は 609_datamart_kpi.js)
## 実装内容
### ① `ProjectRepository`(`202_repository.js` に追記)
`AccountRepository`(304-350 行)の実装を **そのままのパターンで** 踏襲すること。差分のみ示す:
- `_getSheet()`: `Utils.getSheetByKey('MST_PROJ', '14_mst_project')` を返す
- `findAll()`: `readSheetAsDtos_(ProjectRepository._getSheet())` を返す
- `findAsMap()`: キー = `プロジェクト名`(`JournalEntryDTO.PJ名` と突合するカラム)、値 = `{ category: String(dto['事業区分'] || '').trim() }`。有効フラグ = FALSE の行はスキップ。キャッシュパターンは `AccountRepository` と同一。
- `_cache: null` / `resetCache()` を設ける
### ② `101_sys_config.js` への追記
a. `schemas.MST_PROJ.headers` 配列の末尾(`"配賦区分"` の後)に `"事業区分"` を追加:
```
'MST_PROJ': { headers: ["有効フラグ","PJコード","プロジェクト名","PJ小区分","PJ大区分","社内外","資産化","顧客・取引先名","PJ区分","契約形態","ステータス","資産化対象","PM・責任者名","配賦区分","事業区分"], color: "#666666" },
```
b. シートキー登録群(`PL_VAR` 行 `!existKeys.includes('PL_VAR')` の直後)に以下を追加:
```
if (!existKeys.includes('PL_PROD')) confSheet.appendRow(['PL_PROD', '', '67_pl_by_product', 'P/Lプロダクト別 計上']);
```
c. MST_PROJ のドロップダウン・入力列拡張(1192 行周辺の `inputCols` や 1438-1444 の `setVali`)を事業区分列に合わせて拡張する必要があるか確認し、不要なら触らない。
### ③ `002_constants.js` への追記
`MENU_DEFINITION` の `📋 サイドバー: 📊 マート更新` カテゴリ items 配列(`プロジェクト別 採算` の直後・`📊 KPIダッシュボード再描画` の直前)に以下を挿入:
```
{ label: 'プロダクト別 P/L', funcName: 'buildProductPl', description: 'プロダクト事業 / 受託事業 / 全社共通 のセグメント別 P/L を再構築' },
```
### ④ `610_datamart_product_pl.js`(新規作成)
メイン関数 `buildProductPl()` を以下のフローで実装する:
1. `const FUNC = 'buildProductPl';` / try-catch で包む / `Utils.logInfo(FUNC, '処理開始');`
2. `const ss = getWebSpreadsheet_();`
3. `const sheetOut = Utils.getSheetByKey('PL_PROD', '67_pl_by_product');` — null なら `ui.alert('67_pl_by_product が見つかりません。setupAllSchemas を実行してください。');` で処理中断
4. `const projMap = ProjectRepository.findAsMap();` — null チェック必要なし(findAsMap は必ず map を返す)
- 空マップの場合: `Utils.logInfo(FUNC, '警告: ProjectRepository のマップが空。全仕訳が全社共通に集約されます');`
5. `const acctMap = AccountRepository.findAsMap();`
6. 対象期間の生成: `420_project_profitability.js:22-24` と同一ロジックで当期 8 月 ~ 翌年 7 月の 12 ヶ月配列 `targetMonths` を作成
7. `const journal = JournalRepository.findAll();` → `journal.dtos` を使用
8. 仕訳ループ: 各 dto に対して
a. `const ym = Utils.parseDateToYm(dto['発生日(P/L計上日)']);` — 対象期間外はスキップ
b. `const acc = String(dto['科目名'] || '').trim();` — 空 or マスタ未登録ならスキップ
c. `const info = acctMap[acc];` — `info.stmt === 'BS'` ならスキップ
d. `if (String(dto['決済手段'] || '').trim() === '仕訳振替') continue;`
e. `const pjName = String(dto['PJ名'] || '').trim();`
f. セグメント判定: `const cat = (projMap[pjName] && projMap[pjName].category) || '';` → `cat === 'プロダクト'` or `'受託'` or `'共通'` に該当しなければ `seg = '全社共通'`、`共通` は `全社共通` に正規化
g. 金額符号: `const raw = Number(dto['税込金額_実績']) || 0;` → 収支区分が `支出` は `-Math.abs(raw)`、`収入` は `Math.abs(raw)`
h. 3 次元集計マップ `agg[acc][ym][seg] += amount;` に加算
9. 開発人件費の按分:
- `const devBasis = Constants.getParam('PRODUCT_PL_DEV_ALLOC_BASIS', '売上高比');`
- `const devAcctsStr = Constants.getParam('PRODUCT_PL_DEV_ACCOUNTS', '');`
- 空文字列ならスキップ。そうでなければカンマで split して trim し、各科目・各月について
- 月別総売上(セグメント合計)を算出
- 総売上がゼロ → 全額「全社共通」に据え置き(何もしない)
- 総売上が正 → 「全社共通」に計上されている該当科目金額を、プロダクト売上比・受託売上比で按分し「プロダクト」「受託」セグメントに振替
10. その他共通費の配賦:
- `const commonBasis = Constants.getParam('PRODUCT_PL_COMMON_ALLOC_BASIS', '売上高比');`
- 各科目・各月の「全社共通」セグメントに残っている金額を、上記 9 と同じゼロ除算ガード付きで「プロダクト」「受託」に配賦
- 按分基準の実装パターンは `420_project_profitability.js:498-534` を参照(売上高比 / 工数比 / 労務費比 / 均等割の 4 方式)。工数比・労務費比を実装する場合は `27_bud_resource` / `22_bud_headcount` の取得も必要
11. 二重計上チェック: 各科目・各月で `segSum = agg[acc][ym].プロダクト + agg[acc][ym].受託 + agg[acc][ym].全社共通` を算出し、集計前の全社合算と 1 円以内で一致することを検証。差異超過時:
```
Utils.logInfo(FUNC, '二重計上チェック警告: ' + acc + '/' + ym + ' diff=' + diff);
```
12. `67_pl_by_product` にレンダリング: `dmApplyDwhFormat_` を参考にしつつ、各月に対してネスト 4 列(プロダクト / 受託 / 共通 / 合計)を展開。セクション見出し → 科目行 → セクション小計 → 利益行(✨ 売上総利益・✨ 営業利益 等)の順で出力。`608_datamart_render.js:16-35` の書式パターン(色・太字・フィルター)を踏襲。
13. `ss.toast('💡 プロダクト別 P/L を再構築しました', '完了', 5); Utils.logInfo(FUNC, '処理完了');`
14. catch: `Utils.logError(FUNC, e); SpreadsheetApp.getUi().alert('🚨 buildProductPl でエラー', e.message, SpreadsheetApp.getUi().ButtonSet.OK);`
## 制約
- 科目名のコード内ハードコード禁止。`AccountRepository.findAsMap()` 経由で参照する。開発人件費対象は `PRODUCT_PL_DEV_ACCOUNTS` から取得。
- `14_mst_project` シートが存在しない場合は `Utils.logError('buildProductPl', e, '14_mst_project シートが見つかりません')` でエラー記録し処理中断する。
- `発生日(P/L計上日)` は必ず `Utils.parseDateToYm()` で正規化する(Date/文字列の混在対応。`failure_patterns.md #17` 参照)。
- `202_repository.js`・`101_sys_config.js`・`002_constants.js` の既存コードは**変更せず、追記のみ**。既存行の順序も変えない。
- `PropertiesService.getScriptProperties()` を直接呼ばない。パラメータ取得は `Constants.getParam()` を使う(`CLAUDE.md` の Env 規約)。
- メニュー項目は `101_sys_config.js` の `onOpen()` ではなく `002_constants.js` の `MENU_DEFINITION` に追加する(N-38 で移行済)。
## エッジケース
(本仕様書の ## エッジケース セクションの全表を転記すること)
## 実データ検証
(本仕様書の ## 実データ検証 セクションの全項目を転記すること)
## 動作確認
1. `npm run push:dev` でデプロイ
2. `101_sys_config.js` の `setupAllSchemas` を実行(DDL 全更新)
3. `14_mst_project` に「事業区分」列が追加されたことを確認(シート末尾列)
4. `14_mst_project` の既存レコードに「事業区分」値(プロダクト / 受託 / 共通)を最低 3 件手動入力
5. `03_sys_params` に以下を手動登録(未登録でもデフォルトで動作することを確認):
- `PRODUCT_PL_DEV_ALLOC_BASIS = 売上高比`
- `PRODUCT_PL_COMMON_ALLOC_BASIS = 売上高比`
- `PRODUCT_PL_DEV_ACCOUNTS = 給料手当,役員報酬`(実在科目のカンマ区切り)
6. サイドバーの `📊 マート更新 → プロダクト別 P/L` を実行
7. `67_pl_by_product` シートに「プロダクト / 受託 / 共通 / 合計」の 4 列ネスト構造で出力されることを確認
8. 合計列の各科目・各月の値が `61_pl_monthly` の対応月・対応科目と一致することを確認(二重計上チェックの妥当性)
9. `14_mst_project` の「事業区分」が空の PJ を持つ仕訳が「共通」列に計上されることを確認
10. `03_sys_params` の `PRODUCT_PL_DEV_ACCOUNTS` を空に戻し、開発人件費按分がスキップされることを確認
11. dev 動作確認後、`npm run push:prod` でデプロイ
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
① ProjectRepository 追記 | Claude Haiku | AccountRepository パターンの単純横展開。コード完全定義済み |
② DDL(事業区分列追加・PL_PROD キー登録) | Claude Haiku | 既存パターンへの追記のみ。判断要素なし |
③ メニュー項目追加(MENU_DEFINITION) | Claude Haiku | 既存配列への要素追加のみ |
④ buildProductPl() 実装 | Claude Sonnet | 複数 Repository の統合・セグメント判定・按分/配賦ロジック・ゼロ除算ガード・二重計上チェックの設計判断が必要 |
| ⑤ 独自レンダラ(4列ネストの DWH 書き込み) | Claude Sonnet | dmApplyDwhFormat_ を参考にしつつマルチセグメント構造へ拡張する判断が必要 |
| ⑥ 動作確認・統合テスト | Claude Sonnet | ログ検証・二重計上チェックの警告読解・実データ挙動の解釈 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-21 | 初版作成。プロダクト別 P/L 仕様書。ProjectRepository 新設・610_datamart_product_pl.js 新規実装・14_mst_project への事業区分列 DDL 追加の設計を含む |
仕様書作成プロンプト
展開して表示
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
- 拡張思考の使い分け: Phase 1(設計)では拡張思考をフル活用し、ファイル名・エッジケース・Step 分割粒度・固有名詞(関数名/シート名/列名/行番号)を完全に確定させる。Phase 2(清書)の各 Step 内では拡張思考を最小限に抑え、Phase 1 で確定済みの内容の書き下しに徹する。出力途中で再考しない。
- テキスト報告の禁止: 「〜を作成します」等の text のみで tool_use なしに turn を終了しない。説明は 1 文以内。直ちに tool を呼ぶ。
- 4-5 分割の Write/Edit 実行: 2-1(骨格 ~20行) / 2-2(概要〜注意事項 ~300行) / 2-3a(エッジケース〜人間検討事項 ~200行) / 2-3b(実装プロンプト・推奨実行モデル・変更履歴 ~250行) / 2-4(
<details>プロンプト全文記録) に分割。1 回の Write/Edit は約 300 行以内。 - 各 Step で何を書くかを具体指示: Phase 1 で設計判断を完全に確定させ、Phase 2 実行時に再考しない。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
CLIエージェント「Claude Code」として、案件 F-32「プロダクト別P/L」の開発仕様書を作成してください。
開発仕様書を新規作成した場合は、docs/_config.json の nav 配列の適切なセクションにも必ず追記してください。
Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)
Grep vs Read 原則(厳守): Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず Read で行う。仕様書に記述するファイル名・関数名・定数名・シート名・メニュー名は全て Read で裏取りした文字列のみ引用する。「〜のはずだ」と推測した瞬間に手を止めて Read する(failure_patterns #18–#20 の直接対策)。
1-A: 案件要件の把握
docs/_internal/TODO_future.mdを Read し、F-32 の要件(概要・期待される効果・人間が検討すべき事項)を取得する。
1-B: 関連コードの調査(全項目を Read で確認)
① データアクセス層
000_infra/003_contracts.jsを Read し、JournalEntryDTOの全フィールド(特にPJ名・PJコード・科目名・収支区分・発生日(P/L計上日)の型と名称)を確認する。200_data/202_repository.jsを Read し、以下を確認する:JournalRepository.findAll()の戻り値形式{ headers, dtos }AccountRepositoryの_getSheet()(Utils.getSheetByKeyの引数)・findAll()・findAsMap()・_cache・resetCache()の実装パターン(ProjectRepository新設時の完全な参照モデル)readSheetAsDtos_の呼び出し方(private ヘルパー)
000_infra/004_utils.jsを Read し、Utils.parseDateToYm(val)・Utils.getSheetByKey(key, fallbackName)・Utils.logInfo(funcName, message)・Utils.logError(funcName, error, context)の引数と戻り値を確認する。000_infra/002_constants.jsを Read し、Constants.getParam(key, defaultVal)の実装(03_sys_paramsシートから値を読むパターン)を確認する。これが配賦パラメータ取得の正規パターン。
② 設定・DDL
100_config/101_sys_config.jsを Read し、以下を確認する:14_mst_projectの DDL スキーマ定義(setupAllSchemas内)— 「事業区分」列がすでに存在するか確認Utils.getSheetByKey()の第 1 引数として14_mst_projectに使われているシステムキー文字列(例:'MST_PROJ')— 存在するものを正確に引用すること。存在しない場合は「要新規登録」とメモするonOpen()のui.createMenu構造 — 新機能のメニュー追加先(実在するメニュー名のみ引用)- 既存の 6xx シートの DDL 定義パターン(新規シート
67_pl_by_productのDDL定義の参照モデル)
③ 既存レポートロジック
600_report/配下のファイル一覧を Grep で取得し、最大番号のファイルを確認する(新規ファイルの番号決定のため。例:608_datamart_render.jsが存在すれば新規は609_datamart_product_pl.js)。600_report/配下の61_pl_monthly生成に関わるファイル(Grep で特定)を Read し、科目マッピング・行構成・月別集計・シート書き込みパターンを把握する。600_report/の最大番号ファイル(render 系)を Read し、シートへの書き込み方式(setValuesの呼び出しパターン・行構成の組み立て方)を把握する。
④ F-06 PJ別配賦ロジック
400_domain/420_project_profitability.jsを Read し、共通費配賦の計算パターン(配賦基準の計算・ゼロ除算ガード・セグメント別集計の実装方法)を把握する。
1-C: Phase 2 着手前に確定すること
以下を Phase 1 中に確定し、Phase 2 で再調査・再考しない:
- 新規ファイルの番号(
600_report/の最大番号 + 1) 14_mst_projectのシステムキー(Read で確認した実在する文字列)14_mst_projectに「事業区分」列が存在するか否か(DDL変更の要否)ProjectRepository.findAsMap()のキー・値の型(AccountRepository.findAsMap()の実装を参照して決定)- メニュー追加先(
onOpen()の Read で確認した実在するメニュー名)
Phase 2: 仕様書の分割作成
出力先: docs/dev/dev_F-32_product_pl.md
絶対に 1 回のツール呼び出しで全内容を出力しない。以下の Step に分割して実行すること。
Step 2-1: 骨格の作成(File Write、~20行)
docs/_internal/dev_spec_prompt_template.md のセクション構成に準拠した全見出しのみの骨格ファイルを作成する。本文は空で可。必須セクション(この順序):
# F-32: プロダクト別P/L
## 概要
## 目的
## 現在のコード
## 修正方針
## 影響範囲
## 注意事項
## エッジケース
## 実データ検証
## 関連ドキュメント
## 人間が検討すべき事項
## 実装プロンプト(Claude Code 用)
## 推奨実行モデル
## 変更履歴
## 仕様書作成プロンプト
Step 2-2: 前半セクションの追記(File Edit または Bash heredoc、~300行)
以下の内容を記述する:
## 概要(テーブル形式): 案件ID=F-32、カテゴリ=FP&Aレポーティング、Phase・優先度(TODO_future.md から転記)、対象ファイル(Phase 1 で特定した実在ファイル名)を記載。
## 目的: プロダクト事業・受託事業・全社共通のセグメント別に損益を可視化し、事業単位の収益性把握と意思決定を支援する。
## 現在のコード: 既存の月別P/L(61_pl_monthly)が全社合算のみでセグメント別の把握が不可能であることを、Phase 1 で特定したファイル名・関数名・行番号付きで記述する。
## 修正方針: 以下の設計方針を記述する。各固有名詞は Phase 1 の Read 結果を使用すること(推測・記憶からの引用禁止):
- 前提DDL変更(別タスク管理):
14_mst_projectに「事業区分」列(値:プロダクト/受託/共通)を追加する。Phase 1 で「事業区分」列が未存在と確認した場合は101_sys_config.jsのsetupAllSchemasの DDL 定義変更を具体的に記述する。データ整備は人間による作業であることを明示する。 - 新規ファイル:
600_report/【Phase 1 で確定した番号】_datamart_product_pl.jsを新設する。 - 新規シート:
67_pl_by_productを出力先とする(既存のシート番号と衝突しないことを Phase 1 で確認済みとして記述)。レイアウトは61_pl_monthlyに準拠し、列構成をセグメント別(プロダクト事業 / 受託事業 / 全社共通 / 合計)とする。DDL を101_sys_config.jsの既存パターンに倣い追加する。 ProjectRepositoryの新設:200_data/202_repository.jsのAccountRepository直後に追加する。AccountRepositoryの実装を完全に踏襲し、以下の構造とする:_getSheet():Utils.getSheetByKey('【Phase 1 で確認したシステムキー】', '14_mst_project')findAll():readSheetAsDtos_を使用findAsMap(): キー=PJ名、値={ category: String(dto['事業区分'] || '').trim() }のマップを返す。有効フラグ=FALSE の行をスキップ。_cacheでキャッシュする。_cache: null/resetCache()を設ける
- 計算ロジック(メイン関数
buildProductPl()):JournalRepository.findAll()で全仕訳データ取得ProjectRepository.findAsMap()でPJ名→{ category }マップ取得AccountRepository.findAsMap()で科目名 →{ stmt, cat }マップ取得- 各仕訳に事業区分タグを付与(
PJ名が空またはマスタ未登録またはcategoryが空 → 「全社共通」) - 売上・直接費: 事業区分が明確な仕訳はそれぞれのセグメントに直接計上
- 開発人件費の按分:
Constants.getParam('PRODUCT_PL_DEV_ALLOC_BASIS', '売上高比')で按分基準取得。総売上ゼロ月はゼロ除算回避として全額「全社共通」計上(failure_patterns #2 参照) - その他共通費の配賦:
Constants.getParam('PRODUCT_PL_COMMON_ALLOC_BASIS', '売上高比')で基準取得。420_project_profitability.jsの配賦パターンを参照。同ゼロ除算ガードを適用 - 二重計上チェック: セグメント合計 vs 全社合計の差異が絶対値 1 円超なら
Utils.logInfo('buildProductPl', '差異警告: ' + ...)で出力。処理は継続する 67_pl_by_productシートに Phase 1 で確認したレンダリングパターンで書き込む
- メニュー登録:
101_sys_config.jsの Phase 1 で特定したメニュー追加先に、buildProductPlのトリガー項目を追加する。
## 影響範囲:
- 新規作成:
600_report/【番号】_datamart_product_pl.js - 追記:
200_data/202_repository.js(ProjectRepositoryをAccountRepository直後に追加) - 追記:
100_config/101_sys_config.js(67_pl_by_productDDL 定義追加・メニュー追加、必要なら14_mst_projectDDL に「事業区分」列追加) - 変更なし:
000_infra/配下の全ファイル
## 注意事項:
Constants.getParam()が参照する03_sys_paramsにPRODUCT_PL_DEV_ALLOC_BASIS・PRODUCT_PL_COMMON_ALLOC_BASISキーを初期登録する手順を仕様書に明記すること(未登録でもデフォルト値「売上高比」で動作するが、初回setupAllSchemas後に手動登録を推奨)JournalEntryDTOの発生日(P/L計上日)は Date 型と文字列型が混在しうるため、月集計前に必ずUtils.parseDateToYm()で正規化する(failure_patterns #17 参照)- 開発人件費の対象科目名は
AccountRepository.findAsMap()経由で参照し、科目名をコード内にハードコードしない。対象科目リストは03_sys_paramsで設定可能な設計とすること ProjectRepository.findAsMap()のキャッシュはresetCache()で初期化可能にする(テストの干渉防止)- 二重計上チェックの「1円」許容範囲は浮動小数点の丸め誤差を考慮したもの。チェック失敗時も出力は継続し、警告ログのみ出力する(処理中断しない)
Step 2-3a: エッジケース〜人間検討事項の追記(File Edit または Bash、~200行)
(本仕様書の ## エッジケース / ## 実データ検証 / ## 関連ドキュメント / ## 人間が検討すべき事項 セクションに記載の内容と同等のテーブル・箇条書きを出力)
Step 2-3b: 実装プロンプト・推奨実行モデル・変更履歴の追記(File Edit または Bash、~250行)
実装プロンプトは バッククォートで囲まず、行頭 4 スペースインデントで出力すること。(本仕様書の ## 実装プロンプト セクションの内容を出力)
## 推奨実行モデル(テーブル形式)・## 変更履歴(テーブル形式) を追記する。
Step 2-4: 仕様書作成プロンプトの記録(File Edit または Bash)
仕様書末尾の ## 仕様書作成プロンプト セクションに、この <instruction> タグ内のプロンプト全文を <details><summary>展開して表示</summary> で囲んで追記する。
Phase 3: 保存・登録・コミット
3-B: docs/_config.json への登録(必須)
追記前に docs/_config.json を Read して既存エントリの最大連番を確認してから追記する:
{ "file": "dev/dev_F-32_product_pl.md", "title": "E.5.XX F-32 プロダクト別P/L" }
(XX は既存エントリの最大連番 + 1)
3-C: changelog 追記
docs/_internal/changelog.md のヘッダー直後に追記:
| 2026-04-20 | [dev_F-32_product_pl.md](dev_mas-032_product_pl.md) | 初版作成。プロダクト別P/L仕様書。ProjectRepository新設・609_datamart_product_pl.js新規実装の設計を含む |
3-D: コミット&プッシュ
git add docs/dev/dev_F-32_product_pl.md docs/_internal/changelog.md docs/_config.json
git commit -m "docs: F-32 プロダクト別P/Lの開発仕様書を作成
プロダクト/受託/全社共通セグメント別P/L生成ロジックの仕様書。
ProjectRepository新設・609_datamart_product_pl.js新規実装・
14_mst_projectへの事業区分列DDL追加の設計を含む。
https://claude.ai/code/session_XXXXX"
git push -u origin {現在のブランチ}
📌 取り込み時の注記 (2026-06-02 sub 復元)
本仕様書は旧 F-番号体系で作成され PR 未マージのまま孤立していたドラフトを、
origin/docs/dev-*ブランチから内容無改変で復元し、案件ID のみ MAS 体系へ正規化したもの。status: Open(未実装)。⚠️ ファイル番号ドリフト: 本文「対象ファイル」が指す
600_report/610〜612_*.jsは現行 main で 別機能に使用済み(610=投資分析/MAS-013・611=財務モデリング/MAS-010・612=採用sim/MAS-012)。 実装時にファイル番号の再割当が必要。