概要

項目内容
案件IDMAS-074
カテゴリ経費・仕訳
PhaseP1
優先度★★★
所要時間1-2時間
対象ファイル400_domain/403_subledger_engine.js

目的

給与INV(月額給与・報酬)の未決済残高計算に、同一ORD配下の仕訳振替INV(源泉所得税・社保預り等)を仮想消込として含め、残高表示を適正化する。

背景

現在の問題

HC RPA が生成する給与INV群の構造:

ORD (EMP_0001|2025-08)
├─ INV1: 月額給与・報酬 ¥500,000  (決済手段: 口座振込)
│   └─ STL: ¥400,000 (差引支給額 = ¥500,000 − 天引き¥100,000)
├─ INV2: 源泉所得税預り ¥30,000   (決済手段: 仕訳振替) → Action Aで即残高0
├─ INV3: 住民税預り ¥10,000       (決済手段: 仕訳振替) → Action Aで即残高0
└─ INV4: 社保預り ¥60,000         (決済手段: 仕訳振替) → Action Aで即残高0

STL消込後の残高:

INV税込金額STL決済額仕訳振替額現在の残高あるべき残高
INV1(給与)500,000400,000100,0000
INV2(源泉)30,00030,00000
INV3(住民税)10,00010,00000
INV4(社保)60,00060,00000

INV1の残高¥100,000は未払いに見えるが、実際には仕訳振替(INV2-4)で消化済み。天引き額 = ¥30,000 + ¥10,000 + ¥60,000 = ¥100,000 がSTLの差引計算で既に控除されている。

未決済残高の現在の計算ロジック(403_subledger_engine.js L571-608)

// STLからINV別の消込合計を集計
const settledByInv = {};
for (各STL行) {
  if (決済ステータス !== '消込済') continue;
  if (!自動仕訳JNL_ID) continue;  // Action B処理済のみ
  settledByInv[invId] += 税込金額_決済;
}

// INV別に残高再計算
newBal = |税込金額_計画| - settledByInv[invId];
// ★ 仕訳振替による仮想消込が含まれていない

修正方針

Action Bの残高再計算セクション(L571-608)に、仕訳振替INVの金額を仮想消込として加算するロジックを追加する。

Step 1: 仕訳振替INVの天引きマップ構築

settledByInv の集計(L573-585)の直後に、32_wrk_invoiceから仕訳振替INVの天引きマップを構築する。

// S-02: 仕訳振替INVの天引き額をORD別に集計(仮想消込用)
const jvDeductByOrd = {};
const iOrdCol = invIdx['親発注ID(ORD)'];
const iPayMethod = invIdx['決済手段'];
const iMemo = invIdx['摘要'];
const iTotalAmt = invIdx['税込金額_計画'];
for (var ji = 1; ji < invData.length; ji++) {
  var flag = invData[ji][invIdx['有効フラグ']];
  if (flag === false || String(flag).toUpperCase() === 'FALSE') continue;
  var pm = String(invData[ji][iPayMethod] || '').trim();
  if (pm !== '仕訳振替') continue;
  var amt = Number(invData[ji][iTotalAmt]) || 0;
  if (amt <= 0) continue;  // 正の額(預り計上側)のみ対象。負の額(支払側)は除外
  var ordId = String(invData[ji][iOrdCol] || '').trim();
  if (ordId) {
    jvDeductByOrd[ordId] = (jvDeductByOrd[ordId] || 0) + amt;
  }
}

Step 2: メインINVの残高計算に仮想消込を適用

残高再計算ループ(L592-608)で、メインINVの settled に仮想消込額を加算する。

// 既存: L598
var settled = settledByInv[invId] || 0;

// S-02追加: 仕訳振替の仮想消込
var ordIdForJv = String(invSheet.getRange(upd.row, invIdx['親発注ID(ORD)'] + 1).getValue()).trim();
var pmForJv = String(invSheet.getRange(upd.row, iPayMethod + 1).getValue()).trim();
if (pmForJv !== '仕訳振替' && ordIdForJv && jvDeductByOrd[ordIdForJv]) {
  settled += jvDeductByOrd[ordIdForJv];
}

var newBalAbs = totalAbs - settled;

仮想消込の適用条件

条件理由
対象INVの決済手段 ≠ 仕訳振替仕訳振替INV自身は Action A で即残高0。仮想消込はメインINVにのみ適用
同一ORD配下に仕訳振替INVが存在ORDで親子関係を紐付け
仕訳振替INVの金額が正(> 0)預り計上側(給与日の天引き)のみ集計。負の額(納付期限の支払)は除外
有効フラグ = TRUE論理削除されたINVは除外

なぜ「正の額のみ」か

HC RPAは天引きINVをペアで生成する:

  • 源泉所得税預り ¥30,000(正: 給与支給日に預り金計上)
  • 源泉所得税 -¥30,000(負: 納付期限に預り金解消)

STL自動作成時の天引き計算(deductMap)も正の額のみを集計している(401_bat_rpa.js L148-173)。仮想消込も同じロジックに合わせ、正の額のみを対象にする。

影響範囲

  • 変更ファイル: 403_subledger_engine.jsprocessSettlementClearings() 内(Action B)
  • 変更量: 約15行追加
  • 既存ロジックへの影響: 残高計算の settled に仮想消込を加算するのみ。STL集計ロジックは変更なし
  • 84_cf_daily_plan: 未決済残高を参照しているため、残高が正しくなることでCF計画の精度も向上

