WCT Logo

WCT Worldwidetraining

Attendance Management System

🔧 Setup
⚙️ Admin
📋 Form
📊 Records
🚫 Rejected
● Not Connected
1

Create a Google Sheet

Go to sheets.google.com → create a new sheet → name it Attendance.
In row 1, add these headers exactly:

Timestamp	Name	Phone	IC First 6 Digit Class Type	Session	Date	Time	Trainer	Venue

⚠️ Registration Sheet (for verification)

Create a second sheet named Registration with these headers:

Name	Phone	IC First 6 Digit	Paid

Fill this sheet with your participants' registration data. The Paid column must be Y or N. Only participants with Paid = Y can check in.

📋 Rejected Sheet (auto-created)

A third sheet named Rejected will be created automatically the first time someone is rejected. No setup needed — headers and data are written by the script.

2

Open Apps Script

In your Google Sheet, click Extensions → Apps Script. Delete any existing code, then paste this:

const SHEET_NAME = "Attendance";
const REG_SHEET_NAME = "Registration"; // sheet with registration data for verification
const REJECTED_SHEET_NAME = "Rejected"; // sheet to log rejected check-ins

function doPost(e) {
  try {
    const data = JSON.parse(e.postData.contents);

    // ── VERIFICATION ──
    if (data.action === "verify") {
      return verifyParticipant(data);
    }

    // ── LOG REJECTED ──
    if (data.action === "logRejected") {
      return logRejectedEntry(data);
    }

    // ── WRITE ATTENDANCE ──
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
    sheet.appendRow([
      new Date().toLocaleString("en-MY"),
      data.name, data.phone, data.ic4, data.classType,
      data.session, data.date, data.time, data.trainer, data.venue
    ]);
    return ContentService
      .createTextOutput(JSON.stringify({ result: "success" }))
      .setMimeType(ContentService.MimeType.JSON);
  } catch(err) {
    return ContentService
      .createTextOutput(JSON.stringify({ result: "error", error: err.toString() }))
      .setMimeType(ContentService.MimeType.JSON);
  }
}

function logRejectedEntry(data) {
  try {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    let sheet = ss.getSheetByName(REJECTED_SHEET_NAME);
    // Auto-create the sheet + headers if it doesn't exist
    if (!sheet) {
      sheet = ss.insertSheet(REJECTED_SHEET_NAME);
      sheet.appendRow(["Timestamp", "Name", "Phone", "IC First 6 Digit", "Class Type", "Session", "Date", "Time", "Reason"]);
      sheet.getRange(1, 1, 1, 9).setFontWeight("bold");
    }
    sheet.appendRow([
      new Date().toLocaleString("en-MY"),
      data.name || "", data.phone || "", data.ic4 || "", data.classType || "",
      data.session || "", data.date || "", data.time || "", data.reason || ""
    ]);
    return ContentService
      .createTextOutput(JSON.stringify({ result: "success" }))
      .setMimeType(ContentService.MimeType.JSON);
  } catch(err) {
    return ContentService
      .createTextOutput(JSON.stringify({ result: "error", error: err.toString() }))
      .setMimeType(ContentService.MimeType.JSON);
  }
}

