แบบฟอร์มจองห้องประชุม
กรอกรายละเอียดเพื่อทำการจองคิวใช้งานห้องประชุม
อุปกรณ์ภายในห้อง
- รองรับ 21 ที่นั่ง
-
•
พร้อมอุปกรณ์อำนวยความสะดวก :
- โปรเจคเตอร์
- ระบบเสียงไมค์ตั้งโต๊ะ 21 ตัว และ ไมโครโฟนไร้สาย 2 ตัว
ข้อกำหนดการใช้งาน
- รักษาความสะอาดและปิดไฟ/เครื่องปรับอากาศหลังใช้งาน
- หากเกิดความเสียหายต่อห้องประชุม วัสดุอุปกรณ์ ครุภัณฑ์ภายในห้องประชุมหรือโสตทัศนูปกรณ์ อันเนื่องมาจากผู้ใช้บริการ ผู้ใช้บริการจะต้องรับผิดชอบชดใช้ค่าเสียหายทั้งหมด
- ในกรณีที่มีการรับประทานอาหารว่างและเครื่องดื่ม ผู้ขอใช้ห้องประชุมจะต้องจัดหาเจ้าหน้าที่คอยให้บริการ อํานวยความสะดวกแก่ผู้เข้าร่วมประชุมด้วยตนเองและหากเกิดความเสียหายใด ๆ ให้เป็นไปตามข้อกําหนด ข้อที่ 2
- หากต้องการยกเลิก กรุณาดำเนินการก่อนเวลาเริ่มอย่างน้อย 2 ชั่วโมง
ยกเลิกการจองห้องประชุม
ค้นหาเพื่อระบุเหตุผลและยืนยันการยกเลิกการจองห้องประชุม
ค้นหารายการจองของคุณ
พิมพ์รหัสการจอง อีเมล หรือเบอร์โทรศัพท์ เพื่อเรียกดูรายการจองที่เปิดใช้งานอยู่ทั้งหมดของคุณ
ปฏิทินแสดงการจองห้องประชุม
คิวจองที่ได้รับการอนุมัติใช้งาน (ระบบซิงก์ข้อมูลอัปเดตแบบเรียลไทม์)
รายการจองปัจจุบัน
0 รายการ| รหัสการจอง | ชื่อผู้จอง / หน่วยงาน | ชื่องาน/โครงการ | วันที่ - ช่วงเวลาใช้งาน | สถานะ | จัดการ (แอดมิน) |
|---|
ประวัติรายการที่ถูกยกเลิก
0 รายการ| รหัสการจอง | ชื่อผู้จอง | ชื่องาน/โครงการ | วันที่ยกเลิก | เหตุผลในการยกเลิก | จัดการ (แอดมิน) |
|---|
ตั้งค่า Google Sheets & ระบบส่งอีเมล (แอดมิน)
กำหนดสิทธิ์เชื่อมโยงข้อมูลเก็บลงระบบฐานข้อมูลกูเกิ้ลชีต
วางลิงก์ Web App หลังจากเผยแพร่โปรเจกต์ Apps Script ในหน้า Google Sheet ของคุณ
ระบบจะส่งการจองและการยกเลิกไปยังอีเมลนี้อัตโนมัติ (สามารถแก้ไขได้)
เครื่องมือวินิจฉัยและตรวจสอบการเชื่อมต่อ (Apps Script Helpdesk) 🛠️
หากคุณพบปัญหา "ส่งข้อมูลจองแล้วไม่ไปชีต" หรือ "ขึ้น Error ขัดข้อง" ให้ใช้รายการตรวจสอบปุ่มลัดเหล่านี้เพื่อแก้ไขปัญหาแบบก้าวหน้า:
1 ตรวจสอบสิทธิ์และการดีพลอย (Deploy Checklist)
2 ตรวจสอบสิทธิ์ความปลอดภัยใน Google Accounts
หากแอปสคริปต์ยังไม่ส่งเมล หรือยิงแจ้งเตือนเข้าชีตเป็นสีเทา: เกิดจากการลืมยอมรับสิทธิ์ความปลอดภัย (OAuth Authorization) ให้คุณเข้าไปในหน้าโครงการ Apps Script ของท่าน แล้วดำเนินตามดังนี้:
- มองหาป้ายเมนูเลือกฟังก์ชันด้านบนสุด เลื่อนหาฟังก์ชันชื่อ
setupPermissions - คลิกที่ปุ่ม "เรียกใช้งาน (Run)" (ที่เป็นสัญลักษณ์สามเหลี่ยมเล่น)
- กดปุ่ม "ตรวจสอบสิทธิ์ (Review Permissions)" เมื่อหน้าจอป๊อปอัปสีน้ำเงินปรากฏ
- เลือกบัญชี Google ของท่าน → เลือก "ขั้นสูง (Advanced)" ด้านล่าง → คลิก "ไปที่ [ชื่อโปรเจกต์] (ไม่ปลอดภัย)" → กดปุ่ม "อนุญาต (Allow)"
ระบบ Apps Script จะสร้างหัวตารางเหล่านี้ให้อัตโนมัติเมื่อชีตว่างเปล่า แต่คุณสามารถสร้างเตรียมไว้ล่วงหน้าได้ใน แถวที่ 1 (Row 1) ดังนี้:
| A: รหัสการจอง | B: วันที่จอง | C: ชื่อผู้จอง | D: อีเมล | E: ประเภทผู้จอง | F: หน่วยงาน | G: ตั้งแต่วันที่ | H: ถึงวันที่ | I: ช่วงเวลา | J: โครงการ/ชื่องาน | K: เบอร์โทรศัพท์ | L: สถานะ | M: เหตุผลการยกเลิก |
|---|
// ฟังก์ชันสำหรับตั้งค่าและยืนยันสิทธิ์การส่งอีเมลครั้งแรก (สำคัญมาก โปรดกด Run ฟังก์ชันนี้ 1 ครั้งเพื่อเปิดระบบส่งเมล)
function setupPermissions() {
var userEmail = Session.getActiveUser().getEmail();
GmailApp.sendEmail(userEmail, "RDI MCRU - ยืนยันสิทธิ์สำเร็จ", "ระบบจองห้องประชุมของคุณพร้อมใช้งานและสามารถส่งอีเมลได้แล้ว!", {
name: "ระบบจองห้องประชุม สวพ."
});
return "Setup Complete";
}
// ฟังก์ชัน doGet เพื่อเพิ่มความเสถียรในการ Ping ทดสอบจากหน้าเว็บบราวเซอร์โดยตรง
function doGet(e) {
var action = e && e.parameter ? e.parameter.action : null;
if (action === "ping") {
return ContentService.createTextOutput(JSON.stringify({status: "success", message: "RDI MCRU Web App Connection OK!"}))
.setMimeType(ContentService.MimeType.JSON);
}
return ContentService.createTextOutput("ระบบจองห้องประชุม สวพ. ทำงานร่วมกับ Google Sheets เชื่อมต่อเรียบร้อยแล้ว!")
.setMimeType(ContentService.MimeType.TEXT);
}
function doPost(e) {
try {
if (!e || !e.postData || !e.postData.contents) {
return ContentService.createTextOutput(JSON.stringify({status: "error", message: "ไม่พบข้อมูลส่งเข้า (No post data)"}))
.setMimeType(ContentService.MimeType.JSON);
}
var params = JSON.parse(e.postData.contents);
var action = params.action;
var ss = SpreadsheetApp.getActiveSpreadsheet();
// ตรวจสอบกรณี Standalone script เพื่อป้องกันระบบค้างชะงัก
if (!ss) {
return ContentService.createTextOutput(JSON.stringify({status: "error", message: "ข้อผิดพลาดระบบแอดมิน: โครงการ Apps Script ไม่ได้ผูกกับ Google Sheet โปรดเข้าเมนู ส่วนขยาย ในชีตเพื่อดีพลอยใหม่"}))
.setMimeType(ContentService.MimeType.JSON);
}
// ค้นหาหรือสร้างชีต "Bookings"
var sheet = ss.getSheetByName("Bookings") || ss.insertSheet("Bookings");
// ตรวจสอบโครงสร้างหัวตาราง (Headers)
if (sheet.getLastRow() === 0) {
sheet.appendRow([
"รหัสการจอง", "วันที่จอง", "ชื่อผู้จอง", "อีเมล", "ประเภทผู้จอง",
"หน่วยงาน", "ตั้งแต่วันที่", "ถึงวันที่", "ช่วงเวลา", "โครงการ/ชื่องาน",
"เบอร์โทรศัพท์", "สถานะ", "เหตุผลการยกเลิก"
]);
sheet.getRange(1, 1, 1, 13).setBackground("#fbcfe8").setFontWeight("bold").setHorizontalAlignment("center");
}
var adminEmail = params.adminEmail || "smcy.poolsak@gmail.com";
// ฟังก์ชันวิเคราะห์วันที่เพื่อแก้ปัญหาความเหลื่อมล้ำของ Timezone (Timezone-safe)
function parseLocalDate(val) {
if (!val) return null;
if (val instanceof Date) {
var d = new Date(val.getTime());
d.setHours(0,0,0,0);
return d;
}
if (typeof val === 'string') {
var parts = val.split('-');
if (parts.length === 3) {
return new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
}
var slashParts = val.split('/');
if (slashParts.length === 3) {
var d = parseInt(slashParts[0], 10);
var m = parseInt(slashParts[1], 10) - 1;
var y = parseInt(slashParts[2], 10);
if (y > 2400) { y = y - 543; } // แปลงพุทธศักราชเป็นคริสตศักราช
return new Date(y, m, d);
}
var d = new Date(val);
d.setHours(0,0,0,0);
return d;
}
return null;
}
if (action === "create") {
var b = params.data;
var data = sheet.getDataRange().getValues();
// ตรวจสอบความซ้ำซ้อนระดับเซิร์ฟเวอร์อย่างแม่นยำ
var isClashed = false;
var newStart = parseLocalDate(b.start);
var newEnd = parseLocalDate(b.end);
for (var i = 1; i < data.length; i++) {
var existingStatus = data[i][11]; // "สถานะ"
if (existingStatus === "จองแล้ว" || existingStatus === "active" || existingStatus === "งดให้บริการ" || existingStatus === "closed") {
var existingStart = parseLocalDate(data[i][6]); // "ตั้งแต่วันที่"
var existingEnd = parseLocalDate(data[i][7]); // "ถึงวันที่"
var existingPeriod = data[i][8]; // "ช่วงเวลา"
if (existingStart && existingEnd && existingStart <= newEnd && newStart <= existingEnd) {
if (existingStatus === "งดให้บริการ" || existingStatus === "closed" || b.status === "closed" || b.period === "ตลอดวัน" || existingPeriod === "ตลอดวัน" || b.period === existingPeriod) {
isClashed = true;
break;
}
}
}
}
if (isClashed) {
return ContentService.createTextOutput(JSON.stringify({status: "clash", message: "ช่วงเวลาและวันที่ดังกล่าวได้รับการจองห้องประชุมเรียบร้อยแล้ว"}))
.setMimeType(ContentService.MimeType.JSON);
}
var statusValue = b.status === "closed" ? "งดให้บริการ" : "จองแล้ว";
var newRow = [
b.id, b.created, b.name, b.email, b.type,
b.dept, b.start, b.end, b.period, b.title,
b.phone, statusValue, ""
];
sheet.appendRow(newRow);
var emailSent = true;
var emailError = "";
// ยิงส่งอีเมลโดยแยกเป็นอิสระ เพื่อไม่ให้ระบายข้อผิดพลาดจนระบบหลักขัดข้อง (Non-blocking)
if (b.status !== "closed") {
try {
var adminSubject = "🔔 รายการใหม่: แจ้งเรื่องการจองห้องประชุม รหัส " + b.id;
var adminBody = createEmailHtml(b, "การจองใหม่", false);
GmailApp.sendEmail(adminEmail, adminSubject, "", {
htmlBody: adminBody,
name: "ระบบจองห้องประชุม สวพ."
});
} catch (errAdmin) {
emailSent = false;
emailError += "เมลแอดมินล้มเหลว: " + errAdmin.toString() + " | ";
}
try {
var userSubject = "📧 ยืนยันการจอง: การจองใช้ห้องประชุม รหัส " + b.id;
var userBody = createEmailHtml(b, "ได้รับการยืนยันระบบ", true);
GmailApp.sendEmail(b.email, userSubject, "", {
htmlBody: userBody,
name: "ระบบจองห้องประชุม สวพ."
});
} catch (errUser) {
emailSent = false;
emailError += "เมลผู้ใช้งานล้มเหลว: " + errUser.toString();
}
}
return ContentService.createTextOutput(JSON.stringify({status: "success", id: b.id, emailSent: emailSent, emailError: emailError}))
.setMimeType(ContentService.MimeType.JSON);
}
if (action === "cancel") {
var idToCancel = params.id;
var reason = params.reason;
var data = sheet.getDataRange().getValues();
var foundRow = -1;
var rowData = {};
for (var i = 1; i < data.length; i++) {
if (data[i][0] == idToCancel) {
foundRow = i + 1;
var startVal = data[i][6];
var endVal = data[i][7];
var formattedStart = (startVal instanceof Date) ? Utilities.formatDate(startVal, Session.getScriptTimeZone(), "yyyy-MM-dd") : startVal;
var formattedEnd = (endVal instanceof Date) ? Utilities.formatDate(endVal, Session.getScriptTimeZone(), "yyyy-MM-dd") : endVal;
rowData = {
id: data[i][0],
created: data[i][1],
name: data[i][2],
email: data[i][3],
type: data[i][4],
dept: data[i][5],
start: formattedStart,
end: formattedEnd,
period: data[i][8],
title: data[i][9],
phone: data[i][10],
reason: reason
};
break;
}
}
if (foundRow !== -1) {
sheet.getRange(foundRow, 12).setValue("ยกเลิกแล้ว");
sheet.getRange(foundRow, 13).setValue(reason);
var emailSent = true;
var emailError = "";
try {
var adminSubject = "🚨 รายการยกเลิก: แจ้งยกเลิกการจองห้องประชุม รหัส " + idToCancel;
var adminBody = createEmailHtml(rowData, "ถูกยกเลิกแล้ว", false);
GmailApp.sendEmail(adminEmail, adminSubject, "", {
htmlBody: adminBody,
name: "ระบบจองห้องประชุม สวพ."
});
} catch (errAdmin) {
emailSent = false;
emailError += "เมลแจ้งยกเลิกแอดมินล้มเหลว: " + errAdmin.toString() + " | ";
}
try {
var userSubject = "⚠️ แจ้งผล: ยกเลิกการจองห้องประชุมสำเร็จ รหัส " + idToCancel;
var userBody = createEmailHtml(rowData, "ทำการยกเลิกสำเร็จ", true);
GmailApp.sendEmail(rowData.email, userSubject, "", {
htmlBody: userBody,
name: "ระบบจองห้องประชุม สวพ."
});
} catch (errUser) {
emailSent = false;
emailError += "เมลแจ้งยกเลิกผู้จองล้มเหลว: " + errUser.toString();
}
return ContentService.createTextOutput(JSON.stringify({status: "success", message: "Cancelled successfully", emailSent: emailSent, emailError: emailError}))
.setMimeType(ContentService.MimeType.JSON);
} else {
return ContentService.createTextOutput(JSON.stringify({status: "error", message: "Booking ID not found"}))
.setMimeType(ContentService.MimeType.JSON);
}
}
if (action === "update") {
var b = params.data;
var data = sheet.getDataRange().getValues();
var foundRow = -1;
for (var i = 1; i < data.length; i++) {
if (data[i][0] == b.id) {
foundRow = i + 1;
break;
}
}
if (foundRow !== -1) {
sheet.getRange(foundRow, 3).setValue(b.name);
sheet.getRange(foundRow, 4).setValue(b.email);
sheet.getRange(foundRow, 5).setValue(b.type);
sheet.getRange(foundRow, 6).setValue(b.dept);
sheet.getRange(foundRow, 7).setValue(b.start);
sheet.getRange(foundRow, 8).setValue(b.end);
sheet.getRange(foundRow, 9).setValue(b.period);
sheet.getRange(foundRow, 10).setValue(b.title);
sheet.getRange(foundRow, 11).setValue(b.phone);
return ContentService.createTextOutput(JSON.stringify({status: "success", message: "Updated successfully"}))
.setMimeType(ContentService.MimeType.JSON);
} else {
return ContentService.createTextOutput(JSON.stringify({status: "error", message: "Booking ID not found"}))
.setMimeType(ContentService.MimeType.JSON);
}
}
if (action === "delete") {
var idToDelete = params.id;
var data = sheet.getDataRange().getValues();
var foundRow = -1;
for (var i = 1; i < data.length; i++) {
if (data[i][0] == idToDelete) {
foundRow = i + 1;
break;
}
}
if (foundRow !== -1) {
sheet.deleteRow(foundRow);
return ContentService.createTextOutput(JSON.stringify({status: "success", message: "Deleted successfully"}))
.setMimeType(ContentService.MimeType.JSON);
} else {
return ContentService.createTextOutput(JSON.stringify({status: "error", message: "Booking ID not found"}))
.setMimeType(ContentService.MimeType.JSON);
}
}
} catch(err) {
return ContentService.createTextOutput(JSON.stringify({status: "error", message: err.toString()}))
.setMimeType(ContentService.MimeType.JSON);
}
}
function createEmailHtml(data, typeText, isUserCopy) {
var roleLabel = isUserCopy ? "ถึง คุณ " + data.name : "เรียน ผู้ดูแลระบบห้องประชุม (Admin)";
var cancelReasonHtml = data.reason ? `<p style="color: #be185d;"><b>⚠️ เหตุผลการขอยกเลิก:</b> ${data.reason}</p>` : "";
var userNoticeHtml = "";
if (isUserCopy && typeText !== "ทำการยกเลิกสำเร็จ" && typeText !== "ถูกยกเลิกแล้ว") {
userNoticeHtml = `
<div style="margin-top: 22px; padding: 16px; background-color: #fef2f2; border: 1.5px solid #fecaca; border-radius: 8px;">
<p style="color: #dc2626; font-weight: bold; margin: 0 0 10px 0; font-size: 15px; line-height: 1.5;">
** ผู้ขอใช้บริการต้องเตรียมถ่านสำหรับใส่ไมโครโฟน โดยใช้ถ่าน ขนาด AA จำนวน 2 ก้อน ต่อ ไมโครโฟน 1 ตัว
</p>
<p style="margin: 0 0 4px 0; font-size: 14px; color: #475569;">
☎️ ติดต่อสอบถามเพิ่มเติม 0-3272-0536 ต่อ 1082
</p>
<p style="margin: 0; font-size: 14px; color: #475569; font-weight: bold;">
ขอบคุณค่ะ
</p>
</div>
`;
}
return `
<div style="font-family: Prompt, Arial, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #fbcfe8; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);">
<div style="background-color: #db2777; padding: 20px; color: white; text-align: center;">
<h2 style="margin: 0; font-size: 20px;">แจ้งรายละเอียดการจองห้องประชุม (${typeText})</h2>
<span style="font-size: 14px; background-color: rgba(255,255,255,0.2); padding: 4px 10px; border-radius: 20px; display: inline-block; margin-top: 8px;">รหัสการจอง: ${data.id}</span>
</div>
<div style="padding: 24px; color: #334155; line-height: 1.6;">
<p style="font-size: 16px; margin-top: 0;"><b>${roleLabel}</b>,</p>
<p style="margin-bottom: 20px;">ระบบได้รับรายงานการทำรายการ ${typeText} เรียบร้อยแล้ว โดยมีรายละเอียดการปฏิบัติใช้งานดังนี้:</p>
<table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; color: #94a3b8; width: 140px;"><b>ผู้ทำเรื่องจอง</b></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; font-weight: 500;">${data.name}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; color: #94a3b8;"><b>ประเภทผู้ใช้</b></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${data.type}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; color: #94a3b8;"><b>หน่วยงานสังกัด</b></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${data.dept}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; color: #94a3b8;"><b>ช่องทางอีเมล</b></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><a href="mailto:${data.email}" style="color: #db2777; text-decoration: none;">${data.email}</a></td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; color: #94a3b8;"><b>เบอร์โทรศัพท์</b></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${data.phone}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; color: #94a3b8;"><b>วันที่ใช้งาน</b></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; font-weight: 500; color: #db2777;">${data.start} ถึงวันที่ ${data.end}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; color: #94a3b8;"><b>ช่วงเวลาที่ใช้งาน</b></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; font-weight: bold; color: #be185d;">${data.period}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; color: #94a3b8;"><b>ชื่องาน / โครงการ</b></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${data.title}</td>
</tr>
</table>
${cancelReasonHtml}
${userNoticeHtml}
<p style="font-size: 12px; color: #94a3b8; text-align: center; margin-top: 30px; border-top: 1px solid #f1f5f9; padding-top: 15px;">
นี่คือจดหมายตอบกลับอัตโนมัติจากระบบจองห้องประชุม กรุณาอย่าตอบกลับอีเมลฉบับนี้
</p>
</div>
</div>`;
}