この記事では、株式会社チックがどのようにドローン練習場の予約システムを構築したかをご紹介します。
具体的には、Googleフォームで「練習場の予約」を受け付け、スプレッドシートとApps Scriptを介してGoogleカレンダーに反映する仕組みです。
現在の仕様では、予約のみを扱い、キャンセル機能は実装していませんが、予約時にイベントIDを発行しており、今後キャンセル機能を追加する際に活用する想定です。
システム概要
- Googleフォームでドローン練習場の予約情報を受け付け
- スプレッドシートに回答が蓄積される
- Apps Script (
onFormSubmit
) でカレンダーに予定を自動登録 - 重複チェックや「全日」対応
- エラー(重複・日付不正など)の場合は予約者へメール通知
- 予約が完了すると「イベントID」を発行し、予約確定メールを送信
キャンセル機能は現状ありませんが、将来的にイベントIDを活用したキャンセル機能を拡張予定です。
準備するもの
- Googleフォーム
– 質問例: メールアドレス、団体名、氏名、電話番号、予約希望日、利用希望時間(「全日」や「09:00-10:00」など)、備考
– 回答をスプレッドシート「予約管理」に記録 - スプレッドシート
– 例: 「予約管理」という名のシートを用意
– 列構成: A=タイムスタンプ, B=メール, C=団体名, D=氏名, E=電話, F=予約日, G=利用希望時間, H=備考, I=ステータス, J=イベントID - Googleカレンダー
– ドローン練習場のスケジュールを可視化するカレンダー
– 「カレンダーID」を取得し、Apps Script側で設定 (権限は編集可能に) - Apps Script
– スプレッドシートと同じファイル(コンテナバウンド)で管理 (推奨)
– 「フォーム送信時」のトリガーを設定 → onFormSubmit関数でカレンダー登録
実装コード (キャンセル機能なし)
以下が、株式会社チックが構築した「予約のみ対応」のApps Scriptコードです。
フォームから送信されるデータを元に、カレンダーに予定を登録します。
カレンダー取得エラーや重複などが起こった場合、ステータス列(I列)に内容を記録し、予約者へ「予約不可メール」を送信します。
成功時は「予約確定」と記録し、イベントIDをJ列に保存し、予約完了メールを送ります。
// ----------------------------
// Googleフォーム送信時 (予約のみ対応)
// ----------------------------
function onFormSubmit(e) {
const responses = e.values;
// A=timestamp(0), B=email(1), C=orgName(2), D=personName(3),
// E=phone(4), F=reservationDate(5), G=timeRaw(6), H=note(7)
const email = responses[1]; // B列
const orgName = responses[2]; // C列
const personName = responses[3]; // D列
const phone = responses[4]; // E列
const reservationDate = responses[5]; // F列
const timeRaw = responses[6]; // G列
const note = responses[7]; // H列
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("予約管理");
const lastRow = sheet.getLastRow();
// カレンダーID を実際のものに変更
const calendarId = "xxxxx@group.calendar.google.com";
const calendar = CalendarApp.getCalendarById(calendarId);
Logger.log("=== onFormSubmit: 予約 ===");
Logger.log("calendar: " + calendar); // デバッグ用
if (!calendar) {
setStatusAndReject(sheet, lastRow, email, "カレンダー取得エラー");
return;
}
// 予約希望日のDate
const dateObj = new Date(reservationDate);
if (isNaN(dateObj.getTime())) {
setStatusAndReject(sheet, lastRow, email, "日付エラー");
return;
}
// 利用希望時間をカンマ区切りで解析
const halfWidth = toHalfWidth(timeRaw || "");
const timeRangeList = halfWidth.split(",").map(s => s.trim()).filter(Boolean);
if (timeRangeList.length === 0) {
setStatusAndReject(sheet, lastRow, email, "利用希望時間未入力");
return;
}
let createdIds = [];
for (let i = 0; i < timeRangeList.length; i++) { const timeRange = timeRangeList[i]; let startDateTime, endDateTime; // "全日" は 0:00~翌0:00 if (timeRange === "全日") { startDateTime = new Date(dateObj); startDateTime.setHours(0, 0, 0, 0); endDateTime = new Date(dateObj); endDateTime.setDate(endDateTime.getDate() + 1); endDateTime.setHours(0, 0, 0, 0); } else { // 09:00-10:00 等 const parsed = parseDateTimeRange(dateObj, timeRange); if (!parsed.startDateTime || !parsed.endDateTime) { setStatusAndReject(sheet, lastRow, email, "時間形式エラー"); return; } startDateTime = parsed.startDateTime; endDateTime = parsed.endDateTime; } // 重複チェック const events = calendar.getEvents(startDateTime, endDateTime); if (events.length > 0) {
setStatusAndReject(sheet, lastRow, email, "重複");
return;
}
// カレンダーに登録
const event = calendar.createEvent("予約あり", startDateTime, endDateTime, {
description:
"団体名: " + orgName +
"\n氏名: " + personName +
"\n電話: " + phone +
"\n利用時間: " + timeRange +
"\n備考:\n" + (note || "")
});
createdIds.push(event.getId());
}
// 成功 → I列=「予約確定」, J列=イベントID
sheet.getRange(lastRow, 9).setValue("予約確定");
sheet.getRange(lastRow,10).setValue(createdIds.join(","));
// 完了メールを送信
sendCompleteMail(email, orgName, personName, phone, dateObj, timeRangeList, note, createdIds);
}
// 失敗時: ステータス+不可メール
function setStatusAndReject(sheet, lastRow, email, errMsg) {
sheet.getRange(lastRow, 9).setValue(errMsg);
sendRejectMail(email, errMsg);
}
// 不可メール
function sendRejectMail(email, reason) {
if (!email) return;
const subject = "【ドローン練習場】予約不可のお知らせ";
const body =
"下記の理由により予約を受け付けられませんでした。\n\n" +
"理由:" + reason + "\n\n" +
"お手数ですが、内容をご確認のうえ再度お試しください。";
MailApp.sendEmail(email, subject, body);
}
// 予約完了メール
function sendCompleteMail(email, orgName, personName, phone, dateObj, timeRangeList, note, eventIds) {
if (!email) return;
const formatted = Utilities.formatDate(dateObj, "Asia/Tokyo", "yyyy/MM/dd");
const timesStr = timeRangeList.join("\n");
const subject = "【ドローン練習場】予約完了のお知らせ";
const body =
personName + " 様(団体名:" + orgName + ")\n\n" +
"以下の内容で予約を受け付けました。\n" +
"予約日:" + formatted + "\n" +
"利用時間:\n" + timesStr + "\n\n" +
"電話番号:" + phone + "\n" +
(note ? "備考:\n" + note + "\n\n" : "\n") +
"【イベントID】\n" + eventIds.join(",") + "\n\n" +
"当日お待ちしております。";
MailApp.sendEmail(email, subject, body);
}
// "09:00-10:00" 等 → 開始/終了解析
function parseDateTimeRange(dateObj, timeRange) {
let delimiter;
if (timeRange.includes("~")) delimiter = "~";
else if (timeRange.includes("-")) delimiter = "-";
else return { startDateTime:null, endDateTime:null };
const [startStr, endStr] = timeRange.split(delimiter).map(s=>s.trim());
if (!startStr || !endStr) {
return { startDateTime:null, endDateTime:null };
}
const [startH, startM] = startStr.split(":");
const [endH, endM] = endStr.split(":");
if (!startH || !startM || !endH || !endM) {
return { startDateTime:null, endDateTime:null };
}
const startDateTime = new Date(dateObj);
startDateTime.setHours(parseInt(startH,10), parseInt(startM,10), 0,0);
const endDateTime = new Date(dateObj);
endDateTime.setHours(parseInt(endH,10), parseInt(endM,10), 0,0);
return { startDateTime, endDateTime };
}
// 全角→半角
function toHalfWidth(str) {
return str.replace(/[!-~]/g, function(s) {
return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
});
}
カレンダーに反映されない場合の確認項目
カレンダーIDの確認
- 「設定と共有」画面の「カレンダーID」を正しくコピペしているか
- 前後に余計な空白や改行が入っていないか
権限
- スクリプト実行アカウントがそのカレンダーを「予定の変更権限以上」で編集できるか
- 「calendar: null」とログに出るなら、ID不正 or 権限不足が疑われる
トリガー設定
- Apps Scriptで「onFormSubmit」×「フォーム送信時」のトリガーを設定しているか
- 初回保存時にカレンダーアクセス許可ダイアログが出るので、承認が必要
列ズレ
- 本コードではB列=メール、C=団体名、D=氏名、E=電話、F=予約日、G=利用時間、H=備考、I=ステータス、J=イベントID
- 1つ質問を追加/削除すると列がズレてしまうので注意
シートI列(ステータス)
- “予約確定” → カレンダー登録成功
- “重複” / “日付エラー” / “カレンダー取得エラー” など → その原因を対処
まとめ
株式会社チックが今回ご紹介したコードは、ドローン練習場の予約をGoogleフォームで受付し、スプレッドシートとGoogleカレンダーへ自動連携する仕組みを実現します。
「予約のみ」の実装ですが、重複チェック、全日対応、予約完了メールなど基本機能は一通り備えています。
いずれキャンセル機能を追加したい際にも、イベントIDを活用できますのでご安心ください。
ぜひこのコードを参考に、ドローン練習場の予約業務を効率化してみてください。