Membangun Sistem POS Cafe Mobile-First dengan Google Apps Script dan Tailwind CSS
Membangun sistem Point of Sale (POS) untuk bisnis F&B skala kecil hingga menengah seringkali terbentur pada biaya langganan perangkat lunak dan pengadaan perangkat keras khusus. Sebagai solusi alternatif yang efisien, kita dapat memanfaatkan ekosistem Google Workspace. Artikel ini mendokumentasikan langkah-langkah membangun sistem POS cafe yang mobile-friendly, memiliki antarmuka modern khas aplikasi komersial (terinspirasi dari Moka POS), mendukung sistem multi-user (Waiter, Kasir, Admin), serta dilengkapi fitur pembayaran QRIS dan split bill, semuanya berjalan secara serverless menggunakan Google Apps Script (GAS) dan Google Spreadsheet sebagai basis datanya.
Langkah 1: Persiapan Google Spreadsheet dan Backend Script
Langkah pertama adalah menyiapkan database sederhana menggunakan Google Spreadsheet. Spreadsheet ini akan menjadi tempat kita menyimpan data riwayat pesanan meja dan transaksi pembayaran. Buka Google Spreadsheet baru, lalu akses menu Ekstensi > Apps Script.
Salin kode berikut ke dalam file Code.gs. Skrip ini berfungsi sebagai backend untuk merender halaman HTML, mengelola status pesanan (LUNAS/BELUM_LUNAS), dan menyimpan data transaksi kasir secara otomatis.
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('POS Cafe Supono v2')
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
// Fungsi inisialisasi sheet otomatis
function getSheet(name, headers) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(name);
if (!sheet) {
sheet = ss.insertSheet(name);
sheet.appendRow(headers);
}
return sheet;
}
// WAITER: Menyimpan pesanan baru
function simpanPesananBaru(data) {
try {
var sheet = getSheet('Pesanan', ['ID_Pesanan', 'Meja', 'Item', 'Total', 'Sisa_Tagihan', 'Status', 'Waiter', 'Waktu']);
var idPesanan = "ORD-" + new Date().getTime();
var detailItem = data.items.map(function(i) { return i.nama + " (x" + i.qty + ")"; }).join(", ");
sheet.appendRow([
idPesanan, data.meja, detailItem, data.total, data.total, 'BELUM_LUNAS', data.waiter, new Date()
]);
return { status: 'success' };
} catch (e) {
return { status: 'error', message: e.toString() };
}
}
// KASIR: Mengambil daftar pesanan yang belum lunas
function getPesananAktif() {
var sheet = getSheet('Pesanan', []);
var data = sheet.getDataRange().getValues();
var pesanan = [];
for (var i = 1; i < data.length; i++) {
if (data[i][5] === 'BELUM_LUNAS') {
pesanan.push({
row: i + 1,
id: data[i][0],
meja: data[i][1],
item: data[i][2],
total: data[i][3],
sisa: data[i][4]
});
}
}
return pesanan;
}
// KASIR: Memproses pembayaran (Bisa full atau split/sebagian)
function prosesPembayaran(data) {
try {
var sheetPesanan = getSheet('Pesanan', []);
var sheetTransaksi = getSheet('Transaksi', ['ID_Pesanan', 'Nominal_Bayar', 'Metode', 'Kasir', 'Waktu']);
sheetTransaksi.appendRow([data.idPesanan, data.nominal, data.metode, data.kasir, new Date()]);
var sisaBaru = data.sisaAwal - data.nominal;
var statusBaru = sisaBaru <= 0 ? 'LUNAS' : 'BELUM_LUNAS';
sheetPesanan.getRange(data.row, 5).setValue(sisaBaru);
sheetPesanan.getRange(data.row, 6).setValue(statusBaru);
return { status: 'success', sisa: sisaBaru };
} catch (e) {
return { status: 'error', message: e.toString() };
}
}