今までの記事一覧
- GASでGoogleForm回答を取得するなら lastRow?(e)?試してみた
- onFormSubmit(e)を手動実行でデバッグする方法
- どっちを使う?onFormSubmit(e)の values と namedValues の違いと使い分け
- onFormSubmit(e) の e.values 配列順のしくみ
- Googleフォームで質問を変えても壊れない!cleanFormData(e)でnamedValues防御力をアップ
- Googleフォームの質問変更に負けない!「部分一致」と「秘密の暗号」でcleanFormData(e)の防御力を鉄壁に
- 手動コピペはもう卒業!Googleフォームの回答別に処理を自動仕分け
- Googleフォームで同時に大量送信されても踏ん張る!LockServiceで順番制御!try - catch - finally でバトンを繋げ!
- LockServiceでは順番は守れない?受付番号で順序を保証する方法
- 行削除も怖くない!PropertiesServiceでGoogleフォームに「ズレない・飛ばない」受付番号を実装する <この記事
- Googleフォームの「回答を編集」対策!新規と修正を識別してシートへの二重登録/番号ズレを防ぐ
- Googleフォームの添付ファイルを自動整理 専用フォルダ作成&ファイル名変更で管理をラクにする①
- Googleフォームの添付ファイルを自動整理 専用フォルダ作成&ファイル名変更で管理をラクにする②
前提
この記事は、フォーム回答を保存している スプレッドシート側のGAS を前提にしています。
トリガーは以下を設定しています。
- スプレッドシートから
- フォーム送信時"
おさらい
前回はフォームの送信順に受付番号を付ける方法として、
- 「フォームの回答」シートの行番号を付ける方法
- 受付タイムをミリ秒にする方法
を使ってみました。
今回は
- 「フォーム回答」シートで行削除しても番号が飛ばない
- 1からスタートして1つずつ数字が上がっていく
これらを満たす方法としてPropertiesServiceを使ってみたいと思います。
PropertiesServiceとは
「公式リファレンス」より
スクリプトの 1 つのユーザー、またはアドオンが使用されている 1 つのドキュメントにスコープされた Key-Value ペア形式のシンプルなデータをスクリプトで保存できます。
つまり、GASで呼び出すための「名前」と、呼び出される「値」をペアにして保存しておく金庫のようなものです。
API連携するときなどに、パスワードやtokenをコードに直接書き込むとセキュリティ上問題があるため、このPropertiesServiceに保存しておいて、コード上にはkeyだけを書く、という使い方をよくされます。
今回はこのシステムを使って受付番号を作りましょう。
PropertiesServiceの設定方法
- GASエディタの左側にある歯車アイコン(プロジェクトの設定)をクリック
- 画面を一番下までスクロールし、「スクリプトプロパティを追加」をクリック
- 「プロパティ」と「値」を入力して「スクリプトプロパティを保存」をクリック
今回は下図のようにプロパティに「RECEIPT_ID」、値に「0」を設定してみましょう。

プロパティの部分は半角英数字(ただし1文字目に数字はNG)を使いますが、実務では大文字を使うことが一般的です。こうすることで、プログラムコードを見たときに「あ、これはコード外に保存されている特別な設定値だな」と一目で区別がつくようになります。
値は最初の人が「1」を受け取れるようにあらかじめ「0」をセットしておきます。
最初の送信があったときに、プログラムの中で0+1=1と計算させることで、最初の受付番号が「1」から始まるようにできます。
GASでの使い方
以下のように呼び出します。
function myFunction() {
//プロパティサービスを呼び出す。呼び出されたものは文字列なので数字に直す
const lastId = Number(PropertiesService.getScriptProperties().getProperty("RECEIPT_ID"));
//今回使う受付番号(前回のIDに+1する)
const RecNo = lastId + 1;
//プロパティサービスで値を上書きする
PropertiesService.getScriptProperties().setProperty("RECEIPT_ID",String(RecNo));
//ここから下に処理を書く
}
まず、取り出す処理はこちら。
PropertiesService.getScriptProperties().getProperty("RECEIPT_ID");
RECEIPT_IDの部分は、さきほどスクリプトプロパティで設定したプロパティの名前が入ります。
今回の場合だとさきほど設定した"RECEIPT_ID"を書き、この名前とペアになった「値」が呼び出されます。今回呼び出されるのは、さきほど設定した「0」です。

