GASで重複しない受付番号を作る|PropertiesServiceとLockService

GAS Webアプリで受付番号を日付別に自動採番する方法を紹介します。PropertiesService に日ごとの連番を保存し、LockService で採番処理を排他制御して、YYYYMMDD-001 形式の番号を作業依頼シートへ登録します。
第6回までは受付番号列(B列)を空欄で登録していました。今回は、第3回で作成した登録処理に採番を組み込みます。登録処理の基本は第3回、HTMLフォームからGASを呼び出す流れは第5回で説明しています。
シリーズ全体の構成と完成画面は第0回で紹介しています。
1. 今回作るもの
| ファイル | 用途 |
|---|---|
Code.gs | getNextReceiptNumber_() を追加し、saveRequest() を修正する |
index.html、style.html、script.html は第6回のまま変更しません。
2. 完成イメージ
- 登録時に
20260626-001形式の受付番号が発行される - カウンターが未使用の状態で登録に成功すると、番号は
001,002,003と進む - 日付ごとに別のカウンターを使い、その日の初回は
001から始まる - 同時に採番処理が走っても、同じ番号を割り当てない
- 受付番号が生成・保存される
- GASの返却値に受付番号が含まれる(画面表示は第8回で対応)
- 作業依頼シートのB列(受付番号)に記録される
3. 前回までの状態
前回は、顧客マスタから顧客をプルダウンで選択し、件名・依頼内容・担当者を入力して登録できるようにしました。
第7回の starter.zip には、第6回完了時点のファイルを入れています。
4. 使用するファイル
今回使用するファイルは Code.gs のみです。index.html、style.html、script.html は第6回のままです。
5. GASで受付番号を日付別に自動採番する
受付番号は日付別連番とし、次の形式にします。
20260626-001 20260626-002 20260626-003
日付の部分(YYYYMMDD)は登録日によって変わります。ハイフンの後ろの数字は、その日の採番順に 001 から割り当てます。
採番には Script Properties を使います。RECEIPT_NUMBER_20260626 のようなキーで、その日の最終番号を保存します。スクリプトプロパティは同じApps Scriptプロジェクトを使うユーザー間で共有されます。
プロパティはスプレッドシートの行から自動で再計算されません。既存の受付番号がある状態で使い始める場合や、該当プロパティを削除した場合は、既存データと番号が重ならないか先に確認してください。
同時登録時に同じ番号を割り当てないよう、LockService で採番部分を排他制御します。
6. 貼り付けるコード
Code.gs
Code.gs に getNextReceiptNumber_() を追加します。
function getNextReceiptNumber_() {
const lock = LockService.getScriptLock();
try {
lock.waitLock(5000);
} catch (e) {
throw new Error('他の処理が実行中です。しばらくしてから再試行してください。');
}
try {
const props = PropertiesService.getScriptProperties();
const today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyyMMdd');
const key = 'RECEIPT_NUMBER_' + today;
const lastNumberStr = props.getProperty(key);
const nextNumber = lastNumberStr ? parseInt(lastNumberStr, 10) + 1 : 1;
const paddedNumber = String(nextNumber).padStart(3, '0');
const receiptNumber = today + '-' + paddedNumber;
props.setProperty(key, String(nextNumber));
return receiptNumber;
} finally {
lock.releaseLock();
}
}テスト用に、Apps Scriptエディタから直接実行できる公開関数も用意します。
function getNextReceiptNumber() {
const receiptNumber = getNextReceiptNumber_();
Logger.log(receiptNumber);
return receiptNumber;
}このテスト関数を実行するたびに採番カウンターが1つ進みます。番号を発行するだけでシートには登録しないため、動作確認はテスト用プロジェクトで行ってください。
saveRequest() の修正
既存の saveRequest() に、受付番号の採番と保存を追加します。
function saveRequest(data) {
try {
validateRequestData_(data);
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getSheetByName('作業依頼');
if (!sheet) {
return {
success: false,
message: '作業依頼シートが見つかりません。先にsetupAppSheetsを実行してください。',
};
}
const receiptNumber = getNextReceiptNumber_();
const now = new Date();
const id = Utilities.getUuid();
sheet.appendRow([
id,
receiptNumber,
now,
now,
data.customerCode,
data.customerName,
data.title,
data.description,
'未対応',
data.assignee || '',
'',
'',
'',
]);
return {
success: true,
id: id,
receiptNumber: receiptNumber,
message: '受付番号: ' + receiptNumber + ' で登録しました。',
};
} catch (error) {
return {
success: false,
message: error.message,
};
}
}修正点は次の3つです。
getNextReceiptNumber_()を呼び出して受付番号を取得するappendRow()の2列目(受付番号列)にreceiptNumberを設定する- 戻り値に
receiptNumberを追加し、メッセージにも受付番号を含める
7. コードの解説
PropertiesService
PropertiesService.getScriptProperties() は、GASプロジェクトに紐づくキー・バリュー型の保存領域です。各プロパティは文字列として保存され、同じスクリプトを使うユーザー間で共有されます。
シートのセルに採番状態を置かず、日付ごとの最終番号をここで管理します。PropertiesServiceの公式ガイドも参照してください。
今回は、日付ごとのキー(例:RECEIPT_NUMBER_20260626)に「その日の最終番号」を保存します。登録のたびに番号を1つ増やして保存し直します。
LockService
LockService.getScriptLock() は、同じスクリプトプロジェクト内で同時に実行される処理を調整する排他ロックを取得します。コードはロック中にプロパティから前回番号を読み、次の番号を保存します。
この区間を一件ずつ実行するため、同時登録時に同じカウンター値を読みません。ロック解除後に appendRow() を実行するので、シートへ行が追加される順番と番号の順番が前後する場合はあります。LockServiceの公式リファレンスで、ロックの動作も確認できます。
waitLock(5000) は最大5,000ミリ秒、つまり5秒間ロックを待ちます。時間内に取得できなければ例外が発生するため、コードでは再試行を促すメッセージに置き換えています。waitLock()の説明も参照してください。
番号をプロパティへ保存した後、finally ブロックで lock.releaseLock() を呼びます。採番中にエラーが起きた場合もロックを解放し、待機中の別の処理がロックを取得できるようにします。

