0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【GAS】Googleフォーム×在庫管理!注文数の変更・キャンセルに対応④事前入力URL編

0
Last updated at Posted at 2026-06-18

今までの記事一覧
Googleフォーム×GASで実現する業務自動化シリーズ全記事まとめ目次

前提

この記事は、フォーム回答を保存しているスプレッドシート側のGASを前提にしています。

トリガーは以下を設定してください。

  • 実行する関数:onFormSubmit
  • イベントのソース:スプレッドシートから
  • イベントの種類:フォーム送信時

スプレッドシート構成

スプレッドシート「集計シート」をこの図のように設定しています。

image

  • B1セル:販売上限数
  • B2セル:「有効注文数」の合計(=SUM(F6:F)
  • B3セル:残数(=B1-B2
  • 6行目以下のA~D列:フォームから送信された回答
  • 6行目以下のE列:注文内容を変更・キャンセルした場合に、管理者が修正後の注文数を入力する列
  • 6行目以下のF列:在庫計算に使用する「有効注文数」(以下の関数を設定)
    • E列が空欄なら、フォームから送信された注文数(D列)を使用
    • E列に修正数が入力されていれば、その値を使用
=ARRAYFORMULA(IF(D6:D="","",IF(E6:E="",D6:D,E6:E)))
  • 6行目以下のG列:変更用の事前入力URLが入る

おさらい

前々回では、フォーム回答の変更やキャンセルを、送信者(ユーザー) 自らが、変更用のフォームを使って送信する方法を作りました。
前回は、フォームの「編集用URL」を使って変更やキャンセルができないか試した結果、今回のシステムに組み込んでいる動的在庫制御と相性が悪いことが判明し、不採用となった「ボツネタ編」でした。

ということで、無事に実装できた変更用フォームですが、「メインの注文フォームで入力したメールアドレスや名前を、変更用に再度入力しなくてはいけないのは面倒だ」との意見が出ました。

image

でも大丈夫、こういうときのためにGoogleフォームには「事前入力」という便利な機能があります。

事前入力機能とは

手動では、フォームの管理画面右上の三点リーダーから「フォームに事前入力する」を選択します。
出てきたフォームの必要部分を入力後に「リンクを取得」をクリックすると、このリンクの中に事前入力したデータが埋め込まれたリンク(事前入力URL)が作成されます。

image

このリンクを開くと、先ほど入力したデータが入った状態でフォームが開くしくみです。
あらかじめデフォルトデータを入れておきたいときなどによく使われます。

今回はこのしくみを使って、送信者(ユーザー)それぞれのメールアドレスと名前を事前入力した、変更フォーム用のURLを作成して、送信者にメールする、というのをやってみます。

やりたいこと

今まで作ってきたことに以下を追加

  • 注文フォーム送信時に、送信者のメールアドレスと名前を事前入力した変更フォーム用の事前入力URLを作成
  • 作成した事前入力URLを送信者にメール送信し、集計シートにも保存

今回のキモはコレ!

変更フォームからアイテム(各質問)を取得

変更フォームを開き、そこから事前入力したいアイテム(メールアドレスと名前)のオブジェクトを取得します。
getItems()で取得できる各アイテムは配列になっているため、順番(要素番号)に呼び出すことができます。
今回は1番目(要素番号0)に「メールアドレス」、2番目(要素番号1)が「名前」となっています。

要素番号はフォームの質問の並び順になっています。ご自身のフォームの並び順に沿った順番にしてください。

  // 変更フォームのIDを指定して開く
  const corForm = FormApp.openById("1********"); // 変更フォーム

  // 変更フォームの「メールアドレス項目」と「名前項目」のオブジェクトを取得
  const items = corForm.getItems();
  const mailItem = items[0].asTextItem(); // 1番目のアイテム(メールアドレス)
  const nameItem = items[1].asTextItem(); // 2番目のアイテム(名前)

事前入力URLを組み立てる

取得したアイテムのオブジェクトを使って、事前入力したい内容(今回の送信者のメールアドレスと名前)をセットした回答を作成し、最後に事前入力URLを取得します。

  // 変更フォームに対する新しい回答を作成
  let response = corForm.createResponse();

  response = response.withItemResponse(mailItem.createResponse(mail)); // メールをセット
  response = response.withItemResponse(nameItem.createResponse(user)); // 名前をセット

  // 最後に事前入力URLを取得
  const prefilledUrl = response.toPrefilledUrl();

これらのコードは、メイン関数 onFormSubmit(e) 内で、変更フォームを開いた直後、送信されたフォームが「注文フォーム」か「変更フォーム」かを判定する処理の前に追加します。

サブ関数(受付メール送信)

メール送信は以下のサブ関数で対応しましょう。

function acceptMail(mail,user,num,prefilledUrl){
  const title = "お弁当の注文を受け付けました"
  let body = `
  ${user} 様
  ご注文ありがとうございます。
  以下のとおり、お弁当の注文を承りました。

  注文数:${num} 個

  注文内容の変更・キャンセルは、以下のURLから変更内容を送信してください。
   ${prefilledUrl}
  `;
  GmailApp.sendEmail(mail, title, body);
}

メイン関数function onFormSubmit(e)の最後にこれ ↓ を差し込んで呼び出します。

  acceptMail(mail,user,num,prefilledUrl);

動作確認

では、注文フォームから新規に注文を送信してみましょう。
集計シートにはこのように変更フォーム用の事前入力URLが入力され、クリックするとメールアドレスと名前が事前入力された状態で変更フォームが開きます。受付メールも無事、送信されました。

image
image
image

これでユーザーはメールアドレスや名前を再入力することなく、注文数だけを入力して送信できるようになりました。

まとめ

今回は、Googleフォームの事前入力URLをGASから生成する方法を実装しました。
変更フォームにメールアドレスや名前を事前入力しておくことで、利用者は注文数だけを入力すればよくなり、変更やキャンセルのハードルを下げることができます。

また、編集用URLとは異なり、前回までに実装した在庫制御の仕組みをそのまま利用できるため、今回のお弁当システムとの相性も良好でした。

今回の成果コードはこちら

お断りメールにも、この事前入力フォームのURLを入れるように変更しました。

function onFormSubmit(e) {

  const ss = SpreadsheetApp.getActiveSpreadsheet();

  const sheet = e.range.getSheet();  //フォーム回答が入力されたシート
  const row = e.range.getRow();  //フォーム回答が入力された行
  const column = e.values.length;  // フォーム回答シートに書き込まれた列
  const formData = sheet.getRange(row, 1, 1, column).getValues();

  const mail = e.values[1];   //フォーム送信データからメールアドレスを取得(今回は2つめの回答=0から始まるので[1])
  const user = e.values[2];   //フォーム送信データからユーザー名を取得
  const num = Number(e.values[3]);   //フォーム送信データから注文数を取得  

  // 紐づいているフォームを取得(管理用ID)
  const form = FormApp.openById("1#########"); // 注文フォーム
  const corForm = FormApp.openById("1********"); // 変更フォーム

  /// ★★変更フォームの事前入力URLを組み立てる ///////
  // 変更フォームの「メールアドレス項目」と「名前項目」のオブジェクトを取得
  const items = corForm.getItems();
  const mailItem = items[0].asTextItem(); // 1番目がメールアドレスの場合
  const nameItem = items[1].asTextItem(); // 2番目が名前の場合

  let response = corForm.createResponse();

  response = response.withItemResponse(mailItem.createResponse(mail)); // メールをセット
  response = response.withItemResponse(nameItem.createResponse(user)); // 名前をセット

  // 最後にURLを取得
  const prefilledUrl = response.toPrefilledUrl();


  //集計シートの最終行
  const sheet_summary = ss.getSheetByName("集計シート");
  const summaryLastRow = sheet_summary
    .getRange(sheet_summary.getMaxRows(), 1)
    .getNextDataCell(SpreadsheetApp.Direction.UP)
    .getRow();        //A列の最終行を取得

  const mailDataList = sheet_summary.getRange(5, 2, summaryLastRow - 4, 1).getValues().flat();

  let currentRemaining = Number(sheet_summary.getRange("B3").getValue());

  const sheetName = sheet.getName();

  let editRow;
  const lastHitIndex = mailDataList.lastIndexOf(mail);

  if (sheetName === "フォームの回答1") {        // 注文フォーム(新規)
    if (currentRemaining - num < 0) {     // 在庫がマイナスになった場合はお断りメール
      rejectionMail(mail, user, currentRemaining, prefilledUrl);     // function rejectionMail
      return;
    } else {
      editRow = summaryLastRow + 1;

    }

  } else if (sheetName === "フォームの回答2") {     // 変更フォーム(修正)
    // 安全対策:もし過去の注文(メールアドレス)がシート内に見つからなければお断りメール(2)
    if (lastHitIndex == -1) {
      rejectionCorrectionMail(mail, user,form);      //function rejectionCorrectionMail
      return;
    }

    // 先にアドレスがある行を特定する
    editRow = lastHitIndex + 5;

    // 特定した行から、前回の注文数(preNum)を安全に取得(F列=6列目の場合)
    const preNum = Number(sheet_summary.getRange(editRow, 6).getValue());

    // preNumが確定したので、ここで在庫の計算を行う
    if (currentRemaining - num + preNum < 0) {   // 在庫がマイナスになった場合はお断りメール
      rejectionMail(mail, user, currentRemaining + preNum, prefilledUrl);          // function rejectionMail
      return;
    }
  }
  sheet_summary.getRange(editRow, 1, 1, column).setValues(formData);
  sheet_summary.getRange(editRow, 7).setValue(prefilledUrl);
  sheet_summary.getRange(editRow, 5).clearContent();     //管理者が入力したデータをクリアする
  SpreadsheetApp.flush();    //今書き込んだ内容をシートに反映させる

  // 最新の在庫数をシートから再取得
  currentRemaining = Number(sheet_summary.getRange("B3").getValue());

  // もし残数が0以下になったら、フォームを自動で締め切る
  if (currentRemaining === 0) {
    form.setCustomClosedFormMessage("本日の注文受付は【完売】のため終了いたしました。またのご利用をお待ちしております!");
    form.setAcceptingResponses(false); // 新規受付を停止
    //変更フォームにだけ、残数を表示
    corForm.setDescription("【現在の残数: " + currentRemaining + " 個】"
      + "\n" + "元々のご自身の注文数にこの数を足したものが、注文可能数です。");


  } else {  //残数がまだある場合
    //★★フォームが受付停止になっていたら再開
    if (form.isAcceptingResponses() == false) {
      form.setAcceptingResponses(true);
    }
    //フォーム全体の「説明欄」を書き換える場合
    form.setDescription("【現在の残数: " + currentRemaining + " 個】");
    corForm.setDescription("【現在の残数: " + currentRemaining + " 個】"
      + "\n" + "元々のご自身の注文数にこの数を足したものが、注文可能数です。");

    //特定の質問(注文数)の「プルダウン選択肢」を書き換える
    const items = form.getItems();
    for (const item of items) {
      if (item.getTitle() === "注文数を選択してください") {

        // プルダウン(asListItem) に型変換
        const listItem = item.asListItem();

        // 1 から currentRemaining までの「文字列の配列」を作る
        //★★キャンセルの場合は0を入れてもらうため、選択肢を0からに変更
        const choices = [];
        for (let i = 0; i <= currentRemaining; i++) {
          choices.push(String(i)); // "1", "2", "3"... と文字列で入れていく
        }

        // プルダウンの選択肢を最新在庫に合わせて書き換え
        listItem.setChoiceValues(choices);

        // ヘルプテキスト(説明文)を更新
        listItem.setHelpText("※現在の残数は「あと " + currentRemaining + " 個」です。");
        break;
      }
    }
  }  
  //サブ関数 acceptMail を呼び出し、メール送信
  acceptMail(mail,user,num,prefilledUrl);
}
///////////////////////////
//受付メール
//////////////////////////
function acceptMail(mail,user,num,prefilledUrl){
  const title = "お弁当の注文を受け付けました"
  let body = `
  ${user} 様
  ご注文ありがとうございます。
  以下のとおり、お弁当の注文を承りました。

  注文数:${num} 個

  注文内容の変更・キャンセルは、以下のURLから変更内容を送信してください。
   ${prefilledUrl}
  `;
  GmailApp.sendEmail(mail, title, body);
}
///////////////////////////
//お断りメール(1)
//////////////////////////
function rejectionMail(mail, user, num,prefilledUrl) {
  const title = "【重要】お弁当のご注文を承れませんでした"
  
  let body;
  if (num <= 0) {
    body = `
  ${user} 様

  ご注文ありがとうございます。
  大変申し訳ございませんが、先に他のお客様のご注文が確定し、
  残数が0になったため、${user}様の注文を承ることができませんでした。

  またのご利用をお待ちしております。
`;
  } else {
    const time = Utilities.formatDate(new Date(), "JST", "yyyy/MM/dd HH:mm");
    body = `
  ${user} 様

  ご注文ありがとうございます。
  大変申し訳ございませんが、先に他のお客様のご注文が確定したため
  ${user}様の注文を承ることができませんでした。
  恐れ入りますが再度以下のリンクから注文をお願いします。

   ${prefilledUrl}

  なお、${time} 現在の注文可能数は【 ${num} 個】となっております。  
  `;
  }
  GmailApp.sendEmail(mail, title, body);
}

///////////////////////////
//お断りメール(2) 新規なのに変更フォームで送った場合
//////////////////////////
function rejectionCorrectionMail(mail, user,form) {

  /// ★★注文フォームの事前入力URLを組み立てる ///////
  const items = form.getItems();
  const mailItem = items[0].asTextItem(); // 1番目がメールアドレスの場合
  const nameItem = items[1].asTextItem(); // 2番目が名前の場合

  let response = form.createResponse();

  response = response.withItemResponse(mailItem.createResponse(mail)); // メールをセット
  response = response.withItemResponse(nameItem.createResponse(user)); // 名前をセット

  // 最後にURLを取得
  const prefilledUrl = response.toPrefilledUrl();

  const title = "【重要】お弁当のご注文を承れませんでした"
   let body = `
  ${user} 様
  ご注文ありがとうございます。
  変更フォームより送信いただきましたが、お客様の「事前の注文データ」が見つかりませんでした。

  恐れ入りますが、ご注文がお済みでない場合は、
  以下の「注文専用フォーム」から新規のご注文をお願いいたします。

  ▼ 注文専用フォームはこちら
   ${prefilledUrl}

  ※すでに注文済みで、メールアドレスを変更された場合などは、お手数ですが管理者まで直接ご連絡ください。
`;

  GmailApp.sendEmail(mail, title, body);
}

次回は、編集URLを取得するもう一つの方法、URLの構造から文字列を結合する方法をご紹介したいと思います。


image.png

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?