概要

項目内容
案件IDMAS-161
カテゴリ自動入力パイプライン・UX
PhaseP2.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 の行など)は再編集しない限り変更されない

注意事項

  1. ベき等性ガード必須setCellIfChanged で「現在値と新値が同じなら書き込まない」を徹底。これを忘れると onEdit 再帰で無限ループに陥る可能性(既存 32_wrk_invoice は片方向だけなので非対称。MAS-161 は双方向のため対策必須)。
  2. 税込がユーザー権威 — 税抜/消費税を編集しても税込は絶対に触らない。OCR 結果補正の運用上、税込金額は実際の領収書記載額(絶対値)であり、分配だけ人間が再調整するユースケース。
  3. 型変換の安全性Number('') は 0、Number('abc') は NaN。条件分岐で isNaN チェックと空文字チェックを両方行う。
  4. 軽減税率対応はコメントで予約 — Step 4 の TODO を必ず残す。MAS-089 と連動するまで rate = 0.10 ハードコード。
  5. T番号派生ロジック — 「T + 法人番号 13桁ゼロ埋め」は日本のインボイス制度の基本形式だが、個人事業主の適格請求書発行事業者登録番号は「T + 13桁の登録番号(法人番号とは別体系)」であり、派生できない。この場合は MST_PART に T番号列を明示的に追加(将来拡張)して手入力するか、OCR 側の T番号を維持する運用となる。
  6. PartnerRepository への依存 — MAS-120 実装完了が前提。MAS-120 未実装時は MAS-161 実装より先に MAS-120 を完了させるか、MAS-161 の Step 3 内で PartnerRepository を先行実装する判断を行う。
  7. 確認 FLG=TRUE 行の扱い — 確認 FLG が TRUE の行で金額セルが編集された場合、自動再計算を走らせるかは設計判断。本案件ではシンプルさ優先で「確認 FLG 問わず再計算」とする。確認済データを再編集した場合は再度ユーザーが確認 FLG をトグルする前提。
  8. Math.floor の端数処理 — 税抜 = floor(税込 / 1.1) により、110 → 100(正)、103 → 93(税抜)+ 10(税)= 103(OK)、100 → 90(税抜)+ 10(税)= 100(OK)。国税庁の一般的な端数処理(切捨て)に準拠。四捨五入を選択した場合の将来拡張は CFG_TAX_ROUNDING(MAS-089 で提案)と連動。
  9. 列参照はヘッダー名ベースindexOf を貫徹。列番号ハードコード禁止(CLAUDE.md 規約)。
  10. 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 での事前確認)

  1. 35_wrk_receipt の現在の列順 — DDL 定義(L639)と実シートの列位置が一致しているか(特に 税込/税抜/消費税/T番号/取引先名)。
  2. 12_mst_partner の 法人番号 列データ品質 — 空欄比率、13 桁ゼロ埋め有無、文字列 vs 数値型の混在。replace(/\D/g, '') で非数字を除去する設計の妥当性。
  3. 既存 WRK_RCPT データの 税抜+消費税=税込 整合率 — OCR が税抜を不正確に読み取っているケースの割合把握。整合率が低い場合、MAS-161 実装後の初回編集で既存データも再計算されるため影響範囲を事前把握。
  4. validSheets 配列の最新状態 — MAS-120 実装で '26_bud_adhoc' を追加した場合、本案件の '35_wrk_receipt' 追加時に重複や順序崩れがないこと。
  5. PartnerRepository の実装状態 — MAS-120 実装が完了しているか。未完了の場合、MAS-161 開始前に MAS-120 完了を確認。
  6. onEdit のレイテンシ — 30 件規模の MST_PART で findTaxIdMap キャッシュ初回ロード時間を事前計測(dev 環境)。

関連ドキュメント

仕様書関連箇所
dev_mas-157_photo_ocr.md写真 OCR の出力先(35_wrk_receipt)と T番号抽出ロジック
dev_mas-120_partner_payment_terms_autofill.mdPartnerRepository 新設・findPaymentTermsMap パターン(findTaxIdMap の参考)
dev_mas-089_consumption_tax_exclusive_method.md税区分連動(軽減税率・非課税)の将来拡張先
dev_mas-110_document_date.mdonEdit 自動補完のアーキテクチャ原則
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 validSheetsClaude Haiku 4.51 要素追加のみ
Step 2 ハンドラ新設Claude Sonnet 4.6双方向計算のループ防止設計、既存 32_wrk_invoice パターンとの対称性理解が必要
Step 3 Repository 拡張Claude Sonnet 4.6MAS-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_receipthandleUxAssistvalidSheets(L20)に未登録 → Step 1 で追加必須
  • 既存の税額自動計算は 32_wrk_invoice L181-194 の片方向(税抜→税込・消費税)のみ。MAS-161 は双方向(税込→税抜/消費税、税抜/消費税→片方向)のため onEdit 再帰でループするリスクがあり、setCellIfChanged でベき等性ガードが必須
  • 12_mst_partner(MST_PART)には T番号列なし。「T + 法人番号 13桁ゼロ埋め」で派生する設計で DDL 変更不要
  • PartnerRepository は MAS-120 で新設予定(未実装時は先行実装が必要)