日付別連番
Utilities.formatDate() で現在日時をスクリプトのタイムゾーンに合わせて yyyyMMdd 形式へ整え、キーの一部に使います。日付ごとに別のキーを使い、その日の番号は初回に 001 から始まります。
スプレッドシート側のタイムゾーンと異なると、画面に表示される登録日時と受付番号の日付がずれることがあります。両方の設定を確認してください。Utilities.formatDate()とSession.getScriptTimeZone()の公式リファレンスに引数の説明があります。
String(nextNumber).padStart(3, '0') は、番号が3桁になるよう先頭を0で埋めます。これは最低3桁に整える処理であり、999の次は 1000 になります。3桁を超えない仕様にする場合は、上限に達したときの処理を別途決める必要があります。
この採番処理は、スプレッドシートの既存行を調べず、スクリプトプロパティに保存した値から次の番号を決めます。
saveRequest() はシートへの書き込みより先に番号を保存します。その後 appendRow() が失敗すると、次回はさらに大きい番号が発行されます。番号が連続することより、再利用による重複を避ける設計です。
戻り値のメッセージ
message に受付番号を含めることで、GASから受付番号付きの結果を返せます。
第6回までの script.html は成功時に返却値の message を使わず、画面には固定文言「登録しました。」を表示します。受付番号は作業依頼シートのB列に保存されます。第8回でクライアント側を修正し、返却された受付番号を画面に表示します。
8. 導入手順
- 第6回までのファイルを用意する
Code.gsにgetNextReceiptNumber_()を追加するsaveRequest()を受付番号対応に修正する- 保存する
setupAppSheets()を実行済みでない場合は実行する- 採番だけを確認する場合はテスト用プロジェクトで
getNextReceiptNumber()を実行する(実行ごとに番号を一つ消費します) - Webアプリとして画面を開き、登録する
- 作業依頼シートのB列に受付番号が記録されていることを確認する
- 第8回で、登録完了メッセージに受付番号を表示します
9. 動作確認
Apps Scriptエディタで getNextReceiptNumber() を実行すると、Logger.log() が実行ログへ発行した番号を記録します。この関数は採番カウンターを進めるだけで、シートには行を追加しません。番号の採番とシート登録を通して確認するには、テスト用データでWebフォームから登録してください。Logger.log()の公式リファレンスに、ログの記録方法が載っています。

実行ログ用のテスト関数も採番カウンターを進めるため、ログ画像とシート画像の受付番号は連続していません。テスト実行後の番号がそのまま次の登録に使われます。
Webアプリ画面から実際に登録すると、作業依頼シートのB列に受付番号が記録されます。登録完了メッセージへの受付番号表示は第8回で実装します。

受付番号は、作業依頼シートのB列に入ります。

10. よくあるエラー
他の処理が実行中ですと表示される
他の実行中の処理がロックを保持していると、最大5秒間待っても取得できず、このエラーになります。少し待ってから再試行してください。この5秒はロック取得を待つ時間で、登録処理全体の実行時間ではありません。
日付が変わっても番号がリセットされない
Utilities.formatDate() の第2引数に Session.getScriptTimeZone() を指定していることを確認してください。Apps Scriptプロジェクトとスプレッドシートにはそれぞれタイムゾーン設定があるため、意図した日付にならない場合は両方を確認します。
受付番号がB列に保存されない
saveRequest() 内で appendRow() の2番目の要素に receiptNumber が設定されているか確認してください。
11. starter.zipの案内
starter.zip には、第6回完了時点のファイルを入れています。
Code.gsindex.htmlstyle.htmlscript.htmlREADME.txt
この時点では受付番号の自動採番は実装されていません。
12. complete.zipの案内
complete.zip には、Code.gs、index.html、style.html、script.html、README.txt を入れています。
Code.gs に getNextReceiptNumber_() が追加され、saveRequest() が受付番号対応になっています。
13. 次回追加する機能
次回は、登録済みの作業依頼を一覧表示する画面を作ります。getValues() でまとめて取得し、HTMLテーブルで表示します。



Your Message