前日発注を自動化!GASで給食弁当の注文集計と仕入れ連絡を時短
給食弁当業では、電話・FAX・メールの注文を集計し、仕入れ先へ前日発注する作業が毎日発生します。GoogleフォームとGoogle Apps Script(GAS)を使えば、注文集計、発注メール、キャンセル待ち管理を一つの流れにまとめられます。小規模事業者が低コストで始める手順を紹介します。
🎯 この記事のポイント
- 注文受付から集計までを自動化: Googleフォームの回答をGASで集計し、商品別・日付別の数量を確認できます。
- 前日発注メールを自動送信: 集計結果を仕入れ先へ指定時刻に送信し、送信履歴も残せます。
- キャンセル待ちを管理: 待機者への連絡と、繰り上がり後の確定処理を仕組み化できます。
- 追加費用を抑えて開始: Googleアカウントがあれば、まず無料で試せます。
- 誤送信を防ぐ: 実行時間制限とテスト手順を確認し、安全に運用します。
編集長の見解: 給食弁当の注文管理は、売上と仕入れを支える重要な業務です。自動化すれば、転記ミスや確認漏れを減らせます。ただし、最初から全業務を置き換える必要はありません。まずは1日分の集計と発注メールから試し、現場に合う範囲で広げるのが現実的です。
🍱 給食弁当業者の注文管理の課題
給食弁当の現場では、注文管理に手間がかかります。特に前日発注は、正確さと速さの両方が必要です。ここでは、よくある課題を整理します。
複数経路に分散する注文の集約コスト
注文は、電話・FAX・メール・口頭などに分かれがちです。法人向けの給食受託や学校給食を扱う事業者では、複数の受付方法を併用することもあります。
| 注文経路 | 集計時の問題点 | 頻度 |
|---|---|---|
| 電話 | メモを台帳へ転記する必要がある | 毎日 |
| FAX | 注文書の内容を手入力する必要がある | 毎日 |
| メール | 件名や本文の形式がそろわない | 毎日 |
| 口頭(対面) | 記憶に頼るため確認が必要になる | 不定期 |
複数経路の注文を締切後に手作業でまとめると、担当者の負担が増えます。集計が昼食準備と重なる場合は、残業につながることもあります。
集計ミスが引き起こす仕入れ過多と不足
手作業では、数量の読み間違いや転記漏れが起こります。商品名の取り違えも、仕入れ数を誤る原因です。
失敗パターン: FAX注文の読み取りを誤ると、弁当を過剰に発注して廃棄ロスが発生します。反対に、発注数が足りなければ、提供遅延や代替メニューの案内が必要になる場合があります。自動化では、入力項目と商品名をそろえ、送信前に確認できる仕組みを用意します。
キャンセル待ち顧客への連絡漏れ
人気メニューでは、受付開始直後に注文枠が埋まることがあります。キャンセル待ちの対応では、次の作業が必要です。
- キャンセル発生の確認
- 待機者の順番管理
- 繰り上がり連絡
- 確定状況の更新
担当者だけが管理していると、休みの日に連絡漏れが起きやすくなります。リストと状態を共有すれば、対応状況を確認しやすくなります。
前日発注の締切時間との戦い
給食弁当の仕入れは、前日発注が基本です。注文締切から仕入れ先の締切までに、集計と確認を終えなければなりません。
- 注文締切: 前日12時
- 集計作業: 12時〜14時
- 仕入れ先への発注締切: 前日15時
注文数が多い日や、直前に変更が入った日は、作業時間がさらに短くなります。次は、GASを使った自動化の全体像を確認します。
🔧 GASで作る自動化の全体像
Google Apps Script(GAS)は、Googleのサービスを自動化する仕組みです。JavaScriptを使って、Googleフォーム、スプレッドシート、Gmailなどを連携できます。
自動化の4つの柱
本記事では、次の4つを構築します。
| 柱 | 処理内容 | 頻度 |
|---|---|---|
| ①注文受付 | Googleフォームで注文を収集し、スプレッドシートへ保存 | 随時 |
| ②注文集計 | 商品別・日付別の数量を集計 | 毎日 |
| ③前日発注メール | 集計結果を仕入れ先へ送信 | 毎日 |
| ④キャンセル待ち管理 | 待機者の登録と連絡状況を管理 | 随時 |
システム構成のイメージ
[顧客] → Googleフォーム → スプレッドシート → GASで集計
↓
[仕入れ先] ← 発注メール ← 集計結果を本文に整形
↓
[キャンセル待ち顧客] ← 通知メール ← 待機リストを確認
必要なもの
| 必要なもの | 内容 | コスト |
|---|---|---|
| Googleアカウント | Gmail、スプレッドシート、フォームを利用 | 無料 |
| Googleフォーム | 注文受付用フォーム | 無料 |
| Googleスプレッドシート | 注文データの保存・集計 | 無料 |
| GAS(Google Apps Script) | 自動処理のスクリプト | 無料枠あり |
| 仕入れ先のメールアドレス | 発注連絡の送信先 | - |
ポイント: 個人向けGoogleアカウントでは、GASからのメール送信に1日100通までという上限があります。前日発注メールを数通送る運用なら、まず無料枠で試せます。送信数が多い場合は、Google Workspaceの利用も検討してください。
処理の流れ
| 時刻 | 処理 | 実行方法 |
|---|---|---|
| 随時 | 顧客がGoogleフォームから注文 | 顧客が操作 |
| 前日12:00 | 注文締切 | フォームの受付を停止 |
| 前日12:05 | 注文データを集計 | 時間主導型トリガー |
| 前日12:10 | 発注メールを送信 | 時間主導型トリガー |
| 随時 | キャンセル待ちを確認 | 編集または定期実行 |
なぜGASなのか
注文管理の自動化には、複数の選択肢があります。
| 方法 | 初期費用 | 月額費用 | 難易度 | 編集部の評価 |
|---|---|---|---|---|
| GAS+Googleフォーム | 無料 | 無料枠あり | 中 | 小規模事業者の試行に適する |
| 業務用受注管理システム | 数万円〜 | 数千円〜 | 低 | 操作しやすいが比較が必要 |
| カスタム開発 | 数十万円〜 | 保守費用 | 高 | 独自要件が多い事業者向け |
| Excel VBA | 無料 | 無料 | 中 | 利用環境が限定される |
GASは、初期費用を抑えてクラウド上で動かせます。まずは注文集計だけを自動化し、運用に慣れてから発注メールへ進む方法もあります。次は、フォームとスプレッドシートを準備します。
📝 ステップ1: Googleフォームとスプレッドシートの準備
ここでは、注文フォームの項目設計と、回答を保存するシートを準備します。フォームの項目は、後のコードが参照する列と一致させます。
注文フォームの項目設計
| 項目名 | フォームの種類 | 設定例 |
|---|---|---|
| お名前 | 記述式 | 山田太郎 |
| 会社名(任意) | 記述式 | 株式会社サンプル |
| メールアドレス | 記述式 | taro@example.com |
| 受け取り日 | 日付選択 | 2026年5月10日 |
| 商品名 | プルダウン | 日替わり弁当(税込600円) |
| 数量 | プルダウン | 1〜50食 |
| 受け取り時間 | プルダウン | 11:30 / 12:00 / 12:30 |
| 備考 | 記述式 | アレルギー対応希望など |
| キャンセル待ち希望 | ラジオボタン | はい / いいえ |
フォーム作成のポイント: 商品名と数量はプルダウンにします。自由記述では「のり弁」と「のり弁当」のような表記ゆれが起こり、集計が分かれるためです。
回答をスプレッドシートに保存する設定
- Googleフォームの編集画面を開く
- 「回答」タブをクリック
- スプレッドシートのアイコンをクリック
- 「新しいスプレッドシートを作成」を選択
- シート名を入力して作成
フォームの回答は、1行ずつスプレッドシートへ追加されます。
集計用シートとキャンセル待ち用シートの作成
| シート名 | 用途 |
|---|---|
| フォームの回答 1 | フォームの回答を保存 |
| 集計 | GASが集計結果を出力 |
| キャンセル待ち | 待機者を管理 |
| 送信ログ | メール送信履歴を記録 |
スプレッドシートの列構成
| 列 | 項目名 | 例 |
|---|---|---|
| A | タイムスタンプ | 2026/05/09 9:00:00 |
| B | お名前 | 山田太郎 |
| C | 会社名(任意) | 株式会社サンプル |
| D | メールアドレス | taro@example.com |
| E | 受け取り日 | 2026/05/10 |
| F | 商品名 | 日替わり弁当 |
| G | 数量 | 3 |
| H | 受け取り時間 | 12:00 |
| I | 備考 | - |
| J | キャンセル待ち希望 | はい / いいえ |
列を後から追加・削除すると、コードの参照位置とずれる場合があります。運用前に項目を確定してください。次は、注文を集計するコードを作成します。
🤖 ステップ2: GASで注文を自動集計するスクリプト
ここでは、注文を商品別・日付別に集計します。列番号は、前のセクションで示した構成を前提にしています。
GASエディタの開き方
- スプレッドシートを開く
- 「拡張機能」→「Apps Script」をクリック
- GASエディタでコードを貼り付ける
注文データを読み込む基本コード
function getOrderData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('フォームの回答 1');
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return [];
}
const lastCol = sheet.getLastColumn();
const dataRange = sheet.getRange(2, 1, lastRow - 1, lastCol);
return dataRange.getValues();
}
getLastRow()とgetLastColumn()で、データ範囲を動的に取得します。
商品ごとの数量を集計するコード
function aggregateOrders(targetDate) {
const orders = getOrderData();
const summary = {};
orders.forEach(function(order) {
const orderDate = order[4];
const productName = order[5];
const quantity = Number(order[6]);
if (!(orderDate instanceof Date)) {
return;
}
const dateStr = Utilities.formatDate(orderDate, 'Asia/Tokyo', 'yyyy-MM-dd');
if (dateStr !== targetDate) {
return;
}
if (summary[productName]) {
summary[productName] += quantity;
} else {
summary[productName] = quantity;
}
});
return summary;
}
この関数は、指定した日付の注文だけを商品別に集計します。
| 商品名 | 数量 |
|---|---|
| 日替わり弁当 | 45 |
| のり弁当 | 30 |
| カツ弁当 | 22 |
集計結果をシートへ出力するコード
function writeSummaryToSheet(targetDate) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const summarySheet = ss.getSheetByName('集計');
summarySheet.clearContents();
summarySheet.getRange(1, 1, 1, 3).setValues([
['商品名', '数量', '受け取り日']
]);
const summary = aggregateOrders(targetDate);
const rows = Object.keys(summary).map(function(productName) {
return [productName, summary[productName], targetDate];
});
if (rows.length > 0) {
summarySheet.getRange(2, 1, rows.length, 3).setValues(rows);
}
const totalQuantity = Object.values(summary).reduce(function(a, b) {
return a + b;
}, 0);
const totalRow = rows.length + 2;
summarySheet.getRange(totalRow, 1, 1, 3).setValues([
['合計', totalQuantity, targetDate]
]);
}
集計対象日を判断するコード
function getTargetDate() {
const now = new Date();
const deadlineHour = 12;
const deadlineMinute = 0;
const currentHour = now.getHours();
const currentMinute = now.getMinutes();
const targetDate = new Date(now);
if (
currentHour > deadlineHour ||
(currentHour === deadlineHour && currentMinute >= deadlineMinute)
) {
targetDate.setDate(targetDate.getDate() + 1);
}
return Utilities.formatDate(targetDate, 'Asia/Tokyo', 'yyyy-MM-dd');
}
例えば、5月9日11時に実行すると5月9日、13時に実行すると5月10日を返します。実際の締切時刻に合わせて、コードとトリガーの設定をそろえてください。
編集長の見解: 自動化の要は、集計対象日を明確にすることです。締切時刻と対象日を先に決めておけば、担当者が毎回判断する必要がありません。導入時は、実際の日付を使ったテストで結果を確認してください。
メイン関数とトリガーの設定
function main() {
const targetDate = getTargetDate();
writeSummaryToSheet(targetDate);
sendOrderEmail(targetDate);
}
トリガーは、次のように設定します。
| 設定項目 | 値 |
|---|---|
| 実行する関数 | main |
| 実行するデプロイ | Head |
| イベントのソース | 時間主導型 |
| 時間ベースのトリガー | 日ベースのタイマー |
| 時刻を選択 | 午後12時〜1時 |
トリガーの実行時刻には幅があります。締切が12時の場合は、12時05分以降に実行される設定を検討してください。次は、集計結果を発注メールに変換します。
📧 ステップ3: 仕入れ先への前日発注メール自動送信
集計結果をメール本文に整え、仕入れ先へ送信します。本番運用の前に、自分のメールアドレスでテストしてください。
集計結果をメール本文に整形するコード
function formatOrderEmailBody(targetDate, summary) {
const dateParts = targetDate.split('-');
const dateStr =
dateParts[0] + '年' +
Number(dateParts[1]) + '月' +
Number(dateParts[2]) + '日';
let body = '';
body += 'お世話になっております。\n\n';
body += dateStr + ' の弁当発注数量をご連絡いたします。\n\n';
body += '【発注数量】\n';
body += '-----------------------------\n';
for (const productName in summary) {
body += productName + ': ' + summary[productName] + '食\n';
}
body += '-----------------------------\n';
body += '合計: ' +
Object.values(summary).reduce(function(a, b) {
return a + b;
}, 0) + '食\n\n';
body += 'ご確認のほど、よろしくお願いいたします。\n';
body += '株式会社サンプル給食\n';
body += '担当: 山田太郎\n';
body += 'TEL: 03-1234-5678\n';
return body;
}
仕入れ先へ自動送信するコード
function sendOrderEmail(targetDate) {
const summary = aggregateOrders(targetDate);
if (Object.keys(summary).length === 0) {
Logger.log('注文データがありません。メールを送信しません。');
return;
}
const body = formatOrderEmailBody(targetDate, summary);
const recipient = 'supplier@example.com';
const subject = '【発注】' + targetDate + ' の弁当発注数量';
MailApp.sendEmail({
to: recipient,
subject: subject,
body: body
});
writeSendLog(targetDate, recipient, subject);
}
複数の仕入れ先へ送る場合
function sendOrderEmailBySupplier(targetDate, summary) {
const supplierConfig = {
'supplier1@example.com': ['日替わり弁当', 'のり弁当'],
'supplier2@example.com': ['カツ弁当']
};
for (const supplierEmail in supplierConfig) {
const products = supplierConfig[supplierEmail];
const supplierSummary = {};
products.forEach(function(product) {
if (summary[product]) {
supplierSummary[product] = summary[product];
}
});
if (Object.keys(supplierSummary).length === 0) {
continue;
}
const body = formatOrderEmailBody(targetDate, supplierSummary);
MailApp.sendEmail({
to: supplierEmail,
subject: '【発注】' + targetDate + ' の弁当発注数量',
body: body
});
}
}
送信履歴を記録するコード
function writeSendLog(targetDate, recipient, subject) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const logSheet = ss.getSheetByName('送信ログ');
if (logSheet.getLastRow() === 0) {
logSheet.getRange(1, 1, 1, 5).setValues([
['送信日時', '対象日付', '送信先', '件名', 'ステータス']
]);
}
const lastRow = logSheet.getLastRow() + 1;
logSheet.getRange(lastRow, 1, 1, 5).setValues([
[new Date(), targetDate, recipient, subject, '送信済み']
]);
}
メール送信のテスト方法
- 本番用スプレッドシートをコピーする
- 送信先を自分のメールアドレスに変更する
- テストデータをフォームから入力する
main()関数を手動実行する- 集計結果と受信メールを確認する
重大な警告: テストなしで本番実行すると、誤った数量のメールを送るおそれがあります。フォームの列、商品名、数量、送信先、集計結果を順番に確認してください。本番トリガーは、テスト完了後に有効化します。
次は、キャンセル待ちの登録と連絡を自動化します。
⏳ ステップ4: キャンセル待ちリストの自動管理
キャンセルが発生したときに、待機者へ連絡する仕組みを作ります。待機順と状態をシートで管理すると、対応状況を共有できます。
キャンセル待ちリストの設計
| 列 | 項目名 | 例 |
|---|---|---|
| A | 登録日時 | 2026/05/09 9:30:00 |
| B | お名前 | 佐藤花子 |
| C | メールアドレス | hanako@example.com |
| D | 商品名 | 日替わり弁当 |
| E | 受け取り日 | 2026/05/10 |
| F | 数量 | 2 |
| G | 状態 | 待機中 / 連絡済み / 確定 |
| H | 連絡日時 | 2026/05/09 15:00:00 |
| I | 備考 | - |
キャンセル待ち登録のコード
function registerCancellationWaitlist() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const formSheet = ss.getSheetByName('フォームの回答 1');
const waitlistSheet = ss.getSheetByName('キャンセル待ち');
const lastRow = formSheet.getLastRow();
if (lastRow < 2) {
return;
}
const values = formSheet
.getRange(2, 1, lastRow - 1, formSheet.getLastColumn())
.getValues();
const waitlistLastRow = waitlistSheet.getLastRow();
const existingEmails = [];
if (waitlistLastRow >= 2) {
const waitlistData = waitlistSheet
.getRange(2, 3, waitlistLastRow - 1, 1)
.getValues();
waitlistData.forEach(function(row) {
existingEmails.push(row[0]);
});
}
values.forEach(function(order) {
const waitlistFlag = order[9];
const email = order[3];
const productName = order[5];
const orderDate = order[4];
const quantity = order[6];
const name = order[1];
if (
waitlistFlag === 'はい' &&
existingEmails.indexOf(email) === -1
) {
const newRow = waitlistSheet.getLastRow() + 1;
waitlistSheet.getRange(newRow, 1, 1, 7).setValues([
[new Date(), name, email, productName, orderDate, quantity, '待機中']
]);
existingEmails.push(email);
}
});
}
キャンセル発生時の通知コード
function notifyCancellationWaitlist(
productName,
targetDate,
cancelledQuantity
) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const waitlistSheet = ss.getSheetByName('キャンセル待ち');
const lastRow = waitlistSheet.getLastRow();
if (lastRow < 2) {
Logger.log('キャンセル待ちリストが空です。');
return;
}
const values = waitlistSheet
.getRange(2, 1, lastRow - 1, 9)
.getValues();
let remainingQuantity = cancelledQuantity;
values.forEach(function(row, index) {
if (remainingQuantity <= 0) {
return;
}
const status = row[6];
const rowProduct = row[3];
const rowDate = row[4];
const rowEmail = row[2];
const rowName = row[1];
const rowQuantity = Number(row[5]);
const sameDate =
rowDate instanceof Date &&
Utilities.formatDate(rowDate, 'Asia/Tokyo', 'yyyy-MM-dd') ===
targetDate;
if (
status !== '待機中' ||
rowProduct !== productName ||
!sameDate
) {
return;
}
const subject =
'【キャンセル待ち繰り上げのご案内】' +
productName + ' ' + targetDate;
const body =
rowName + ' 様\n\n' +
'お世話になっております。株式会社サンプル給食です。\n\n' +
'ご希望の「' + productName + '」(' + targetDate + ')に\n' +
'キャンセルが発生しました。\n\n' +
'ご注文を確定する場合は、以下のフォームからお申し込みください。\n' +
'https://forms.gle/xxxxxxxxxx\n\n' +
'24時間以内にご返信がない場合は、次の待機者へ案内します。\n\n' +
'よろしくお願いいたします。\n' +
'株式会社サンプル給食\n' +
'担当: 山田太郎\n';
MailApp.sendEmail({
to: rowEmail,
subject: subject,
body: body
});
const rowNumber = index + 2;
waitlistSheet.getRange(rowNumber, 7, 1, 2).setValues([
['連絡済み', new Date()]
]);
remainingQuantity -= rowQuantity;
});
}
繰り上がり確定処理
function confirmWaitlistOrder() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const formSheet = ss.getSheetByName('フォームの回答 1');
const waitlistSheet = ss.getSheetByName('キャンセル待ち');
const lastRow = formSheet.getLastRow();
if (lastRow < 2) {
return;
}
const lastOrder = formSheet
.getRange(lastRow, 1, 1, formSheet.getLastColumn())
.getValues()[0];
const email = lastOrder[3];
const productName = lastOrder[5];
const orderDate = lastOrder[4];
const waitlistLastRow = waitlistSheet.getLastRow();
if (waitlistLastRow < 2) {
return;
}
const waitlistData = waitlistSheet
.getRange(2, 1, waitlistLastRow - 1, 9)
.getValues();
waitlistData.forEach(function(row, index) {
const status = row[6];
const rowEmail = row[2];
const rowProduct = row[3];
const rowDate = row[4];
const sameDate =
rowDate instanceof Date &&
orderDate instanceof Date &&
Utilities.formatDate(rowDate, 'Asia/Tokyo', 'yyyy-MM-dd') ===
Utilities.formatDate(orderDate, 'Asia/Tokyo', 'yyyy-MM-dd');
if (
status === '連絡済み' &&
rowEmail === email &&
rowProduct === productName &&
sameDate
) {
const rowNumber = index + 2;
waitlistSheet.getRange(rowNumber, 7).setValue('確定');
waitlistSheet.getRange(rowNumber, 9).setValue('注文フォームから確定');
}
});
}
キャンセル待ち管理のトリガー
| トリガー | 実行関数 | タイミング |
|---|---|---|
| 時間主導型 | registerCancellationWaitlist | 1時間ごと |
| フォーム送信時 | confirmWaitlistOrder | フォーム送信時 |
運用のポイント: キャンセル発生時にすぐ通知したい場合は、管理シートの編集を起点にします。キャンセルの入力者、商品名、数量、受け取り日を決めた列にそろえると、通知処理を安定させやすくなります。
次は、導入後に起こりやすい問題と対策を確認します。
⚠️ 編集部の警告: 失敗しがちな落とし穴
GASは便利ですが、設定を誤るとトラブルにつながります。導入前に、次の注意点を確認してください。
落とし穴1: GASの実行時間制限(6分)
GASには、1回の実行につき6分間という時間制限があります。大量データを一度に処理すると、制限を超えて終了する場合があります。
| データ量 | 処理時間の目安 | 6分以内に収まるか |
|---|---|---|
| 100件の注文 | 数秒 | ✅ おおむね問題なし |
| 1,000件の注文 | 数十秒 | ✅ 構成による |
| 10,000件の注文 | 数分 | ⚠️ 要検討 |
| 100,000件の注文 | 数十分 | ❌ 分割が必要 |
対策は次のとおりです。
- 注文データを月単位で分ける
- 500行などの単位で処理する
- 過去データを定期的に保存する
function processOrdersInBatches() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('フォームの回答 1');
const lastRow = sheet.getLastRow();
const batchSize = 500;
for (let startRow = 2; startRow <= lastRow; startRow += batchSize) {
const endRow = Math.min(
startRow + batchSize - 1,
lastRow
);
const batchData = sheet
.getRange(
startRow,
1,
endRow - startRow + 1,
sheet.getLastColumn()
)
.getValues();
processBatch(batchData);
}
}
落とし穴2: メール送信の誤送信
自動メールは、設定を誤ると誤った数量を送るおそれがあります。
- テストデータが本番に残っている
- 商品名の表記がそろっていない
- フォーム変更後に列番号を修正していない
- 送信先を確認していない
対策: 本番用シートをコピーしてテスト環境を作り、送信先を自分のメールアドレスに変更してください。数量、送信先、件名、送信ログを確認してから、本番トリガーを有効にします。
手動実行時に確認画面を出す場合は、次のコードを使えます。
function mainWithConfirmation() {
const ui = SpreadsheetApp.getUi();
const response = ui.alert(
'発注メール送信確認',
'集計結果を仕入れ先へ送信します。よろしいですか?',
ui.ButtonSet.YES_NO
);
if (response === ui.Button.YES) {
main();
} else {
Logger.log('送信をキャンセルしました。');
}
}
落とし穴3: フォームの締切と実行時刻のずれ
フォームの受付停止とGASの実行時刻がずれると、締切前後の注文を誤って扱う場合があります。
| 状況 | 問題 |
|---|---|
| 受付停止前に回答が入る | 集計対象に含まれる |
| 受付停止を忘れる | 締切後の注文が入る |
| 締切と実行が同時刻 | 対象範囲が不明確になる |
フォームを自動で停止・再開する例です。
function closeOrderForm() {
const form = FormApp.openById('フォームIDをここに入力');
form.setAcceptingResponses(false);
}
function openOrderForm() {
const form = FormApp.openById('フォームIDをここに入力');
form.setAcceptingResponses(true);
}
トリガーは、次の順で設定します。
| トリガー | 実行関数 | 時刻 |
|---|---|---|
| 日ベースのタイマー | closeOrderForm | 12:00 |
| 日ベースのタイマー | main | 12:05 |
| 日ベースのタイマー | openOrderForm | 12:10 |
落とし穴4: メールの重複送信
複数のトリガーや手動実行が重なると、同じメールを複数回送ることがあります。送信ログで対象日付を確認し、送信済みなら処理を止めます。
function isAlreadySent(targetDate) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const logSheet = ss.getSheetByName('送信ログ');
const lastRow = logSheet.getLastRow();
if (lastRow < 2) {
return false;
}
const logData = logSheet
.getRange(2, 2, lastRow - 1, 1)
.getValues();
return logData.some(function(row) {
return row[0] === targetDate;
});
}
function sendOrderEmailWithDupCheck(targetDate) {
if (isAlreadySent(targetDate)) {
Logger.log(
'対象日付 ' + targetDate + ' のメールは送信済みです。'
);
return;
}
sendOrderEmail(targetDate);
}
次は、料金や運用に関する質問に答えます。
❓ よくある質問
Q1. GASの利用料金はかかる?
GASは、個人向けGoogleアカウントでもGoogle Workspaceでも利用できます。メール送信数などに上限があるため、事業規模に合わせて確認してください。
| プラン | メール送信上限(1日) | 料金 |
|---|---|---|
| 個人向けアカウント | 100通 | 無料 |
| Google Workspace Business Starter | 1,500通 | 月額 ¥690 / ユーザー |
| Google Workspace Business Plus | 1,500通 | 月額 ¥1,380 / ユーザー |
給食弁当の発注メールやキャンセル待ち通知が少数であれば、無料枠から始められます。Google Workspaceを利用する場合も、上記の月額費用は中小規模の事業者が検討しやすい水準です。料金や上限は変更される場合があるため、契約前に公式情報を確認してください。
GASには、次のような制限もあります。
- 1日のトリガー総実行時間: 90分(個人向けアカウント)
- URL取得サービスの1日あたりの取得量: 20,000回
Q2. 複数の仕入れ先に分けて送信するには?
仕入れ先ごとに商品を分ける場合は、商品名と送信先の対応表をコードで管理します。
const supplierConfig = {
'supplier1@example.com': ['日替わり弁当', 'のり弁当'],
'supplier2@example.com': ['カツ弁当', '唐揚げ弁当']
};
別の方法として、仕入れ先ごとに集計シートを分ける方法もあります。
| 方法 | メリット | デメリット |
|---|---|---|
| 商品名で対応付け | 設定を一覧で管理できる | 商品変更時に修正が必要 |
| 集計シートを分ける | 画面上で確認しやすい | シート管理が増える |
編集部では、商品数が少ない場合は商品名での対応付けを推奨します。
Q3. キャンセル待ちの自動通知を止めたい場合は?
一時停止には、次の方法があります。
方法1: トリガーを削除する
- GASエディタのトリガー画面を開く
- 対象トリガーのメニューを開く
- 「トリガーを削除」を選択する
方法2: 設定シートで制御する
function isWaitlistNotificationEnabled() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const configSheet = ss.getSheetByName('設定');
return configSheet.getRange(1, 2).getValue() === 'ON';
}
通知関数の冒頭で、この関数を呼び出して判定します。
Q4. GASのコードがエラーになった場合、どうすればよい?
次の手順で確認します。
- GASエディタからテスト実行する
- 実行履歴でエラー行を確認する
Logger.log()の内容を確認する- シート名、列番号、メールアドレスを見直す
| よくあるエラー | 原因 | 対処法 |
|---|---|---|
Cannot read property 'getRange' of null | シート名が違う | シート名を確認する |
Exception: Invalid email | メール形式が違う | 送信先を確認する |
Service invoked too many times | 実行回数の上限 | 実行間隔を調整する |
Q5. スマートフォンからでも注文を受け付けられる?
Googleフォームは、スマートフォンから回答できます。
- フォームのURLをLINEやメールで案内する
- QRコードを店頭に掲示する
- 回答後の確認メッセージを設定する
Q6. 注文データのバックアップはどうすればよい?
スプレッドシートはGoogleドライブに保存されます。誤削除や設定変更に備え、定期的にコピーを保存してください。
| バックアップ方法 | 頻度 | 手間 |
|---|---|---|
| スプレッドシートをコピー | 週次 | 数クリック |
| バージョン履歴を確認 | 必要時 | 少ない |
| CSVで書き出す | 月次 | 数分 |
Q7. 従業員がGASを操作できない場合でも大丈夫?
コードを直接操作しなくても、構築後は注文集計やメール送信を自動化できます。従業員が行う作業は、次のとおりです。
| 作業 | 頻度 | 担当者 |
|---|---|---|
| フォームURLを顧客に案内 | 導入時 | 管理者 |
| 注文状況を確認 | 随時 | 担当者 |
| キャンセル処理 | 随時 | 管理者 |
🔗 出典・参考情報
本記事で紹介した設定方法は、次の公式ドキュメントを参照してください。
- Google Apps Script 公式ドキュメント: https://developers.google.com/apps-script
- Googleフォーム ヘルプセンター: https://support.google.com/docs/answer/6281888
- GASでのメール送信(MailApp)リファレンス: https://developers.google.com/apps-script/reference/mail/mail-app
- GASでのトリガー設定ガイド: https://developers.google.com/apps-script/guides/triggers/installable
- GASでのスプレッドシート操作リファレンス: https://developers.google.com/apps-script/reference/spreadsheet
📝 まとめ
給食弁当業の注文管理は、GASを使うと段階的に自動化できます。最初からすべてを任せるのではなく、集計、発注メール、キャンセル待ちの順に試すと、現場で検証しやすくなります。
自動化で期待できる変化
| 業務 | 導入前 | 導入後の目安 |
|---|---|---|
| 注文集計 | 1日30〜60分 | 確認中心 |
| 発注連絡 | 1日10〜15分 | 内容確認中心 |
| キャンセル待ち管理 | 1日15〜30分 | 状況確認中心 |
| 集計ミス | 手作業に依存 | 入力とコードの確認が重要 |
削減できる時間は、注文数や運用方法で変わります。導入後も、送信ログと集計結果を確認してください。
導入のステップ
- Googleフォームとスプレッドシートを準備(約30分)
- GASコードを実装(約60分)
- テスト環境で動作確認(約30分)
- 本番トリガーを設定
- 送信ログを定期的に確認
編集長の最終見解: 自動化の目的は、人員を減らすことではありません。注文確認や顧客対応など、人が判断すべき仕事に時間を戻すことです。GASは、給食弁当業のような小規模事業者が、低コストで業務改善を始める選択肢になります。まずは集計だけを自動化し、確認しながら範囲を広げてください。
Mira / AI経営ラボ 編集長