MAS-126: 発注→INV自動展開(継続契約)
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-126 |
| カテゴリ | 新機能 |
| Phase | 未定 |
| 優先度 | 未定 |
| 所要時間 | M |
| 対象ファイル | 400_domain/408_rpa_ord_auto_expand.js(新規)、400_domain/407_rpa_orchestrator.js(追記)、000_infra/002_constants.js(MENU_DEFINITION 追記) |
| 前提案件 | なし |
目的
31_wrk_order に登録された継続契約の発注(ORD)から、開始年月〜終了年月 の範囲で月次 INV を自動生成し、手動起票による漏れ・重複・ミスを排除する。生成された INV は 請求ステータス = '未処理' 固定とし、Human-in-the-Loop 原則に従いユーザーが目視確認してから承認する運用フローを前提とする。
現在のコード
現在は 32_wrk_invoice の INV を手動で 1 件ずつ起票している。継続契約 ORD に対する月次 INV 自動生成機能は未実装。コードスニペットは不要(新規追加のため)。
修正方針
変更 1: 400_domain/408_rpa_ord_auto_expand.js を新規作成
新関数 generateOrdAutoExpand() を実装する(グローバル公開関数、メニュー呼び出し可能)。
変更 2: 400_domain/407_rpa_orchestrator.js に RPAService エントリを追加
generateOrdExpand: function() { return generateOrdAutoExpand(); },
変更 3: 000_infra/002_constants.js の MENU_DEFINITION に新メニュー項目を追加
Constants.MENU_DEFINITION 配列に新カテゴリ「📋 ORD管理」を追加し、以下のアイテムを登録する:
{
category: '📋 ORD管理',
items: [
{ label: '継続契約→INV自動展開', funcName: 'generateOrdAutoExpand', description: '契約形態=継続のORDから月次INVを一括生成' },
]
},
処理フロー
LockService.getScriptLock()で排他ロックを取得。取得失敗時は処理を中断しUtils.logError()でログを残す。OrderRepository.findAll()で全 ORD を取得し、dto['契約形態'] === '継続'かつdto['発注ステータス'] === '発注済'かつdto['有効フラグ'] === trueでフィルタ。InvoiceRepository.findAll()で全 INV を取得し、冪等性チェック用 Set を構築。キー形式:dto['親発注ID(ORD)'] + '_' + Utils.parseDateToYm(dto['発生日(P/L計上日)'])- 対象 ORD ごとに
Utils.parseDateToYm()で開始年月・終了年月を正規化し、Utils.addMonths()で月次ループ(上限:Constants.MONTH_ITERATION_LIMIT = 120)。 - 各月の InvoiceDTO を以下のフィールドマッピングテーブルに従い生成する。
- 冪等性チェック: Set にキーが存在する月はスキップ(二重計上防止)。
- 生成した InvoiceDTO 配列を
InvoiceRepository.append()で書き込む。 Utils.toastResult(FUNC, '生成件数: ' + count + '件')でトースト通知。Utils.auditLog('RUN', ...)で監査ログを記録。
OrderDTO → InvoiceDTO フィールドマッピングテーブル
| InvoiceDTO フィールド | 導出元 / 計算式 | デフォルト値 |
|---|---|---|
有効フラグ | — | true |
請求ID(INV) | RpaCommon.generateInvId(dateStr, offset) で採番 | — |
親発注ID(ORD) | OrderDTO['発注ID(ORD)'] | — |
起票日時 | new Date() | — |
起票者 | 'RPA_ORD_AUTO' | — |
申請種別 | — | '請求書受領(AP)' |
発生日(P/L計上日) | 月次ループの curYm(例: '2026-04')を curYm + '-01' に変換した文字列 | — |
決済日_計画 | — | ''(空文字) |
請求ステータス | — | '未処理' |
収支区分 | — | '支出' |
取引先名 | OrderDTO['取引先名'] | — |
PJ名 | OrderDTO['PJ名'](空の場合は InvoiceRepository.append() が '指定なし_共通費など' を自動付与) | — |
組織名 | OrderDTO['組織名'] | — |
諸表区分 | InvoiceRepository.append() が科目マスタから自動付与 | — |
大分類 | InvoiceRepository.append() が科目マスタから自動付与 | — |
科目名 | ''(空文字)※別途ユーザーが入力 or 別案件で ORD に科目列を追加して転記 | '' |
税区分 | — | '課税' |
通貨 | — | 'JPY' |
税抜金額_計画 | Math.round(OrderDTO['税抜金額_発注'] / 継続月数) ※継続月数 = 開始年月から終了年月の月数差 + 1 | — |
消費税額_計画 | Math.round(OrderDTO['消費税額_発注'] / 継続月数) | — |
税込金額_計画 | 税抜金額_計画 + 消費税額_計画 ※独立して丸め計算(端数は最終月に集計しない) | — |
未決済残高(自動計算) | — | '' |
決済手段 | — | '' |
摘要 | OrderDTO['契約・件名'] + ' ' + curYm | — |
証憑URL | OrderDTO['証憑URL'] | — |
自動仕訳JNL_ID | — | '' |
InvoiceRepository.append() の自動付与仕様
InvoiceRepository.append() は 科目名 フィールドを元に AccountRepository.findAsMap() を呼び出し、諸表区分・大分類 を自動付与する。科目名 が空文字のまま append() を呼ぶと 諸表区分・大分類 は付与されないため、財務諸表の分類が機能しない点に注意(「人間が検討すべき事項」参照)。
INV ID 発番方法
RpaCommon.generateInvId(dateStr, offset) を使用する。dateStr は実行日の yyyyMMdd 形式文字列(Utilities.formatDate(new Date(), tz, 'yyyyMMdd'))。offset はバッチ内の連番(0始まり)。戻り値: 'INV_yyyyMMdd_NNNN'。
影響範囲
| ファイル / シート | 変更内容 |
|---|---|
400_domain/408_rpa_ord_auto_expand.js | 新規作成。generateOrdAutoExpand() を実装 |
400_domain/407_rpa_orchestrator.js | RPAService に generateOrdExpand を追記 |
000_infra/002_constants.js | MENU_DEFINITION に「📋 ORD管理」カテゴリを追加 |
32_wrk_invoice シート | 新規 INV 行が末尾に追記される |
| 既存機能 | 変更なし(既存 Repository・RpaCommon は読み取りのみ使用) |
注意事項
- 途中解約時に既存生成済み INV を無効化する場合は、ユーザーが手動で
有効フラグを FALSE に変更すること(本案件スコープ外)。 契約形態の格納値は実データ検証で確認した表記と完全一致させること('継続'と'継続(定期)'等の表記ゆれに注意)。LockService.getScriptLock()のロック取得失敗時は処理を中断しUtils.logError()でログを残すこと。
影響範囲
注意事項
エッジケース
| 条件 | 処理内容 | 理由 |
|---|---|---|
税抜金額_発注 < 0 | 当該 ORD をスキップ、Utils.logError() でログ出力 | 負の月次 INV は会計上の異常値 |
税抜金額_発注 = 0 | 月額 0 円の INV を生成(スキップしない) | 0 円仕訳として記録する(将来の変更に備えた継続契約の存在証明) |
開始年月 > 終了年月(期間不正) | 当該 ORD をスキップ、Utils.logInfo() で警告ログ出力 | 月次ループが無限ループになるのを防ぐ |
継続月数 ≤ 0(Utils.addMonths の差分が 0 以下) | ゼロ除算回避のためスキップ、Utils.logInfo() でログ出力 | 月次按分の除算でゼロ除算が発生する |
| 冪等性キーが既存 INV に存在する月 | 当該月の INV 生成をスキップ | 二重計上防止 |
有効フラグ = FALSE の ORD | フィルタ段階で除外済みのためスキップロジック不要 | OrderRepository.findAll() 後のフィルタで除外 |
実データ検証
実装前に MCP 等で以下を確認すること:
31_wrk_orderの「契約形態」列の実際の格納値('継続'と'継続(定期)'等の表記ゆれがないか)31_wrk_orderの「発注ステータス」列の実際の格納値('発注済'の表記を確認)32_wrk_invoiceの「親発注ID(ORD)」列の値形式(冪等性キー設計の裏取り。'ORD_YYYYMMDD_NNNN'形式であることを確認)11_mst_accountに継続契約向け科目(例: 外注費・支払手数料等)が登録済みかを確認
関連ドキュメント
| ドキュメント | 用途 |
|---|---|
| E.2.17 MAS-122 ORD↔INV 消化状況の整合性チェック | ORD と INV の紐付けロジック参照 |
| E.2.18 MAS-124 31タブ発注残高エイジング&超過発注アラート | 継続発注の残高管理 |
| E.2.14 MAS-114 インボイス適格請求書の要件チェック | 生成 INV のバリデーション |
| E.6.2 MAS-118 RPA対象月列 | RPA月次処理の実装パターン参照 |
| MAS-125 発注ステータス管理ワークフロー | 発注ステータス = '発注済' のフィルタ前提 |
人間が検討すべき事項
科目名の決定方針(最重要)
InvoiceRepository.append() は 科目名 を元に 諸表区分・大分類 を自動付与するため、科目名 が空のまま生成すると財務諸表分類が機能しない。
推奨方針: 31_wrk_order シートに「科目名」列を追加する DDL 変更(別案件)を行い、その値を INV に転記する。実装時は 科目名 を空で生成しつつ、請求ステータス = '未処理' のレビュー工程でユーザーが手動設定する運用とする。
代替方針: 生成後にユーザーが手動で科目名を設定する(請求ステータス = '未処理' のレビュー工程で対応)。財務諸表への反映は科目名設定後に Action A を再実行。
Human-in-the-Loop 運用フロー
生成される 請求ステータス は '未処理' 固定。ユーザーが目視確認後に手動承認する運用フローを前提とする(PRD プロダクトポリシー準拠)。
途中解約時のルール
TODO_future.md に記載の「途中解約時のルール」は本案件スコープ外。既存生成済み INV の無効化は手動運用(有効フラグ を FALSE に変更)とする。
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-126「発注→INV自動展開(継続契約)」を実装してください。
## 実行前タスク
以下のファイルを Read して内容を確認してから実装すること:
- `000_infra/003_contracts.js` — OrderDTO / InvoiceDTO の全フィールド名を確認
- `200_data/202_repository.js` — OrderRepository.findAll() / InvoiceRepository.findAll() / InvoiceRepository.append() の仕様を確認
- append() は科目名を元に諸表区分・大分類を自動付与し、PJ名未指定時は '指定なし_共通費など' をデフォルト設定する
- `400_domain/400_rpa_common.js` — RpaCommon.generateInvId() の引数シグネチャを確認
- `400_domain/407_rpa_orchestrator.js` — RPAService の既存エントリ構造を確認し、追記箇所を特定
- `000_infra/002_constants.js` — MENU_DEFINITION の構造を確認し、新カテゴリ追記箇所を特定。MONTH_ITERATION_LIMIT の値を確認
- `000_infra/004_utils.js` — Utils.parseDateToYm() / Utils.addMonths() / Utils.toastResult() / Utils.logInfo() / Utils.logError() の引数シグネチャを確認
## 修正対象ファイル
- `400_domain/408_rpa_ord_auto_expand.js` — 新規作成
- `400_domain/407_rpa_orchestrator.js` — RPAService への generateOrdExpand エントリ追記のみ
- `000_infra/002_constants.js` — MENU_DEFINITION への「📋 ORD管理」カテゴリ追加のみ
## 実装内容
### `400_domain/408_rpa_ord_auto_expand.js`
```
function generateOrdAutoExpand() {
const FUNC = 'generateOrdAutoExpand';
// 1. LockService で排他ロック
const lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) {
Utils.logError(FUNC, new Error('排他ロック取得失敗。他の処理が実行中です。'));
return;
}
try {
Utils.logInfo(FUNC, '処理開始');
const tz = Session.getScriptTimeZone();
const now = new Date();
const dateStr = Utilities.formatDate(now, tz, 'yyyyMMdd');
RpaCommon.resetIdCache();
// 2. 全 ORD を取得し継続・発注済・有効のものをフィルタ
const ordResult = OrderRepository.findAll();
const targets = ordResult.dtos.filter(function(dto) {
if (dto['有効フラグ'] === false || String(dto['有効フラグ']).toUpperCase() === 'FALSE') return false;
if (dto['契約形態'] !== '継続') return false;
if (dto['発注ステータス'] !== '発注済') return false;
return true;
});
// 3. 全 INV を取得し冪等性 Set を構築
const invResult = InvoiceRepository.findAll();
const existingKeys = new Set();
invResult.dtos.forEach(function(dto) {
const key = String(dto['親発注ID(ORD)'] || '') + '_' + Utils.parseDateToYm(dto['発生日(P/L計上日)']);
if (key !== '_') existingKeys.add(key);
});
// 4-7. 対象 ORD ごとに月次 INV を生成
const newDtos = [];
let offset = 0;
targets.forEach(function(ord) {
const startYm = Utils.parseDateToYm(ord['開始年月']);
const endYm = Utils.parseDateToYm(ord['終了年月']);
// エッジケース: 期間不正チェック
if (!startYm || !endYm || startYm > endYm) {
Utils.logInfo(FUNC, '期間不正のためスキップ: ' + String(ord['発注ID(ORD)']));
return;
}
// 継続月数を計算
const [sy, sm] = startYm.split('-').map(Number);
const [ey, em] = endYm.split('-').map(Number);
const months = (ey - sy) * 12 + (em - sm) + 1;
// エッジケース: 月数 <= 0
if (months <= 0) {
Utils.logInfo(FUNC, '継続月数不正のためスキップ: ' + String(ord['発注ID(ORD)']));
return;
}
const baseAmt = ord['税抜金額_発注'];
// エッジケース: 金額マイナス
if (typeof baseAmt === 'number' && baseAmt < 0) {
Utils.logError(FUNC, new Error('税抜金額_発注が負のためスキップ: ' + String(ord['発注ID(ORD)'])));
return;
}
const monthlyExcl = Math.round(baseAmt / months);
const monthlyTax = Math.round((ord['消費税額_発注'] || 0) / months);
const monthlyIncl = monthlyExcl + monthlyTax;
let limit = 0;
let curYm = startYm;
while (curYm <= endYm && limit < Constants.MONTH_ITERATION_LIMIT) {
limit++;
const idempotencyKey = String(ord['発注ID(ORD)']) + '_' + curYm;
if (!existingKeys.has(idempotencyKey)) {
const invDto = {
'有効フラグ': true,
'請求ID(INV)': RpaCommon.generateInvId(dateStr, offset++),
'親発注ID(ORD)': ord['発注ID(ORD)'],
'起票日時': now,
'起票者': 'RPA_ORD_AUTO',
'申請種別': '請求書受領(AP)',
'発生日(P/L計上日)': curYm + '-01',
'決済日_計画': '',
'請求ステータス': '未処理',
'収支区分': '支出',
'取引先名': ord['取引先名'],
'PJ名': ord['PJ名'] || '',
'組織名': ord['組織名'],
'科目名': '',
'税区分': '課税',
'通貨': 'JPY',
'税抜金額_計画': monthlyExcl,
'消費税額_計画': monthlyTax,
'税込金額_計画': monthlyIncl,
'未決済残高(自動計算)': '',
'決済手段': '',
'摘要': String(ord['契約・件名'] || '') + ' ' + curYm,
'証憑URL': ord['証憑URL'] || '',
'自動仕訳JNL_ID': '',
};
newDtos.push(invDto);
existingKeys.add(idempotencyKey);
}
curYm = Utils.addMonths(curYm, 1);
}
});
// 7. append
const count = InvoiceRepository.append(newDtos);
// 8. トースト通知 & 監査ログ
Utils.toastResult(FUNC, '生成完了: ' + count + '件の INV を生成しました。');
Utils.auditLog('RUN', '32_wrk_invoice', '', '', FUNC, '', { generated: count }, 'Env=' + Env.name());
Utils.logInfo(FUNC, '処理完了: ' + count + '件');
} catch (e) {
Utils.logError(FUNC, e);
SpreadsheetApp.getUi().alert('🚨 ' + FUNC + ' でエラーが発生しました', e.message, SpreadsheetApp.getUi().ButtonSet.OK);
} finally {
lock.releaseLock();
}
}
```
## 制約
- シートを直接操作しない。読み取りは OrderRepository.findAll() / InvoiceRepository.findAll()、書き込みは InvoiceRepository.append() のみ使用
- 既存関数・既存シートのスキーマを変更しない(科目名列追加は別案件)
- 列番号のハードコード禁止。ヘッダー名ベースで参照する(Repository 経由で自動対応)
## エッジケース
| 条件 | 処理内容 | 理由 |
|---|---|---|
| 税抜金額_発注 < 0 | 当該 ORD をスキップ、Utils.logError() でログ出力 | 負の月次 INV は会計上の異常値 |
| 税抜金額_発注 = 0 | 月額 0 円の INV を生成(スキップしない) | 0 円仕訳として記録する |
| 開始年月 > 終了年月(期間不正) | 当該 ORD をスキップ、Utils.logInfo() で警告ログ出力 | 月次ループが無限ループになるのを防ぐ |
| 継続月数 ≤ 0 | ゼロ除算回避のためスキップ、Utils.logInfo() でログ出力 | 月次按分の除算でゼロ除算が発生する |
| 冪等性キーが既存 INV に存在する月 | 当該月の INV 生成をスキップ | 二重計上防止 |
| 有効フラグ = FALSE の ORD | フィルタ段階で除外済みのためスキップロジック不要 | OrderRepository.findAll() 後のフィルタで除外 |
## 動作確認
1. npm run push:dev でデプロイ
2. メニュー「📋 ORD管理」→「継続契約→INV自動展開」を実行
3. 32_wrk_invoice に期待する月数分の INV が生成されたことを確認
4. 同じ ORD に対して再実行し、INV が重複しないこと(冪等性)を確認
5. エッジケース対象 ORD(金額マイナス・期間不正)でスキップされることをログで確認
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| 実行前タスク(Read/調査) | あり | フィールドマッピング・冪等性キー確定 |
| 実装(Write/Edit) | なし | 確定済み内容の書き下し |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| Phase 1(Read・調査・設計) | Sonnet | Repository / Utils の組み合わせ確認など中程度の判断が必要 |
| Phase 2(仕様書 Write/Edit) | Sonnet | 確定済み内容の書き下し |
| Phase 3(コード実装) | Sonnet | 既存 Repository・Utils を組み合わせた中程度の判断。コードが完全定義済みなら Haiku でも可 |
変更履歴
| 日付 | 内容 |
|---|---|
| 2026-04-21 | 初版作成 |
仕様書作成プロンプト(再現性・監査性のため)
展開して表示
<instruction>
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
1. **拡張思考の使い分け**: Phase 1(調査・設計)ではフル活用。ファイル名・関数名・行番号・エッジケース・Step 分割粒度・固有名詞(フィールド名/定数名/メニューラベル)を**ここで完全確定**させる。Phase 2(清書)では各 Step 内で拡張思考を最小限に抑え、Phase 1 確定済み内容の書き下しに徹する。出力途中で再考しない。
2. **テキスト報告の禁止**: 「〜を作成します」等、text のみで tool_use なしに turn を終了しない。説明は 1 文以内。直ちに tool を呼ぶ。
3. **4-5 分割の Write/Edit 実行**: Step 2-1(骨格 ~20行)/ 2-2(概要〜注意事項 ~300行)/ 2-3a(エッジケース〜人間検討事項 ~200行)/ 2-3b(実装プロンプト〜変更履歴 ~250行)/ 2-4(`<details>` プロンプト記録)に分割。1 回の Write/Edit は 300 行以内。
4. **各 Step で何を書くかを具体指示**: 設計判断を Phase 2 実行時に持ち込まない。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 S-54「発注→INV自動展開(継続契約)」の開発仕様書を作成してください。
作成後は `docs/_config.json` の `nav` 配列の適切なセクションに必ず登録すること。
---
## Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)
以下を順番に Read / Grep し、Phase 2 で設計判断を再考しなくて済む状態まで確定させること。
**Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず Read で裏取り。推測した瞬間に手を止めて Read する。**
### 1-A: 案件定義の読み込み
- `docs/_internal/TODO_future.md` を Grep し、S-54 の案件名・概要・人間が検討すべき事項を取得する。
### 1-B: プロジェクト規約の読み込み
- `CLAUDE.md` を Read し、コーディング規約・ファイル番号体系・メニュー登録ルール・マイグレーションガイドラインを把握する。
### 1-C: 既存コードの調査(Read で必ず裏取り。以下を順に実行)
1. **`000_infra/003_contracts.js`** — `OrderDTO` と `InvoiceDTO` の全フィールド名を確認する。仕様書に記載するフィールド名はすべてここから引用すること(推測・造語禁止)。
2. **`200_data/202_repository.js`** — 以下を確認する:
- `OrderRepository.findAll()` の戻り値型(`{ headers, dtos }` の構造)
- `InvoiceRepository.findAll()` の戻り値型
- `InvoiceRepository.append()` が自動付与するフィールド(`諸表区分`・`大分類` を AccountRepository 経由で付与すること、`PJ名` 未指定時のデフォルト値 `'指定なし_共通費など'` を把握する)
3. **`400_domain/407_rpa_orchestrator.js`** — 以下を確認する:
- `RPAService` の公開関数の命名パターン(public か private `_` suffix か)
- 既存のメニュー呼び出し可能関数の構造(ラッパー関数の有無)
- 新関数をこのファイルに追記するか、新規ファイル `408_*.js` を作成するかを判断する
4. **`400_domain/400_rpa_common.js`** — INV の ID 発番方法を確認する(`RpaCommon` に `generateNextId_` 等のヘルパーがあるか、`Utils.getIdPrefixConfig()` との組み合わせを把握する)。
5. **`100_config/101_sys_config.js`** — `onOpen()` の `ui.createMenu` を Read し、新機能のメニュー登録先(メニュー名・区切り位置・隣接ラベル)を**実在する文字列のまま**確認する。造語禁止。
6. **`000_infra/004_utils.js`** — `Utils.parseDateToYm()`・`Utils.addMonths()`・`Utils.toastResult()`・`Utils.logInfo()`・`Utils.logError()` の引数シグネチャを確認する。
7. **`000_infra/002_constants.js`** — `Constants.SHEET_DEFAULTS` の `32_wrk_invoice` エントリを Read し、`smartAddRow` 経由でのデフォルト値マップを確認する(`InvoiceRepository.append()` が参照しないことにも注意)。
### 1-D: 参考テンプレートの読み込み
- `docs/dev/` 配下の新機能系仕様書を 1 件 Read し、セクション構成・実装プロンプトのフォーマット(行頭 4 スペースインデント)を把握する。
### Phase 1 で確定させること(Phase 2 では再考しない)
- 新関数の配置ファイル・関数名(menu-callable なら public、内部ヘルパーなら `_` suffix)
- メニュー追加先のラベル文字列(`onOpen()` から引用した実在する文字列)
- OrderDTO → InvoiceDTO の全フィールドマッピング(`発生日(P/L計上日)` の値形式、`税抜金額_計画`・`消費税額_計画`・`税込金額_計画` の月次按分計算式)
- INV の ID 発番方法(`RpaCommon` 等の実在する関数名)
- 冪等性チェックキーの正確な形式(実際の InvoiceDTO フィールド名を使った文字列、例: `dto['親発注ID(ORD)'] + '_' + Utils.parseDateToYm(dto['発生日(P/L計上日)'])`)
- 科目名の決定方針(DDL変更案 or 代替案)
- `InvoiceRepository.append()` が `科目名` 空のまま呼ばれると `諸表区分`・`大分類` が付与されない点の影響確認
---
## Phase 2: 仕様書の分割作成
出力先: `docs/dev/dev_mas-126_auto_inv_from_ord.md`
**1 回の Write/Edit は 300 行以内。必ず以下の Step に分割すること。**
### Step 2-1: 骨格の作成 (Write ~20行)
見出しのみ。本文は空で可。必須セクション順:
MAS-126: 発注→INV自動展開(継続契約)
概要 / ## 目的 / ## 現在のコード / ## 修正方針 / ## 影響範囲 / ## 注意事項
エッジケース / ## 実データ検証 / ## 関連ドキュメント / ## 人間が検討すべき事項
実装プロンプト(Claude Code 用)/ ## 推奨実行モデル / ## 変更履歴
仕様書作成プロンプト(再現性・監査性のため)
### Step 2-2: 前半セクションの追記 (Edit or Bash ~300行)
Phase 1 で確定済みの内容を書き下す。再考しない。以下を書く:
- **概要**: テーブル(案件ID=S-54, カテゴリ=新機能, Phase=未定, 優先度=未定, 所要時間=M, 対象ファイル=Phase 1 で確定したファイルパス, 前提案件=なし)
- **目的**: 継続契約 ORD から月次 INV を自動生成し、手動起票ミス・漏れを排除する旨を 1〜3 文で記述
- **現在のコード**: 「現在は INV を手動で 1 件ずつ起票している。継続契約に対する月次 INV 自動生成機能は未実装」と記述。コードスニペットは不要(新規追加のため)
- **修正方針**: 以下の構成で記述
- 変更 1: Phase 1 で確定した配置ファイルに新関数(Phase 1 で確定した実在する関数名)を追加
- 変更 2: `100_config/101_sys_config.js` の `onOpen()` に新メニュー項目を追加(Phase 1 で確定した実在するラベル文字列)
- 処理フロー(箇条書き):
1. `LockService.getScriptLock()` で排他ロック取得
2. `OrderRepository.findAll()` で全 ORD 取得 → `契約形態 === '継続'`・`発注ステータス === '発注済'`・`有効フラグ === true` でフィルタ
3. `InvoiceRepository.findAll()` で全 INV 取得 → 冪等性 Set を構築(キー形式は Phase 1 で確定した文字列)
4. 対象 ORD ごとに `Utils.parseDateToYm()` で `開始年月`〜`終了年月` を正規化し、`Utils.addMonths()` で月次ループ
5. 各月の InvoiceDTO を生成(フィールドマッピングは下記テーブル参照)
6. 冪等性チェック:Set にキーが存在する月はスキップ
7. 生成した InvoiceDTO 配列を `InvoiceRepository.append()` で書き込み
8. `Utils.toastResult()` で生成件数をトースト通知
- **OrderDTO → InvoiceDTO フィールドマッピングテーブル**(Phase 1 で確定した実在するフィールド名のみ使用。造語禁止): 導出元・計算式・デフォルト値を列ごとに明記。`消費税額_計画`・`税込金額_計画` の月次按分計算式も含む
- `InvoiceRepository.append()` が `科目名` を元に `諸表区分`・`大分類` を自動付与する仕様を明記(科目名が空のまま append すると自動付与されない)
- INV ID 発番方法(Phase 1 で確定した実在する関数名)を明記
- **影響範囲**: 変更ファイル(Phase 1 で確定)、`32_wrk_invoice` シートへの行追記、既存機能への影響なし
- **注意事項**:
- 途中解約時に既存生成済み INV を無効化する場合はユーザーが手動で `有効フラグ` を FALSE に変更すること(本案件スコープ外)
- `契約形態` の格納値は実データ検証で確認した表記と完全一致させること(表記ゆれに注意)
- `LockService` のロック取得失敗時は処理を中断し `Utils.logError()` でログを残すこと
### Step 2-3a: エッジケース〜人間検討事項の追記 (Edit or Bash ~200行)
Phase 1 で確定済みの内容を書き下す。以下を書く:
- **エッジケース**: テーブル形式(条件 | 処理内容 | 理由)
- `税抜金額_発注` < 0: 当該 ORD をスキップ、`Utils.logError()` でログ出力
- `税抜金額_発注` = 0: 月額 0 円の INV を生成(スキップしない。0 円仕訳として記録する)
- `開始年月 > 終了年月`(期間不正): 当該 ORD をスキップ、`Utils.logInfo()` で警告ログ出力
- 継続月数 ≤ 0(`Utils.addMonths` の差分が 0 以下): ゼロ除算回避のためスキップ、`Utils.logInfo()` でログ出力
- 冪等性キーが既存 INV に存在する月: 当該月の INV 生成をスキップ(二重計上防止)
- `有効フラグ` = FALSE の ORD: フィルタ段階で除外済みのためスキップロジック不要
- **実データ検証**: MCP 等で実装前に確認すべき項目
- `31_wrk_order` の「契約形態」列の実際の格納値(`'継続'` と `'継続(定期)'` 等の表記ゆれがないか)
- `31_wrk_order` の「発注ステータス」列の実際の格納値(`'発注済'` の表記を確認)
- `32_wrk_invoice` の「親発注ID(ORD)」列の値形式(冪等性キー設計の裏取り)
- `11_mst_account` に継続契約向け科目(例: 外注費・支払手数料等)が登録済みかを確認
- **関連ドキュメント**: Phase 1 で発見した関連仕様書リンクをテーブル形式で記載
- **人間が検討すべき事項**:
- **科目名の決定方針(最重要)**: `InvoiceRepository.append()` は `科目名` を元に `諸表区分`・`大分類` を自動付与するため、空のまま生成すると財務諸表分類が機能しない。**推奨方針**: `31_wrk_order` シートに「科目名」列を追加する DDL 変更(別案件)を行い、その値を INV に転記する。**代替方針**: 生成後にユーザーが手動で科目名を設定する(`請求ステータス = '未処理'` のレビュー工程で対応)
- 生成される `請求ステータス` は `'未処理'` 固定(Human-in-the-Loop)。ユーザーが目視確認後に手動承認する運用フローを前提とする
- `TODO_future.md` に記載の「途中解約時のルール」は本案件スコープ外。既存生成済み INV の無効化は手動運用とする
### Step 2-3b: 実装プロンプト〜変更履歴の追記 (Edit or Bash ~250行)
行頭 4 スペースインデント。バッククォートで囲まない。以下を書く:
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 S-54「発注→INV自動展開(継続契約)」を実装してください。
## 実行前タスク
(Phase 1 で特定した全ファイルを列挙し、各ファイルで確認すべきポイントを明記)
## 修正対象ファイル
(Phase 1 で確定したファイルパスと「のみ」または「への追記」を明示)
## 実装内容
(処理フロー・OrderDTO→InvoiceDTO フィールドマッピングテーブル・冪等性ロジック・ID発番方法を具体的に記述)
## 制約
- シートを直接操作しない。読み取りは OrderRepository.findAll() / InvoiceRepository.findAll()、書き込みは InvoiceRepository.append() のみ使用
- 既存関数・既存シートのスキーマを変更しない(科目名列追加は別案件)
- 列番号のハードコード禁止。ヘッダー名ベースで参照する
## エッジケース
(Step 2-3a のエッジケーステーブルをそのまま転記)
## 動作確認
1. npm run push:dev でデプロイ
2. メニューから新機能を実行(Phase 1 で確定した実在するメニューラベルを記載)
3. 32_wrk_invoice に期待する月数分の INV が生成されたことを確認
4. 同じ ORD に対して再実行し、INV が重複しないこと(冪等性)を確認
5. エッジケース対象 ORD(金額マイナス・期間不正)でスキップされることをログで確認
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| 実行前タスク(Read/調査) | あり | フィールドマッピング・冪等性キー確定 |
| 実装(Write/Edit) | なし | 確定済み内容の書き下し |
- **推奨実行モデル**: テーブル(工程 | 推奨モデル | 理由)。既存 Repository・Utils を組み合わせた中程度の判断 → Sonnet
- **変更履歴**: `| 2026-04-20 | 初版作成 |`
### Step 2-4: 仕様書作成プロンプトの記録 (Edit or Bash)
`## 仕様書作成プロンプト(再現性・監査性のため)` セクションに `<details><summary>展開して表示</summary>` ブロックを追加し、この `<instruction>` 全文をそのまま記録する。
---
## Phase 3: 保存・登録・コミット
### 3-A: `docs/_config.json` にナビゲーション登録(必須)
`nav` 配列の §E.6(パイプライン・RPA・外部連携)セクションに追加(既存エントリの連番を確認してから採番する):
```json
{ "file": "dev/dev_mas-126_auto_inv_from_ord.md", "title": "E.6.X MAS-126 発注→INV自動展開(継続契約)" }
追記後、JSON 構文エラーがないことを確認する。
3-B: docs/_internal/changelog.md への追記
ヘッダー直後の先頭行に追記:
| 2026-04-20 | [dev_mas-126_auto_inv_from_ord.md](dev_mas-126_auto_inv_from_ord.md) | 初版作成。継続契約ORDから月次INVを自動生成する機能の仕様書 |
3-C: コミット&プッシュ
git add docs/dev/dev_mas-126_auto_inv_from_ord.md docs/_config.json docs/_internal/changelog.md
git commit -m "docs: MAS-126 発注→INV自動展開(継続契約)の開発仕様書を作成
継続契約ORDから月次INVを自動生成する機能の仕様書。
冪等性チェック・エッジケース・科目名DDL変更案(別案件)を含む。"
git push -u origin <現在のブランチ名>
```