概要

項目内容
案件IDMAS-171
案件名クレカ明細 N:1 消込(複数カード明細 → 1 銀行STL 合算マッチ)
カテゴリ取込・マッチング
PhaseP2
優先度★★
所要時間3-4時間(Step 1: 1h / Step 2: 1.5h / Step 3: 1h / Step 4: 0.5h)
対象ファイル500_import/501_cc_importer.js(合算マッチロジック追加)
500_import/502_bank_importer.js(銀行側のカード引落検知フック)
200_data/202_repository.jsSettlementRepository の N:1 対応、必要に応じて)
docs/_config.json(仕様書登録)
前提案件MAS-159(銀行・クレカCSV取込の重複防止・実装済 PR #221)/ MAS-162(銀行CSV合算マッチング 複数STL→1明細・実装済、本案件の参考実装)

目的

MAS-162 は「銀行側で 1 明細 = 複数 STL」のパターン(例: 給与一括振込 → 社員別STL)に対応したが、本案件はその カード側対称版

典型例:

  • カード会社から月末に 1 回の銀行引落(例: JCB 2026-04-27 引落 ¥150,000)
  • 対応するカード利用明細は 複数件(14 件の個別利用明細 × ¥150,000 合計)
  • これまで → 手動で「どのカード明細を合算すれば銀行引落と一致するか」を毎月計算する必要があった
  • 本案件 → 自動で合算グループを特定し、N:1 消込を成立させる

現状の課題

実測によって判明した前提

#項目実測値本案件への影響
1MAS-162 の既存実装502_bank_importer.js の payee グルーピングで N:1 マッチ済カード側も同様のパターンで実装可能
2MAS-159(PR #221)の成果WRK_CARD に取込ハッシュ列追加、computeCcHash_(yyyyMMdd|merchant|amount)冪等性担保本案件は冪等性を前提に、ハッシュ衝突のない明細に対して合算マッチを実施できる
3カード会社マスタの有無12_mst_partner にカード会社エントリはあるが、「締日」「引落日」「引落口座」のマスタ列は未整備合算グループの判定キーとしてカード会社 + 締日 + 引落日が必要。未整備の場合はカード会社 + 引落日のみで仮運用
431_wrk_order / 32_wrk_invoice / 33_wrk_bank のリレーションSTL に親子関係を持たせる列は未整備本案件で「親STL(銀行側)← 子STLs(カード側)」の関連付け方式を設計

ギャップ

  • カード会社の月次一括引落に対して、手動で合算計算する必要がある
  • 合算金額と銀行引落金額が一致しない場合、原因(カード明細の取込漏れ / 重複取込 / 金額誤り)が特定困難
  • カード明細が複数月にまたがる場合、どの月の合算に含めるかの判断が属人化

修正方針(4ステップ段階実装)

Step 1: カード会社マスタの拡張

12_mst_partner に以下 3 列を追加(または 18_mst_cc_issuer 新設も検討):

列名内容
カード締日月次締日15 (毎月15日)、月末
カード引落日月次引落日27翌月10日
カード引落口座引落口座名三井住友銀行 本店

Step 2: 合算マッチロジックの実装

500_import/501_cc_importer.js に以下を追加:

/**
 * I-29: カード明細を銀行引落 STL に合算マッチする。
 * 同一カード会社・同一引落日でグルーピングし、合算金額が銀行STLと一致すれば N:1 消込を成立させる。
 * @param {string} bankStlId - マッチ対象の銀行側 STL_ID
 * @returns {{matched: boolean, ccStls: string[], totalAmount: number}}
 */
function tryCombineCcMatch_(bankStlId) {
  // 1. 銀行STL情報取得
  var bankStl = SettlementRepository.findById(bankStlId);
  if (!bankStl || bankStl.マッチングステータス !== 'UNMATCHED') return { matched: false };
  
  // 2. カード会社マスタ参照(振込先名から特定)
  var issuer = resolveCcIssuer_(bankStl.振込先名);
  if (!issuer) return { matched: false };
  
  // 3. 引落日から「対応する利用期間」を逆算
  // (例: 引落日=4/27, 締日=3/15 → 利用期間 2/16〜3/15)
  var usagePeriod = computeUsagePeriod_(bankStl.決済日_実績, issuer);
  
  // 4. 利用期間内の CC 明細を集約(WRK_CARD から取込ハッシュで dedup 済みのものを対象)
  var ccCandidates = CcImportRepository.findByIssuerAndPeriod(issuer, usagePeriod);
  var totalAmount = ccCandidates.reduce(function(sum, c) { return sum + Utils.parseAmt(c.税込金額); }, 0);
  
  // 5. 合算金額が銀行STLと一致(±0円)
  if (Math.abs(totalAmount - Utils.parseAmt(bankStl.税込金額_決済)) < 1) {
    // 親子関係の書込み: bankStl → 親、ccCandidates → 子(全て同じ bankStlId を「親決済ID」列に記録)
    return { matched: true, ccStls: ccCandidates.map(function(c) { return c.STL_ID; }), totalAmount: totalAmount };
  }
  return { matched: false, totalAmount: totalAmount };
}

Step 3: 銀行側フック(既存 MAS-162 との整合)

500_import/502_bank_importer.js の銀行CSVマッチング処理に、カード会社引落検知フックを追加:

// 既存の銀行CSVマッチング UNMATCHED 判定後に以下を挿入
if (matchResult === 'UNMATCHED' && isCcIssuerPayee_(bankRow.振込先名)) {
  var comboResult = tryCombineCcMatch_(bankStlId);
  if (comboResult.matched) {
    // UNMATCHED → MATCHED(N:1 消込成立)
    // 確認FLG=FALSE(人間レビュー必須)
  }
}

Step 4: UI 改善(合算結果の可視化)

  • 銀行CSV取込完了ダイアログに「N:1 合算マッチ成立: X 件」を表示
  • 33_wrk_bank に「子STL数」列を追加し、親STLに子件数を表示
  • 34_wrk_card に「親STL_ID」列を追加し、どの銀行引落に紐づいているかを表示

エッジケース

#ケース挙動
1合算金額が完全一致しない(数円の誤差)許容範囲を 03_sys_params (CFG_CC_COMBO_TOLERANCE) で設定(デフォルト: ±10 円)
2カード明細の取込漏れ差額分の合算不一致 → UNMATCHED のまま、ユーザーに「取込漏れの可能性」を通知
3同一カード会社・同一引落日の STL が複数(複数銀行口座で同時引落)振込元口座 + カード会社 + 引落日でグルーピングし、グループ別にマッチング
4カード会社マスタ未登録合算マッチ対象外 → UNMATCHED。Utils.logWarn でマスタ未登録を警告
5カード利用期間が月を跨ぐカード会社マスタの締日設定で正確に計算(例: 15日締・翌月27日引落 → 利用期間は前月16日〜当月15日)
6返品・キャンセル(マイナス金額)合算に含めて差し引き計算
7分割払い・リボ払い当月請求額のみが合算対象(残額は次月以降)。初期スコープでは一括払いのみ対応、分割払いは「人間が検討すべき事項」として記述

データ整合性

  • 冪等性: MAS-159 の取込ハッシュにより、同じカード明細が二重取込されないことを前提に合算実施
  • 競合回避: LockService.tryLock(10000) で排他制御(銀行CSV / CC 両方の取込処理を単一トランザクション化)

Human-in-the-Loop(人間が検討すべき事項)

  • 合算マッチ成立時の確認フロー: 自動で確認FLG=FALSE とし、ユーザーレビュー必須(監査観点の慎重アプローチ)。MAS-172(CC MATCHED 自動承認化検討)と連動
  • 分割払い・リボ払いへの対応範囲: 初期は一括払いのみ。分割払いは別案件として切り出し
  • カード会社マスタの粒度: 12_mst_partner 拡張 vs 新設 18_mst_cc_issuer のどちらを採用するか
  • 親子STL関連列の配置: 33_wrk_bank34_wrk_card で統一的に扱うか、専用の「STL親子関係表」を新設するか
  • 差額許容範囲の設定: ±10 円で厳しすぎる / 甘すぎるかのチューニング(運用しながら調整)
  • 既存 MAS-162(銀行側 N:1)との実装統合: 共通ロジックを RpaCommon に抽出する余地

実データ検証

  1. カード会社マスタに 3 件以上登録(例: JCB、楽天カード、アメックス)
  2. 任意月の銀行CSV + CC明細を取込
  3. カード会社からの引落 STL に対して N:1 合算マッチが成立することを確認
  4. 合算金額と引落金額の差額がある場合は UNMATCHED のままであることを確認
  5. 取込ダイアログに「N:1 合算マッチ X 件」が表示されることを確認

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

案件 MAS-171「クレカ明細 N:1 消込(複数カード明細 → 1 銀行STL 合算マッチ)」を実装してください。

## 前提
- `docs/dev/dev_mas-171_cc_combo_matching.md` を熟読
- MAS-159(PR #221)の取込ハッシュが有効であること
- MAS-162(銀行側 N:1)の実装を参考に、対称パターンで実装

## Step 1: カード会社マスタ拡張
- 12_mst_partner に締日・引落日・引落口座列を追加(または 18_mst_cc_issuer 新設)

## Step 2: tryCombineCcMatch_() 実装
- 501_cc_importer.js に追加。カード会社 + 引落日 + 引落口座でグルーピング
- 合算金額と銀行STL金額を比較、±CFG_CC_COMBO_TOLERANCE 以内で MATCHED

## Step 3: 銀行側フック
- 502_bank_importer.js の UNMATCHED 判定後にカード会社引落検知 → tryCombineCcMatch_

## Step 4: UI 改善
- 取込ダイアログに N:1 合算マッチ件数を表示
- 33_wrk_bank に「子STL数」列追加、34_wrk_card に「親STL_ID」列追加

## 動作確認
- カード会社引落の CSV を取込 → 合算マッチ成立を確認
- 差額ケース → UNMATCHED のままを確認

## 推奨実行モデル
- **Claude Sonnet 4.6**(既存 MAS-162 パターンの踏襲が中心)

関連ドキュメント

変更履歴

日付変更内容
2026-04-20初版作成。MAS-159 完了に伴い新規起票。MAS-162(銀行側 N:1)の対称パターンでカード側 N:1 を設計
2026-04-21関連ドキュメントに MAS-147 v2 (Document AI ハイブリッド) を追記。Subset Sum ヘルパー (000_infra/004_utils.js) を MAS-147 の Pass 3 と共通化する方針を明示