最終更新: 2026/06/22 18:56
MAS-171: クレカ明細 N:1 消込(複数カード明細 → 1 銀行STL 合算マッチ)
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-171 |
| 案件名 | クレカ明細 N:1 消込(複数カード明細 → 1 銀行STL 合算マッチ) |
| カテゴリ | 取込・マッチング |
| Phase | P2 |
| 優先度 | ★★ |
| 所要時間 | 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.js(SettlementRepository の 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 消込を成立させる
現状の課題
実測によって判明した前提
| # | 項目 | 実測値 | 本案件への影響 |
|---|---|---|---|
| 1 | MAS-162 の既存実装 | 502_bank_importer.js の payee グルーピングで N:1 マッチ済 | カード側も同様のパターンで実装可能 |
| 2 | MAS-159(PR #221)の成果 | WRK_CARD に取込ハッシュ列追加、computeCcHash_(yyyyMMdd|merchant|amount) で冪等性担保 | 本案件は冪等性を前提に、ハッシュ衝突のない明細に対して合算マッチを実施できる |
| 3 | カード会社マスタの有無 | 12_mst_partner にカード会社エントリはあるが、「締日」「引落日」「引落口座」のマスタ列は未整備 | 合算グループの判定キーとしてカード会社 + 締日 + 引落日が必要。未整備の場合はカード会社 + 引落日のみで仮運用 |
| 4 | 31_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_bankと34_wrk_cardで統一的に扱うか、専用の「STL親子関係表」を新設するか - 差額許容範囲の設定: ±10 円で厳しすぎる / 甘すぎるかのチューニング(運用しながら調整)
- 既存 MAS-162(銀行側 N:1)との実装統合: 共通ロジックを
RpaCommonに抽出する余地
実データ検証
- カード会社マスタに 3 件以上登録(例: JCB、楽天カード、アメックス)
- 任意月の銀行CSV + CC明細を取込
- カード会社からの引落 STL に対して N:1 合算マッチが成立することを確認
- 合算金額と引落金額の差額がある場合は UNMATCHED のままであることを確認
- 取込ダイアログに「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 パターンの踏襲が中心)
関連ドキュメント
- MAS-159 CSV取込冪等性ガード — 前提案件
- MAS-162 銀行CSV合算マッチング(銀行側 N:1) — 参考実装
- MAS-147 請求書 PDF → INV 自動起票 (v2) — Pass 3 の Subset Sum ヘルパーを
000_infra/004_utils.jsに共通化。MAS-171 と MAS-147 の両方から呼び出される設計 - TODO_future.md MAS-171 — 案件サマリ
変更履歴
| 日付 | 変更内容 |
|---|---|
| 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 と共通化する方針を明示 |