概要

項目
案件IDMAS-121
カテゴリDDL・マスタ設計
PhaseP2
優先度★★
所要時間目安3-4時間
対象ファイル100_config/101_sys_config.js, 200_data/202_repository.js, 400_domain/403_rpa_capex.js, 000_infra/002_constants.js, 800_ops/809_migration_s49_capex_master.js(新規)
前提案件MAS-119(22タブ人件費料率マスタ化、マイグレーション 809 枠の連動確認)、MAS-120(12マスタ標準決済条件 — 決済手段連携は本案件スコープ外)

目的

24_bud_capex_loan の CPX レコードで手入力されている 償却月数 / 返済月数 / 月額返済額 / 月額支払利息 / 頭金分割回数 / 返済日 を、①科目マスタ(11_mst_account) の「標準償却月数」列②新規マスタ 19_mst_loan_pattern の 2 系統のマスタから自動補完する。400_domain/403_rpa_capex.jsRPAJOIN 方式へ改修するが、32_wrk_invoice への生成フォーマットは現状互換を維持する。

現在のコード

400_domain/403_rpa_capex.js L87-93: ハードコードではないが「行から直接パース」している箇所

const depMonths     = col['償却月数']     !== -1 ? (parseInt(String(row[col['償却月数']]), 10) || 0) : 0;
const debtAccount   = col['負債科目']     !== -1 ? String(row[col['負債科目']]).trim() : '';
const loanAmount    = col['借入金額']     !== -1 ? (Number(row[col['借入金額']]) || 0) : 0;
const repayMonths   = col['返済月数']     !== -1 ? (parseInt(String(row[col['返済月数']]), 10) || 0) : 0;
const monthlyRepay  = col['月額返済額']   !== -1 ? (Number(row[col['月額返済額']]) || 0) : 0;
const firstRepay    = col['初回返済額']   !== -1 ? (Number(row[col['初回返済額']]) || 0) : 0;
const monthlyInterest = col['月額支払利息'] !== -1 ? (Number(row[col['月額支払利息']]) || 0) : 0;

24タブ上で人間が「償却月数 60」「月額返済額 120000」等を手入力するフォーマット依存。L212 の downSplits(頭金分割回数)L218 / L309 の downDayRaw / repayDayRaw(返済日)L216 / L310 の downLagStr / capLagStr(決済ラグ) も同様に行入力依存。

100_config/101_sys_config.js L644 / L658: 既存 DDL スキーマ

'MST_ACCT': { headers: ["有効フラグ","主科目コード","諸表区分","大分類","表示区分","表示科目","正式科目名","固変区分","デフォルト税率","科目名","説明"], color: "#666666" },
...
'BUD_CAPEX': { headers: ["有効フラグ","管理ID","発生日(P/L計上日)","資産・契約名","取引先名","資産科目","取得価額","償却月数","負債科目","借入金額","返済月数","月額返済額","初回返済額","月額支払利息","頭金分割回数","決済ラグ(月)","返済日","休日調整","会社支払開始年月","決済手段","組織名","起票ターゲット月","最終起票年月日","備考"], color: "#b45f06" },

「標準償却月数」列は MST_ACCT に未設定BUD_CAPEX には「借入パターン」列が未設定。

200_data/202_repository.js L304-350 AccountRepository.findAsMap()

findAsMap: function() {
  if (AccountRepository._cache) return AccountRepository._cache;
  var result = AccountRepository.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 name = String(dto['科目名'] || '').trim();
    if (name) {
      map[name] = {
        stmt: String(dto['諸表区分'] || '').trim(),
        cat: String(dto['大分類'] || '').trim(),
      };
    }
  }
  AccountRepository._cache = map;
  return map;
},

戻り値は { stmt, cat } のみで deprecMonths は未対応。キャッシュは AccountRepository._cache (L344) で、AccountRepository.resetCache() (L347) でクリア。

000_infra/002_constants.js L75 SHEET_DEFAULTS の 24_bud_capex エントリ

{ pattern: '24_bud_capex', prefix: 'CPX_', defaults: { '取引先名': '', '負債科目': '長期借入金', '月額返済額': 0, '月額支払利息': 0, '借入金額': 0, '取得価額': 0, _dynamic: { '発生日(P/L計上日)': 'nextYm' } } },

「借入パターン」の既定値は未設定。ID_PREFIX_MAP(L93-112)の末尾は 15_mst_dictionary(L111)で、19_mst_loan_pattern 用のエントリは未登録。

100_config/101_sys_config.js L363 onEdit(e) ハンドラ

onEdit(e) は L363 で宣言され、L412 の末尾で if (typeof handleUxAssist === 'function') handleUxAssist(e); を呼ぶ。つまり「24タブのセル編集→handleUxAssist300_ui/301_ui_assist.js L15)→ 24_bud_capex_loan 系の分岐がまだ無い」構造。現状、24タブの自動補完は handleUxAssistvalidSheets(L20)に '24_bud_capex_loan' が列挙されているだけで、CAPEX 固有の補完分岐は未実装。

templates/operations_sidebar.html L89 🔧 マイグレーション メニュー

<h3>🔧 マイグレーション</h3>
<button class="btn" onclick="run('migrationD01D03', this)">D-01〜D-03 科目マスタ追加</button>
<button class="btn" onclick="run('migrationD04D06', this)">D-04/D-06 説明列データ</button>
<button class="btn" onclick="run('migrationI10', this)">I-10 取引先略称</button>
<button class="btn" onclick="run('migrationI24', this)">I-24 立替精算STL</button>

修正方針

5 Step 構成。各 Step は独立に dev デプロイ→動作確認→prod デプロイのゲートを挟んで進める。