function verifyParticipant(data) {
  try {
    const regSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(REG_SHEET_NAME);
    if (!regSheet) {
      // No registration sheet = allow all (open mode)
      return ContentService
        .createTextOutput(JSON.stringify({ result: "success", verified: true, mode: "open" }))
        .setMimeType(ContentService.MimeType.JSON);
    }
    const rows = regSheet.getDataRange().getValues();
    const headers = rows[0].map(h => String(h).trim().toLowerCase());
    const nameIdx  = headers.indexOf("name");
    const phoneIdx = headers.indexOf("phone");
    const ic4Idx   = headers.indexOf("ic first 6 digit");
    const paidIdx  = headers.indexOf("paid");

    const ic4Input   = String(data.ic4 || "").trim();
    const phoneInput = String(data.phone || "").trim().replace(/\D/g, "").slice(-4);
    const nameInput  = String(data.name || "").trim().toLowerCase();

    let matched = false;
    for (let i = 1; i < rows.length; i++) {
      const row = rows[i];
      const regPhone = String(row[phoneIdx] || "").replace(/\D/g, "").slice(-4);
      const regIc4   = String(row[ic4Idx]   || "").trim();
      const regName  = String(row[nameIdx]  || "").trim().toLowerCase();
      const regPaid  = paidIdx >= 0 ? String(row[paidIdx] || "").trim().toUpperCase() : "Y";

      // Reject immediately if not paid
      if (regPaid !== "Y") continue;

      // Priority check: IC First 6 + Phone last 4 + Paid Y
      if (ic4Input && regIc4 && ic4Input === regIc4 && phoneInput === regPhone) {
        matched = true; break;
      }
      // Fallback: Name + IC First 6 + Paid Y
      if (ic4Input && regIc4 && ic4Input === regIc4 && nameInput === regName) {
        matched = true; break;
      }
    }

    return ContentService
      .createTextOutput(JSON.stringify({ result: "success", verified: matched }))
      .setMimeType(ContentService.MimeType.JSON);
  } catch(err) {
    return ContentService
      .createTextOutput(JSON.stringify({ result: "error", error: err.toString() }))
      .setMimeType(ContentService.MimeType.JSON);
  }
}

function doGet(e) {
  try {
    // ── Verify via GET (called from client for readable response) ──
    if (e && e.parameter && e.parameter.action === "verify") {
      return verifyParticipant({
        name:  e.parameter.name  || "",
        phone: e.parameter.phone || "",
        ic4:   e.parameter.ic4   || ""
      });
    }

    // ── Check duplicate check-in ──
    if (e && e.parameter && e.parameter.action === "checkDuplicate") {
      const nameInput  = String(e.parameter.name  || "").trim().toLowerCase();
      const phoneInput = String(e.parameter.phone || "").trim().replace(/\D/g, "");

      const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
      const rows = sheet.getDataRange().getValues();
      const headers = rows[0].map(h => String(h).trim());
      const nameIdx  = headers.indexOf("Name");
      const phoneIdx = headers.indexOf("Phone");

      let duplicate = false;
      for (let i = 1; i < rows.length; i++) {
        const rName  = String(rows[i][nameIdx]  || "").trim().toLowerCase();
        const rPhone = String(rows[i][phoneIdx] || "").trim().replace(/\D/g, "");
        if (rName === nameInput || rPhone === phoneInput) {
          duplicate = true; break;
        }
      }
      return ContentService
        .createTextOutput(JSON.stringify({ result: "success", duplicate: duplicate }))
        .setMimeType(ContentService.MimeType.JSON);
    }

    // ── Load attendance records ──
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
    const rows = sheet.getDataRange().getValues();
    const headers = rows[0];
    const data = rows.slice(1).map(row => {
      const obj = {};
      headers.forEach((h, i) => obj[h] = row[i]);
      return obj;
    });
    return ContentService
      .createTextOutput(JSON.stringify({ result: "success", data: data }))
      .setMimeType(ContentService.MimeType.JSON);
  } catch(err) {
    return ContentService
      .createTextOutput(JSON.stringify({ result: "error", error: err.toString() }))
      .setMimeType(ContentService.MimeType.JSON);
  }
}
3

Deploy as Web App

Click Deploy → New deployment
• Type: Web app
• Execute as: Me
• Who has access: Anyone
Click Deploy → copy the URL it gives you.

4

Paste the URL above

Paste the deployment URL into the field at the top of this page and click Save. You're done — data will now go directly to your Google Sheet.

Each session can send data to a different Google Sheet. Paste a unique Apps Script URL here, or leave blank to use the default.

Attendance Check-In

Please fill in your details to record your attendance.

Your information is only used for attendance purposes.

0
Total Rejected
0
Today
0
Sessions
🚫
No rejected entries yet. Rejected check-ins will appear here.
Total
Today
Sessions
📊
Click Refresh to load records from Google Sheets.