在完成了上篇的前端 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 檔案,並寫入以下程式碼:
/**
/**
// 搜尋目標預約單 (假設 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: "❌ 找不到對應的預約紀錄。" };
}
/**
// 緩衝寬限時間:預定開始時間超過 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} 筆逾時預約。);
}
/**
${webAppUrl}?action=checkin&bookingId=${encodeURIComponent(bookingId)};// 使用 QuickChart 免費 API 生成 QR Code 圖檔
return https://quickchart.io/qr?text=${encodeURIComponent(checkInUrl)}&size=300;
}
/**
// 處理 QR Code 簽到掃描
if (action === 'checkin' && bookingId) {
const result = processCheckIn(bookingId);
// 簡單回傳簽到結果 HTML 頁面
const output = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.tailwindcss.com"></script>
<title>現場簽到結果</title>
</head>
<body class="bg-slate-100 flex items-center justify-center min-h-screen p-4">
<div class="bg-white p-8 rounded-xl shadow-md text-center max-w-sm w-full">
<h1 class="text-2xl font-bold mb-4 ${result.success ? 'text-emerald-600' : 'text-rose-600'}">
${result.success ? '✅ 簽到成功' : '❌ 簽到失敗'}
</h1>
<p class="text-slate-600 mb-6">${result.message}</p>
<button onclick="window.close()" class="bg-slate-800 text-white px-4 py-2 rounded-lg text-sm">關閉頁面</button>
</div>
</body>
</html>
`;
return HtmlService.createHtmlOutput(output);
}
// 預設渲染 FullCalendar 主介面 (Day 21)
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('NGO 房間與資源預約系統')
.setXframeOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
開啟左側選單的 「觸發條件 (Triggers)」 ⏰。
點擊右下角 「新增觸發條件」。
設定參數如下:
選擇要執行的函式:autoReleaseNoShowBookings
選擇部署作業:Head
選擇活動來源:時間驅動 (Time-driven)
選擇時間型觸發條件類型:分鐘計時器 (Minute timer)
選擇分鐘間隔:每 15 分鐘
5. 小結
**簽到/簽退模組與 QR Code 自動釋放機制 (CheckInService.gs)**在今天完成了。透過 QR Code 現場簽到與自動化排程,能有效防止場地被幽靈預約佔用,顯著提升 NGO 空間資源的利用率。