Step 1: DDL スキーマ拡張(100_config/101_sys_config.jssetupAllSchemas

  • L644 MST_ACCTheaders 末尾に "標準償却月数" を追加("説明" の後ろ)
  • L658 BUD_CAPEXheaders"償却月数" 直後に "借入パターン" を追加
  • schemas オブジェクト'MST_LOAN' を新規エントリ追加:
    'MST_LOAN': { headers: ["有効フラグ","パターン名","金利","返済方式","返済月数","頭金分割回数","返済日","休日調整","備考"], color: "#666666" },
    
  • L593 付近の confSheet.appendRow(['BUD_CAPEX', ...]) 近傍に、MST_LOAN 用の 01_sys_config エントリ追加:
    if (!existKeys.includes('MST_LOAN')) confSheet.appendRow(['MST_LOAN', '', '19_mst_loan_pattern', 'マスタ_借入パターン']);
    
  • 000_infra/002_constants.js L75SHEET_DEFAULTS24_bud_capex エントリに '借入パターン': '' を追加
  • ID_PREFIX_MAP(L93-112){ pattern: '19_mst_loan_pattern', prefix: 'LPN_', digit: 4, isDate: false } を末尾に追加(自動ID発番要否は「人間が検討すべき事項」に記載、デフォルトは追加する方針)

Step 2: Repository の拡張(200_data/202_repository.js

2-A: AccountRepository.findAsMap() (L323-341) の戻り値拡張

map[name] = {
  stmt: String(dto['諸表区分'] || '').trim(),
  cat:  String(dto['大分類']   || '').trim(),
  deprecMonths: parseInt(Utils.parseAmt(dto['標準償却月数']), 10) || 0,
};

Utils.parseAmt000_infra/004_utils.js L191)はカンマ区切り・全角数字を許容。parseInt でさらに整数化。値が不正(NaN / 負値)の場合は 0 をフォールバック(onEdit 側でゼロはスキップ)。

2-B: LoanPatternRepository202_repository.js 末尾(L350 の後)に新規追加

AccountRepository のパターンを完全踏襲:

var LoanPatternRepository = {
  _getSheet: function() {
    return Utils.getSheetByKey('MST_LOAN', '19_mst_loan_pattern');
  },
  findAll: function() {
    return readSheetAsDtos_(LoanPatternRepository._getSheet());
  },
  findAsMap: function() {
    if (LoanPatternRepository._cache) return LoanPatternRepository._cache;
    var result = LoanPatternRepository.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 name = String(dto['パターン名'] || '').trim();
      if (name) {
        map[name] = {
          rate:           Number(Utils.parseAmt(dto['金利']))          || 0,
          method:         String(dto['返済方式'] || '').trim(),
          repayMonths:    parseInt(Utils.parseAmt(dto['返済月数']), 10)   || 0,
          downSplits:     parseInt(Utils.parseAmt(dto['頭金分割回数']), 10) || 1,
          repayDay:       parseInt(Utils.parseAmt(dto['返済日']), 10)     || 0,
          holidayAdjust:  String(dto['休日調整'] || '').trim(),
        };
      }
    }
    LoanPatternRepository._cache = map;
    return map;
  },
  _cache: null,
  resetCache: function() { LoanPatternRepository._cache = null; },
};

