GASで経費申請フォームを作ってみた(3/6)ー入力された内容をスプレッドシートに記録するー

このブログでは、Google Apps Script(GAS)で作ったツールや、作る途中で気づいたことを紹介しています。「こんな使い方もできるんだ」と、普段の作業を見直すヒントになればうれしいです。

前回は、Webアプリとして入力画面を公開し、入力の誤りを画面で知らせる仕組みを紹介しました。

今回は、フォームに入力した内容をGoogleスプレッドシートへ記録する部分です。まとめて申請された経費を、後から確認しやすい形で残すことを考えました。

やりたかったこと

1回の申請で複数の経費を入力できるようにしても、保存した内容が分かりにくければ、確認する人の手間が増えてしまいます。

そこで、申請者の名前や部署などの共通情報と、それぞれの経費の情報を組み合わせて、経費1件につき1行で記録する形にしました。

例えば、1回の申請に3件の経費があれば、スプレッドシートには3行追加されます。申請者情報を各行に入れることで、1行ずつ見ても誰の経費なのか分かるようにしています。

使ったロジック・ポイント

入力画面からGASへデータを渡す

入力画面のJavaScriptと、スプレッドシートへ記録するGASは、それぞれ別の場所で動きます。画面の入力欄に文字を入れただけでは、シートには保存されません。

この橋渡しに使うのが、google.script.runです。HTMLで作った画面からGASの関数を呼び出し、入力内容を渡せます。スプレッドシート上のダイアログでも使える仕組みですが、今回の経費申請フォームではWebアプリの入力画面で使っています。

流れは、次のようになります。

  1. 画面側で、申請者情報と各経費の入力内容を取り出します。
  2. 入力チェックを通過した内容をまとめ、GAS側の関数へ渡します。
  3. GAS側で、受け取った内容をシートの列順に並べます。
  4. 書き込む範囲を指定し、スプレッドシートへ記録します。
  5. 処理結果を画面へ返し、完了したことを表示します。

GASの処理は、呼び出した直後に結果が返るとは限りません。完了後の表示はwithSuccessHandler、処理に失敗したときの表示はwithFailureHandlerで指定します。

実際のシステムでは、申請内容の確認、レシート画像の保存、経費の記録、確認メールの送信を段階的に進めています。以下のコード例では、その中から「画面からデータを渡して、シートへ記録する」部分を取り出しています。

共通情報と経費ごとの情報を組み合わせる

フォームの情報には、名前・メールアドレス・部署など、申請全体で共通するものがあります。一方で、購入日・商品名・使用目的・金額などは、経費ごとに異なります。

保存するときには、この2種類の情報を組み合わせて、経費ごとの行データを作ります。なお、ここで記録するメールアドレスは、申請者がフォームに入力したものです。ログイン中のGoogleアカウントの表示とは分けています。

列の順番に合わせて、まとめて書き込む

スプレッドシートでは、各列に何を入れるかを決めておく必要があります。金額を入れるはずの列に商品名が入るといったずれを防ぐため、見出しと同じ順番で行データを並べています。

主に記録する内容は、申請日時、申請者情報、購入日、商品名、使用目的、税込金額、税率ごとの金額・消費税、レシートURL、提出状況です。

複数の経費から作った行データは、書き込み先の範囲を指定して、まとめて追加します。実装では、既存シートの見出しが想定した並びかどうかも確認しています。

レシート画像はURLでつなぐ

画像そのものはGoogleドライブに保存し、スプレッドシートには、その画像を開くためのURLを記録します。

これにより、経費の内容と対応するレシートを、同じ行から確認できる形にしています。画像の保存とURLの受け渡しについては、次回の記事で紹介します。

同じ経費を繰り返し登録しないための情報を持つ

通信の途中で処理が止まった場合などに備えて、申請を識別するIDと、その申請内の経費番号も記録しています。

保存処理では、この組み合わせを使って登録済みの経費を確認し、まだ記録されていないものを追加します。普段確認する申請内容と区別するため、これらの管理用の列は非表示にしています。