注意事項

  1. invData のスコープ: Action B の processSettlementClearings 内で invData がどのスコープで参照可能か確認すること。invSheet.getDataRange().getValues() で取得されているはず
  2. invBalUpdates の対象行: 残高再計算は invBalUpdates に含まれる行のみ。Action B で処理されなかったINVの残高は更新されない
  3. 二重減算防止: 仮想消込は pmForJv !== '仕訳振替' ガードで、仕訳振替INV自身には適用されない。仕訳振替INVは Action A で既に残高0
  4. シート読み取り回数: invSheet.getRange(upd.row, ...) でループ内に getRange が増える。パフォーマンスが問題なら invData 配列から直接読み取る方式に変更

関連ドキュメント

仕様書関連箇所
CLAUDE.md未決済残高は請求総額から再計算(減算方式ではなく全STL合計から逆算)
CLAUDE.mdSTL天引き計算は摘要「月額給与・報酬」を含むINVのみ
CLAUDE.mdP/L INV消込の未払金解消は期ずれあり(pYm≠sYm)の場合のみ(二重減算防止)
B.3 統合テスト手順Action A → Action B テスト

人間が検討すべき事項

  • 仮想消込のルール(全額 or 部分): 本仕様では全額(同一ORD配下の正の仕訳振替INV全額を仮想消込)を採用。部分消込(例: 一部の天引き項目のみ)が必要なら条件を追加
  • 対象ORDの範囲: 本仕様ではORD単位で集計するため、給与INV以外のORDにも仕訳振替INVがあれば同様に仮想消込される。給与INVのみに限定する場合は摘要フィルタ(「月額給与・報酬」)の追加が必要

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

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
CLIエージェントである「Claude Code」として、以下の指示に従い、案件 S-02「給与INVの未決済残高に仕訳振替分を反映」を実装してください。

## 実行前タスク(コンテキストの読み込み)

実装前に**必ず**以下のファイルを読み込んで、現在の実装を正確に把握してください。

1. 修正対象: `400_domain/403_subledger_engine.js` — `processSettlementClearings()` の全体構造。特に以下を確認:
   - L571-608: 未決済残高の再計算セクション(settledByInv集計 → INV別残高更新)
   - L287-293: Action Aでの仕訳振替INV即決済処理
   - invData, invIdx の変数スコープ
2. 参考(天引きロジック): `400_domain/401_bat_rpa.js` — L148-173の deductMap 構築パターン(正の額のみ集計)
3. プロジェクト規約: `CLAUDE.md` — 未決済残高の計算ルール、天引き計算の条件
4. 開発仕様書: `docs/dev/dev_mas-074_jv_virtual_settlement.md`

## 修正対象ファイル

`400_domain/403_subledger_engine.js` の `processSettlementClearings()` のみ。

## 実装内容

### A: 仕訳振替天引きマップの構築(L585の直後に追加)

settledByInv の集計ループ完了直後に、32_wrk_invoice の invData から仕訳振替INVの天引き額をORD別に集計するループを追加。

**集計条件:**
- 有効フラグ = TRUE
- 決済手段 === '仕訳振替'
- 税込金額_計画 > 0(正の額のみ。預り計上側)
- 親発注ID(ORD) が非空

**出力:** `jvDeductByOrd` = { ordId: 天引き合計額 }

### B: 残高計算への仮想消込の適用(L598付近を修正)

残高再計算ループ内で、`settled` に仮想消込額を加算:

1. 対象INVの親発注ID(ORD)を取得
2. 対象INVの決済手段を取得
3. 決済手段 ≠ '仕訳振替' かつ ORDに仮想消込がある場合: `settled += jvDeductByOrd[ordId]`

**重要:** invSheet.getRange() でループ内にシート読み取りが増えるのを避けるため、invData 配列から直接読み取ることを推奨。invBalUpdates の各 upd.row は1始まり行番号なので、invData のインデックスは `upd.row - 1`。

## 制約

- settledByInv の集計ロジック(L573-585)は変更しない
- Action A の仕訳振替即決済処理(L287-293)は変更しない
- 仕訳振替INV自身の残高は変更しない(既に Action A で 0)
- 負の額の仕訳振替INV(納付側)は集計に含めない

## 動作確認

`npm run push:dev` 後:
1. HC RPA を実行して給与INV群を生成
2. 32タブで給与INVを「承認済」に変更
3. Action A を実行(仕訳振替INVが即決済完了になること)
4. 33タブでSTLに決済日入力+「消込済」に変更
5. Action B を実行
6. **検証**: INV1(月額給与・報酬)の未決済残高が 0 になっていること
7. **検証**: 仕訳振替INV(源泉・社保等)の残高は引き続き 0 であること

### 拡張思考の使用状況

| フェーズ | 拡張思考 | 備考 |
|---------|:--------:|------|
| ファイル読み込み・構造理解 | あり | invData/invIdx のスコープ確認、invBalUpdates の行番号体系 |
| 天引きマップ構築 | なし | deductMap パターンの踏襲 |
| 残高計算への統合 | あり | invData からの直接読み取り、二重減算防止ガード、符号処理の確認 |

推奨実行モデル

工程推奨モデル理由
仕様書作成(本ドキュメント)Claude Opus 4.6給与INVの親子関係、天引きペアの符号ルール、二重減算防止の分析に高い推論力が必要
実装Claude Sonnet 4.6仕様書で挿入コードが概ね定義済みだが、invData スコープ確認と行番号変換に中程度の判断力が必要
動作確認ユーザー手動HC RPA → Action A → STL消込 → Action B の一連の手動操作が必要

変更履歴

日付変更内容
2026-04-14初版作成