Cara Membuat Aplikasi Inventaris Stok dengan Google Spreadsheet & Apps Script
7 menit baca · Diperbarui 2026-07-22
Stok fisik di gudang gampang meleset dari catatan. Barang keluar tapi lupa dicatat, dua orang input di jam yang sama, angka stok jadi kacau. Google Spreadsheet plus Apps Script sebenarnya cukup buat bikin aplikasi inventaris yang rapi: tiap barang masuk dan keluar tercatat, stok berjalan otomatis, ada kartu stok per barang, dan sistem menegur waktu stok mau habis.
Satu hal yang bikin aplikasi stok bikinan sendiri sering berantakan: dua transaksi masuk barengan lalu saling menimpa angka stok. Kita tutup celah itu dari awal pakai LockService dan baca-tulis borongan (getValues/setValues), bukan sel per sel. Kalau kamu belum pernah menyentuh Apps Script sama sekali, mampir dulu ke pengenalan Apps Script.
Yang kita bangun
- Catat barang masuk dan keluar lewat satu form.
- Stok berjalan yang otomatis naik-turun tiap transaksi.
- Kartu stok: riwayat pergerakan lengkap per barang, plus saldo setelah tiap transaksi.
- Alert stok menipis yang kirim email otomatis tiap hari.
- Kunci antar-transaksi biar stok tidak bentrok saat banyak orang input bareng.
Struktur data: cukup dua tab
Buka satu Spreadsheet, siapkan dua tab. Keputusan struktur ini yang bikin sisa kodenya jadi pendek.
Tab Barang (master stok)
- Kolom A: SKU (kode unik barang)
- Kolom B: Nama
- Kolom C: Stok (saldo berjalan)
- Kolom D: MinStok (ambang alert)
- Kolom E: Satuan (pcs, box, kg)
Tab Mutasi (kartu stok)
- Kolom A: Tanggal
- Kolom B: SKU
- Kolom C: Tipe (MASUK / KELUAR)
- Kolom D: Qty
- Kolom E: Keterangan
- Kolom F: StokSesudah (saldo setelah transaksi ini)
Kunci stok biar tidak bentrok
Inti aplikasi ada di satu fungsi server: catatMutasi. Fungsi ini dipanggil tiap ada barang masuk atau keluar, dan wajib mematuhi dua aturan. Pertama, bungkus dengan LockService supaya transaksi kedua antre sampai yang pertama selesai, jadi stok baru selalu dihitung dari angka terkini. Kedua, baca tab Barang sekali lewat getValues() lalu cari barisnya di memori, bukan getValue() di dalam loop yang lambat dan boros kuota.
// Code.gs - dipanggil dari UI tiap ada barang masuk/keluar
const SS = SpreadsheetApp.getActiveSpreadsheet();
function catatMutasi(sku, tipe, qty, keterangan) {
qty = Number(qty);
if (!sku || !qty || qty <= 0) throw new Error('SKU dan qty wajib, qty harus > 0');
if (tipe !== 'MASUK' && tipe !== 'KELUAR') throw new Error('Tipe harus MASUK atau KELUAR');
const lock = LockService.getScriptLock();
lock.waitLock(30000); // transaksi lain antre maksimal 30 detik
try {
const shBarang = SS.getSheetByName('Barang');
const data = shBarang.getDataRange().getValues(); // baca borongan sekali
// header: [SKU, Nama, Stok, MinStok, Satuan]
let row = -1;
for (let i = 1; i < data.length; i++) {
if (String(data[i][0]) === String(sku)) { row = i; break; }
}
if (row === -1) throw new Error('SKU tidak ditemukan: ' + sku);
const stokLama = Number(data[row][2]) || 0;
const stokBaru = tipe === 'MASUK' ? stokLama + qty : stokLama - qty;
if (stokBaru < 0) throw new Error('Stok tidak cukup. Sisa ' + stokLama);
// tulis balik hanya sel Stok (kolom C) di baris yang ketemu
shBarang.getRange(row + 1, 3).setValue(stokBaru);
// catat ke kartu stok
SS.getSheetByName('Mutasi')
.appendRow([new Date(), sku, tipe, qty, keterangan || '', stokBaru]);
return { sku: sku, stokBaru: stokBaru };
} finally {
lock.releaseLock(); // wajib, biar antrean lanjut
}
}Perhatikan alurnya: baca seluruh tab Barang sekali, cari barisnya di memori, hitung stok baru, baru tulis balik satu sel. Tanpa lock, dua orang yang input di detik yang sama bisa sama-sama membaca stok 10, sama-sama menghitung dari 10, dan hasil akhirnya salah. waitLock(30000) yang mencegah itu. Pola baca-tulis borongan yang dipakai di sini dibahas lebih dalam di panduan baca-tulis data Sheet.
Membaca stok dan kartu stok
Dua fungsi baca yang nanti dipanggil UI, plus alert yang menumpang di getStok. Semuanya baca borongan, jadi ringan walau barangnya banyak.
// --- BACA ---
// getStok: seluruh barang + flag stok menipis (tabel utama di UI)
function getStok() {
const data = SS.getSheetByName('Barang').getDataRange().getValues();
return data.slice(1).map(function (r) {
const stok = Number(r[2]) || 0;
const min = Number(r[3]) || 0;
return { sku: r[0], nama: r[1], stok: stok, minStok: min,
satuan: r[4], menipis: stok <= min };
});
}
// getKartuStok: riwayat pergerakan satu SKU
function getKartuStok(sku) {
const data = SS.getSheetByName('Mutasi').getDataRange().getValues();
const out = [];
for (let i = 1; i < data.length; i++) {
if (String(data[i][1]) === String(sku)) {
out.push({ tanggal: data[i][0], tipe: data[i][2], qty: data[i][3],
keterangan: data[i][4], stokSesudah: data[i][5] });
}
}
return out;
}
// --- ALERT (pasang di trigger harian) ---
function alertStokMenipis() {
const menipis = getStok().filter(function (b) { return b.menipis; });
if (menipis.length === 0) return; // semua aman, tidak usah kirim
let body = 'Barang yang perlu di-restock:\n\n';
menipis.forEach(function (b) {
body += '- ' + b.nama + ' (' + b.sku + '): sisa ' + b.stok +
' ' + b.satuan + ', min ' + b.minStok + '\n';
});
MailApp.sendEmail('gudang@contoh.com',
'Stok menipis: ' + menipis.length + ' barang', body);
}getStok mengembalikan semua barang dengan flag menipis (stok sudah menyentuh atau di bawah MinStok), pas buat tabel utama. getKartuStok menyaring riwayat satu SKU. alertStokMenipis tinggal menyaring yang menipis lalu mengirim email. Supaya jalan sendiri tiap pagi, pasang lewat trigger berjadwal seperti dijelaskan di cara menjadwalkan trigger Apps Script.
Bungkus jadi Web App
Biar operator gudang tidak menyentuh Spreadsheet mentah, sajikan sebagai halaman. doGet(e) mengembalikan HtmlService, dan form-nya memanggil server lewat google.script.run, bukan fetch. Kalau operatormu input dari HP sambil keliling gudang, lihat form input data dari HP, dan untuk kerangka Web App-nya ada cara membuat web app dari Spreadsheet.
// ===== Code.gs =====
function doGet() {
return HtmlService.createHtmlOutputFromFile('index')
.setTitle('Inventaris Stok')
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
<!-- ===== index.html ===== -->
<input id="sku" placeholder="SKU">
<input id="qty" type="number" placeholder="Qty">
<button id="simpan">Barang masuk</button>
<p id="status"></p>
<script>
document.getElementById('simpan').addEventListener('click', function () {
var sku = document.getElementById('sku').value;
var qty = document.getElementById('qty').value;
google.script.run
.withSuccessHandler(function (res) {
document.getElementById('status').textContent =
'Stok ' + res.sku + ' sekarang ' + res.stokBaru;
})
.withFailureHandler(function (err) {
document.getElementById('status').textContent = 'Gagal: ' + err.message;
})
.catatMutasi(sku, 'MASUK', qty, 'Input dari web');
});
</script>Lihat contoh PRD Web GAS (KasirSheet) →Batasan yang harus disadari
- Satu eksekusi maksimal sekitar 6 menit di akun gratis. Aman buat ratusan hingga ribuan baris, tapi kalau tab Mutasi sudah puluhan ribu baris, arsipkan riwayat lama ke tab terpisah.
- Ada kuota harian untuk kirim email dan pemanggilan layanan. Rinciannya di [batas kuota Apps Script](/panduan/batas-kuota-google-apps-script).
- LockService bikin transaksi antre, bukan paralel. Untuk gudang dengan input sangat padat, antrean bisa kena timeout 30 detik; naikkan angka waitLock atau kelompokkan input.
- Spreadsheet bukan database beneran. Cocok untuk satu toko atau gudang. Kalau butuh multi-cabang real-time dengan ribuan transaksi per menit, ini sudah bukan tempatnya.
Rapikan idenya jadi PRD dulu
Kode di atas kelihatan pendek karena strukturnya sudah diputuskan lebih dulu: dua tab, beberapa fungsi, satu aturan penguncian. Bagian yang sering bikin proyek molor justru memikirkan struktur itu. Sebelum menyuruh Cursor atau Claude Code menuliskannya, tulis dulu PRD-nya, spesifikasi ringkas soal apa yang dibangun dan aturannya. Belum pernah? Ada cara membuat PRD untuk aplikasi.
Skematik punya mode Web GAS khusus untuk kasus seperti ini: dari deskripsi idemu, dia susun struktur tab Spreadsheet sebagai skema data, daftar fungsi server yang perlu ada, plus konvensi yang sadar batasan Apps Script (baca-tulis borongan, LockService, google.script.run). Hasilnya bisa di-export jadi setup-sheets.gs biar tab-nya kebentuk otomatis. Masih bingung merangkai idenya jadi kalimat? Ada panduan menjelaskan ide aplikasi ke AI.
Buat PRD Web GAS gratis di Skematik →