Cara Membuat Sistem Tiket Travel dengan Pilih Kursi di Google Apps Script: Layout Armada, Anti Kursi Dobel, dan Kursi Kedaluwarsa
Banyak usaha travel antar kota masih menerima pesanan lewat telepon dan WhatsApp, lalu mencatat kursi di kertas berbentuk denah mobil. Hasilnya bisa ditebak: kursi depan terjual dua kali, penumpang yang batal tanpa kabar tetap "memegang" kursi, dan sopir baru tahu daftar penumpang lima menit sebelum berangkat.
Di tutorial ini kita membangun inti sistem tiket travel dengan pilih kursi menggunakan Google Apps Script dan Google Sheets: layout armada yang ditulis sebagai teks, jadwal berulang tanpa membuat perjalanan satu per satu, kursi yang tidak bisa terjual dua kali, kursi yang otomatis dilepas jika tidak dibayar, dan perhitungan jam tiba. Contohnya diambil dari aplikasi Travel Shuttle yang bisa kamu download gratis di Papicode (link di akhir artikel).
1. Layout armada sebagai teks
Setiap jenis mobil punya susunan kursi berbeda. Daripada membuat tabel kursi, kita tulis layout dalam satu sel: baris dipisah |, sel dipisah ,, S untuk sopir, _ untuk lorong.
Hiace 14 kursi: S,_,1|2,_,3|4,5,6|7,8,9|10,11,12|13,14,_
Parsernya pendek:
function parseLayout_(s) {
var baris = String(s || '').split('|').map(function (b) {
return b.split(',').map(function (c) { return c.trim(); });
}).filter(function (b) { return b.length && b.join(''); });
var kursi = [];
baris.forEach(function (b) { b.forEach(function (c) { if (/^\d{1,2}[A-Z]?$/.test(c)) kursi.push(c); }); });
return { baris: baris, kursi: kursi };
}
baris dipakai browser untuk menggambar denah dengan CSS grid, sedangkan kursi dipakai server untuk validasi. Pemilik travel bisa menambah armada baru hanya dengan mengetik satu baris teks, dan denah di aplikasi otomatis sama dengan susunan di mobil.
2. Jadwal berulang, perjalanan = tanggal + kode jadwal
Travel biasanya berangkat di jam yang sama setiap hari. Membuat baris perjalanan untuk setiap tanggal akan membuat sheet membengkak. Sebagai gantinya, sheet Jadwal berisi pola (YS15, rute Yogyakarta-Semarang, jam 15:00, armada Elf, hari 1,2,3,4,5,6,7), dan satu perjalanan diidentifikasi sebagai tanggal + kode jadwal.
function hariKe_(tgl) { return Number(Utilities.formatDate(new Date(tgl + 'T12:00:00+07:00'), 'Asia/Jakarta', 'u')); }
var jalan = bacaJadwal_().filter(function (j) {
var r = rute[j.rute];
return j.aktif && r && r.asal === asal && r.tujuan === tujuan && j.hari.indexOf(hariKe_(tanggal)) >= 0;
});
Jadwal khusus akhir pekan cukup diisi 5,6,7. Booking cukup menyimpan kolom Tanggal dan Jadwal, lalu kursi terisi dihitung dari booking yang cocok dengan pasangan itu.
3. Jam tiba dan batas penjualan
Jam tiba dihitung dari durasi rute. Perjalanan malam Yogyakarta-Jakarta berangkat 19:00 dan tiba 04:00 keesokan harinya, jadi kita juga mengembalikan penanda hari berikutnya:
function jamTambah_(jam, menit) {
var m = (+jam.slice(0, 2)) * 60 + (+jam.slice(3)) + menit;
return { jam: ('0' + Math.floor(m / 60) % 24).slice(-2) + ':' + ('0' + m % 60).slice(-2), hariPlus: Math.floor(m / 1440) };
}
jamTambah_('19:00', 540); // { jam: '04:00', hariPlus: 1 }
Penjualan ditutup sejumlah menit sebelum berangkat, dihitung dari waktu server dengan zona Jakarta yang ditulis eksplisit:
var tutup = new Date(tanggal + 'T' + j.jam + ':00+07:00').getTime() - Number(cfg.tutup_menit) * 60000;
var buka = Date.now() < tutup;
4. Satu kursi, satu penumpang
Inilah bagian yang paling sering bermasalah di sistem manual. Dua orang membuka denah, sama-sama melihat kursi 1 kosong, lalu sama-sama menekan pesan. Solusinya: pengecekan dan penulisan harus terjadi di dalam satu kunci, dan pengecekan dilakukan ulang dengan data terbaru setelah kunci didapat.
var lock = LockService.getScriptLock();
lock.waitLock(20000);
try {
var denah = getDenah(tanggal, jadwal); // dibaca ulang di dalam kunci
kursi.forEach(function (k) {
if (semuaKursi.indexOf(k) < 0) throw new Error('Kursi ' + k + ' tidak ada di armada ini.');
if (denah.terisi.indexOf(k) >= 0) throw new Error('Kursi ' + k + ' baru saja dipesan orang lain. Pilih kursi lain.');
});
sh.getRange(sh.getLastRow() + 1, 1, 1, KOL.length).setNumberFormat('@').setValues([baris]);
SpreadsheetApp.flush();
} finally {
lock.releaseLock();
}
Di browser, pesan error "baru saja dipesan orang lain" ditangkap lalu denah dimuat ulang, sehingga penumpang langsung melihat kursi mana yang masih tersedia. Validasi "kursi ada di layout" juga penting: tanpa itu, seseorang bisa mengirim kursi nomor 99 lewat DevTools.
5. Kursi yang menunggu pembayaran
Penumpang yang memilih transfer perlu waktu untuk membayar, jadi kursinya harus ditahan. Tapi kalau ditahan selamanya, penumpang yang iseng memesan tanpa membayar akan membuat mobil terlihat penuh. Kita definisikan siapa yang "menahan" kursi:
function menahan_(b, cfg) {
if (b.status === 'Lunas') return true;
if (b.status !== 'Menunggu') return false;
var umur = Date.now() - new Date(b.dibuat.replace(' ', 'T') + ':00+07:00').getTime();
return umur <= Number(cfg.batas_bayar_menit || 60) * 60000;
}
Tidak perlu trigger terjadwal untuk melepas kursi. Setiap kali denah dihitung, booking menunggu yang sudah lewat batas otomatis tidak dihitung. Ada satu kasus tepi yang ditangani di panel operator: penumpang membayar terlambat setelah kursinya diambil orang lain. Sebelum melunasi booking yang kedaluwarsa, server mengecek ulang apakah kursinya masih kosong. Jika tidak, operator diberi tahu kursi mana yang bentrok, bukan diam-diam membuat dua penumpang di satu kursi.
Ringkasan
- Tulis layout armada sebagai teks; satu parser melayani denah browser dan validasi server.
- Jadwal berulang per hari, perjalanan diidentifikasi tanggal + kode jadwal.
- Hitung jam tiba dari durasi, termasuk tanda tiba besok; tutup penjualan beberapa menit sebelum berangkat.
- Cek kursi ulang di dalam LockService dan pastikan kursi benar-benar ada di layout.
- Kursi menunggu bayar ditahan sementara, dilepas otomatis lewat perhitungan saat baca, dan dicek ulang sebelum dilunasi.
Download source code lengkapnya
Versi lengkapnya sudah termasuk landing page boarding pass dengan kotak pencarian, rute populer, armada, FAQ, denah kursi interaktif, nama per penumpang, titik jemput, e-tiket siap cetak, cek tiket, panel operator dengan manifest dan check-in per kursi, pengingat WhatsApp, laporan per rute dan jam, serta ekspor CSV. Semua berjalan di Google Sheets tanpa hosting.
Source code lengkapnya gratis
Lihat semua yang dibahas di artikel ini langsung berjalan.