iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0

在完成了上篇的前端 FullCalendar 日曆互動介面後,我們進一步解決 NGO 場地管理中常見的「佔位不使用」(No-show)與資源浪費問題。

今天我們將開發 模組 E:現場簽到與自動釋放機制 (CheckInService.gs)。透過生成的專屬 QR Code 簽到連結、逾時未簽到自動釋放(Auto-release) 以及 Telegram 警示推播,確保資源能有效率地輪轉給有需要的團隊。


1. 簽到與資源釋放核心邏輯設計

系統的簽到與自動釋放流程如下:

  1. 簽到 QR Code 生成:預約成功後,系統產生包含 bookingId 與安全驗證碼(Token)的簽到網址與 QR Code。
  2. 現場掃碼簽到:使用者到達現場後,掃描貼在門口的 QR Code,系統更新 Sheet 狀態為 CHECKED_IN 並記錄時間。
  3. 時基觸發器(Time-driven Trigger):排程每 15 分鐘檢查一次所有當天預約。若超過預定開始時間 15 分鐘仍未簽到,系統將自動:
  • 將狀態變更為 CANCELLED_NO_SHOW。
  • 清除 FullCalendar 佔用時段,釋放場地給其他使用者。
  • 發送 Telegram 警示通知給申請人與管理員。

2. 後端 Service 完整程式碼 (CheckInService.gs)

請在 Apps Script 專案中新增 CheckInService.gs 檔案,並寫入以下程式碼:

/**
 * CheckInService.gs
 * 負責處理預約的 QR Code 簽到、簽退以及逾時未簽到自動釋放邏輯
 */

/**
 * 1. 執行現場簽到驗證
 * @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: "❌ 找不到對應的預約紀錄。" };
}

/**
 * 2. 核心排程任務:自動排查逾時未簽到並釋放資源 (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} 筆逾時預約。`);
}

/**
 * 3. 生成專屬 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`;
}


3. 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 = `
      <!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');
}


4. 設定自動排查觸發器 (Time-Driven Trigger)

要讓 autoReleaseNoShowBookings 能夠定時執行,可在 Apps Script 中完成設定:

  1. 開啟左側選單的 「觸發條件 (Triggers)」 ⏰。
  2. 點擊右下角 「新增觸發條件」。
  3. 設定參數如下:
  • 選擇要執行的函式:autoReleaseNoShowBookings
  • 選擇部署作業:Head
  • 選擇活動來源:時間驅動 (Time-driven)
  • 選擇時間型觸發條件類型:分鐘計時器 (Minute timer)
  • 選擇分鐘間隔:每 15 分鐘

5. 小結

**簽到/簽退模組與 QR Code 自動釋放機制 (CheckInService.gs)**在今天完成了。透過 QR Code 現場簽到與自動化排程,能有效防止場地被幽靈預約佔用,顯著提升 NGO 空間資源的利用率。


上一篇
【Day 21】前端 FullCalendar 日曆整合與 Web App 介面
系列文
零預算 NGO 數位轉型挑戰:30 天打造智慧訂房系統 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言