概要

項目内容
案件IDMAS-170
カテゴリ外部連携・マスタ自動補完
PhaseP1
優先度★★
所要時間目安2〜3h
対象ファイル(新規)500_import/503_corporate_number_api.js300_ui/305_select_corporation.html
対象ファイル(編集)300_ui/301_ui_assist.js100_config/101_sys_config.js000_infra/002_constants.jsMENU_DEFINITION 追記含む)
前提案件MAS-120(取引先マスタ拡張・実装済 PR #215)

目的

国税庁が無償公開する法人番号公表サイト Web-API(https://api.houjin-bangou.nta.go.jp/)を利用し、12_mst_partner法人番号 列を自動補完する。手入力ミスによる T 番号誤記・仕入税額控除の否認リスクを排除し、インボイス制度対応(MAS-114)および電帳法リネーム(MAS-152)の前提データ品質を確保する。

現在のコード

000_infra/003_contracts.js — PartnerDTO(152〜168行)

/**
 * 12_mst_partner — 取引先マスタ
 * @typedef {Object} PartnerDTO
 * @property {boolean} 有効フラグ
 * @property {string}  取引先コード
 * @property {string}  法人番号          ← 既にフィールド定義済み(13桁数字)
 * @property {string}  略称_4文字
 * @property {string}  取引先名_正式
 * @property {string}  略称
 * @property {string}  銀行摘要名
 * @property {string}  UI用取引先名
 * @property {string}  取引先区分
 * @property {string}  標準決済手段
 * @property {number}  標準決済ラグ(月)
 * @property {string|number} 標準支払基準日
 * @property {string}  標準休日調整
 * @property {string}  標準CF計上区分
 */

確定事項: PartnerDTO には 法人番号 フィールドが既に定義されている(003_contracts.js L156)。T番号(インボイス登録番号)フィールドは PartnerDTO に存在しない(本案件スコープ外)。

200_data/202_repository.js — PartnerRepository(353〜406行)

var PartnerRepository = {
  _getSheet: function() {
    return Utils.getSheetByKey('MST_PART', '12_mst_partner');
  },
  /**
   * @returns {{ headers: string[], dtos: Object[] }}
   */
  findAll: function() {
    return readSheetAsDtos_(PartnerRepository._getSheet());
  },
  // save() メソッドは PartnerRepository に未実装(読み取り専用マスタ)
  // → I-28 の書き戻しには writeDtosToSheet_ を直接呼ぶか、save を追加する
};

確定事項: PartnerRepositoryfindAll() のみ(読み取り専用マスタ)。save(dtos) は未定義。一括処理の書き戻しには PartnerRepositorysave() を追加するか、readSheetAsDtos_ + writeDtosToSheet_ を直接利用する(後述の修正方針 Step 4 で確定)。

300_ui/301_ui_assist.jshandleUxAssist ハンドラ(70〜250行付近)

  • L70-76: handleUxAssist(e) の入口。有効シートリスト(validSheets)で早期リターン。12_mst_partnervalidSheets に含まれていない。
  • L80-95: 16_wrk_master 分岐(MAS-154 略称自動生成を含む)。
  • L432: 101_sys_config.jsonEdit から handleUxAssist(e) を呼び出し。

MAS-170 の挿入位置: handleUxAssist 内の validSheets リストに '12_mst_partner' を追加し、専用分岐を追加する。

100_config/101_sys_config.jsonOpen(299〜324行)

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  try {
    Constants.MENU_DEFINITION.forEach(function(catDef) {
      var menu = ui.createMenu(catDef.category);
      // ... MENU_DEFINITION ループでメニュー生成
    });
  } catch (e) {}
}

確定事項: onOpenConstants.MENU_DEFINITION000_infra/002_constants.js 内定義)を動的にループしてメニューを生成する。ハードコードされたメニュー追加箇所は存在しない。MAS-170 のメニュー追加は 002_constants.jsMENU_DEFINITION 配列に新カテゴリまたは既存カテゴリのアイテムを追記する方式。現在の MENU_DEFINITION に定義されているカテゴリは '🚀 BizLP''💾 バックアップ' の 2 つのみ。

MAS-170 の挿入方針: MENU_DEFINITION{ category: '🏢 マスタ管理', items: [...] } を追加し、「取引先マスタの法人番号を一括取得」→ batchFetchCorporateNumbers を登録する。

500_import/ ディレクトリ(実在ファイル)

501_cc_importer.js
502_bank_importer.js
502_receipt_reader.js

確定事項: 503 番は未使用。MAS-170 の API 通信モジュールは 500_import/503_corporate_number_api.js を使用する。

キャッシュシート番号

  • 98_audit_log(LOG_AUDIT): 使用中(MAS-179)
  • 99_error_log: 使用中(エラーログ)
  • 99_ プレフィックス帯は使用不可。95_ 番台が空き。

確定事項: キャッシュシート名は 95_cache_houjin を使用する。