Step 3: onEdit 自動補完ロジック(100_config/101_sys_config.jsonEdit

実装先は 101_sys_config.jsonEdit(e)(L363)の末尾、handleUxAssist 呼び出しの直前(L412 の手前)。handleUxAssist は汎用アシストを担当し、CAPEX 固有のマスタ JOIN 補完は分離して 101_sys_config.js 側に置くことで、DDL と補完規則を 1 ファイルで管理する(DDL 変更時に両ファイルを触らないため)。

// S-49: CAPEX マスタ JOIN 自動補完
try {
  if (sheetName === '24_bud_capex_loan') {
    var sheetRef = e.range.getSheet();
    var hdr = sheetRef.getRange(1, 1, 1, sheetRef.getLastColumn()).getValues()[0];
    var iAssetAcct = hdr.indexOf('資産科目');
    var iDepMonths = hdr.indexOf('償却月数');
    var iLoanPtn   = hdr.indexOf('借入パターン');
    var iMonths    = hdr.indexOf('返済月数');
    var iDownSp    = hdr.indexOf('頭金分割回数');
    var iDay       = hdr.indexOf('返済日');
    var iAdjust    = hdr.indexOf('休日調整');

    // 3-A: 資産科目 → 標準償却月数
    if (iAssetAcct >= 0 && col === iAssetAcct + 1 && val) {
      var acctMap = AccountRepository.findAsMap();
      var acct = acctMap[String(val).trim()];
      var dm = acct ? acct.deprecMonths : 0;
      if (iDepMonths >= 0 && dm > 0) {
        var depCell = sheetRef.getRange(row, iDepMonths + 1);
        if (depCell.isBlank()) depCell.setValue(dm);
      } else if (iDepMonths >= 0 && acct && dm <= 0) {
        SpreadsheetApp.getActiveSpreadsheet().toast(
          '科目「' + val + '」の標準償却月数が未登録です', 'S-49 補完');
      }
    }

    // 3-B: 借入パターン → 返済月数 / 頭金分割回数 / 返済日 / 休日調整
    if (iLoanPtn >= 0 && col === iLoanPtn + 1 && val) {
      var ptnMap = LoanPatternRepository.findAsMap();
      var ptn = ptnMap[String(val).trim()];
      if (!ptn) {
        SpreadsheetApp.getActiveSpreadsheet().toast(
          '借入パターン「' + val + '」が未登録です', 'S-49 補完');
      } else {
        var setIfBlankAndPositive = function(colIdx, v) {
          if (colIdx < 0 || !v) return;
          var c = sheetRef.getRange(row, colIdx + 1);
          if (c.isBlank()) c.setValue(v);
        };
        setIfBlankAndPositive(iMonths, ptn.repayMonths);
        setIfBlankAndPositive(iDownSp, ptn.downSplits);
        setIfBlankAndPositive(iDay,    ptn.repayDay);
        if (iAdjust >= 0 && ptn.holidayAdjust) {
          var adjC = sheetRef.getRange(row, iAdjust + 1);
          if (adjC.isBlank()) adjC.setValue(ptn.holidayAdjust);
        }
      }
    }
  }
} catch (err) { console.error('[S49_ONEDIT] ' + err.message); }

設計根拠: 月額返済額・月額支払利息は「借入金額×金利×返済方式」で計算するため Step 4 の RPA 側で算出し、24 タブには原則書き込まない(書き込みたい場合は「人間が検討すべき事項」の端数処理方針を確定してから別案件で対応)。

Step 4: RPA 改修(400_domain/403_rpa_capex.js

4-A: L87-93 の行直接パースを一部マスタ JOIN に置換

  • depMonths が 0/空欄の場合、AccountRepository.findAsMap()[assetAccount].deprecMonths でフォールバック
  • repayMonths / monthlyRepay / monthlyInterest / 頭金分割 / 返済日 / 決済ラグ が空欄の場合、LoanPatternRepository.findAsMap()[ptn] の値でフォールバック
  • ただし 24 タブに既に手入力された値は尊重(マスタ値で上書きしない。L87-93 の既存ロジックの「0 は 0」動作は維持)
  • parseIntUtils.parseAmt に段階的に置換して全角・カンマ混入を吸収(L88-93, L212, L218, L309, L310

4-B: 月額返済額・月額支払利息の算出ロジック(借入パターン参照時のみ)

// 借入パターンから推定する場合(24タブの値が空欄 or 0)
var ptnDef = null;
if (col['借入パターン'] !== -1) {
  var ptnName = String(row[col['借入パターン']]).trim();
  if (ptnName) ptnDef = LoanPatternRepository.findAsMap()[ptnName] || null;
}
if (ptnDef && loanAmount > 0 && ptnDef.repayMonths > 0) {
  var annualRate = ptnDef.rate;
  if (ptnDef.method === '元利均等') {
    var r = annualRate / 12;
    if (r === 0) {
      if (monthlyRepay === 0) monthlyRepay = Math.floor(loanAmount / ptnDef.repayMonths);
    } else {
      if (monthlyRepay === 0) {
        monthlyRepay = Math.floor(loanAmount * r * Math.pow(1 + r, ptnDef.repayMonths) /
                                                 (Math.pow(1 + r, ptnDef.repayMonths) - 1));
      }
    }
    if (monthlyInterest === 0) monthlyInterest = Math.floor(loanAmount * r);
  } else { // '元金均等' or 空
    if (monthlyRepay === 0)   monthlyRepay   = Math.floor(loanAmount / ptnDef.repayMonths);
    if (monthlyInterest === 0) monthlyInterest = Math.floor(loanAmount * annualRate / 12);
  }
}

4-C: 返済日の休日調整

L326-L330new Date(...) で作っている repayDate について、休日調整 列(既存)に値があれば Utils.adjustToBusinessDay(ymdStr, direction) を適用:

var adjust = col['休日調整'] !== -1 ? String(row[col['休日調整']]).trim() : '';
if (adjust === '前' || adjust === '後') {
  var yStr = Utilities.formatDate(repayDate, Session.getScriptTimeZone(), 'yyyy-MM-dd');
  var adjusted = Utils.adjustToBusinessDay(yStr, adjust);
  var ap = adjusted.split('-').map(Number);
  repayDate = new Date(ap[0], ap[1] - 1, ap[2]);
}

頭金返済 (downRepayDate, L231-L236) にも同等処理を追加。

4-D: 既存の冪等性チェック(isDuplicate_ 呼び出し、L167/L181/L197/L239/L269/L286/L336/L351)は変更しない

Step 5: マイグレーション(800_ops/809_migration_s49_capex_master.js 新規)

CLAUDE.md §マイグレーションスクリプト運用ガイドラインに準拠:

  • ファイル名: 800_ops/809_migration_s49_capex_master.js
  • 関数名: migrationS49CapexMaster()
  • Human-in-the-Loop: 19_mst_loan_pattern へ直接書き込まず、新規ワークシート 99_wrk_loan_pattern_proposals に提案を出力する方式
    • 列: [提案パターン名, 金利, 返済方式, 返済月数, 頭金分割回数, 返済日, 休日調整, 抽出元CPX件数, 代表例_管理ID]
    • ユーザーはレビュー・修正後、承認したパターンのみを手動で 19_mst_loan_pattern へコピー
  • 冪等性: 99_wrk_loan_pattern_proposals 既存行を読み込み、提案パターン名 で重複スキップ
  • ログ出力: Utils.logInfo('migrationS49CapexMaster', summary) + SpreadsheetApp.getUi().alert('マイグレーション完了', summary, ...)
  • メニュー登録: templates/operations_sidebar.html L89 の <h3>🔧 マイグレーション</h3> ブロック末尾に:
    <button class="btn" onclick="run('migrationS49CapexMaster', this)">S-49 CAPEX借入パターン抽出</button>
    

影響範囲

種別ファイル変更規模
修正100_config/101_sys_config.js(DDL 追加 + onEdit 補完ロジック追加 + 01_sys_config エントリ)~40行
修正200_data/202_repository.js(AccountRepository.findAsMap 拡張 + LoanPatternRepository 新規)~40行
修正400_domain/403_rpa_capex.js(マスタ JOIN + 休日調整 + 月額算出)~40行
修正000_infra/002_constants.js(SHEET_DEFAULTS + ID_PREFIX_MAP)~3行
修正templates/operations_sidebar.html(マイグレーションボタン 1 行追加)~1行
新規800_ops/809_migration_s49_capex_master.js~80行
新規シート19_mst_loan_pattern(DDL 管理下)-
一時シート99_wrk_loan_pattern_proposals(マイグレーション実行時に動的生成)-

注意事項

  1. キャッシュクリア: AccountRepository.findAsMap() 拡張後は AccountRepository.resetCache() を呼んでから動作確認する。LoanPatternRepository も同様に resetCache() を備える。setupAllSchemas の直後にも両 resetCache() を呼ぶこと(DDL で列追加した直後にキャッシュが古い値を返すリスク)。
  2. DDL 上書きリスク: setupAllSchemasisFull=true 実行時に既存データが上書きされる列はない(ヘッダー追加のみ)が、MST_LOAN 新規作成時は既存同名シートの有無を必ず confSheet.appendRow 前の existKeys.includes で確認する(失敗パターン #3)。
  3. マイグレーション番号 809: CLAUDE.md 記載「次のマイグレーションは 809 から」に基づく。MAS-119(人件費料率マスタ化)も 809 を予約している可能性がある。着手前に 800_ops/ 配下の既存ファイルと他ブランチを確認し、競合する場合は本案件を 810 / 811 にずらす。現時点では 808_migration_i24.js までが最新。
  4. LoanPatternRepository は新規ファイルではなく 202_repository.js 末尾に追記。MAS-192 で Repository 一元化方針が決定済み。
  5. 24 タブの sheet 名は 24_bud_capex_loan24_bud_capex ではない)。handleUxAssist 側(L20 validSheets)・403_rpa_capex.js(L18)・SHEET_DEFAULTS(L75)は 24_bud_capex を prefix として扱うため、onEdit 分岐では 完全一致で '24_bud_capex_loan' を使う(失敗パターン #19)。
  6. onEdit は編集 1 回で複数セルが変化するバルク編集(ペースト)もあり得るe.range.getNumRows() > 1 の場合は補完をスキップ(handleUxAssist が L16 で同様ガードしている既存実装に倣う)。
  7. 型変換ガード: 24 タブにユーザーが "60ヶ月" などの文字列を直打ちするケースがあるため、マスタ JOIN では常に Utils.parseAmtparseInt の二段階で整数化する(失敗パターン #21-#24)。

エッジケース

条件動作・表示値理由
「標準償却月数」が空欄自動補完しない(既存値を維持)。Toast警告「科目『XXX』の標準償却月数が未登録です」不正値での上書き防止
「標準償却月数」が 0 または負値自動補完しない。Toast警告同文ゼロ除算防止。マスタ側の誤登録を早期検出
「返済月数」が 0RPA 側で月額返済額 = 0 を設定。ループ未実行ゼロ除算ガード必須
「借入金額」= 0RPA 側で月額返済額 = 0、月額支払利息 = 0。Row B(借入計上)も生成しない(既存 L181 ガード)正常系として計算スキップ
「金利」= 0利息計算結果 = 0(計算は実行)。Row F(支払利息)は monthlyInterest > 0 ガードで非生成無利子借入として扱う
月額返済額の端数Math.floor() で切り捨て。最終回の残差調整は本案件では未対応(既存 L265 fee = (m === depMonths) ? remaining : monthlyDep と同パターンで将来対応)端数処理方針は「人間が検討すべき事項」で業務要件確認後に別案件
ユーザーが自動補完値を手動上書き上書き後の値を尊重(onEdit 補完は isBlank() 時のみ)。再度「借入パターン」を編集しても既値は上書きされないユーザー意図の上書きは尊重する原則
19_mst_loan_pattern にパターン未登録自動補完しない。Toast警告「借入パターン『XXX』が未登録です」フォールバック動作の明確化
11_mst_account に科目未登録AccountRepository.findAsMap()[科目名]undefinedacct ? acct.deprecMonths : 0 で 0 扱い → 補完しない(Toast も出さない)既存動作(未登録 = 無視)に準拠
バルク編集(複数行ペースト)e.range.getNumRows() > 1 で補完スキップ既存 handleUxAssist L16 と同ガード
休日調整 が空 or '前'/'後' 以外既存ロジック(repayDate そのまま)を維持既存互換
返済方式'元利均等' 以外(空含む)元金均等として処理(Math.floor(loanAmount / repayMonths) + Math.floor(loanAmount * rate / 12)デフォルト単純式にフォールバック
24 タブで 償却月数 が手入力済み手入力値を優先(マスタで上書きしない)既存データ互換性
AccountRepository / LoanPatternRepository のキャッシュが古いsetupAllSchemas 直後と migrationS49CapexMaster 直後に resetCache() を呼ぶキャッシュ鮮度保証
全角数字「60」が入力されたUtils.parseAmt で半角化後 parseInt で整数化失敗パターン #22 対策
標準償却月数"60ヶ月" と文字列入力Utils.parseAmt('60ヶ月') が 60 を返す([^\d.-] を除去)既存ヘルパー仕様に準拠

実データ検証

実装前に MCP で以下を確認すること(失敗パターン #3: DDLコード値 vs 実データの乖離防止):

項目確認対象期待値
列存在11_mst_account の「標準償却月数」列未存在(DDL で新規追加対象)
列存在24_bud_capex_loan の「借入パターン」列未存在(DDL で新規追加対象)
シート存在19_mst_loan_pattern未存在(DDL で新規作成対象)
シート存在99_wrk_loan_pattern_proposals未存在(マイグレーション実行時に動的生成)
現在列構成24_bud_capex_loan の既存列DDL 定義(L658)の 24 列と一致しているか
データ型403_rpa_capex.js で参照される 償却月数 / 返済月数数値 or 文字列混在の可能性 → Utils.parseAmt で吸収
マイグレーション番号800_ops/ の既存 8NN_migration_*.js808 までが使用済。809 の他案件予約有無を git log --all --oneline で確認
キー登録01_sys_configMST_LOAN キー未登録(Step 1 で追加対象)

関連ドキュメント

ドキュメント関連箇所
CLAUDE.md§マイグレーションスクリプト運用ガイドライン / §GAS ファイル番号体系 / §プロダクトポリシー(Human-in-the-Loop)
MAS-119 人件費料率マスタ化マイグレーション 809 番号帯の共有。類似の「予算タブから料率系マスタを抽出」パターン
MAS-120 取引先マスタ拡張onEdit 自動補完パターンの先行事例(取引先選択時の補完)
MAS-116 マイグレーション基盤マイグレーション設計原則
failure_patterns.md #3DDL コード値 vs 実データの乖離
failure_patterns.md #18-#20コード未読による固有名詞誤記(対策: Phase 1 で Read)
failure_patterns.md #21-#24数式設計の落とし穴(全角スペース・getLastColumn 膨張・型変換)
prd.mdHuman-in-the-Loop プロダクトポリシー

人間が検討すべき事項

  1. 元利均等 vs 元金均等の両対応: Step 4-B の数式は両対応済みだが、実務で使うのはどちらか? 金融機関ごとの標準契約パターンを確認。
  2. 金利期中変動の扱い: 本案件は固定金利のみサポート。変動金利借入は将来案件で対応。既存 CPX に変動金利案件がある場合、借入パターンは「固定金利換算の代表値」で登録する運用ルールを合意する。
  3. 端数処理方針: Math.floor 切り捨て採用。最終回での残差一括調整は本案件では未対応(既存 L265 償却ロジック fee = (m === depMonths) ? remaining : monthlyDep パターンは将来別案件で踏襲)。
  4. 資産科目毎の償却月数: 税法耐用年数表との整合(例: ソフトウェア=60、什器備品=48、建物付属設備=180)を税理士と確認。暫定値での移行と本確定値の切替フローを決める。
  5. 自動補完時の上書きポリシー: 本案件は「空欄のみ補完」。編集済みセルへの強制再補完は confirm dialog を出す等の拡張は別案件。
  6. 19_mst_loan_pattern への自動 ID 発番要否: Step 1 では ID_PREFIX_MAPLPN_ 接頭辞を追加する方針だが、人間がパターン名を自由に命名する運用なら ID 列は不要の可能性。→ 現時点はスキーマに ID 列を含めず、パターン名 をキーとする(Step 1 の DDL 定義はこれに従う)。
  7. 提案シート名 99_wrk_loan_pattern_proposals99_error_log の命名競合: 既存 99_ で始まるシートは 99_error_log(実データ確認で有無を確認)のみ想定。異なる用途なので命名競合は実害なし。必要なら wrk_loan_pattern_proposals(番号なし)へ変更。
  8. マイグレーション番号 809 の他案件ブランチ競合: 着手前に git log --all --oneline -- 800_ops/809_* で確認。MAS-119 も 809 候補(TODO_future.md 記載)のため要調整。
  9. 既存 CPX の借入パターン抽出: 同一取引先でも案件別に条件が異なるケース(例: 日本政策金融公庫の複数回借入)があり、「取引先名 + 金利 + 返済月数」の複合キーで最頻値抽出する方針でよいか。
  10. 「借入パターン」列のプルダウン設定: Step 1 DDL 後に 100_config/101_sys_config.jssetVali('BUD_CAPEX', ...) に借入パターン列のバリデーション設定が必要(L1127 パターンに追加)。本案件は Step 1 スコープに含めるが、MST_LOAN キーを L1127 の setVali 呼び出し形式で追加するかどうかは要検討。

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

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-121「24タブCAPEX特有の償却・借入条件マスタ化」を実装してください。

## 実行前タスク
1. `100_config/101_sys_config.js` を Read: setupAllSchemas 内 MST_ACCT 定義(L644)・BUD_CAPEX 定義(L658)・01_sys_config 追記箇所(L593 付近)・onEdit ハンドラ(L363-L413)の handleUxAssist 呼び出し(L412)位置。メニュー文字列は `templates/operations_sidebar.html` L89「🔧 マイグレーション」ブロックを確認。
2. `400_domain/403_rpa_capex.js` を Read: 行直接パース箇所(L87-L93、L212、L216、L218、L309、L310)、isDuplicate_ 呼び出し(L166/L181/L197/L239/L269/L286/L336/L351)、`repayDate` 生成(L326-L330)、`downRepayDate` 生成(L231-L236)、24 タブ名 `'24_bud_capex_loan'`(L18/L20)。
3. `200_data/202_repository.js` を Read: AccountRepository.findAsMap()(L323-L341)、_cache(L344)、resetCache(L347)、readSheetAsDtos_(L19-L29)、末尾(L350)。
4. `000_infra/002_constants.js` を Read: SHEET_DEFAULTS の 24_bud_capex エントリ(L75)、ID_PREFIX_MAP 末尾 15_mst_dictionary(L111)。
5. `000_infra/004_utils.js` を Read: `adjustToBusinessDay(ymdStr, direction)` (L168-L184、`direction` は '前' / '後')、`parseAmt(val)` (L191-L198)。
6. MCP で実データ確認: `11_mst_account` の「標準償却月数」列が未存在 / `24_bud_capex_loan` の「借入パターン」列が未存在 / `19_mst_loan_pattern` シートが未存在 / `800_ops/` 配下で 808 が最新。

## 修正対象ファイル
- `100_config/101_sys_config.js`(DDL 3 箇所 + onEdit 補完ロジック 1 箇所 追加)
- `200_data/202_repository.js`(AccountRepository.findAsMap 拡張 + LoanPatternRepository 新規追加)
- `400_domain/403_rpa_capex.js`(マスタ JOIN フォールバック + 休日調整 + 月額算出)
- `000_infra/002_constants.js`(SHEET_DEFAULTS の 24_bud_capex に '借入パターン': '' 追加、ID_PREFIX_MAP 末尾に LPN_ 追加)
- `templates/operations_sidebar.html`(🔧 マイグレーション セクションに 1 行追加)
- `800_ops/809_migration_s49_capex_master.js`(新規作成)

## 実装内容

Step 1: DDL スキーマ拡張(`101_sys_config.js` + `002_constants.js`)
- L644 MST_ACCT headers 末尾に "標準償却月数" を追加
- L658 BUD_CAPEX headers の "償却月数" の直後に "借入パターン" を追加
- schemas オブジェクトに 'MST_LOAN': { headers: ["有効フラグ","パターン名","金利","返済方式","返済月数","頭金分割回数","返済日","休日調整","備考"], color: "#666666" } を追加
- L593 付近に if (!existKeys.includes('MST_LOAN')) confSheet.appendRow(['MST_LOAN', '', '19_mst_loan_pattern', 'マスタ_借入パターン']); を追加
- 002_constants.js L75 の 24_bud_capex エントリ defaults に '借入パターン': '' を追加
- 002_constants.js L112(15_mst_dictionary の次行)に { pattern: '19_mst_loan_pattern', prefix: 'LPN_', digit: 4, isDate: false } を追加

Step 2: 202_repository.js の拡張
- AccountRepository.findAsMap() の map[name] = { ... } に deprecMonths: parseInt(Utils.parseAmt(dto['標準償却月数']), 10) || 0 を追加
- 末尾(L350 の後)に LoanPatternRepository を新規追加(AccountRepository のパターンを完全踏襲。findAsMap のキーは「パターン名」、値は { rate, method, repayMonths, downSplits, repayDay, holidayAdjust })

Step 3: onEdit 自動補完ロジック(`101_sys_config.js`)
- L412 の `handleUxAssist` 呼び出しの直前に、sheetName === '24_bud_capex_loan' の分岐を try/catch で追加
- バルク編集時は e.range.getNumRows() > 1 でスキップ
- 「資産科目」列編集時: AccountRepository.findAsMap()[科目名].deprecMonths で補完、0/負値は Toast 警告
- 「借入パターン」列編集時: LoanPatternRepository.findAsMap()[パターン名] から返済月数・頭金分割回数・返済日・休日調整を補完、未登録は Toast 警告
- 補完は isBlank() 時のみ

Step 4: 403_rpa_capex.js の改修
- L87-L93 の行直接パースに、マスタ JOIN フォールバックを追加(行の値が 0/空欄の場合のみマスタ値を使う)
- 月額返済額 / 月額支払利息が 0 かつ borrowPattern 指定ありの場合、元利均等/元金均等の式で算出
- L326-L330 の repayDate / L231-L236 の downRepayDate に、Utils.adjustToBusinessDay で休日調整
- parseInt を Utils.parseAmt 経由に置換(L88, L90, L212, L218, L309, L310)
- 既存の isDuplicate_ ガードは変更しない

Step 5: 809_migration_s49_capex_master.js を新規作成
- 関数名: migrationS49CapexMaster()
- 24_bud_capex_loan の有効行から「取引先名 + 金利 + 返済月数 + 頭金分割回数 + 返済日 + 休日調整」の複合キーで最頻パターン抽出
- 提案を 99_wrk_loan_pattern_proposals シートに書き込み(無ければ新規作成)
- 19_mst_loan_pattern へ直接書き込まない
- 冪等性: 99_wrk_loan_pattern_proposals 既存行のパターン名と重複はスキップ
- Utils.auditLog / Utils.logInfo / SpreadsheetApp.getUi().alert でログ出力
- templates/operations_sidebar.html L89-L94 の <h3>🔧 マイグレーション</h3> ブロック末尾に <button class="btn" onclick="run('migrationS49CapexMaster', this)">MAS-121 CAPEX借入パターン抽出</button> を追加

## 制約
- 既存の冪等性チェック機構(403_rpa_capex.js の isDuplicate_)は変更しない
- AccountRepository.findAll() インターフェースは変更しない(findAsMap の戻り値のみ拡張)
- DDL 変更は setupAllSchemas 内のみ。スプレッドシートを直接操作しない
- マイグレーションスクリプトは 19_mst_loan_pattern へ直接書き込まない
- LoanPatternRepository は新規ファイルではなく 202_repository.js 末尾に追記する
- 24 タブのシート名判定は `'24_bud_capex_loan'` の完全一致を使う(`24_bud_capex` 部分一致は NG。名前衝突リスク)
- ユーザーが 24 タブに手入力した値は尊重(マスタ値で上書きしない)

## エッジケース
| 条件 | 動作 |
|------|------|
| 標準償却月数が空欄・0・負値 | 補完しない。Toast 警告 |
| 返済月数が 0 | 月額返済額 = 0(ゼロ除算ガード) |
| 借入金額 = 0 | 月額返済額 = 0、月額支払利息 = 0 |
| 金利 = 0 | 利息 = 0(計算は実行、Row F は既存 monthlyInterest > 0 ガードで非生成) |
| 月額端数 | Math.floor() 切り捨て |
| パターン未登録 | 補完しない。Toast 警告 |
| バルク編集(複数行ペースト) | e.range.getNumRows() > 1 で補完スキップ |
| 返済方式=空 | 元金均等として処理 |
| 休日調整が「前」「後」以外 | 既存の repayDate 維持 |
| 全角数字入力 | Utils.parseAmt で吸収 |

## 実データ検証
- MCP で 11_mst_account の「標準償却月数」列が未存在であること
- MCP で 24_bud_capex_loan の「借入パターン」列が未存在であること
- MCP で 19_mst_loan_pattern シートが未存在であること
- 800_ops/ で 809 が未使用であること(MAS-119 と競合する場合は番号を再調整)

## 動作確認
1. npm run push:dev でデプロイ
2. GASエディタから setupAllSchemas を実行 → 19_mst_loan_pattern シート作成、11_mst_account に「標準償却月数」列追加、24_bud_capex_loan に「借入パターン」列追加を確認
3. 11_mst_account で代表的な資産科目(ソフトウェア=60、什器備品=48 等)に標準償却月数を登録
4. 19_mst_loan_pattern に 1-2 パターン登録(例: "日本政策金融公庫_A": 金利 0.02, 元金均等, 返済月数 60, 頭金分割 1, 返済日 27, 休日調整 "後")
5. 24_bud_capex_loan の「資産科目」を編集 → 「償却月数」が自動補完されること
6. 24_bud_capex_loan の「借入パターン」を編集 → 「返済月数」「頭金分割回数」「返済日」「休日調整」が自動補完されること
7. 未登録科目・未登録パターンで Toast 警告が出ること
8. 既に値が入ったセルが編集で上書きされないこと
9. migrationS49CapexMaster() を実行 → 99_wrk_loan_pattern_proposals に提案が書き出され、19_mst_loan_pattern へ直接書き込まれないことを確認
10. generateCapexInvoices() を dev 環境で実行 → マスタ参照で INV が正常生成されること。発行済 INV との重複(isDuplicate_)が維持されること
11. 900_test/901_test_runner.js の全テストを実行し回帰なしを確認

### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| Phase 1(調査・設計) | あり | 固有名詞・行番号の確定に活用 |
| Phase 2(清書) | なし | Phase 1 確定内容の書き下しに徹する |

推奨実行モデル

工程推奨モデル理由
Step 1 DDL 追加Claude Haiku 4.5仕様書で列名・挿入位置が完全定義済み、判断要素なし
Step 2 Repository 拡張Claude Sonnet 4.6既存 AccountRepository パターンの正確な踏襲、_cache 制御を含む
Step 3 onEdit 追加Claude Sonnet 4.6挿入位置(handleUxAssist 呼び出し直前)の特定、既存ガード(L16 numRows > 1)の流用
Step 4 RPA 改修Claude Opus 4.6月額算出の元利均等/元金均等分岐、休日調整の日付型変換、既存 isDuplicate_ との整合性維持など複数ファイル横断の会計ロジック判断
Step 5 マイグレーションClaude Sonnet 4.6808_migration_i24.js パターンの踏襲、Human-in-the-Loop 提案シート方式の実装

変更履歴

日付変更内容
2026-04-19初版作成。CAPEX 償却・借入条件マスタ化の DDL 設計・Repository 拡張・onEdit 自動補完・403_rpa_capex.js の JOIN 改修・マイグレーション 809 の全5 Step を確定

仕様書作成プロンプト(再現性・監査性のため必ず記録)

仕様書作成プロンプト(展開して表示)
【タイムアウト回避・実行原則(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(骨格 ~20行)/ 2-2(概要〜注意事項 ~300行)/ 2-3a(エッジケース〜人間検討事項 ~200行)/ 2-3b(実装プロンプト〜変更履歴 ~250行)/ 2-4(`<details>` プロンプト記録)に分割。1回の Write/Edit は 300 行以内を目安とする。
4. **各 Step で何を書くかを具体指示**: 設計判断を Phase 2 実行時に持ち込まないよう、Phase 1 で全固有名詞・行番号・ファイル名を確定させてから Phase 2 に進む。

======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 S-49「24タブCAPEX特有の償却・借入条件マスタ化」の開発仕様書を作成してください。
作成後は `docs/_config.json` の `nav` 配列の適切なセクションに必ず追記してください。

---

## Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)

### 1-A: 案件定義の読み込み
`docs/_internal/TODO_future.md` で S-49 の案件名・概要・期待される効果・人間が検討すべき事項を取得する。

### 1-B: プロジェクト規約の読み込み
`CLAUDE.md` を読み込み、以下を把握する:
- マイグレーション番号体系(§マイグレーションスクリプト運用ガイドラインに「804-808使用済み。次のマイグレーションは809から」と記載)
- DDL管理は `setupAllSchemas` で行う規則
- onEdit ハンドラの配置に関する規約

### 1-C: 関連コードの調査(Grep で発見 → 必ず Read で構造を確認。名前からの類推・記憶での記述禁止)

以下の順番で Read し、仕様書に記述する固有名詞・構造・行番号を実コードから取得すること。

**① `100_config/101_sys_config.js`**(最優先)
- `setupAllSchemas` 内の既存DDLスキーマ定義パターン(`11_mst_account`・`24_bud_capex` のカラム定義の実際の形式と行番号)
- `onEdit(e)` ハンドラの実装箇所と行番号、シート別自動補完ロジックの既存パターン
- 「🔧 マイグレーション」メニューの実在する文字列(造語禁止。Read した文字列のみ引用)

**② `400_domain/403_rpa_capex.js`**
- 償却月数・金利・返済月数がハードコードされている変数名と行番号
- INVレコード生成ロジックの構造
- 既存の冪等性チェック機構の実装箇所と行番号
- `AccountRepository` や `Constants` を参照している既存箇所

**③ `200_data/202_repository.js`**
- `AccountRepository` の全実装(`_getSheet`・`findAll`・`findAsMap`・`_cache`・`resetCache`)の行番号
- `findAsMap()` の現在の戻り値構造(実コードを Read して確認: `{ stmt: string, cat: string }` が現状の形)
- 内部ヘルパー関数 `readSheetAsDtos_`・`appendDtosToSheet_` 等の行番号(`LoanPatternRepository` 実装の参考)

**④ `000_infra/002_constants.js`**
- `SHEET_DEFAULTS` 内の `24_bud_capex` エントリの現在の `defaults` フィールド構造(「借入パターン」列追加前の実状態)
- `ID_PREFIX_MAP` の末尾行番号(`19_mst_loan_pattern` の自動ID発番要否の判断後、追加が必要な場合の挿入位置)

**⑤ `300_ui/301_ui_assist.js`**
- `24_bud_capex` 関連のUI処理が存在するか確認
- onEdit補完ロジックの実装先が `101_sys_config.js` と `301_ui_assist.js` のどちらか判断する

**⑥ `docs/_internal/failure_patterns.md`**
- #3(DDLコード値 vs 実データの乖離)を確認 → 実データ検証ステップに反映
- #18-#20(コード未読による固有名詞誤記)を確認 → Phase 1 調査の徹底
- #21-#24(数式設計の落とし穴)を確認 → onEdit補完時の型変換リスクに反映

### 1-D: 実データ確認(MCP で確認。Phase 2 着手前に完了すること)
- `11_mst_account` シートに「標準償却月数」列が既存か未存在か(DDL追加対象の確認)
- `24_bud_capex` シートの現在の列構成と「借入パターン」列の有無
- `19_mst_loan_pattern` シートが既存か未存在か(新規DDL作成の要否)
- `403_rpa_capex.js` のハードコード値のデータ型(文字列 vs 数値。型変換ガードの要否判断)

---

## Phase 2: 仕様書の分割作成

出力先: `docs/dev/dev_mas-121_capex_master_ref.md`
**【重要】1回のツール呼び出しで全内容を出力しない。以下5 Stepに分割して実行する。**

### Step 2-1: 骨格の作成(Write / ~20行)
`docs/_internal/dev_spec_prompt_template.md` のセクション構成に従い、見出しのみの骨格ファイルを新規作成する。

---

### Step 2-2: 概要〜注意事項の追記(Edit or Bash / ~300行)

#### 概要テーブル
| 項目 | 値 |
|------|---|
| 案件ID | S-49 |
| カテゴリ | DDL・マスタ設計 |
| 対象ファイル | `100_config/101_sys_config.js`, `200_data/202_repository.js`, `400_domain/403_rpa_capex.js`, `000_infra/002_constants.js`, `800_ops/809_migration_s49_capex_master.js`(新規) |
| 前提案件 | TODO_future.md の S-49 エントリを参照 |

#### 修正方針(Phase 1 の Read 結果を元に固有名詞・行番号を埋め込んで記述)

**Step 1: DDLスキーマ拡張(`100_config/101_sys_config.js` 内 `setupAllSchemas`)**
- `11_mst_account` に「標準償却月数」列を追加(Phase 1 で確認した既存カラム定義の直後。列名は Read で確認した実際の形式に合わせる)
- `24_bud_capex` に「借入パターン」列を追加
- `19_mst_loan_pattern` を新規DDL定義(既存 `11_mst_account`〜`15_mst_dictionary` の命名規則に準拠した新規マスタシート。項目: パターン名, 金利, 返済方式, 返済月数, 頭金分割回数, 返済日, 休日調整)
- `000_infra/002_constants.js` の `SHEET_DEFAULTS` 内 `24_bud_capex` エントリに `'借入パターン': ''` を追加
- 自動ID発番が必要と判断した場合: `Constants.ID_PREFIX_MAP` に `{ pattern: '19_mst_loan_pattern', prefix: 'LPN_', digit: 4, isDate: false }` を追加(要検討)

**Step 2: Repository の拡張(`200_data/202_repository.js`)**
- `AccountRepository.findAsMap()` を拡張: 現在の戻り値 `{ stmt: string, cat: string }` に `deprecMonths: number` を追加(「標準償却月数」列の値を `Utils.parseAmt()` でパース)
- `LoanPatternRepository` を `202_repository.js` 末尾に新規追加(`AccountRepository` の `_getSheet` / `findAll` / `findAsMap` / `_cache` / `resetCache` パターンを完全踏襲。`findAsMap()` のキーはパターン名、値は借入条件オブジェクト)
- `LoanPatternRepository._getSheet()` は `Utils.getSheetByKey('MST_LOAN', '19_mst_loan_pattern')` パターンで実装(システムキー文字列は要検討)

**Step 3: onEdit 自動補完ロジック(実装先は Phase 1 調査結果で確定)**
- `24_bud_capex` の「資産科目」セル編集時: `AccountRepository.findAsMap()` から `deprecMonths` を取得し「標準償却月数」列へ自動補完。値が `0`・負値・空欄の場合は補完しない
- `24_bud_capex` の「借入パターン」セル編集時: `LoanPatternRepository.findAsMap()` から借入条件を取得し対応列へ自動補完。パターン未登録の場合は `SpreadsheetApp.getActiveSpreadsheet().toast()` で警告
- ユーザーによる手動上書きは尊重する(補完は空欄のみ対象とするか、確認ダイアログを出すかは「人間が検討すべき事項」に委ねる)

**Step 4: RPA改修(`400_domain/403_rpa_capex.js`)**
- Phase 1 で特定したハードコード箇所(行番号を明記)を `AccountRepository.findAsMap()` / `LoanPatternRepository.findAsMap()` 参照に置換
- 既存の冪等性チェック機構(Phase 1 で確認した実装箇所)は変更しない
- 返済日の休日調整: `Utils.adjustToBusinessDay(ymdStr, direction)` を使用(`direction` の引数値 `'前'` / `'後'` は `004_utils.js` の実装を Read して確認済みのものを使用)
- 金額・率の読み取り: `Utils.parseAmt(val)` を使用してデータ形式の揺れに対応

**Step 5: マイグレーション(`800_ops/809_migration_s49_capex_master.js`)**
- CLAUDE.md §マイグレーションスクリプト運用ガイドラインに準拠(関数命名: `migrationS49CapexMaster()`、冪等性必須、`Utils.logInfo` + `SpreadsheetApp.getUi().alert` でログ出力)
- Human-in-the-Loop: `19_mst_loan_pattern` へ直接書き込まず、提案内容を一時ワークシートへ出力する方式
  - 提案シート名: `99_wrk_loan_pattern_proposals`(注: `99_error_log` との命名競合を Phase 1 で確認し、問題があれば別名を採用すること)
  - ユーザーがレビュー・修正後、承認したパターンのみを手動で `19_mst_loan_pattern` へコピーする運用フローを記述
- 冪等性: 実行前に `19_mst_loan_pattern` の既存データを読み込み、パターン名が重複するレコードはスキップ
- メニュー登録: `101_sys_config.js` の「🔧 マイグレーション」メニューに追加(メニュー文字列は Phase 1 で Read した実在する文字列のみ使用)

#### 影響範囲
- 変更ファイル: `100_config/101_sys_config.js`(DDL・onEdit)、`200_data/202_repository.js`(Repository拡張・追加)、`400_domain/403_rpa_capex.js`(マスタ参照)、`000_infra/002_constants.js`(SHEET_DEFAULTS・ID_PREFIX_MAP)
- 新規ファイル: `800_ops/809_migration_s49_capex_master.js`
- 新規シート: `19_mst_loan_pattern`(DDL管理下)

#### 注意事項
- `AccountRepository.findAsMap()` 拡張後は必ず `AccountRepository.resetCache()` を呼び出してキャッシュをクリアしてから動作確認すること
- DDLスキーマ変更後に `setupAllSchemas` を実行すると既存データが上書きされる列がある — 実行前に影響列を確認すること(失敗パターン #3 参照)
- マイグレーション番号 `809` は CLAUDE.md 記載の「次のマイグレーションは809から」に基づく。着手前に他案件ブランチとの競合がないか確認すること
- `LoanPatternRepository` は新規ファイルではなく `202_repository.js` 末尾に追記する

---

### Step 2-3a: エッジケース〜人間が検討すべき事項の追記(Edit or Bash / ~200行)

(エッジケーステーブル、実データ検証、人間が検討すべき事項を記載。上記本文を参照)

### Step 2-3b: 実装プロンプト〜変更履歴の追記(Edit or Bash / ~250行)

(実装プロンプトは行頭4スペースインデントで出力。上記本文を参照)

### Step 2-4: 仕様書作成プロンプトの記録(Edit or Bash / プロンプトサイズ依存)

末尾に `<details><summary>仕様書作成プロンプト(展開して表示)</summary>` ブロックを追加し、この `<instruction>` タグの全文を記録する。

---

## Phase 3: 保存・登録・記録

### 3-A: ファイル保存確認
`docs/dev/dev_mas-121_capex_master_ref.md` が正しく保存されていることを確認する。

### 3-B: `docs/_config.json` への登録(必須)
追加前に `git pull origin main` で最新化すること。
`nav` 配列の §E.2(バグ修正・実務要件・DDL)セクションに追加:
{ "file": "dev/dev_mas-121_capex_master_ref.md", "title": "E.2.X S-49 CAPEX償却・借入条件マスタ化" }

### 3-C: `docs/_internal/changelog.md` への追記
ヘッダー直後の先頭行に追記:
| 2026-04-19 | [dev_mas-121_capex_master_ref.md](dev_mas-121_capex_master_ref.md) | 初版作成。CAPEX償却・借入条件マスタ化(DDL・Repository拡張・onEdit補完・RPA改修・マイグレーション設計) |

### 3-D: コミット&プッシュ
git add docs/dev/dev_mas-121_capex_master_ref.md docs/_internal/changelog.md docs/_config.json
git commit -m "docs: S-49 CAPEX償却・借入条件マスタ化の開発仕様書を作成

DDLスキーマ拡張・LoanPatternRepository新規追加・onEdit自動補完・
403_rpa_capex.js改修・マイグレーション(809)設計を含む。

https://claude.ai/code/session_XXXXX"
git push -u origin {現在のブランチ}