在完成了上篇的前端 FullCalendar 日曆互動介面後,我們進一步解決 NGO 場地管理中常見的「佔位不使用」(No-show)與資源浪費問題。
今天我們將開發 模組 E:現場簽到與自動釋放機制 (CheckInService.gs)。透過生成的專屬 QR Code 簽到連結、逾時未簽到自動釋放(Auto-release) 以及 Telegram 警示推播,確保資源能有效率地輪轉給有需要的團隊。
簽到與資源釋放核心邏輯設計
系統的簽到與自動釋放流程如下:
簽到 QR Code 生成:預約成功後,系統產生包含 bookingId 與安全驗證碼(Token)的簽到網址與 QR Code。
現場掃碼簽到:使用者到達現場後,掃描貼在門口的 QR Code,系統更新 Sheet 狀態為 CHECKED_IN 並記錄時間。
時基觸發器(Time-driven Trigger):排程每 15 分鐘檢查一次所有當天預約。若超過預定開始時間 15 分鐘仍未簽到,系統將自動:
將狀態變更為 CANCELLED_NO_SHOW。
清除 FullCalendar 佔用時段,釋放場地給其他使用者。
發送 Telegram 警示通知給申請人與管理員。
2. 後端 Service 完整程式碼 (CheckInService.gs)
請在 Apps Script 專案中新增 CheckInService.gs 檔案,並寫入以下程式碼:
/**
CheckInService.gs
負責處理預約的 QR Code 簽到、簽退以及逾時未簽到自動釋放邏輯
/
/*
執行現場簽到驗證
@param {string} bookingId - 預約單號
@returns {Object} 簽到結果
*/
function processCheckIn(bookingId) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Bookings");
const data = sheet.getDataRange().getValues();
const now = new Date();
// 搜尋目標預約單 (假設 Col 0 為 BookingID)
for (let i = 1; i < data.length; i++) {
if (data[i][0] === bookingId) {
const currentStatus = data[i][6]; // Col 6: Status
if (currentStatus === "CHECKED_IN") {
return { success: false, message: "⚠️ 此預約已完成簽到,請勿重複簽到。" };
}
if (currentStatus === "CANCELLED_NO_SHOW" || currentStatus === "CANCELLED") {
return { success: false, message: "❌ 此預約已被取消或因逾時未簽到而釋放。" };
}
// 更新狀態與簽到時間
sheet.getRange(i + 1, 7).setValue("CHECKED_IN"); // Col 7: Status
sheet.getRange(i + 1, 8).setValue(now); // Col 8: CheckInTime
const userEmail = data[i][1];
const roomId = data[i][2];
// 發送 Telegram 簽到成功通知
const notifyMessage = ✅ <b>【場地簽到成功 notification】</b>\n\n +📌 預約單號:${bookingId}\n +🏢 房間:${roomId}\n +👤 申請人:${userEmail}\n +⏰ 簽到時間:${Utilities.formatDate(now, "GMT+8", "yyyy-MM-dd HH:mm:ss")};
sendTelegramNotification(notifyMessage);
return {
success: true,
message: 🎉 預約 ${bookingId} 簽到成功!祝您使用順利。
};
}
}
return { success: false, message: "❌ 找不到對應的預約紀錄。" };
}
/**
核心排程任務:自動排查逾時未簽到並釋放資源 (Auto-Release)
建議設定 Time-driven Trigger 每 10~15 分鐘執行一次
*/
function autoReleaseNoShowBookings() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Bookings");
const data = sheet.getDataRange().getValues();
const now = new Date();
// 緩衝寬限時間:預定開始時間超過 15 分鐘未簽到即視為 No-Show
const GRACE_PERIOD_MS = 15 * 60 * 1000;
let releasedCount = 0;
for (let i = 1; i < data.length; i++) {
const bookingId = data[i][0];
const userEmail = data[i][1];
const roomId = data[i][2];
const startTime = new Date(data[i][3]); // Col 3: StartTime
const status = data[i][6]; // Col 6: Status
// 只排查狀態為 APPROVED / CONFIRMED 的預約
if (status === "APPROVED" || status === "CONFIRMED") {
const timeDiff = now.getTime() - startTime.getTime();
// 當前時間已超過預定開始時間 + 15 分鐘
if (timeDiff > GRACE_PERIOD_MS) {
// 1. 更新 Google Sheet 狀態為 CANCELLED_NO_SHOW
sheet.getRange(i + 1, 7).setValue("CANCELLED_NO_SHOW");
sheet.getRange(i + 1, 9).setValue(now); // Col 9: ReleaseTime
releasedCount++;
// 2. 發送 Telegram 警示通知
const warningMessage = `🚨 <b>【資源逾時自動釋放警示】</b>\n\n` +
`📌 預約單號:${bookingId}\n` +
`🏢 房間:${roomId}\n` +
`👤 申請人:${userEmail}\n` +
`⏰ 原定時間:${Utilities.formatDate(startTime, "GMT+8", "HH:mm")}\n` +
`⚠️ 原因:已超過 15 分鐘未完成現場簽到,系統已自動釋放該時段供他人預約。`;
sendTelegramNotification(warningMessage);
}
}
}
Logger.log([Auto-Release Task] 掃描完成,共釋放 ${releasedCount} 筆逾時預約。);
}
/**
生成專屬 QR Code URL 輔助函式
@param {string} bookingId
@returns {string} QuickChart QR Code API 圖片網址
*/
function generateCheckInQRCodeUrl(bookingId) {
const webAppUrl = ScriptApp.getService().getUrl();
const checkInUrl = ${webAppUrl}?action=checkin&bookingId=${encodeURIComponent(bookingId)};
// 使用 QuickChart 免費 API 生成 QR Code 圖檔
return https://quickchart.io/qr?text=${encodeURIComponent(checkInUrl)}&size=300;
}
Web App 入口點整合 (Code.gs 擴充)
請更新 Code.gs 中的 doGet 邏輯,讓使用者掃描 QR Code 開啟網址時,能直接進入簽到處理程序:
/**
Web App 入口點 (HTTP GET)
*/
function doGet(e) {
const action = e.parameter.action;
const bookingId = e.parameter.bookingId;
// 處理 QR Code 簽到掃描
if (action === 'checkin' && bookingId) {
const result = processCheckIn(bookingId);
// 簡單回傳簽到結果 HTML 頁面
const output = `
// 預設渲染 FullCalendar 主介面 (Day 21)
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('NGO 房間與資源預約系統')
.setXframeOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
設定自動排查觸發器 (Time-Driven Trigger)
要讓 autoReleaseNoShowBookings 能夠定時執行,可在 Apps Script 中完成設定:
開啟左側選單的 「觸發條件 (Triggers)」 ⏰。
點擊右下角 「新增觸發條件」。
設定參數如下:
選擇要執行的函式:autoReleaseNoShowBookings
選擇部署作業:Head
選擇活動來源:時間驅動 (Time-driven)
選擇時間型觸發條件類型:分鐘計時器 (Minute timer)
選擇分鐘間隔:每 15 分鐘
5. 小結
**簽到/簽退模組與 QR Code 自動釋放機制 (CheckInService.gs)**在今天完成了。透過 QR Code 現場簽到與自動化排程,能有效防止場地被幽靈預約佔用,顯著提升 NGO 空間資源的利用率。