これは重複登録を防ぐための工夫です。通信や保存がどのような状況でも必ず成功する、という意味ではありません。

コード例

GASでコード例を試す際の基本操作や認証は、次のガイドで確認できます。この記事のファイル名・設定などは、各コード例の説明に従ってください。

各回のコード例は独立しています。順番に追加して、経費申請フォーム全体を完成させるものではありません。

以下は、画面とGASのつながりを紹介するために簡略化した例です。実際の経費申請フォームへ差し替えるコードではありません。入力チェック、画像保存、権限確認、重複防止などの処理は省略しています。

① 画面側:入力内容をまとめてGASへ渡す

名前の入力欄(id="name")と、複数の経費入力欄(class="expense-card")がある前提です。経費ごとの購入日・商品名・金額を取り出し、expensesという一覧にまとめます。

下の関数は、入力チェックを通過したあとに呼び出す想定です。表示先にはid="status"の要素を使います。

function sendExpenseExample() {
  const status = document.getElementById('status');
  const cards = document.querySelectorAll('.expense-card');

  // 入力チェックを通過した内容をまとめます。
  const data = {
    name: document.getElementById('name').value.trim(),
    expenses: Array.from(cards).map(function(card) {
      return {
        purchaseDate: card.querySelector('.purchase-date').value,
        productName: card.querySelector('.product-name').value.trim(),
        amount: Number(card.querySelector('.amount').value)
      };
    })
  };

  status.textContent = '保存しています';

  google.script.run
    .withSuccessHandler(function(result) {
      status.textContent = result.count + '件を記録しました';
    })
    .withFailureHandler(function() {
      status.textContent = '保存できませんでした';
    })
    .saveExpenseExample(data);
}

最後のsaveExpenseExample(data)は、GAS側に用意した同名の関数を呼び出しています。その戻り値を受け取り、記録した件数を画面に表示します。

② GAS側:受け取った内容を行にして書き込む

この例では、保存先に「保存例」というシートを作り、1行目に「申請日時・名前・購入日・商品名・金額」の5つの見出しを用意しておきます。スプレッドシートのIDは、説明用の仮の文字列です。

function saveExpenseExample(data) {
  const spreadsheet = SpreadsheetApp.openById(
    '保存先スプレッドシートのID'
  );
  const sheet = spreadsheet.getSheetByName('保存例');
  const now = new Date();

  // シートの見出しと同じ順番で、1件分の行を作ります。
  const rows = data.expenses.map(function(expense) {
    return [
      now,
      data.name,
      expense.purchaseDate,
      expense.productName,
      expense.amount
    ];
  });

  if (!rows.length) {
    return { count: 0 };
  }

  sheet.getRange(
    sheet.getLastRow() + 1,
    1,
    rows.length,
    rows[0].length
  ).setValues(rows);

  return { count: rows.length };
}

mapで、経費の一覧から「1件につき1行」のデータを作っています。申請者の名前は共通情報なので、すべての行に同じ値を入れています。

getLastRow() + 1で既存データの次の行を求め、getRangeで書き込み先を指定します。setValuesに、行ごとのデータをまとめた配列を渡すことで、複数件を一度に記録できます。

書き込み後には件数を返します。画面側はその結果を受け取ってから「○件を記録しました」と表示するため、ボタンを押した直後に保存完了と表示してしまうことを避けられます。

メモ・反省・次回やりたいこと

フォームを作るときは入力画面に目が向きますが、保存した後にどう確認するかも、合わせて考える必要があります。

今回は、複数の経費をまとめて申請する操作と、経費を1件ずつ確認する作業の両方に合わせて、保存する形を分けています。

また、列の並びや、途中で止まった処理を再開するときの扱いも、画面からは見えない大切な部分でした。

次回は、フォームで受け取ったレシート画像をGoogleドライブへ保存し、今回の記録とつなげる仕組みを紹介します。

自分の業務に自動化機能を追加したい方へ

経費申請や画像保存、申請後の連絡など、今の業務に自動化機能を追加したい方はご相談ください。現在の作業と希望する自動化を伺い、対応できる範囲をご案内します。

トップページへ戻る