MAS-170: 法人番号自動取得(国税庁 法人番号公表サイト Web-API 連携)
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-170 |
| カテゴリ | 外部連携・マスタ自動補完 |
| Phase | P1 |
| 優先度 | ★★ |
| 所要時間目安 | 2〜3h |
| 対象ファイル(新規) | 500_import/503_corporate_number_api.js、300_ui/305_select_corporation.html |
| 対象ファイル(編集) | 300_ui/301_ui_assist.js、100_config/101_sys_config.js(000_infra/002_constants.js の MENU_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 を追加する
};
確定事項: PartnerRepository は findAll() のみ(読み取り専用マスタ)。save(dtos) は未定義。一括処理の書き戻しには PartnerRepository に save() を追加するか、readSheetAsDtos_ + writeDtosToSheet_ を直接利用する(後述の修正方針 Step 4 で確定)。
300_ui/301_ui_assist.js — handleUxAssist ハンドラ(70〜250行付近)
- L70-76:
handleUxAssist(e)の入口。有効シートリスト(validSheets)で早期リターン。12_mst_partnerはvalidSheetsに含まれていない。 - L80-95:
16_wrk_master分岐(MAS-154 略称自動生成を含む)。 - L432:
101_sys_config.jsのonEditからhandleUxAssist(e)を呼び出し。
MAS-170 の挿入位置: handleUxAssist 内の validSheets リストに '12_mst_partner' を追加し、専用分岐を追加する。
100_config/101_sys_config.js — onOpen(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) {}
}
確定事項: onOpen は Constants.MENU_DEFINITION(000_infra/002_constants.js 内定義)を動的にループしてメニューを生成する。ハードコードされたメニュー追加箇所は存在しない。MAS-170 のメニュー追加は 002_constants.js の MENU_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.js の setupAllSchemas に追加):
| 列名 | 型 | 説明 |
|---|---|---|
| 検索クエリ | string | 国税庁 API に渡した名称文字列 |
| APIレスポンス(JSON) | string | API レスポンスの JSON 文字列(全件) |
| 取得日時 | datetime | キャッシュ記録日時 |
キャッシュ有効期間: 30 日以内(取得日時 から計算)。
1-2. fetchCorporateInfo_(name) — プライベート関数
引数: name {string} — 取引先名_正式 の文字列
戻り値: { corporateNumber: string, name: string, address: string }[] — ヒット企業リスト(0件・複数件含む)
処理順:
95_cache_houjinシートで検索クエリ === nameかつ取得日時が 30 日以内の行を検索。ヒットすればJSON.parseして返すLockService.getScriptLock().waitLock(1000)— ロック取得失敗時はthrow new Error('lock_timeout')を上位に伝播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 不要)
- エンドポイント:
- HTTP ステータス ≠ 200 の場合は
throw new Error('api_error_' + responseCode) - レスポンス JSON をパース → 候補リストを返却
- キャッシュシート末尾に
[name, JSON.stringify(candidates), new Date()]を追記
1-3. batchFetchCorporateNumbers() — 公開関数(メニューから呼ぶ)
処理順:
LockService.getScriptLock().waitLock(1000)— ロック取得失敗時はUtils.toastResultで「別処理が実行中です」を通知して returnPartnerRepository.findAll()で全件取得({ headers, dtos })- 有効フラグ=TRUE かつ
法人番号列が空白の dto のみを抽出(CLAUDE.md 規約) - 各 dto に対してループ:
取引先名_正式が空白ならスキップ +Utils.logInfo記録fetchCorporateInfo_(dto['取引先名_正式'])を呼び出す- 候補 1 件:
dto['法人番号'] = candidates[0].corporateNumberをセット - 候補 複数件: スキップ + 「要手動確認」を
Utils.logInfo記録(manualCheckCountカウント) - 候補 0 件: スキップ +
Utils.logInfo記録 - エラー:
Utils.logError+ 処理続行(1件で全体停止させない) Utilities.sleep(600)を毎回挿入(100 リクエスト/分上限対策)
writeDtosToSheet_で12_mst_partnerシート全件書き戻し(PartnerRepository._getSheet()でシートを取得)Utils.toastResultで「法人番号取得完了: X件更新、要手動確認: Y件」を通知finallyでlock.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)を呼び出し writeCorporateNumberは503_corporate_number_api.jsに定義するサーバー側関数(指定セルへの書き込み +Utils.auditLog)
Step 4: メニュー・DDL 追加(000_infra/002_constants.js + 100_config/101_sys_config.js)
4-1. 002_constants.js の MENU_DEFINITION に追加
{
category: '🏢 マスタ管理',
items: [
{ label: '取引先マスタの法人番号を一括取得', funcName: 'batchFetchCorporateNumbers',
description: '国税庁法人番号APIを使い12_mst_partnerの法人番号列を一括補完' },
]
},
4-2. 101_sys_config.js の setupAllSchemas にキャッシュシート 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 | 編集 | setupAllSchemas に 95_cache_houjin DDL 追加 | 約 10 行追加 |
既存動作への影響: handleUxAssist の既存分岐(23_bud_subscription、31_wrk_order 等)はいずれも変更なし。12_mst_partner 分岐は新規追加のため既存ロジックに干渉しない。
注意事項
LockService.getScriptLock()を使うこと。PropertiesServiceにgetScriptLockメソッドは存在しない(GAS に存在しない誤 API)。排他ロック取得は必ずLockService.getScriptLock().waitLock(msec)を使用する。列参照はヘッダー名ベースの
indexOfで取得すること。列番号ハードコード禁止(CLAUDE.md 規約)。headers.indexOf('法人番号')/headers.indexOf('取引先名_正式')で取得する。onEdit(e)の列判定はe.range.getColumn()とheaders.indexOf('取引先名_正式') + 1を比較する(1-indexed)。有効フラグ=FALSE の行は一括処理・onEdit ともにスキップ(CLAUDE.md 規約)。
flag === false || String(flag).toUpperCase() === 'FALSE'の両方をチェック。Utils.logError(funcName, error, context)の引数順に注意。第 1 引数: 関数名文字列、第 2 引数: Error オブジェクト、第 3 引数: 文字列コンテキスト(004_utils.jsL242 参照)。getWebSpreadsheet_()をSpreadsheetApp.getActiveSpreadsheet()の代わりに使用する(Web アプリ経由でのシート取得に対応)。Utils.toastResultは内部でgetWebSpreadsheet_()を呼ぶため直接使用可。T番号(インボイス登録番号)の取得は本案件スコープ外。法人番号(13桁数字)のみを対象とする。「法人番号にTを付けるとインボイス登録番号」との混同注意(適格請求書発行事業者公表サイトは別 API)。99_番台のシートには98_audit_logと99_error_logが存在する。キャッシュシート名は95_cache_houjinを使用する(99_cache_houjin_apiは使用不可)。国税庁 Web-API はアプリケーション ID 要申請(無料)。API エンドポイント・パラメータ仕様・レスポンス JSON 構造は実装時に最新ドキュメントを確認すること(
https://www.houjin-bangou.nta.go.jp/webapi/)。本仕様書は 2026-04-20 時点の仕様を前提とするが、変更があり得る。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_log(LOG_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 時点の情報に基づく。
関連ドキュメント
- 国税庁 法人番号公表サイト Web-API 仕様
200_data/202_repository.js—PartnerRepository設計(L353-406)000_infra/003_contracts.js—PartnerDTO定義(L152-168)300_ui/301_ui_assist.js—handleUxAssist/applyPartnerPaymentTerms_(MAS-120 実装パターン参照)- dev_mas-120_partner_payment_terms_autofill.md — 同じく取引先マスタ onEdit 補完の先行実装
- dev_mas-154_partner_logical_abbr.md —
16_wrk_masterの略称自動生成(onEdit パターン参照) - dev_mas-114_invoice_validation.md — インボイス T 番号検証(MAS-170 の後続案件)
人間が検討すべき事項
TODO_future.md MAS-170 欄からの転記
- API 利用申込(2026 年 4 月時点で要登録)の手続き。利用申込後にアプリケーション ID を取得し、GAS スクリプトプロパティ(
Envモジュール)に追加する必要がある。 - 同名異社の UX 設計。複数候補ヒット時のモーダルダイアログの使いやすさ確認。
- インボイス登録有無の確認が本案件スコープに含まれるか否か。含める場合は適格請求書発行事業者公表サイト(別 API)を追加で呼ぶ必要があり、実装コストが増大する。
- 法人番号のない個人事業主の扱い。0 件として記録するのみで問題ないか確認。
- 海外取引先の扱い。0 件として記録するのみで問題ないか確認。
- 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 件ヒット):
alert()確認ダイアログ: 「〇〇の法人番号 XXXXXXXXX を登録しますか?」(住所も表示)- 「はい」→
corpNumCell.setValue(c.corporateNumber)+Utils.auditLog('UPDATE', '12_mst_partner', ...)で記録 - 「いいえ」→ 空欄のまま(何もしない)
onEdit 時(複数件ヒット):
305_select_corporation.htmlモーダル表示 → 候補一覧をテーブル表示- ユーザーが行を選択 → 「登録」ボタン
google.script.run.writeCorporateNumber(row, col, number, name)でサーバー側書き込み +Utils.auditLog記録
一括処理時(batchFetchCorporateNumbers):
- 1 件ヒット: 自動書き込み(確認ダイアログなし。バッチ処理のため Human-in-the-Loop は完了後通知)
- 複数件ヒット: スキップ +
manualCheckCountカウント - 実行完了後: 「法人番号取得完了: X 件更新、要手動確認: Y 件」を
Utils.toastResultで通知 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 Haiku | UI パターンが明確で設計判断要素が少ない |
| 一括バッチ関数・PartnerRepository 連携 | Claude Sonnet | DTO の読み書きと Repository パターンの理解が必要 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-20 | 初版作成 |
仕様書作成プロンプト(再現性・監査性のため必ず記録)
展開して表示
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
- 拡張思考の使い分け: Phase 1(設計)では拡張思考をフル活用し、ファイル名・関数名・行番号・エッジケースをここで完全に確定させる。Phase 2(清書)の各 Step 内では拡張思考を最小限に抑え、Phase 1 で確定済みの内容の書き下しに徹する。出力途中で再考しない。
- テキスト報告の禁止: 「〜を作成します」等の text のみで tool_use なしに turn を終了しない。説明は 1 文以内。直ちに tool を呼ぶ。
- 4-5 分割の Write/Edit 実行: 2-1(骨格 ~20行)/ 2-2(概要〜注意事項 ~300行)/ 2-3a(エッジケース〜人間検討事項 ~200行)/ 2-3b(実装プロンプト〜変更履歴 ~250行)/ 2-4(
<details>記録)に分割。1 回の Write/Edit は 300 行以内。 - 各 Step で何を書くかを具体指示: Phase 1 で確定した内容をそのまま書き出す。出力途中で設計判断を持ち込まない。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 MAS-170「法人番号自動取得(国税庁 法人番号公表サイト Web-API 連携)」の開発仕様書を作成してください。
仕様書新規作成後は docs/_config.json の nav 配列(§E.6 パイプライン・RPA・外部連携)にも必ず追記すること。
Phase 1: 案件情報の収集(テキスト報告禁止。即座にツール実行)
1-A: 案件定義の読み込み
docs/_internal/TODO_future.md で MAS-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 でここに確定させる。
000_infra/003_contracts.jsPartnerDTOの@typedefを全フィールド確認。法人番号フィールドが既に存在するかを Read で裏取り。T番号(インボイス登録番号)フィールドの有無も確認(法人番号とは別物: 法人番号=13桁数字、T番号=T+13桁)。
000_infra/002_constants.jsSHEET_DEFAULTS配列の実際の要素型({ pattern, prefix, defaults }オブジェクト配列)を Read で確認し、12_mst_partnerエントリの有無を把握する。ID_PREFIX_MAPに12_mst_partner向けエントリが存在するか確認。
200_data/202_repository.jsPartnerRepository.findAll()の戻り値型({ headers: string[], dtos: Object[] })とPartnerRepository.save(dtos)の引数型を Read で確認する。findPaymentTermsMap()のキャッシュ構造も把握する。
000_infra/004_utils.jsUtils.logError(funcName, error, context)/Utils.toastResult(funcName, message, duration)/Utils.logInfo(funcName, message)の引数順と型を Read で確認する。getWebSpreadsheet_()の使い方を確認する(SpreadsheetApp.getActiveSpreadsheet()の代わりに使う)。
300_ui/301_ui_assist.js- 既存の
onEdit(e)ハンドラの全体構造・シート名分岐パターンを Read で確認する。12_mst_partnerへの分岐が既に存在するか確認し、MAS-170 のロジックを挿入すべき行番号のランドマークを特定する。
- 既存の
100_config/101_sys_config.jsonOpen()内のui.createMenuを Read し、「🔧 開発・設定」メニューが実際に存在するか、その正確な文字列と階層を確認する。存在しない場合は正しいメニュー名を特定する。MAS-170 のメニュー項目の挿入行番号を特定する。
500_import/ディレクトリのファイル一覧- Bash の
ls 500_import/で実在するファイル名を取得し、次に使える番号(503 が空いているか)を確認する。空きがあれば503_corporate_number_api.jsを使用。競合する場合は次の空き番号を使用する。
- Bash の
既存シート番号の確認
- Bash または Grep で
98_audit_log,99_error_logが参照されていることを確認する。キャッシュシートに99_プレフィックスは使用不可(競合)。空いている番号帯(例:95_,96_)を確認し、キャッシュシート名を確定させる。
- Bash または Grep で
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-170。dev_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.json の nav 配列 §E.6 末尾に追記する。