MAS-167: 33タブSTL重複行の検出・削除ツール
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-167 |
| カテゴリ | 事後監査 (Post-audit) |
| Phase | P2 |
| 優先度 | ★★(Top 15 昇格済) |
| 所要時間 | 1-2時間 |
| 実装ステータス | 📝 仕様書段階・実装未着手 (2026-04-28 監査時点) |
| 対象ファイル | 100_config/101_sys_config.js(既存 cleanupDuplicateRows 拡張または新関数追加)、templates/operations_sidebar.html(ボタン追加) |
| 前提案件 | なし(独立実装可) |
| 実装日 | 未実装 |
目的
33_wrk_bank タブの STL 重複行を定期スキャンし、Action A の多重実行等によるデータ不整合を事後検知・修復可能にする。既存の cleanupDuplicateRows は ID 単純一致による削除のみだが、本案件では「同一 INV_ID に対して複数の未処理 STL が存在する(Action A 多重実行パターン)」「決済金額が同額かつ状態が同じ重複行」など STL 固有の重複パターンを追加検出し、安全に削除できるツールを提供する。
現在のコード
既存の重複行削除ロジック(100_config/101_sys_config.js L25-84)
// 100_config/101_sys_config.js L22-84
function cleanupDuplicateRows() {
var targets = [
{ key: 'WRK_ORDR', fallback: '31_wrk_order', idHeader: '発注ID(ORD)' },
{ key: 'WRK_INVC', fallback: '32_wrk_invoice', idHeader: '請求ID(INV)' },
{ key: 'WRK_BANK', fallback: '33_wrk_bank', idHeader: '決済ID(STL)' }
];
// ...
// 同一 ID の 2 行目以降を赤ハイライトしてアラート → 手動削除
if (totalDeleted === 0) {
ui.alert('✅', '重複行はありませんでした。', ui.ButtonSet.OK);
} else {
ui.alert('⚠️ 重複行を赤色でマークしました。確認後、手動で削除してください。\n\n' + details.join('\n'));
}
}
現在の問題点:
- STL_ID が異なっていても「同一 INV_ID に複数の未処理 STL」が作られる Action A 多重実行パターンを検出できない
- 赤ハイライトのみで、実際の削除は手動(ユーザー負担)
- 31/32/33 全タブを対象とするため、33 タブ固有のパターン分析が薄い
既存の整合性チェック(100_config/101_sys_config.js L91-220)
checkInvStlConsistency が INV↔STL 整合性チェックを実装済み。しかし重複行の検出・削除は対象外。
操作パネルのボタン(templates/operations_sidebar.html L74)
<button class="btn warn" onclick="run('cleanupDuplicateRows', this)">🧹 重複行削除</button>
修正方針
Step 1: 33タブ専用 STL 重複スキャン関数の追加
100_config/101_sys_config.js の cleanupDuplicateRows 直後(L85付近)に新関数 scanStlDuplicates_ を追加する。
検出すべき重複パターン:
| パターン | 検出条件 | 優先保持行 |
|---|---|---|
| A: STL_ID 完全一致 | 同一 決済ID(STL) が 2 行以上 | 最初の行 |
| B: 同一INV の未処理STL重複 | 同一 消込対象請求ID(INV) かつ 決済ステータス=未処理 が 2 件以上 | 最初の行(作成日昇順) |
| C: 金額同額の消込済STL重複 | 同一 消込対象請求ID(INV) かつ 決済ステータス=消込済 かつ 税込金額_決済 が同額 | 自動仕訳JNL_ID が空でない行(空なら最初) |
Step 2: Human-in-the-Loop 対応の確認ダイアログ
プロダクトポリシーに従い、削除前に確認ダイアログを表示する。
【STL重複行の検出結果】
パターンA (STL_ID重複): 0件
パターンB (同一INV未処理重複): 2件
- STL_20250401_0002 (INV_20250401_0001)
- STL_20250401_0003 (INV_20250401_0001)
パターンC (消込済金額重複): 0件
削除予定: 2件(赤ハイライト行)
保持: 残行
削除してよいですか?
[削除実行] [キャンセル]
Step 3: 実際の削除処理
確認後、下から上の順(行番号降順)に sheet.deleteRow(rowNum) を実行する。
削除前の監査ログ記録:
Utils.auditLog('DELETE', '33_wrk_bank', stlId, '', 'cleanupStlDuplicates',
{ reason: patternType, duplicateOf: keepStlId },
null,
'I-25 STL重複削除');
Step 4: 操作パネルへの追加
templates/operations_sidebar.html L75 付近に新ボタンを追加する:
<button class="btn warn" onclick="run('cleanupStlDuplicates', this)">🔍 STL重複検出・削除</button>
Step 5: 公開関数として定義
/**
* I-25: 33_wrk_bank の STL 重複行を検出・削除するツール
* Human-in-the-Loop: 削除前に確認ダイアログを表示
*/
function cleanupStlDuplicates() { ... }
影響範囲
| 対象 | 変更内容 | 変更量 |
|---|---|---|
100_config/101_sys_config.js | cleanupStlDuplicates 関数追加(L85付近に挿入) | 約 80-100 行追加 |
templates/operations_sidebar.html | STL重複スキャンボタン追加(L74-75 付近) | 1 行追加 |
000_infra/002_constants.js | MENU_DEFINITION への項目追加(任意・MAS-214 メニューカタログ連携) | 1 エントリ追加 |
既存の cleanupDuplicateRows は変更しない。新関数を追加することで後方互換を維持する。
注意事項
- 行番号降順での削除: 行削除は上から行うと後続行のインデックスがずれる。
deleteRowsは必ず行番号の降順(大→小)で実行すること - 消込済STLの慎重な扱い: パターンC(消込済金額重複)は
自動仕訳JNL_IDが埋まっている行が存在する場合、その行を必ず保持すること。JNL_ID のある行を削除すると TRN との整合性が崩れる - 有効フラグ=FALSE の行はスキップ: 論理削除済みの行は重複判定から除外する
- STL_ID が空の行はスキップ: ヘッダー行や不完全行を誤検出しないよう、
決済ID(STL)が空の行は処理対象外 - 監査ログ必須: 削除前に
Utils.auditLog('DELETE', ...)を記録すること(MAS-179 対応) - dev 環境で先行テスト:
npm run push:devでデプロイ後、開発用スプレッドシートで実行確認してから prod に適用する
エッジケース
| 条件 | 動作 | 理由 |
|---|---|---|
| 33_wrk_bank にデータが 1 行以下(ヘッダーのみ) | "重複行はありませんでした" アラートで終了 | スキャン不要 |
| STL_ID が空の行 | スキップ(重複判定対象外) | 不完全行を誤削除しない |
| 有効フラグ=FALSE の行 | スキップ | 論理削除済みは対象外 |
| パターンB: 同一INV未処理STLが 3 件以上 | 最初の 1 件のみ保持、残り全件を削除対象に | Action A の多重実行を完全除去 |
| パターンC: 消込済STLで両行に JNL_ID がある | 両行を削除候補として表示するが、ユーザー確認ダイアログで「キャンセル」を推奨 | 手動判断が必要なケース |
| パターンC: 消込済STLで片方に JNL_ID がある | JNL_ID のある行を保持、もう一方を削除対象 | TRN 整合性を最優先 |
| 重複が 0 件の場合 | "STL の重複行はありませんでした" アラートで終了 | 正常系 |
| ユーザーが確認ダイアログで「キャンセル」 | 削除を中断、ハイライトのみ残す | Human-in-the-Loop ポリシー準拠 |
| 33_wrk_bank シートが見つからない | エラーアラートを表示して終了 | Utils.getSheetByKey でシートキーを解決できない場合 |
実データ検証
実装前に開発用スプレッドシートで以下を確認すること:
| 確認項目 | 手順 |
|---|---|
| 33_wrk_bank のヘッダー行(列名) | 決済ID(STL), 消込対象請求ID(INV), 決済ステータス, 税込金額_決済, 自動仕訳JNL_ID, 有効フラグ が存在するか確認 |
| パターンB の再現 | dev 環境で Action A を同一 INV に対して 2 回実行し、STL が重複生成されるか確認 |
| 削除後の TRN 整合性 | checkInvStlConsistency で孤立 TRN が発生しないことを確認 |
| 監査ログ記録 | 削除実行後、98_audit_log タブに DELETE ログが記録されていること |
関連ドキュメント
| ドキュメント | 関連箇所 |
|---|---|
| E.6.11 MAS-166 立替精算STLの振込先名データ改善 | STL 生成ロジック(Action A)の参考 |
| E.1.8 MAS-179 監査証跡の強化 | 削除時の Utils.auditLog の使い方 |
| E.2.17 MAS-122 ORD↔INV 消化状況の整合性チェック | 整合性チェック実装のパターン参考 |
| 仕様書: 4.4.1 Action A / Action B | STL 自動生成の詳細仕様 |
人間が検討すべき事項
- 削除方式: 物理削除(
sheet.deleteRow)か論理削除(有効フラグを FALSE に更新)か。物理削除はシートをすっきりさせるが、証跡が残らない。監査ログに記録するならどちらでも可だが、誤削除リスクを考慮すると「有効フラグ=FALSE + 赤ハイライト → 別途物理削除」の 2 段階が安全 - パターンC の自動削除可否: 消込済STLが重複する場合(Action B 多重実行)、JNL_ID の有無だけで保持/削除を自動判断してよいか。TRN 側も重複していれば手動対応が必要
- 定期実行トリガーの要否: TODO_future.md では「定期スキャン」と記載あり。時間トリガーで週次実行し、重複検出時にメール通知する仕様にするかどうか(初期実装はメニュー手動実行で十分)
- 31/32 タブへの波及: STL 固有ツールとして 33 タブ専用にするか、既存の
cleanupDuplicateRowsを拡張して全タブ対応にするか
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-167「33タブSTL重複行の検出・削除ツール」を実装してください。
## 実行前タスク
以下のファイルを Read して内容を把握してから実装を開始すること:
- `100_config/101_sys_config.js` L22-84: 既存の `cleanupDuplicateRows` 関数(挿入位置・スタイル確認)
- `100_config/101_sys_config.js` L86-220: `checkInvStlConsistency` 関数(パターン)
- `templates/operations_sidebar.html` L70-80: ボタン追加位置の確認
- `000_infra/003_contracts.js` L69-89: `BankTxDTO` の列名一覧(ヘッダー名の正確な確認)
- `000_infra/004_utils.js`: `Utils.auditLog` の引数仕様の確認
## 修正対象ファイル
1. `100_config/101_sys_config.js` に `cleanupStlDuplicates` 関数を追加(L85 付近、`cleanupDuplicateRows` 直後)
2. `templates/operations_sidebar.html` に STL重複スキャンボタンを追加(L74 の「重複行削除」ボタンの直後)
## 実装内容
### 1. `cleanupStlDuplicates` 関数 (`100_config/101_sys_config.js`)
関数の概要:
- `Utils.getSheetByKey('WRK_BANK', '33_wrk_bank')` でシートを取得
- `sheet.getDataRange().getValues()` でデータを一括取得
- `buildHeaderIndex_` と同等のヘッダーMap構築(`data[0].indexOf(列名)` で各列インデックスを取得)
- 有効フラグ=FALSE の行・STL_ID が空の行はスキップ
- 以下の 3 パターンで重複を検出:
- パターンA: 同一 `決済ID(STL)` が 2 行以上
- パターンB: 同一 `消込対象請求ID(INV)` かつ `決済ステータス='未処理'` が 2 件以上
- パターンC: 同一 `消込対象請求ID(INV)` かつ `決済ステータス='消込済'` かつ `税込金額_決済` が同額
- 削除対象行を赤ハイライト(`#F4CCCC`)でマーク
- 確認ダイアログ(`ui.alert` + `ui.ButtonSet.YES_NO`)で一覧を表示
- ユーザーが「はい」を選択した場合のみ物理削除を実行(行番号降順で `sheet.deleteRow`)
- 削除前に各行の `Utils.auditLog('DELETE', '33_wrk_bank', stlId, '', 'cleanupStlDuplicates', ...)` を記録
パターンC の保持ルール:
- `自動仕訳JNL_ID` が空でない行を保持し、空の行を削除対象にする
- 両行とも JNL_ID が埋まっている場合は削除対象をマークするが、ダイアログで「手動確認推奨」を表示
### 2. `templates/operations_sidebar.html` ボタン追加
L74 の既存ボタン直後に以下を追加:
```html
<button class="btn warn" onclick="run('cleanupStlDuplicates', this)">🔍 STL重複スキャン</button>
```
## 制約
- `cleanupDuplicateRows` 関数(L25-84)は変更しないこと
- 列番号のハードコード禁止。必ず `data[0].indexOf('列名')` でヘッダーベースの参照を使う
- 削除は `sheet.deleteRow(行番号)` を行番号降順で実行すること(昇順だとインデックスがずれる)
- `SpreadsheetApp.getUi()` は関数の先頭で一度だけ取得すること
## エッジケース
| 条件 | 動作 |
|------|------|
| データが 1 行以下(ヘッダーのみ) | "重複行はありませんでした" で終了 |
| 重複が 0 件 | "STL の重複行はありませんでした" アラートで終了 |
| ユーザーがキャンセル | 削除中断、赤ハイライトのみ残す |
| パターンC で両行 JNL_ID あり | 削除候補をマークしダイアログに「要手動確認」を表示 |
| シートが見つからない | エラーアラートを表示して return |
## 実データ検証
1. dev 環境で同一 INV_ID に対して Action A(processInvoiceApprovals)を 2 回実行し、33 タブに STL が 2 件生成されることを確認
2. `cleanupStlDuplicates` を実行し、2 件目の STL が赤ハイライトされることを確認
3. 確認ダイアログで「はい」を選択し、2 件目が物理削除されることを確認
4. 98_audit_log タブに DELETE ログが記録されていることを確認
5. `checkInvStlConsistency` を実行し、整合性エラーが出ないことを確認
## 動作確認
1. `npm run push:dev` でデプロイ
2. 開発用スプレッドシートの 33_wrk_bank タブで Action A を同一 INV に対して 2 回実行
3. 操作パネル → 「🔍 STL重複スキャン」ボタンをクリック
4. 確認ダイアログに重複内容が表示されることを確認
5. 「はい」を選択して削除実行
6. 33_wrk_bank タブで重複行が削除されていることを確認
7. 98_audit_log タブで DELETE ログが記録されていることを確認
8. `checkInvStlConsistency` で整合性エラーが出ないことを確認
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|----------|---------|------|
| Phase 1(設計・調査) | あり | ファイル構造・パターン設計の確定 |
| Phase 2(清書・実装) | なし | 確定済み内容の書き下しに徹する |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| 実装全体 | Claude Sonnet 4.6 | 挿入位置の特定・既存パターン(cleanupDuplicateRows)の踏襲が必要。ロジックは中程度の複雑さ |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-21 | 初版作成 |
仕様書作成プロンプト(再現性・監査性のため必ず記録)
展開して表示
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
作業ディレクトリ: /workspaces/bizlp-gas-accounting 現在のブランチ: docs/add-spec-i-25 (main から分岐済・クリーン状態)
指示書 /workspaces/bizlp-gas-accounting/tasks/prompts/task_I-25.md を Read して、その内容を厳密に実行してください。
(注: task_I-25.md が存在しなかったため、docs/_internal/TODO_future.md の MAS-167 エントリ(「33タブSTL重複行の検出・削除ツール」)から案件定義を読み取り、関連コードを調査した上で仕様書を作成した。)
調査した主なファイル
docs/_internal/TODO_future.md— MAS-167 案件定義の確認100_config/101_sys_config.jsL22-220 —cleanupDuplicateRows,checkInvStlConsistencyの実装確認400_domain/410_subledger_engine.jsL1-706 — Action A/B の STL 生成ロジック(重複原因の理解)000_infra/003_contracts.jsL69-89 —BankTxDTOの列名一覧templates/operations_sidebar.htmlL70-80 — ボタン追加位置の確認docs/_internal/dev_spec_prompt_template.md— 仕様書構成・フォーマットの確認
実行スコープ
- Phase 1(Read による関連ファイル調査): 完全実行
- Phase 2(仕様書の分割作成 Step 2-1〜2-4): 完全実行
- Phase 3: Step 1(docs/_config.json 追記)と Step 2(docs/_internal/changelog.md 追記)のみ実行
- Phase 3 の git commit / push / PR 作成は実行しないこと。呼び出し元が処理します。