呼び出された値は文字列型です。このまま足し算すると"0"+1="01"となってしまいます。正しく足し算できるようにNumberを付けて数値型にしておきましょう。
呼び出した数字はこのように +1 して今回の受付番号として使います。
const RecNo = lastId + 1;
忘れないうちに、次回用にsetPropertyを使ってプロパティサービスを上書きしておきましょう。
setProperty("書き換えたいプロパティ名","新しく入れたい値")
RecNoをそのまま使うと環境によっては値が「"1.0"」という文字列として保存されてしまうことがあります。予期せぬ挙動を防ぐため、String(RecNo)を使って「1」というきれいな数字の文字列として保存しておきます。
PropertiesService.getScriptProperties().setProperty("RECEIPT_ID",String(RecNo));
これを実行すると、設定画面で「0」だった値が「1」になっているのがわかります。

スプレッドシートにも入れておきましょう
せっかく作った受付番号、シート上でも「見える化」したいですよね。
幸い、変数 RecNo にしているのでこれをシートにsetValueすればいいだけ。
まずは「フォームの回答」シートに項目を作りましょう。理想はA列に入れたいけど、システムが使ってしまうので表の外側、今後質問が増えたときに上書きされないよう、少し離れた列に入れるようにします。
今回はI列に「受付番号」と入れます。今後、列が増えたり減ったりしても追尾できるよう、「名前付き範囲を定義」で「受付番号」のように名前も付けておくと安心です。
これによってsheet.getRange("受付番号")のような呼び出し方ができます。

function form_edited(e) {
//プロパティサービスを呼び出す。呼び出されたものは文字列なので数字に直す
const lastId = Number(PropertiesService.getScriptProperties().getProperty("RECEIPT_ID"));
//今回使う受付番号(前回のIDに+1する)
const RecNo = lastId + 1;
//プロパティサービスで値を上書きする
PropertiesService.getScriptProperties().setProperty("RECEIPT_ID",String(RecNo));
// 回答が書き込まれたシートを直接取得(シート名が変わっても大丈夫)
const sheet = e.range.getSheet();
//フォームの回答に対応するシートの行を取得
const row = e.range.getRow();
//受付番号の列を取得
const column = sheet.getRange("受付番号").getColumn();
//受付番号を書き込む
sheet.getRange(row,column).setValue(RecNo);
}
忘れがちなポイント:表の範囲を広げよう
このままだとデータを並べ替えしたときに受付番号だけ蚊帳の外になってしまいます。
スプレッドシートの「データ」メニューなどから、表の範囲(あるいはフィルタの範囲)をI列まで含めるように調整しておきましょう。
他のシートにも展開
もちろん他のシートにも書き込めます。
例えば、管理用に新しく「受付シート」を作ってA列に受付番号を入れるようにしてみましょう。

さっきのコードの最後にこれを追加するだけです。
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet_uketuke = ss.getSheetByName("受付シート");
const lastRow = sheet_uketuke.getLastRow() +1;
//e.values(フォームの回答)をまるごとシートのB列以降に入れる
sheet_uketuke.getRange(lastRow,2,1,e.values.length).setValues([e.values]);
//A列に今回の受付番号(RecNo)を入れる
sheet_uketuke.getRange(lastRow,1).setValue(RecNo);
フォームを送信すると「受付シート」のA列に受付番号が入りました!

フォームの回答シートのI列にもこのように今回の受付番号が入っています。