修正方針

Step 1: API 通信モジュール新規作成(500_import/503_corporate_number_api.js

1-1. キャッシュシート設計(95_cache_houjin

DDL スキーマ定義(101_sys_config.jssetupAllSchemas に追加):

列名説明
検索クエリstring国税庁 API に渡した名称文字列
APIレスポンス(JSON)stringAPI レスポンスの JSON 文字列(全件)
取得日時datetimeキャッシュ記録日時

キャッシュ有効期間: 30 日以内(取得日時 から計算)。

1-2. fetchCorporateInfo_(name) — プライベート関数

引数: name {string} — 取引先名_正式 の文字列
戻り値: { corporateNumber: string, name: string, address: string }[] — ヒット企業リスト(0件・複数件含む)

処理順:

  1. 95_cache_houjin シートで 検索クエリ === name かつ 取得日時 が 30 日以内の行を検索。ヒットすれば JSON.parse して返す
  2. LockService.getScriptLock().waitLock(1000) — ロック取得失敗時は throw new Error('lock_timeout') を上位に伝播
  3. UrlFetchApp.fetch(url, { muteHttpExceptions: true }) で国税庁 API を呼び出す
    • エンドポイント: https://api.houjin-bangou.nta.go.jp/4/name?name=<URLエンコード>&type=12&mode=1 (実装者は最新ドキュメントで確認すること)
    • 認証: 申請により取得したアプリケーション ID を URL パラメータ id に付与(OAuth 不要)
  4. HTTP ステータス ≠ 200 の場合は throw new Error('api_error_' + responseCode)
  5. レスポンス JSON をパース → 候補リストを返却
  6. キャッシュシート末尾に [name, JSON.stringify(candidates), new Date()] を追記

1-3. batchFetchCorporateNumbers() — 公開関数(メニューから呼ぶ)

処理順:

  1. LockService.getScriptLock().waitLock(1000) — ロック取得失敗時は Utils.toastResult で「別処理が実行中です」を通知して return
  2. PartnerRepository.findAll() で全件取得({ headers, dtos }
  3. 有効フラグ=TRUE かつ 法人番号 列が空白の dto のみを抽出(CLAUDE.md 規約)
  4. 各 dto に対してループ:
    • 取引先名_正式 が空白ならスキップ + Utils.logInfo 記録
    • fetchCorporateInfo_(dto['取引先名_正式']) を呼び出す
    • 候補 1 件: dto['法人番号'] = candidates[0].corporateNumber をセット
    • 候補 複数件: スキップ + 「要手動確認」を Utils.logInfo 記録(manualCheckCount カウント)
    • 候補 0 件: スキップ + Utils.logInfo 記録
    • エラー: Utils.logError + 処理続行(1件で全体停止させない)
    • Utilities.sleep(600) を毎回挿入(100 リクエスト/分上限対策)
  5. writeDtosToSheet_12_mst_partner シート全件書き戻し(PartnerRepository._getSheet() でシートを取得)
  6. Utils.toastResult で「法人番号取得完了: X件更新、要手動確認: Y件」を通知
  7. finallylock.releaseLock()

Step 2: onEdit ハンドラ拡張(300_ui/301_ui_assist.js

2-1. validSheets リストへの追加

handleUxAssist L75 の validSheets 配列末尾に '12_mst_partner' を追加する。

2-2. 12_mst_partner 分岐の追加

handleUxAssist 内の既存シート別分岐末尾(32_wrk_invoice 分岐の後)に以下を追加:

if (sName === '12_mst_partner') {
  var corpNumIdx = headers.indexOf('法人番号');
  var nameIdx    = headers.indexOf('取引先名_正式');
  // トリプル条件: 取引先名_正式列編集 + 法人番号列が空白
  if (col === nameIdx + 1 && corpNumIdx !== -1) {
    var corpNumCell = sheet.getRange(row, corpNumIdx + 1);
    if (corpNumCell.isBlank() && val) {
      // 有効フラグ=FALSE 行はスキップ
      var flagIdx = headers.indexOf('有効フラグ');
      if (flagIdx !== -1) {
        var flag = sheet.getRange(row, flagIdx + 1).getValue();
        if (flag === false || String(flag).toUpperCase() === 'FALSE') return;
      }
      try {
        var candidates = fetchCorporateInfo_(val);
        if (candidates.length === 0) {
          Utils.toastResult('法人番号検索', '「' + val + '」は国税庁法人番号データベースに登録されていません', 5);
        } else if (candidates.length === 1) {
          var c = candidates[0];
          var res = SpreadsheetApp.getUi().alert(
            '法人番号の確認',
            '「' + c.name + '」\n法人番号: ' + c.corporateNumber + '\n住所: ' + c.address + '\n\nこの法人番号を登録しますか?',
            SpreadsheetApp.getUi().ButtonSet.YES_NO
          );
          if (res === SpreadsheetApp.getUi().Button.YES) {
            corpNumCell.setValue(c.corporateNumber);
            Utils.auditLog('UPDATE', '12_mst_partner', String(sheet.getRange(row, headers.indexOf('取引先コード') + 1).getValue()), '法人番号', 'onEdit_I28', '', c.corporateNumber, 'I-28 法人番号自動取得');
          }
        } else {
          // 複数候補: HtmlService モーダルを表示
          var tmpl = HtmlService.createTemplateFromFile('305_select_corporation');
          tmpl.candidates = JSON.stringify(candidates);
          tmpl.row = row;
          tmpl.corpNumCol = corpNumIdx + 1;
          SpreadsheetApp.getUi().showModalDialog(
            tmpl.evaluate().setWidth(500).setHeight(400),
            '法人番号候補を選択してください'
          );
        }
      } catch (err) {
        if (err.message === 'lock_timeout') {
          Utils.toastResult('法人番号検索', '別処理が実行中です。しばらく経ってから再試行してください', 5);
        } else {
          Utils.logError('onEdit_I28', err, 'val=' + val);
          Utils.toastResult('法人番号検索', 'APIエラーが発生しました: ' + err.message, 5);
        }
      }
    }
  }
}

Step 3: HTML モーダル新規作成(300_ui/305_select_corporation.html

複数候補をテーブル表示し、ユーザーが選択した法人番号をセルに書き込む。

  • <?= candidates ?> でサーバー側からスクリプトレットでデータを注入
  • 選択ボタン押下時: google.script.run.writeCorporateNumber(row, corpNumCol, selectedNumber, selectedName) を呼び出し
  • writeCorporateNumber503_corporate_number_api.js に定義するサーバー側関数(指定セルへの書き込み + Utils.auditLog

Step 4: メニュー・DDL 追加(000_infra/002_constants.js + 100_config/101_sys_config.js

4-1. 002_constants.jsMENU_DEFINITION に追加

{
  category: '🏢 マスタ管理',
  items: [
    { label: '取引先マスタの法人番号を一括取得', funcName: 'batchFetchCorporateNumbers',
      description: '国税庁法人番号APIを使い12_mst_partnerの法人番号列を一括補完' },
  ]
},

4-2. 101_sys_config.jssetupAllSchemas にキャッシュシート DDL を追加

{
  key: 'CACHE_HOUJIN', name: '95_cache_houjin',
  headers: ['検索クエリ', 'APIレスポンス(JSON)', '取得日時'],
  headerBg: '#D9EAD3',
}

影響範囲

ファイル変更種別変更内容変更行数目安
500_import/503_corporate_number_api.js新規作成API 通信モジュール全体約 120 行
300_ui/305_select_corporation.html新規作成複数候補選択モーダル約 60 行
300_ui/301_ui_assist.js編集validSheets 追加 + 12_mst_partner 分岐追加約 40 行追加
000_infra/002_constants.js編集MENU_DEFINITION にマスタ管理カテゴリ追加約 8 行追加
100_config/101_sys_config.js編集setupAllSchemas95_cache_houjin DDL 追加約 10 行追加

既存動作への影響: handleUxAssist の既存分岐(23_bud_subscription31_wrk_order 等)はいずれも変更なし。12_mst_partner 分岐は新規追加のため既存ロジックに干渉しない。

注意事項

  1. LockService.getScriptLock() を使うことPropertiesServicegetScriptLock メソッドは存在しない(GAS に存在しない誤 API)。排他ロック取得は必ず LockService.getScriptLock().waitLock(msec) を使用する。

  2. 列参照はヘッダー名ベースの indexOf で取得すること。列番号ハードコード禁止(CLAUDE.md 規約)。headers.indexOf('法人番号') / headers.indexOf('取引先名_正式') で取得する。

  3. onEdit(e) の列判定は e.range.getColumn()headers.indexOf('取引先名_正式') + 1 を比較する(1-indexed)。

  4. 有効フラグ=FALSE の行は一括処理・onEdit ともにスキップ(CLAUDE.md 規約)。flag === false || String(flag).toUpperCase() === 'FALSE' の両方をチェック。

  5. Utils.logError(funcName, error, context) の引数順に注意。第 1 引数: 関数名文字列、第 2 引数: Error オブジェクト、第 3 引数: 文字列コンテキスト(004_utils.js L242 参照)。

  6. getWebSpreadsheet_()SpreadsheetApp.getActiveSpreadsheet() の代わりに使用する(Web アプリ経由でのシート取得に対応)。Utils.toastResult は内部で getWebSpreadsheet_() を呼ぶため直接使用可。

  7. T番号(インボイス登録番号)の取得は本案件スコープ外。法人番号(13桁数字)のみを対象とする。「法人番号に T を付けるとインボイス登録番号」との混同注意(適格請求書発行事業者公表サイトは別 API)。

  8. 99_ 番台のシートには 98_audit_log99_error_log が存在する。キャッシュシート名は 95_cache_houjin を使用する(99_cache_houjin_api は使用不可)。

  9. 国税庁 Web-API はアプリケーション ID 要申請(無料)。API エンドポイント・パラメータ仕様・レスポンス JSON 構造は実装時に最新ドキュメントを確認すること(https://www.houjin-bangou.nta.go.jp/webapi/)。本仕様書は 2026-04-20 時点の仕様を前提とするが、変更があり得る。

  10. PartnerRepository に save() が未実装。一括処理の書き戻しは writeDtosToSheet_(PartnerRepository._getSheet(), headers, dtos) を直接呼ぶか、実装時に PartnerRepository.save(dtos) を追加して利用する。追加する場合は 202_repository.js を編集する。

エッジケース

条件onEdit 時の動作一括処理時の動作理由
API レスポンス 0 件Utils.toastResult で「データベースに登録されていません」を通知、セルは空欄のままスキップ + Utils.logInfo でログ記録個人事業主・海外法人も 0 件になる。強制書き込み禁止
API レスポンス 複数件305_select_corporation.html モーダル表示 → ユーザー選択 → writeCorporateNumber でセル書き込みスキップ + 「要手動確認」を Utils.logInfo 記録、完了時 manualCheckCount を Toast に表示一括処理時はユーザー介入不可のため、複数候補はスキップして後で手動確認
API エラー(HTTP エラー・タイムアウト等)Utils.logError + Utils.toastResult でユーザー通知同左。処理は continue(該当行のみスキップ、全体は継続)エラー 1 件で全体停止させない
法人番号 列が既入力スキップ(上書き禁止)スキップ(上書き禁止)。batchFetchCorporateNumbers の抽出条件「法人番号列が空白」でフィルタ手動入力済み値を保護。Human-in-the-Loop 原則
取引先名_正式 が空白val が空なので handleUxAssist の先頭ガード(!e.value)で早期リターンdto['取引先名_正式'] が空白のためスキップ + Utils.logInfo 記録空文字での API 呼び出しは無意味
有効フラグ=FALSE の行有効フラグ 列確認後スキップ(CLAUDE.md 規約)batchFetchCorporateNumbers の抽出条件「有効フラグ=TRUE」でフィルタ無効行への書き込み禁止
ロック取得タイムアウト(1 秒)err.message === 'lock_timeout' を catch して「別処理が実行中です」を Utils.toastResult で通知同左。LockService.getScriptLock().waitLock(1000) 失敗時に Utils.toastResult 後 return二重実行・レース条件防止
30 日以内のキャッシュが存在キャッシュを返す(API 呼び出しなし)同左API レートリミット(100 リクエスト/分)対策
バルク貼り付け(複数行同時編集)handleUxAssist L71 の e.range.getNumRows() > 1 ガードで早期リターン(処理しない)一括処理対象のため通常通り処理onEdit は 1 セル単位を想定
個人事業主0 件扱いと同様同左法人番号は法人のみ付番。個人事業主には存在しない
海外取引先0 件扱いと同様同左国税庁データベースは国内法人のみ。Utils.toastResult で通知

実データ検証

12_mst_partner の列構造

003_contracts.js L152-168 の PartnerDTO から確認済み:

有効フラグ / 取引先コード / 法人番号 / 略称_4文字 / 取引先名_正式 / 略称 /
銀行摘要名 / UI用取引先名 / 取引先区分 / 標準決済手段 / 標準決済ラグ(月) /
標準支払基準日 / 標準休日調整 / 標準CF計上区分

法人番号 列は列インデックス 2(0始まり)に定義されている(PartnerDTO の 3 番目フィールド)。
ただし実際のシートの列順はヘッダー名 indexOf で動的に取得すること(列番号ハードコード禁止)。

キャッシュシート番号の競合確認

  • 98_audit_logLOG_AUDIT キー): 使用中(MAS-179 実装済)
  • 99_error_log: 使用中
  • 95_ / 96_ / 97_: 現時点で未使用
  • 採用: 95_cache_houjin

国税庁 Web-API 概要

  • エンドポイントベース: https://api.houjin-bangou.nta.go.jp/4/(バージョン番号は実装時に最新ドキュメントで確認)
  • 認証方式: OAuth 不要。アプリケーション ID(無料・利用登録制)を URL パラメータに付与
  • レートリミット: 約 100 リクエスト/分(実装時に最新ドキュメントで確認)→ Utilities.sleep(600) 挿入で対応
  • 応答形式: JSON(type=12 パラメータ指定時)
  • 検索モード: 前方一致・部分一致・完全一致(mode パラメータ)
  • 無料公開。ただし利用申込と利用規約への同意が必要

注意: 上記エンドポイント・パラメータ・レスポンス構造は実装者が最新ドキュメント(https://www.houjin-bangou.nta.go.jp/webapi/)で確認すること。本仕様書は 2026-04-20 時点の情報に基づく。

関連ドキュメント

人間が検討すべき事項

TODO_future.md MAS-170 欄からの転記

  1. API 利用申込(2026 年 4 月時点で要登録)の手続き。利用申込後にアプリケーション ID を取得し、GAS スクリプトプロパティ(Env モジュール)に追加する必要がある。
  2. 同名異社の UX 設計。複数候補ヒット時のモーダルダイアログの使いやすさ確認。
  3. インボイス登録有無の確認が本案件スコープに含まれるか否か。含める場合は適格請求書発行事業者公表サイト(別 API)を追加で呼ぶ必要があり、実装コストが増大する。
  4. 法人番号のない個人事業主の扱い。0 件として記録するのみで問題ないか確認。
  5. 海外取引先の扱い。0 件として記録するのみで問題ないか確認。
  6. Web-API のレートリミット(1 分 100 リクエスト等)。現在の取引先マスタ件数を確認し、sleep(600) で十分か判断する。件数が多い場合は分割バッチ設計が必要。

スコープ明記

  • インボイス登録番号(T番号)の取得は本案件スコープ外法人番号(13 桁数字)のみを対象とする。T 番号は「法人番号に T を付けたもの」が原則だが、適格請求書発行事業者登録の有無は別途確認が必要(MAS-114 の課題)。

DDL 変更

12_mst_partner シートの 法人番号 列は PartnerDTO(003_contracts.js L156)に定義済み。MAS-120(PR #215)実装時に既に列追加済みと想定されるが、実装前に実シートで 法人番号 列の存在を確認すること。存在しない場合は setupAllSchemas の DDL スキーマに 法人番号 列を追加する必要がある。

Human-in-the-Loop フロー

onEdit 時(1 件ヒット):

  1. alert() 確認ダイアログ: 「〇〇の法人番号 XXXXXXXXX を登録しますか?」(住所も表示)
  2. 「はい」→ corpNumCell.setValue(c.corporateNumber) + Utils.auditLog('UPDATE', '12_mst_partner', ...) で記録
  3. 「いいえ」→ 空欄のまま(何もしない)

onEdit 時(複数件ヒット):

  1. 305_select_corporation.html モーダル表示 → 候補一覧をテーブル表示
  2. ユーザーが行を選択 → 「登録」ボタン
  3. google.script.run.writeCorporateNumber(row, col, number, name) でサーバー側書き込み + Utils.auditLog 記録

一括処理時(batchFetchCorporateNumbers:

  1. 1 件ヒット: 自動書き込み(確認ダイアログなし。バッチ処理のため Human-in-the-Loop は完了後通知)
  2. 複数件ヒット: スキップ + manualCheckCount カウント
  3. 実行完了後: 「法人番号取得完了: X 件更新、要手動確認: Y 件」を Utils.toastResult で通知
  4. Utils.auditLog('UPDATE', '12_mst_partner', partnerId, '法人番号', 'batchFetchCorporateNumbers', '', corpNum, 'I-28 一括取得') で各更新を記録

アプリケーション ID の管理

国税庁 API のアプリケーション ID は Env モジュール(000_infra/001_env.js)に新規メソッドを追加して管理することを推奨する。PropertiesService.getScriptProperties() は直接呼ばずに Env.houjinApiId() のようなラッパーを設ける(CLAUDE.md「環境依存値の参照」規約)。

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

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-170「法人番号自動取得(国税庁 法人番号公表サイト Web-API 連携)」を実装してください。

## 実行前タスク(必須。Grep・記憶・推測による判断を禁止する)

以下を Read ツールで開いて確認すること:
1. `000_infra/003_contracts.js` — PartnerDTO の `法人番号` フィールドの有無・フィールド名の正確な文字列(L152-168)
2. `200_data/202_repository.js` — PartnerRepository の全メソッドを確認(L353-406)。save() が存在しない場合は追加方針を確認
3. `300_ui/301_ui_assist.js` — handleUxAssist の validSheets リスト(L75)と全体構造を確認し、挿入位置を特定
4. `000_infra/002_constants.js` — MENU_DEFINITION の現在の全エントリを確認(L206-228)
5. `100_config/101_sys_config.js` — setupAllSchemas 内の既存シート DDL 定義を確認
6. `500_import/` — ls で実在ファイル一覧を取得し、503 が空きであることを確認
7. `docs/_internal/failure_patterns.md` — #3(DDLコード値不一致)、#4(スコープ外変数)、#18-#20(固有名詞誤記)をレビュー

## 修正対象ファイル

- 新規作成: `500_import/503_corporate_number_api.js`
- 新規作成: `300_ui/305_select_corporation.html`
- 編集: `300_ui/301_ui_assist.js`(handleUxAssist の validSheets + 12_mst_partner 分岐追加)
- 編集: `000_infra/002_constants.js`(MENU_DEFINITION にマスタ管理カテゴリ追加)
- 編集: `100_config/101_sys_config.js`(setupAllSchemas に 95_cache_houjin DDL 追加)

## 実装内容

### 1. API 通信モジュール(500_import/503_corporate_number_api.js)

#### fetchCorporateInfo_(name)
- 引数: name {string} — 取引先名_正式の文字列
- 戻り値: { corporateNumber, name, address }[] — 候補リスト(0〜複数件)
- 処理: 95_cache_houjin シートで 30 日以内のキャッシュ確認 → ヒットすれば返す
- キャッシュ未ヒット時: LockService.getScriptLock().waitLock(1000) → UrlFetchApp.fetch で国税庁 API 呼び出し → レスポンスをキャッシュ保存 → 候補リスト返却
- ロック取得失敗時: throw new Error('lock_timeout')
- HTTP ≠ 200 時: throw new Error('api_error_' + responseCode)
- 国税庁 API のエンドポイント・パラメータは https://www.houjin-bangou.nta.go.jp/webapi/ の最新ドキュメントで確認

#### batchFetchCorporateNumbers()(メニューから呼ぶ公開関数)
- LockService.getScriptLock().waitLock(1000) でロック取得(失敗時は Utils.toastResult で通知して return)
- PartnerRepository.findAll() で全件取得
- 有効フラグ=TRUE かつ 法人番号列が空白の dto のみ処理
- fetchCorporateInfo_ 呼び出し後に Utilities.sleep(600) を挿入(レートリミット対策)
- 1 件ヒット: dto['法人番号'] をセット
- 複数件ヒット: スキップ + manualCheckCount カウント
- 0 件 / エラー: スキップ(エラーは Utils.logError + 処理続行)
- 全件処理後: writeDtosToSheet_(PartnerRepository._getSheet(), headers, dtos) で書き戻し
- Utils.toastResult で「X件更新、要手動確認: Y件」を通知
- finally で lock.releaseLock()

#### writeCorporateNumber(row, col, number, name)(HTML から google.script.run で呼ぶサーバー側関数)
- 指定行・列のセルに法人番号を書き込む
- Utils.auditLog('UPDATE', '12_mst_partner', ...) で記録

### 2. onEdit ハンドラ拡張(300_ui/301_ui_assist.js)

- handleUxAssist の validSheets(L75)に '12_mst_partner' を追加
- 既存の最終シート分岐の後に 12_mst_partner 分岐を追加:
  - col === headers.indexOf('取引先名_正式') + 1 かつ 法人番号列が空白 かつ val が空白でない かつ 有効フラグ=TRUE
  - fetchCorporateInfo_(val) を呼び出し
  - 0 件: Utils.toastResult で通知
  - 1 件: alert() で確認ダイアログ → YES で setValue + Utils.auditLog
  - 複数件: HtmlService.createTemplateFromFile('305_select_corporation') でモーダル
  - エラー: lock_timeout と その他を分けて Utils.toastResult または Utils.logError で処理

### 3. HTML モーダル(300_ui/305_select_corporation.html)

- テンプレートレット <?= candidates ?> でサーバー側から JSON データを注入
- 候補一覧をテーブル表示(法人名・法人番号・所在地)
- 選択ボタン押下: google.script.run.withFailureHandler(...).writeCorporateNumber(row, corpNumCol, selectedNumber, selectedName)
- キャンセルボタン: google.script.host.close()

### 4. MENU_DEFINITION 追加(000_infra/002_constants.js)

MENU_DEFINITION 配列末尾('💾 バックアップ' の後)に以下を追加:
{
  category: '🏢 マスタ管理',
  items: [
    { label: '取引先マスタの法人番号を一括取得', funcName: 'batchFetchCorporateNumbers',
      description: '国税庁法人番号APIを使い12_mst_partnerの法人番号列を一括補完' }
  ]
}

### 5. DDL 追加(100_config/101_sys_config.js の setupAllSchemas)

95_cache_houjin シートの DDL を既存スキーマ配列に追加:
{ key: 'CACHE_HOUJIN', name: '95_cache_houjin',
  headers: ['検索クエリ', 'APIレスポンス(JSON)', '取得日時'],
  headerBg: '#D9EAD3' }

## 制約

- 列番号ハードコード禁止。ヘッダー名で indexOf して取得すること
- 有効フラグ=FALSE の行はスキップ(CLAUDE.md 規約)
- PropertiesService.getScriptLock() は使わない。必ず LockService.getScriptLock() を使うこと
- handleUxAssist の既存分岐ロジックを削除・変更しないこと
- T番号(インボイス登録番号)の取得はスコープ外
- メニュー名・関数名は Read で確認した実在する文字列のみ使用する
- getWebSpreadsheet_() を SpreadsheetApp.getActiveSpreadsheet() の代わりに使用すること

## エッジケース

仕様書「エッジケース」セクションのテーブルに定義した全ケースを実装すること:
0件/複数件/APIエラー/法人番号既入力/取引先名空白/有効フラグFALSE/ロックタイムアウト/30日キャッシュ有効/バルク貼り付け/個人事業主/海外取引先

## 実データ検証(実装開始前に確認すること)

1. 12_mst_partner の実シートで「法人番号」列が存在することを確認
2. 95_cache_houjin と 98_audit_log / 99_error_log が競合しないことを確認
3. 500_import/ ディレクトリに 503 番のファイルが存在しないことを確認

## 動作確認

1. npm run push:dev 後、GAS エディタで batchFetchCorporateNumbers を直接実行
2. 12_mst_partner の「取引先名_正式」列を編集し、onEdit トリガー発火を確認
3. 95_cache_houjin シートに「検索クエリ / APIレスポンス(JSON) / 取得日時」が保存されることを確認
4. 法人番号列が既入力の行がスキップされることを確認
5. 複数候補ヒット時のモーダルダイアログ表示・選択動作を確認
6. ロック取得テスト: 2 タブで同時実行し、2 つ目が「別処理が実行中です」と通知されることを確認
7. 30 日以内のキャッシュが存在する場合に API 呼び出しがスキップされることを確認

推奨実行モデル

工程推奨モデル理由
API 通信モジュール実装(LockService・UrlFetchApp・キャッシュ)Claude Sonnet複数 GAS API の組み合わせに中程度の判断が必要
onEdit ハンドラ拡張(301_ui_assist.js)Claude Sonnet既存コードへの挿入位置特定と既存分岐の保護が必要
HTML モーダル実装(305_select_corporation.html)Claude HaikuUI パターンが明確で設計判断要素が少ない
一括バッチ関数・PartnerRepository 連携Claude SonnetDTO の読み書きと Repository パターンの理解が必要

変更履歴

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

仕様書作成プロンプト(再現性・監査性のため必ず記録)

展開して表示

【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】

  1. 拡張思考の使い分け: Phase 1(設計)では拡張思考をフル活用し、ファイル名・関数名・行番号・エッジケースをここで完全に確定させる。Phase 2(清書)の各 Step 内では拡張思考を最小限に抑え、Phase 1 で確定済みの内容の書き下しに徹する。出力途中で再考しない。
  2. テキスト報告の禁止: 「〜を作成します」等の text のみで tool_use なしに turn を終了しない。説明は 1 文以内。直ちに tool を呼ぶ。
  3. 4-5 分割の Write/Edit 実行: 2-1(骨格 ~20行)/ 2-2(概要〜注意事項 ~300行)/ 2-3a(エッジケース〜人間検討事項 ~200行)/ 2-3b(実装プロンプト〜変更履歴 ~250行)/ 2-4(<details> 記録)に分割。1 回の Write/Edit は 300 行以内。
  4. 各 Step で何を書くかを具体指示: Phase 1 で確定した内容をそのまま書き出す。出力途中で設計判断を持ち込まない。

====================================================================== あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。 案件 MAS-170「法人番号自動取得(国税庁 法人番号公表サイト Web-API 連携)」の開発仕様書を作成してください。 仕様書新規作成後は docs/_config.jsonnav 配列(§E.6 パイプライン・RPA・外部連携)にも必ず追記すること。


Phase 1: 案件情報の収集(テキスト報告禁止。即座にツール実行)

1-A: 案件定義の読み込み

docs/_internal/TODO_future.mdMAS-170 の行を検索し、案件名・カテゴリ・Phase・優先度・概要・人間が検討すべき事項を取得する。

1-B: プロジェクト規約の読み込み

CLAUDE.md を読み込み、以下を把握する:

  • 500_import/ の採番ルールと現在の使用済み番号(501_cc_importer, 502_bank_importer, 502_receipt_reader が列挙されているが実際のファイル名は後述の調査で確定する)
  • 800_ops/ の採番ルール(810 番以降はマイグレーション用予約)
  • コーディング規約(列番号ハードコード禁止・有効フラグ=FALSE スキップ等)

1-C: 既存仕様書テンプレートの読み込み

docs/dev/dev_mas-077_settlement_date_sync.md(データ同期・外部連携案件)をテンプレートとして参照し、セクション構成・フォーマットを把握する。

1-D: 関連コードの調査

Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず Read で行うこと(失敗パターン #18-#20 の直接対策)。 以下を全て Read ツールで実際に開いて確認し、名前・型・行番号を Phase 1 でここに確定させる。

  1. 000_infra/003_contracts.js

    • PartnerDTO@typedef を全フィールド確認。法人番号 フィールドが既に存在するかを Read で裏取り。
    • T番号(インボイス登録番号)フィールドの有無も確認(法人番号 とは別物: 法人番号=13桁数字、T番号=T+13桁)。
  2. 000_infra/002_constants.js

    • SHEET_DEFAULTS 配列の実際の要素型({ pattern, prefix, defaults } オブジェクト配列)を Read で確認し、12_mst_partner エントリの有無を把握する。
    • ID_PREFIX_MAP12_mst_partner 向けエントリが存在するか確認。
  3. 200_data/202_repository.js

    • PartnerRepository.findAll() の戻り値型({ headers: string[], dtos: Object[] })と PartnerRepository.save(dtos) の引数型を Read で確認する。findPaymentTermsMap() のキャッシュ構造も把握する。
  4. 000_infra/004_utils.js

    • Utils.logError(funcName, error, context) / Utils.toastResult(funcName, message, duration) / Utils.logInfo(funcName, message) の引数順と型を Read で確認する。
    • getWebSpreadsheet_() の使い方を確認する(SpreadsheetApp.getActiveSpreadsheet() の代わりに使う)。
  5. 300_ui/301_ui_assist.js

    • 既存の onEdit(e) ハンドラの全体構造・シート名分岐パターンを Read で確認する。12_mst_partner への分岐が既に存在するか確認し、MAS-170 のロジックを挿入すべき行番号のランドマークを特定する。
  6. 100_config/101_sys_config.js

    • onOpen() 内の ui.createMenu を Read し、「🔧 開発・設定」メニューが実際に存在するか、その正確な文字列と階層を確認する。存在しない場合は正しいメニュー名を特定する。MAS-170 のメニュー項目の挿入行番号を特定する。
  7. 500_import/ ディレクトリのファイル一覧

    • Bash の ls 500_import/ で実在するファイル名を取得し、次に使える番号(503 が空いているか)を確認する。空きがあれば 503_corporate_number_api.js を使用。競合する場合は次の空き番号を使用する。
  8. 既存シート番号の確認

    • Bash または Grep で 98_audit_log, 99_error_log が参照されていることを確認する。キャッシュシートに 99_ プレフィックスは使用不可(競合)。空いている番号帯(例: 95_, 96_)を確認し、キャッシュシート名を確定させる。
  9. docs/_internal/failure_patterns.md

    • #3(DDLコード値 vs 実データ乖離)、#4(変数スコープ)、#16(合算マッチでのロック漏れ)、#18-#20(仕様書での固有名詞誤記)をレビューし、本案件の設計に反映する。

【Phase 1 で必ず確定させる事項】

確定項目確定方法
PartnerDTO法人番号 フィールドが存在するか003_contracts.js Read
12_mst_partner シートに 法人番号 列が実在するかMCP で実データ確認
API通信モジュールのファイル番号(503 か別番号か)ls 500_import/
キャッシュシートの番号(95_96_ か)既存シート番号の調査結果
一括バッチ関数の配置先(500_import/5XX_corporate_number_api.js 内 or 別ファイル)上記調査結果に基づき判断
onEdit ハンドラへの挿入行番号301_ui_assist.js Read
メニューの実在文字列と挿入行番号101_sys_config.js Read
LockService.getScriptLock() を使うこと(PropertiesService.getScriptLock() は GAS 非存在 API)GAS 仕様の既知事実として確定

Phase 2: 仕様書の分割作成

出力先: docs/dev/dev_mas-170_corporate_number_api.md (ID は大文字 MAS-170dev_i-28_ のように小文字にしない)

絶対に 1 回のツール呼び出しで全内容を出力せず、以下の 5 Step に分割して実行すること。

Step 2-1: 骨格の作成(Write ~20行)

以下のセクション見出しのみのファイルを新規作成する(本文は空で可):

# I-28: 法人番号自動取得(国税庁 法人番号公表サイト Web-API 連携)
## 概要 / ## 目的 / ## 現在のコード / ## 修正方針
## 影響範囲 / ## 注意事項 / ## エッジケース / ## 実データ検証
## 関連ドキュメント / ## 人間が検討すべき事項 / ## 実装プロンプト(Claude Code 用)
## 推奨実行モデル / ## 変更履歴 / ## 仕様書作成プロンプト

Step 2-2: 前半セクションの追記(Edit or Bash ~300行)

概要注意事項 に書き込む。(詳細はプロンプト本文参照)

Step 2-3a: エッジケース〜人間検討事項の追記(Edit or Bash ~200行)

エッジケース人間が検討すべき事項 に書き込む。(詳細はプロンプト本文参照)

Step 2-3b: 実装プロンプト〜変更履歴の追記(Edit or Bash ~250行)

実装プロンプトは行頭 4 スペースインデントで記述。推奨実行モデルと変更履歴も同 Step 内に追記。

Step 2-4: 仕様書作成プロンプトの記録(Edit or Bash)

ファイル末尾に <details> タグでプロンプト全文を記録する。


Phase 3: _config.json への追記とコミット

3-A: _config.json 登録(必須)

docs/_config.jsonnav 配列 §E.6 末尾に追記する。

3-B: changelog 追記

3-C: コミット&プッシュ