MAS-161: OCR結果の手動補正アシスト(UxAssist拡張)
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-161 |
| カテゴリ | 自動入力パイプライン・UX |
| Phase | P2.5 |
| 優先度 | ★★ |
| 所要時間 | 1〜2 時間 |
| 実装ステータス | 📝 仕様書段階・実装未着手 (2026-04-28 監査時点) |
| 対象ファイル | 300_ui/301_ui_assist.js, 200_data/202_repository.js(PartnerRepository 拡張) |
| 前提案件 | MAS-157(写真OCR), MAS-120(PartnerRepository 新設) |
目的
MAS-157(写真 OCR)で読み取った 35_wrk_receipt の金額・取引先名に OCR 誤認識があった場合、人間が「税込金額_決済」を手動修正すると、税抜金額・消費税額を自動再計算して整合性を保つ。また、「取引先名」を補正した際、12_mst_partner から T番号(適格請求書発行事業者登録番号)を自動補完する。OCR 結果の補正作業負荷を削減し、確認 FLG ONの前に整合性が確保された状態にする。
現在のコード
handleUxAssist の既存構造(300_ui/301_ui_assist.js)
handleUxAssist(e)がonEditトリガーから呼ばれる(L15-277)- L20 の
validSheets配列に35_wrk_receiptは未登録 — 現状 WRK_RCPT へのどんな編集も handleUxAssist 内で早期 return される - 既存の税額自動計算パターン: 32_wrk_invoice L181-194(順方向のみ)
// 32_wrk_invoice L181-194: 税抜→消費税・税込の片方向計算
if (colName === '税抜金額_計画' && val) {
const amount = Number(val);
if (!isNaN(amount)) {
const tax = Math.floor(amount * 0.1);
const total = amount + tax;
if (taxIdx !== -1) sheet.getRange(row, taxIdx + 1).setValue(tax);
if (totalIdx !== -1) sheet.getRange(row, totalIdx + 1).setValue(total);
}
}
32_wrk_invoice は税抜入力スタート(請求書手入力)の前提で設計されている。一方、MAS-161 は税込入力スタート(OCR で税込しか明確に読めない領収書・レシート)で、逆方向(税込 → 税抜・消費税)+ 双方向調整が必要。
35_wrk_receipt スキーマ(101_sys_config.js L639)
'WRK_RCPT': { headers: [
"管理ID","処理日時","証憑種別","取引先名","🏢住所",
"税込金額_決済","税抜金額_決済","消費税額_決済","源泉税額","T番号",
"帳票番号","発行日","発生日(P/L計上日)","決済日_実績","決済手段",
"摘要","ファイル名","証跡リンク","確認FLG","処理結果",
"マッチ決済ID(STL)","STL決済日_計画","STL税込金額_決済","STL取引先名","STL摘要"
], color: "#38761d" },
金額 3 列の位置: 税込金額_決済(F列=6)、税抜金額_決済(G列=7)、消費税額_決済(H列=8) T番号: J列=10
MST_PART スキーマ(101_sys_config.js L618)
'MST_PART': { headers: [
"有効フラグ","取引先コード","法人番号","略称_4文字","取引先名_正式",
"略称","銀行摘要名","UI用取引先名","取引先区分"
], color: "#666666" },
MST_PART には T番号 列は未登録 — 法人番号のみ存在。インボイス制度の T番号は「T + 法人番号(13桁)」が基本形のため、法人番号から派生して補完する設計が可能(DDL 変更不要)。将来 T番号列を追加する場合は直接参照に切替可。
PartnerRepository の状態
MAS-120 仕様書で新設予定だが、本案件の実装時点で MAS-120 がマージ未了の場合は MAS-161 内で PartnerRepository を先行実装する必要がある(または MAS-120 実装完了まで本案件を待機)。
再計算ロジックのフロー図
MAS-161 の核心は「どの列が変更されたらどこを更新するか」の方向制御。以下のフローチャートで定義する。
┌────────────────────────────┐
│ onEdit (35_wrk_receipt) │
└──────────┬─────────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
税込金額_決済 税抜金額_決済 消費税額_決済
が編集された が編集された が編集された
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ 税込=N │ │ 税抜=N │ │ 消費税=N │
│ → 税抜= │ │ 税込を読む │ │ 税込を読む │
│ floor(N/1.1)│ │ → 消費税= │ │ → 税抜= │
│ → 消費税= │ │ 税込 - 税抜│ │ 税込 - 税 │
│ N - 税抜 │ │ ※税込は │ │ ※税込は │
│ │ │ 触らない │ │ 触らない │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└─────────────────┴─────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ setCellIfChanged (ベき等性ガード) │
│ 目標値と現在値が異なる場合のみ setValue │
│ → onEdit の再帰的発火でもループしない │
└─────────────────────────────────────────────┘
┌────────────────────────────┐
│ 取引先名 が編集された │
└──────────┬─────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ PartnerRepository.findTaxIdMap()[取引先名] │
│ → { 法人番号, T番号 } │
│ T番号が空なら "T" + 法人番号(13桁ゼロ埋め) を派生 │
│ setIfBlank('T番号', 派生値) │
└──────────────────────────────────────────────┘
ループ防止の原則:
| 編集列 | 書込先 1 | 書込先 2 | 書込しない列 |
|---|---|---|---|
| 税込金額_決済 | 税抜金額_決済 | 消費税額_決済 | (税込は触らない=自身) |
| 税抜金額_決済 | 消費税額_決済 | — | 税込金額_決済(ユーザー権威) |
| 消費税額_決済 | 税抜金額_決済 | — | 税込金額_決済(ユーザー権威) |
Human-in-the-Loop: 「税込」がユーザーの権威値。「税抜」「消費税」を直接編集した場合、分配を調整する意図として税込は保持する。税込を再入力すれば強制的に 10% 分配で上書きされる。
修正方針
Step 1: validSheets への 35_wrk_receipt 追加
300_ui/301_ui_assist.js L20:
// 変更前
const validSheets = ['41_trn_budget', '24_bud_capex_loan', '22_bud_headcount', '21_bud_pipeline', '70_bud_resource', '23_bud_subscription', 'wrk_expense', '33_wrk_finance', '16_wrk_master', '31_wrk_order', '32_wrk_invoice', '33_wrk_bank'];
// 変更後
const validSheets = ['41_trn_budget', '24_bud_capex_loan', '22_bud_headcount', '21_bud_pipeline', '70_bud_resource', '23_bud_subscription', 'wrk_expense', '33_wrk_finance', '16_wrk_master', '31_wrk_order', '32_wrk_invoice', '33_wrk_bank', '35_wrk_receipt'];
Step 2: 35_wrk_receipt ハンドラの新設
handleUxAssist 内の else if (sName.includes('21_bud_pipeline')) の後ろ(L276 付近)に、35_wrk_receipt 専用ブロックを追加:
if (sName.includes('35_wrk_receipt')) {
// ベき等性ガード: 目標値と現在値が異なる場合のみ setValue(onEdit 再帰対策)
const setCellIfChanged = (hName, newValue) => {
const cIdx = headers.indexOf(hName);
if (cIdx === -1) return;
const cell = sheet.getRange(row, cIdx + 1);
const current = cell.getValue();
if (String(current) !== String(newValue)) cell.setValue(newValue);
};
const setIfBlank = (hName, value) => {
const cIdx = headers.indexOf(hName);
if (cIdx !== -1 && sheet.getRange(row, cIdx + 1).isBlank()) sheet.getRange(row, cIdx + 1).setValue(value);
};
const readNum = (hName) => {
const cIdx = headers.indexOf(hName);
if (cIdx === -1) return null;
const v = sheet.getRange(row, cIdx + 1).getValue();
if (v === '' || v === null || v === undefined) return null;
const n = Number(v);
return isNaN(n) ? null : n;
};
// 金額の双方向自動計算
if (colName === '税込金額_決済') {
if (val === '' || Number(val) === 0) {
// クリア/ゼロ → 従属列もクリア(分配が無意味)
setCellIfChanged('税抜金額_決済', '');
setCellIfChanged('消費税額_決済', '');
} else {
const total = Number(val);
if (!isNaN(total)) {
const rate = 0.10; // デフォルト 10%。将来 税区分 参照で 8% 対応
const exTax = Math.floor(total / (1 + rate));
const tax = total - exTax;
setCellIfChanged('税抜金額_決済', exTax);
setCellIfChanged('消費税額_決済', tax);
}
}
} else if (colName === '税抜金額_決済') {
const exTax = Number(val);
const total = readNum('税込金額_決済');
if (!isNaN(exTax) && total !== null) {
setCellIfChanged('消費税額_決済', total - exTax);
}
// 税込は触らない(ユーザー権威)
} else if (colName === '消費税額_決済') {
const tax = Number(val);
const total = readNum('税込金額_決済');
if (!isNaN(tax) && total !== null) {
setCellIfChanged('税抜金額_決済', total - tax);
}
// 税込は触らない(ユーザー権威)
}
// 取引先名 → T番号 自動補完
if (colName === '取引先名' && val) {
try {
const taxIdMap = PartnerRepository.findTaxIdMap();
const partner = taxIdMap[String(val).trim()];
if (partner) {
let tNum = String(partner['T番号'] || '').trim();
if (!tNum) {
// フォールバック: 法人番号から T番号を派生(T + 13桁ゼロ埋め)
const houjinNum = String(partner['法人番号'] || '').replace(/\D/g, '');
if (houjinNum) {
tNum = 'T' + houjinNum.padStart(13, '0');
}
}
if (tNum) setIfBlank('T番号', tNum);
}
} catch(e) { Utils.logWarn && Utils.logWarn('handleUxAssist', 'T番号補完失敗: ' + e.message); }
}
}
Step 3: PartnerRepository に findTaxIdMap を追加
200_data/202_repository.js の PartnerRepository に以下を追加(MAS-120 の findPaymentTermsMap と同パターン):
// PartnerRepository 内に追加
findTaxIdMap: function() {
if (PartnerRepository._taxIdCache) return PartnerRepository._taxIdCache;
var result = PartnerRepository.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 uiName = String(dto['UI用取引先名'] || '').trim();
var formal = String(dto['取引先名_正式'] || '').trim();
var entry = {
'法人番号': String(dto['法人番号'] || '').trim(),
'T番号': String(dto['T番号'] || '').trim(), // 将来 MST_PART に追加された場合に使用、現状は空
};
if (uiName) map[uiName] = entry;
if (formal && !map[formal]) map[formal] = entry;
}
PartnerRepository._taxIdCache = map;
return map;
},
_taxIdCache: null,
// resetCache は S-48 の既存 resetCache に以下を追記:
// PartnerRepository._taxIdCache = null;
注: MAS-120 の resetCache 実装時に _taxIdCache も同時リセットするよう修正する。本案件実装時に MAS-120 が既にマージされている前提。
Step 4: 税区分連動への拡張(将来用、本案件スコープ外)
現状 WRK_RCPT には 税区分 列が存在しない。将来 MAS-089(消費税税抜方式)と連動して税区分列を追加した際に、税率を 8%/10% 動的切替できるよう、上記コード内の const rate = 0.10; 部分をコメントで明示:
// TODO (S-17連動): 税区分列追加時に以下に差し替え
// const taxClass = String(sheet.getRange(row, headers.indexOf('税区分')+1).getValue() || '').trim();
// const rate = taxClass === '課税8%' ? 0.08 : taxClass === '対象外' || taxClass === '非課税' ? 0 : 0.10;
// if (rate === 0) { /* 税抜=税込、消費税=0 */ }
Step 4 自体の実装は本案件では行わない(コメントのみ)。
影響範囲
| 変更ファイル | 変更量 | 内容 |
|---|---|---|
300_ui/301_ui_assist.js | 約 60 行 | validSheets L20 に 1 要素追加 + 35_wrk_receipt ハンドラブロック新設 |
200_data/202_repository.js | 約 25 行 | PartnerRepository に findTaxIdMap + _taxIdCache 追加、resetCache にクリア 1 行追加 |
既存動作への影響:
- handleUxAssist の他シート(32_wrk_invoice 等)には影響しない(35_wrk_receipt 専用ブロック)
- PartnerRepository の既存
findAll/findPaymentTermsMapには影響しない(キャッシュ変数は独立) - 既存 35_wrk_receipt データ(確認 FLG ON の行など)は再編集しない限り変更されない
注意事項
- ベき等性ガード必須 —
setCellIfChangedで「現在値と新値が同じなら書き込まない」を徹底。これを忘れると onEdit 再帰で無限ループに陥る可能性(既存 32_wrk_invoice は片方向だけなので非対称。MAS-161 は双方向のため対策必須)。 - 税込がユーザー権威 — 税抜/消費税を編集しても税込は絶対に触らない。OCR 結果補正の運用上、税込金額は実際の領収書記載額(絶対値)であり、分配だけ人間が再調整するユースケース。
- 型変換の安全性 —
Number('')は 0、Number('abc')は NaN。条件分岐でisNaNチェックと空文字チェックを両方行う。 - 軽減税率対応はコメントで予約 — Step 4 の TODO を必ず残す。MAS-089 と連動するまで
rate = 0.10ハードコード。 - T番号派生ロジック — 「T + 法人番号 13桁ゼロ埋め」は日本のインボイス制度の基本形式だが、個人事業主の適格請求書発行事業者登録番号は「T + 13桁の登録番号(法人番号とは別体系)」であり、派生できない。この場合は MST_PART に T番号列を明示的に追加(将来拡張)して手入力するか、OCR 側の T番号を維持する運用となる。
- PartnerRepository への依存 — MAS-120 実装完了が前提。MAS-120 未実装時は MAS-161 実装より先に MAS-120 を完了させるか、MAS-161 の Step 3 内で PartnerRepository を先行実装する判断を行う。
- 確認 FLG=TRUE 行の扱い — 確認 FLG が TRUE の行で金額セルが編集された場合、自動再計算を走らせるかは設計判断。本案件ではシンプルさ優先で「確認 FLG 問わず再計算」とする。確認済データを再編集した場合は再度ユーザーが確認 FLG をトグルする前提。
- Math.floor の端数処理 — 税抜 = floor(税込 / 1.1) により、110 → 100(正)、103 → 93(税抜)+ 10(税)= 103(OK)、100 → 90(税抜)+ 10(税)= 100(OK)。国税庁の一般的な端数処理(切捨て)に準拠。四捨五入を選択した場合の将来拡張は
CFG_TAX_ROUNDING(MAS-089 で提案)と連動。 - 列参照はヘッダー名ベース —
indexOfを貫徹。列番号ハードコード禁止(CLAUDE.md 規約)。 - onEdit の軽量性 —
PartnerRepository.findTaxIdMap()はキャッシュされるため 2 回目以降は O(1)。初回は MST_PART 全件読込で 100-300ms 程度。onEdit の応答性許容範囲内。
エッジケース
| 条件 | 挙動 | 理由 |
|---|---|---|
| 税込金額_決済 に 0 を入力 | 税抜=0、消費税=0(空ではなく 0) | 実質ゼロ。floor(0/1.1)=0 の数学的結果を採用 |
| 税込金額_決済 をクリア(空に) | 税抜=空、消費税=空 | 分配の意味がなくなるため従属列もクリア |
| 税込金額_決済 に非数値("abc")入力 | 何もしない(isNaN で早期 skip) | 無効入力はセル検証に任せる |
| 税込金額_決済 に負値(-110)入力 | 税抜=floor(-110/1.1)=-100、消費税=-10 | 返金・訂正ケース。負値許容 |
| 税抜を先に入力 → 税込を入れる | 税抜が再計算で上書きされる | 税込入力が後 = 権威。編集順序で意図を伝える |
| 税抜を編集したが税込が空 | 何もしない(total===null で早期 skip) | 税込が無いと消費税を決定できない |
| 消費税だけ編集 | 税抜を再計算(税込-税)、税込は触らない | 非課税率時の手動調整を尊重 |
| 税抜 = 税込 を入力 | 消費税 = 0 | 非課税・対象外の手動表現(将来 Step 4 で税区分連動) |
| 軽減税率(8%)の領収書 | デフォルト 10% で計算される → ユーザーが手動で税抜を修正 → 消費税が自動追従 | 本案件では 10% のみ。8% は人間介入で対応(将来 Step 4 で自動化) |
| 非課税取引(対象外) | 税込=5000 入力すると税抜=4545, 消費税=455 が誤って入る | 現状は税区分列がないため区別不能。ユーザーが税抜=5000 を手動で入れ直す→消費税=0 に修正される |
| 取引先名を入力したが MST_PART 未登録 | T番号は何もしない(既存値保持) | マスタ追加を促す運用 |
| 取引先名を入力、MST_PART に登録ありだが法人番号空 | T番号は派生不可、既存値保持 | 法人番号マスタ更新を促す |
| T番号が既に OCR で設定済 | setIfBlank のため上書きしない | OCR 読取値 vs マスタ値の衝突を避ける(マスタ補完は空欄のみ) |
| 取引先名を A → B に変更 | T番号は上書きしない(非空欄のため) | 取引先変更時は T番号セルを手動クリアしてから再選択する運用 |
| 13桁未満の法人番号(例: "1234567890") | padStart(13, '0') で "T0001234567890" | 派生値の形式統一 |
実データ検証(MCP での事前確認)
- 35_wrk_receipt の現在の列順 — DDL 定義(L639)と実シートの列位置が一致しているか(特に 税込/税抜/消費税/T番号/取引先名)。
- 12_mst_partner の
法人番号列データ品質 — 空欄比率、13 桁ゼロ埋め有無、文字列 vs 数値型の混在。replace(/\D/g, '')で非数字を除去する設計の妥当性。 - 既存 WRK_RCPT データの 税抜+消費税=税込 整合率 — OCR が税抜を不正確に読み取っているケースの割合把握。整合率が低い場合、MAS-161 実装後の初回編集で既存データも再計算されるため影響範囲を事前把握。
- validSheets 配列の最新状態 — MAS-120 実装で
'26_bud_adhoc'を追加した場合、本案件の'35_wrk_receipt'追加時に重複や順序崩れがないこと。 - PartnerRepository の実装状態 — MAS-120 実装が完了しているか。未完了の場合、MAS-161 開始前に MAS-120 完了を確認。
- onEdit のレイテンシ — 30 件規模の MST_PART で findTaxIdMap キャッシュ初回ロード時間を事前計測(dev 環境)。
関連ドキュメント
| 仕様書 | 関連箇所 |
|---|---|
| dev_mas-157_photo_ocr.md | 写真 OCR の出力先(35_wrk_receipt)と T番号抽出ロジック |
| dev_mas-120_partner_payment_terms_autofill.md | PartnerRepository 新設・findPaymentTermsMap パターン(findTaxIdMap の参考) |
| dev_mas-089_consumption_tax_exclusive_method.md | 税区分連動(軽減税率・非課税)の将来拡張先 |
| dev_mas-110_document_date.md | onEdit 自動補完のアーキテクチャ原則 |
| CLAUDE.md | コーディング規約(ヘッダー名ベース参照、onEdit ルール) |
| failure_patterns.md | #18-#20(固有名詞は Read で確認してから仕様書記述) |
人間が検討すべき事項
✅ MAS-305 にて調査済(2026-04-19):MAS-305 研究結果 に基づく決定事項を以下に反映。確定済の項目は 【MAS-305 確定】 タグで明示。
- 【MAS-305 確定】軽減税率 8% の扱い — 軽減税率判定マトリクス(飲食料品 / 酒類 / 外食・ケータリング / 一体資産 / 新聞)を実装。MAS-089 で追加される税区分列と組み合わせ、OCR 結果からカテゴリ推定+ユーザー確認の 2 段階 UI を構築する。詳細は
RQ-007_result.md§③ 参照。 - 非課税取引(住居家賃、社保等) — 税区分列がない現状では OCR 結果でも判別不能。現時点は人間が税抜を手動入力で対応、MAS-089 完了後に自動化。
- 【MAS-305 確定】取引区分の判別フロー — OCR 補正 UI に
国外? → 事業/対価? → 非課税別表一? → 輸出免税? → 課税(10%/8%)の 4 段階意思決定フローを組み込み、ユーザーに迷いなく税区分を選ばせる。詳細はRQ-007_result.md§④ 参照。 - 【MAS-305 確定】T 番号バリデーション — OCR で抽出した T 番号について、
^T\d{13}$の正規表現+モジュラス 11 系チェックデジットで検証。誤認識は背景色警告で Human-in-the-Loop を促す。国税庁 Web-API 連携は別案件として切り出し(事前承認申請が必要)。 - 【MAS-305 確定】T 番号未記載・誤記載時の SOP — 補正 UI で「未記載」「誤記載」を検知した場合のフロー:
- (1) 売手に修正インボイス再発行を依頼するか、(2) 買手側で仕入明細書を作成して売手確認を得る、の選択肢を経理担当者に提示
- 取引先の登録ステータス(登録日・遡及登録の有無)をシステム上で厳密管理
- 【MAS-305 確定】簡易インボイス対応 — OCR 結果が「宛名なし」または「税率のみ記載(金額詳細なし)」の場合は、小売業・飲食店業・タクシー業の簡易インボイスとして受入可能と判定。通常インボイスとの出し分けロジックを追加する。
- 確認 FLG=TRUE 行の再編集ポリシー — 「確認済データの金額編集時に自動再計算する or 警告を出す」。本案件ではシンプルに再計算する方針だが、監査証跡(MAS-179)の観点で編集履歴を残すかの議論が必要。
- 個人事業主の適格請求書発行事業者番号 — 法人番号からの派生ができないため、MST_PART に T番号列を追加する拡張(MAS-120 後続 or 独立 DDL 変更)が必要。【MAS-305 確定】: T 番号の有効性検証のためには
validateTNumber()ユーティリティ関数を 000_infra/004_utils.js に実装。 - OCR 誤認識の統計 — MAS-157 実運用 1 ヶ月後に「どの列の補正が最も多いか」を集計し、本案件のスコープ(税込補正・取引先補正・T 番号補正)が十分か再評価。
- 【MAS-305 確定】電帳法との連携 — 補正した OCR データは「取引年月日 / 取引先 / 取引金額」の 3 項目で検索可能な形式で保存する。真実性の確保(編集履歴)と可視性の確保を両立させる。
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-161「OCR結果の手動補正アシスト(UxAssist拡張)」を実装してください。
## 実行前タスク
以下のファイルを Read し、構造を把握すること(failure_patterns.md #18-#20 の Read 原則参照):
- `docs/dev/dev_mas-161_ocr_manual_correction_assist.md` — 本仕様書全文
- `docs/dev/dev_mas-120_partner_payment_terms_autofill.md` — PartnerRepository パターン
- `docs/_internal/failure_patterns.md` — #18-#20(Read 原則)
- `300_ui/301_ui_assist.js`
- L20 validSheets 配列の現在の要素
- L128-L194 32_wrk_invoice ハンドラの税額自動計算パターン(片方向)
- L251-L276 21_bud_pipeline ハンドラの末尾位置(新規ブロック挿入位置)
- L15-L22 早期 return 条件の理解(row===1、e.value等)
- `200_data/202_repository.js`
- PartnerRepository の実装状態確認(MAS-120 マージ済みか)
- 未マージなら MAS-120 の PartnerRepository 定義を先行実装する必要あり
- AccountRepository のキャッシュ + resetCache パターン
- `100_config/101_sys_config.js` L618 MST_PART headers の確認(法人番号列の位置)
- `100_config/101_sys_config.js` L639 WRK_RCPT headers の確認(税込/税抜/消費税/T番号/取引先名の位置)
## 修正対象ファイル
1. `300_ui/301_ui_assist.js` — validSheets に 1 要素追加 + 35_wrk_receipt ハンドラブロック新設
2. `200_data/202_repository.js` — PartnerRepository に findTaxIdMap + _taxIdCache 追加、resetCache 更新
## 実装内容
### Step 1: validSheets 拡張
L20 の validSheets 配列末尾に `'35_wrk_receipt'` を追加。
### Step 2: 35_wrk_receipt ハンドラブロック新設
handleUxAssist 内、`else if (sName.includes('21_bud_pipeline'))` ブロックの閉じ括弧直後(L276 付近)に新規 if ブロックを追加:
- `setCellIfChanged(hName, newValue)` ヘルパー: 現在値と目標値が異なる場合のみ setValue(ベき等性ガード)
- `setIfBlank(hName, value)` ヘルパー: 既存パターン踏襲
- `readNum(hName)` ヘルパー: セル値を数値化、空/NaN は null
金額の双方向計算:
- `colName === '税込金額_決済'`: val が 0 or 空 → 税抜/消費税もクリア、それ以外 → 税抜=floor(val/1.1), 消費税=val-税抜
- `colName === '税抜金額_決済'`: 消費税=税込-税抜 のみ(税込は触らない)
- `colName === '消費税額_決済'`: 税抜=税込-消費税 のみ(税込は触らない)
取引先 T番号 補完:
- `colName === '取引先名'` && val: PartnerRepository.findTaxIdMap()[val] を検索
- T番号が空なら「T + 法人番号(13桁ゼロ埋め)」を派生
- setIfBlank で T番号列に書き込み(既存値上書きしない)
### Step 3: PartnerRepository 拡張
- `findTaxIdMap()`: 有効フラグ=TRUE の全取引先について `{UI用取引先名|取引先名_正式 → {法人番号, T番号}}` を返す。キャッシュ付き
- `_taxIdCache`: キャッシュ変数(null 初期化)
- `resetCache()`: 既存のクリア処理に `_taxIdCache = null` を追加
## 制約
- 列参照は必ずヘッダー名ベース(indexOf)。列番号ハードコード禁止
- 既存 handleUxAssist の他シートブロック(16_wrk_master, 32_wrk_invoice 等)は変更しない
- setCellIfChanged を必ず使い、再帰ループを防止
- 税込は税抜/消費税編集時に絶対に書き換えない(ユーザー権威)
- 税率は `0.10` ハードコードで OK(MAS-089 連動は TODO コメントのみ)
- PartnerRepository の既存 findPaymentTermsMap / _termsCache は変更しない
- MAS-120 未マージの場合は作業を停止し、MAS-120 実装を先行させる
## エッジケース
仕様書「エッジケース」テーブル全 14 行を実装時の判定条件に反映。特に:
- 税込をクリア(空)→ 税抜/消費税もクリア
- 税込=0 → 税抜=0、消費税=0
- 非数値入力 → 何もしない
- 負値入力 → そのまま計算(返金ケース対応)
- 税抜編集で税込が空 → 何もしない
- MST_PART 未登録取引先 → T番号補完スキップ
- T番号既存(OCR 設定済)→ setIfBlank で上書きしない
## 実データ検証
実装着手前に MCP で以下を確認:
1. 35_wrk_receipt のヘッダー行と DDL(L639)の完全一致
2. 12_mst_partner の 法人番号 列のデータ形式(文字列/数値、ゼロ埋め有無)
3. 既存 WRK_RCPT データの 税抜+消費税=税込 整合率
4. PartnerRepository が実装済みか(MAS-120 マージ状態)
5. validSheets の現在の要素数と並び(重複チェック)
## 動作確認
1. `npm run push:dev` で開発 GAS にデプロイ
2. 35_wrk_receipt に新規行作成、税込金額_決済 に 1100 を入力 → 税抜=1000, 消費税=100 が自動セット
3. 税抜金額_決済 を 900 に修正 → 消費税=200 に自動更新、税込=1100 のまま
4. 消費税額_決済 を 150 に修正 → 税抜=950 に自動更新、税込=1100 のまま
5. 税込金額_決済 をクリア → 税抜/消費税もクリア
6. 税込金額_決済 に 0 を入力 → 税抜=0、消費税=0
7. 税込金額_決済 に "abc" を入力 → 何も変化しない
8. 取引先名に MST_PART 登録済の取引先を入力 → T番号が「T + 法人番号13桁」形式で自動セット
9. 既に T番号が入っている行で取引先名を別社に変更 → T番号は維持される
10. MST_PART 未登録の取引先名を入力 → T番号セルは変化しない
11. 32_wrk_invoice の既存動作(税抜→消費税・税込)が影響を受けていないこと
12. 21_bud_pipeline / 23_bud_subscription の既存動作が影響を受けていないこと
13. onEdit の応答性(1〜2 秒以内で再計算完了)
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|:--------:|------|
| Step 1 validSheets 追加 | なし | 配列末尾への文字列追加 |
| Step 2 ハンドラブロック | あり | 双方向計算のループ防止設計、既存パターンとの差分理解 |
| Step 3 PartnerRepository | あり | MAS-120 パターン踏襲、キャッシュ変数追加と resetCache 更新の整合 |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| Step 1 validSheets | Claude Haiku 4.5 | 1 要素追加のみ |
| Step 2 ハンドラ新設 | Claude Sonnet 4.6 | 双方向計算のループ防止設計、既存 32_wrk_invoice パターンとの対称性理解が必要 |
| Step 3 Repository 拡張 | Claude Sonnet 4.6 | MAS-120 の findPaymentTermsMap パターン踏襲、キャッシュ整合 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-17 | 初版作成。税込をユーザー権威とする双方向再計算、setCellIfChanged によるループ防止、PartnerRepository.findTaxIdMap 追加、T番号は法人番号派生(T + 13桁ゼロ埋め)の 3 ステップ設計 |
仕様書作成プロンプト(再現性・監査性のため記録)
展開して表示
あなたはGAS会計システム(bizlp-gas-accounting)のシニアアーキテクト兼仕様書ライターです。
案件 MAS-161「OCR結果の手動補正アシスト(UxAssist拡張)」の開発仕様書を作成してください。
## 実行前タスク
以下のファイルを読み込み、既存の入力補完ロジックと領収書シートの構造を把握してください。
- `docs/_internal/TODO_future.md` — MAS-161 の概要確認
- `300_ui/301_ui_assist.js` — `handleUxAssist` の既存ロジック
- `100_config/101_sys_config.js` — `schemas` 定義(WRK_RCPT)
- `docs/dev/dev_mas-110_document_date.md` — 参考:onEdit による自動補完の仕様書
## 既存実装の前提知識
- 入力補完は `301_ui_assist.js` の `handleUxAssist` が `onEdit` トリガーから呼び出されて実行されます。
- 現在、`35_wrk_receipt`(領収書/請求書)シートに対する `handleUxAssist` のロジックはまだ実装されていません。
## 仕様書の出力先ファイル名
`docs/dev/dev_mas-161_ocr_manual_correction_assist.md`
## 固有の設計要件
1. **金額の自動再計算ロジック**:
- `35_wrk_receipt` シートで「税込金額_決済」が編集された際、以下の項目を自動計算してセットする。
- 税抜金額_決済 = 税込金額 / 1.1(円未満切捨て)
- 消費税額_決済 = 税込金額 - 税抜金額
- **Human-in-the-Loop**: ユーザーが「税抜金額」や「消費税額」を直接編集した場合は、この自動計算が走らないように制御すること(無限ループ防止および意図的な手動修正の尊重)。
2. **取引先情報の自動補完**:
- 「取引先名」が編集された際、`12_mst_partner` を検索し、登録されている「T番号(適格請求書発行事業者番号)」を自動でセットする。
3. **Repository の活用**:
- 取引先情報の検索には `PartnerRepository` を使用すること。
4. **エッジケース**:
- 金額が空になった場合(クリアされた場合)の挙動。
- 軽減税率(8%)への対応方針(今回はデフォルト10%としつつ、将来的に税区分カラムと連動できる拡張性を考慮)。
- 非課税取引(対象外)の場合の処理。
## 出力品質の基準
- `docs/_internal/dev_spec_prompt_template.md` のセクション構成に完全準拠すること。
- **再計算ロジックのフロー図**(どの列が変更されたらどこを更新するか)を仕様書内に含めること。
- エッジケーステーブル(ゼロ金額、非課税等)を必ず含めること。
- 実装プロンプトはバッククォートで囲まず、行頭4スペースインデントで出力すること。
## 重要な制約
- 必要な指示をすべて自己完結的に含め、既存の `handleUxAssist` の他シートへの影響が出ないように配慮してください。
調査中に判明した重要な構造:
35_wrk_receiptはhandleUxAssistのvalidSheets(L20)に未登録 → Step 1 で追加必須- 既存の税額自動計算は 32_wrk_invoice L181-194 の片方向(税抜→税込・消費税)のみ。MAS-161 は双方向(税込→税抜/消費税、税抜/消費税→片方向)のため onEdit 再帰でループするリスクがあり、
setCellIfChangedでベき等性ガードが必須 12_mst_partner(MST_PART)には T番号列なし。「T + 法人番号 13桁ゼロ埋め」で派生する設計で DDL 変更不要PartnerRepositoryは MAS-120 で新設予定(未実装時は先行実装が必要)