今回はプロパティサービスの受付番号を使いましたが、前回の記事の行番号式やミリ秒式でももちろん使えます。
LockServiceでは順番は守れない?受付番号で順序を保証する方法
スプレッドシートにも受付番号を入れることで、問い合わせがあったときでも受付番号で照合することができて便利ですね。
上のコードではフォームの回答=e.valuesをそのままシートに流し込んでいますが、項目の順番がシートと合わない場合は、以下の記事を参考にnamedValuesを使ってみてください。
どっちを使う?onFormSubmit(e)の values と namedValues の違いと使い分け
必ずLockServiceとセットで使おう!
ここで1つ注意です。
同時多発的にフォーム送信されると、前の上書きが終わらないうちに次の処理がプロパティサービスを呼び出してしまい、同じ受付番号が複数できてしまいます。これを避けるために必ずLockServiceとセットで使うようにしましょう。
function myFunction() {
const lock = LockService.getScriptLock();
try {
lock.waitLock(30000);
//プロパティサービスを呼び出す。呼び出されたものは文字列なので数字に直す
const lastId = Number(PropertiesService.getScriptProperties().getProperty("RECEIPT_ID"));
//今回使う受付番号(前回のIDに+1する)
const RecNo = lastId + 1;
//プロパティサービスで値を上書きする
PropertiesService.getScriptProperties().setProperty("RECEIPT_ID",String(RecNo));
//ここから下に処理を書く
} catch (e) {
//エラーのときにやりたい処理をここに書く
} finally {
//エラーがあってもなくても鍵を開放する
lock.releaseLock();
}
}
LockServiceについてはこちら
Googleフォームで同時に大量送信されても踏ん張る!LockServiceで順番制御!try - catch - finally でバトンを繋げ!
まとめ
今回はプロパティサービスを使って
- 行を消してもズレない
- 番号が飛ばない
- 1からスタートする
そんな、実務で使える受付番号システムを作りました。
LockServiceとPropertiesServiceを組み合わせることで、同時送信にも負けない強固なシステムになっています。
今日の成果コード
function form_edited(e) {
const lock = LockService.getScriptLock();
try {
lock.waitLock(30000);
//プロパティサービスを呼び出す。呼び出されたものは文字列なので数字に直す
const lastId = Number(PropertiesService.getScriptProperties().getProperty("RECEIPT_ID"));
//今回使う受付番号(前回のIDに+1する)
const RecNo = lastId + 1;
//プロパティサービスで値を上書きする
PropertiesService.getScriptProperties().setProperty("RECEIPT_ID",String(RecNo));
////ここから下が実際の処理////
////★「フォームの回答」シートに受付番号を入れる
// 回答が書き込まれたシートを直接取得(シート名が変わっても大丈夫)
const sheet = e.range.getSheet();
//フォームの回答に対応するシートの行を取得
const row = e.range.getRow();
//受付番号の列を取得
const column = sheet.getRange("受付番号").getColumn();
//受付番号を書き込む
sheet.getRange(row,column).setValue(RecNo);
////★他のシートにも書き込む
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet_uketuke = ss.getSheetByName("受付シート");
const lastRow = sheet_uketuke.getLastRow() +1;
//e.values(フォームの回答)をまるごとシートのB列以降に入れる
sheet_uketuke.getRange(lastRow,2,1,e.values.length).setValues([e.values]);
//A列に今回の受付番号(RecNo)を入れる
sheet_uketuke.getRange(lastRow,1).setValue(RecNo);
} catch (e) {
//エラーのときにやりたい処理をここに書く
} finally {
//エラーがあってもなくても鍵を開放する
lock.releaseLock();
}
}
これで安心、と思いきや・・・・
今度はフォームの「回答を編集(修正して再送信)」するユーザーが現れました!
今のコードのままだと、新規送信でも「回答を編集」でも同じ「送信」として扱われ、新しい番号が振られてしまいます。
次回は「新規」か「編集(修正)」かを切り分けて処理する方法を考えてみたいと思います。(近日公開予定)

