AppSheet初心者向け|経費精算アプリをゼロから構築する手順まとめ②

2025年4月13日日曜日

Appsheet GAS Google Apps Script

AppSheet初心者向け|経費精算アプリをゼロから構築する手順まとめ②

目次を表示/非表示

背景と目的

前回の記事「構築手順まとめ①」では、AppSheetで経費精算アプリのスプレッドシート設計と初期セットアップを行いました。

今回はその続編として、Step3「カラム設定」〜 Step5「テスト登録」までの操作編をまとめています。いよいよアプリとして動かす段階に入ってきます!

Step3|カラム設定とREFの定義

AppSheetはスプレッドシートのカラム名を読み込んだ際にデータ型を自動で読み取ってくれますが、意図しない場合もあるので、下記の通り設定しましょう。



以下の設定は手動で必ず見直しましょう。

Appsheet データ型登録①

Appsheet データ型登録②

  • ID経費登録(登録テーブル):Key に設定(主キー)
  →これは自動的に選ばれるはず。

Appsheet データ型登録③

Appsheet データ型登録④

  • ID経費明細(経費明細テーブル):Key に設定(主キー)
  • 経費明細テーブルの、カラム「ID経費登録」:データType を REF にし、経費登録テーブルを参照



これにより、明細が「どの登録データに属するか」をAppSheet上で認識させることができ、フォームでも「親子関係」を構築できます。

💡補足|主キーと親キーの違い

経費明細TBLを例とすると

  • 主キー(Key):そのレコードを一意に識別するカラム(例:ID経費明細)。
→TBLの中で同じコードが一つしかないよっていう意味
  • 親キー(REF):他のテーブルの主キーを参照するカラム(例:ID経費登録)。

Step4|UX設計(フォームと表示)

次に、AppSheetの「UX」タブからビュー(見た目)を設計していきます。

  • 経費登録:フォーム形式に設定し、入力画面を作成


  • 経費明細:経費登録のフォーム内で明細を追加できるよう設定
なんだか似たような画面でいまいちややこしいのがAppsheetの唯一の欠点です。
  • アイコン・表示名なども調整するとUXが向上します

鉛筆マークから編集画面へ移動
※鉛筆マークが出ない場合は下記Switch to the Improved editor をクリック。






お好みのアイコンに変更しましょう。



Step5|動作確認とテスト登録

作成したアプリをプレビューまたはスマホで開き、テストデータを1件入力してみましょう。

今回は、過去の記事で作成したサンプル画像


を取り込んでみます!

  • plusマークから明細を追加

経費登録に適切な項目が無ければNewから追加


領収書を撮影or領収書データを取り込み(ping・Jpegなどの写真情報に限る)


明細を確認してみましょう。
無事に登録されOCRTEXTも機能していることが確認できます。


  • ここでGASが回ると・・・?

    勘定科目がGEMINI API により自動生成されます。
GASのスクリプトは下記通り!!
同様のスプレッドシートカラム構成であれば、APIKEYとスプレッドシートIDを打ち換えるだけで使用できます!

/** * Google Apps Script: 領収書OCRテキストから勘定科目を取得し、 * シートに書き込む自動処理(Generative Language API v1beta + Gemini 1.5 Pro) * * モデル呼び出し時の JSON ペイロードには `contents` フィールドを使います :contentReference[oaicite:0]{index=0}。 * レスポンスからは最初の候補の content.parts[0].text を抽出します :contentReference[oaicite:1]{index=1}。 */ // ——— 定数設定 ——— const GEMINI_API_KEY = 'XXXX'; // ご自身の Gemini API キー const SPREADSHEET_ID = 'YYYY'; // 対象のスプレッドシート ID const SHEET_NAME = '経費明細'; // 対象シート名 const ERROR_EMAIL = 'your-email@example.com'; // エラー通知先メールアドレス // ——— モデル名 & エンドポイント ——— const MODEL_NAME = 'gemini-1.5-pro-002'; const ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta/models/' + MODEL_NAME + ':generateContent?key=' + encodeURIComponent(GEMINI_API_KEY); /** * メイン関数: * 2行目以降の未処理行を1行だけ処理し、 * K列に勘定科目、M列に「完了」を書き込む */ function processReceiptCategory() { const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`シート「${SHEET_NAME}」が見つかりません。`); } // データ取得(1行目はヘッダー) const values = sheet.getDataRange().getValues(); for (let i = 1; i < values.length; i++) { const ocrText = values[i][11]; // L列 (0-based index 11) const doneFlag = values[i][12]; // M列 (0-based index 12) // OCRテキストあり & 未処理 の行を処理 if (ocrText && !doneFlag) { try { // プロンプト作成 const prompt = [ '以下は領収書のOCRテキストです(ノイズが含まれています)。', ocrText, '', 'この中から、会計処理に必要な情報のみ抽出して、最も適切な「勘定科目」を1つだけ日本語で出力してください。' ].join('\n'); // リクエストペイロード const payload = JSON.stringify({ contents: [ { parts: [ { text: prompt } ] } ] // generation_config などを追加したい場合はここに }); const options = { method: 'post', contentType: 'application/json', payload, muteHttpExceptions: true }; // API 呼び出し const response = UrlFetchApp.fetch(ENDPOINT, options); const code = response.getResponseCode(); const body = response.getContentText(); if (code === 200) { const json = JSON.parse(body); // 最初の候補のテキストを抽出 const parts = json.candidates[0].content.parts; const category = parts.map(p => p.text).join('').trim(); // K列(勘定科目)と M列(処理完了フラグ)に書き込み sheet.getRange(i + 1, 11).setValue(category); // K列 (1-based) sheet.getRange(i + 1, 13).setValue('完了'); // M列 (1-based) } else { throw new Error(`Gemini API Error (${code}): ${body}`); } } catch (e) { // エラー時はログ & メール通知 Logger.log(e); MailApp.sendEmail({ to: ERROR_EMAIL, subject: '【GAS エラー通知】領収書処理エラー', body: e.toString() }); } // 1行だけ処理したら必ずループを抜ける break; } } } /** * デバッグ用: 利用可能なモデル一覧を Logger に出力 */ function listModels() { const url = 'https://generativelanguage.googleapis.com/v1beta/models' + '?key=' + encodeURIComponent(GEMINI_API_KEY); const res = UrlFetchApp.fetch(url, { muteHttpExceptions: true }); Logger.log(res.getContentText()); }

前回:構築手順まとめ①   |   次回:OCR×勘定科目の自動判定(Coming Soon)