All Posts

18 Agustus 20268 menit

Beberapa bulan lalu saya mendapat kabar yang mungkin sudah tidak asing di telinga pemilik usaha retail. Internet di salah satu cabang toko sedang bermasalah, dan aplikasi kasir yang dipakai selama ini ikut mati. Antrean di depan kasir membengkak. Pelanggan mulai menoleh ke pintu keluar. Di cabang lain, stok yang kemarin dibilang tinggal dua ternyata kosong, padahal malam sebelumnya sudah dicatat ulang lewat telepon.

Masalah seperti ini sebenarnya bukan soal internet. Internet hanya kambing hitam. Akar masalahnya lebih dalam, yaitu operasional toko tidak punya satu sumber kebenaran. Data penjualan, stok, dan laporan tersebar di buku catatan, grup WhatsApp, dan ingatan kepala toko masing-masing.

Artikel ini bukan daftar fitur. Ini cerita tentang bagaimana Retail Management System (RMS) dibangun untuk menyelesaikan masalah itu, satu per satu, beserta keputusan teknis yang saya dan tim ambil di sepanjang jalan.

Operasional yang jalan lewat telepon dan buku catatan

Sebelum RMS ada, begini kira-kira alur sehari di klien ini. Toko punya beberapa cabang di kota yang berbeda. Tiap malam, kepala toko di masing-masing cabang menghitung uang kas, menulis ulang stok yang tersisa, lalu melaporkan lewat telepon atau pesan ke pusat. Paginya, admin pusat membandingkan angka itu satu per satu.

Satu-satunya cara transfer barang antar cabang adalah teleponan "Cabang A butuh dua puluh dus ini, besok kirim ya." Tidak ada bukti tertulis. Tidak ada status. Barang sampai, baru ketahuan, kadang jumlahnya kurang karena salah catat.

Laporan penjualan bulanan baru tersusun beberapa hari setelah bulan berakhir. Sementara itu keputusan pembelian tetap jalan berdasarkan tebakan.

Kalau digambarkan, semua proses bergantung pada tiga hal yang paling rawan salah, yaitu manusia, telepon, dan ingatan.

Masalah sebenarnya bukan cuma internet

Waktu saya duduk bersama tim, kami memetakan masalah ini dan menemukan hal yang lebih menarik untuk dibedah. Masalahnya bukan "internet sering mati". Masalahnya ada di lima hal ini.

  • Data penjualan tidak pernah sinkron antar cabang, kapan pun koneksinya.
  • Stok transfer antar toko manual, jadi rawan salah hitung.
  • Laporan telat, jadi keputusan belanja selalu terlambat.
  • Pemilik tidak bisa memantau semua cabang dari satu tempat.
  • Tidak ada batasan akses, jadi semua orang bisa melihat semua data, termasuk yang bukan urusannya.

Pola ini penting. Lima masalah satu akar, tidak ada satu sistem terpusat yang bisa dipercaya semua cabang. Solusinya bukan beli mesin kasir baru. Solusinya membangun sistem yang menjadi sumber kebenaran tunggal, yang tetap bisa bekerja saat koneksi putus.

Keputusan bangun sistem sendiri

Kami sempat mempertimbangkan aplikasi POS yang sudah jadi. Tapi kebutuhan klien ini cukup spesifik seperti multi-cabang, sinkronisasi offline, alur distribusi antar toko, dan laporan yang harus muncul per cabang maupun gabungan. Mengotak-atik aplikasi jadi untuk kebutuhan sekompleks ini biasanya lebih mahal daripada membangun dari nol dengan arsitektur yang memang dirancang untuk itu.

Jadi kami membangun RMS. Modular, satu sistem untuk semua cabang, dengan terminal kasir yang ringan dan bisa diandalkan.

Arsitektur yang dipilih

RMS dibangun dengan pola client-server yang terpusat. Satu API melayani semua cabang, dan setiap cabang terhubung ke sana.

KomponenTeknologi
BackendLaravel 12 (PHP 8.4)
AdminVue 3 + Vuetify 3
Terminal POSVue 3 versi ringan
DatabasePostgreSQL (produksi), MySQL (lokal)
Antrian dan cacheRedis
Autentikasi APILaravel Sanctum

Kenapa pola ini? Karena satu API terpusat membuat laporan jadi konsisten secara otomatis, semua cabang menulis ke sumber data yang sama, jadi tidak ada lagi versi kebenaran yang berbeda-beda.

Admin dibangun dengan Vue 3 dan Vuetify 3 karena kebutuhan manajemen data yang padat seperti tabel, filter, form, dan status yang berubah cepat. Terminal POS sengaja dipisah menjadi aplikasi Vue 3 yang jauh lebih ringan, tanpa semua modul admin. Alasan teknisnya sederhana, kebanyakan perangkat kasir di toko speknya pas-pasan, dan memuat aplikasi admin penuh di sana tidak realistis.

Fitur paling menentukan: kasir yang tetap jalan tanpa internet

Ini inti ceritanya. Internet di tempat cabang-cabang berada memang tidak stabil, dan kasir tidak boleh berhenti hanya karena koneksi putus.

Solusinya adalah pendekatan offline-first. Terminal POS mencatat transaksi ke antrian lokal di perangkat dulu. Begitu koneksi kembali, antrian itu otomatis dikirim ke server melalui Redis queue, tanpa perlu tindakan dari kasir. Kasir tidak pernah sadar sedang offline atau online. Yang dia lihat hanyalah layar yang tetap bekerja.

Bagian yang paling menantang bukan sinkronisasinya, tapi penanganan konflik. Contoh kasus nyata adalah harga barang berubah saat kasir sedang offline. Kalau transaksi yang tercatat saat offline langsung dipaksa masuk, laporan bisa salah. Karena itu setiap transaksi offline divalidasi ulang di sisi server saat sinkron. Kalau ada yang tidak cocok, transaksi ditandai dan butuh konfirmasi, bukan diam-diam dihapus.

Desain ini mengubah pengalaman operasional secara mendasar. Koneksi mati tidak lagi berarti toko tutup. Koneksi mati berarti antrian, dan antrian selalu terselesaikan.

Semua orang tidak perlu melihat semua data

Masalah berikutnya soal akses. Sebelumnya semua orang yang pegang akun admin bisa melihat data apa pun, termasuk gaji dan margin di semua cabang. Itu tidak sehat, baik secara keamanan maupun psikologi kerja.

RMS menerapkan RBAC, role-based access control, dengan lima peran yang dipisahkan tegas.

  • Owner, akses penuh ke semua cabang dan data strategis.
  • Admin Pusat, mengelola master data, operasional pusat, dan audit.
  • Procurement, khusus pengadaan barang dan distributor.
  • Kepala Toko, mengelola stok dan laporan di cabangnya sendiri.
  • Kasir, hanya transaksi harian di terminal POS.

Setiap peran juga dibatasi oleh scope cabang. Kepala Toko cabang A tidak akan pernah melihat angka cabang B. Autentikasi token ditangani Laravel Sanctum, dan setiap request API diperiksa perannya dulu sebelum dieksekusi.

Pelajarannya di sini adalah batasan akses bukanlah fitur pelengkap. Batasan akses adalah bagian dari desain operasional. Ketika setiap orang hanya melihat yang menjadi urusannya, kesalahan menurun dan kepercayaan justru naik.

Stok antar cabang, tanpa teleponan

Transfer stok dulu dimulai dengan telepon dan diakhiri dengan "semoga sampai dengan benar". Di RMS, alur ini menjadi workflow yang tercatat dari awal sampai akhir.

Kepala toko membuat stock request lewat sistem. Admin pusat atau procurement menyetujui, lalu distribusi disiapkan sebagai distribution order. Barang yang menuju beberapa cabang bisa digabung dalam satu consolidated shipment, jadi pengiriman lebih efisien dan ongkos lebih hemat. Cabang penerima menandai barang sebagai diterima, dan stok kedua sisi langsung diperbarui di sumber yang sama.

Tidak ada lagi perdebatan "kamu bilang dua puluh, saya terima delapan belas". Semua tercatat, semua punya status, semua bisa diaudit.

Laporan yang sudah siap sebelum pagi

Dulu rekap penjualan makan waktu semalaman. Sekarang, laporan penjualan harian, valuasi stok, dan performa produk dihitung otomatis dari data yang sudah masuk. Pemilik membuka dashboard di pagi hari, dan angka kemarin sudah ada di sana, lengkap per cabang maupun gabungan.

Ini dampak yang paling terasa bagi pemilik. Keputusan tidak lagi menunggu manusia menyusun angka. Angka sudah tersedia, tinggal dibaca.

Urusan yang tidak terlihat di balik layar

Selain lima masalah utama, ada dua hal yang ikut dibangun karena ekosistem retail memang butuh keduanya, yaitu procurement dan loyalty.

Procurement menangani purchase order ke pemasok dan verifikasi goods receipt saat barang tiba. Jadi pembelian ke supplier juga tercatat rapi, dari pesanan sampai barang masuk gudang. Loyalty member mencatat poin, riwayat transaksi, dan status aktif pelanggan, yang cukup membantu cabang menjaga pelanggan setia.

Keduanya menegaskan satu hal yang sama, yaitu retail sehat bukan hanya soal kasir yang jalan, tapi soal seluruh rantai yang tercatat.

Dari laptop ke produksi

Bagian terakhir yang sering dilupakan pada proyek serupa adalah operasional server. RMS dirancang agar mudah dideploy. Satu container Docker berisi Nginx dan PHP-FPM, dengan Redis dan PostgreSQL sebagai pendukung. Untuk platform seperti Render, tinggal buat Web Service, set environment variable, dan jalankan migrasi. Untuk VPS, tersedia skrip deploy yang mengurus optimasi Laravel, build aset, dan setup worker antrian.

Migrasi database sengaja tidak otomatis demi keamanan data. Semua migrasi dijalankan manual dengan flag force, sehingga tidak ada perubahan struktur yang terjadi tanpa disengaja.

Pelajaran teknis singkat dari bagian ini adalah aplikasi yang bagus di laptop belum jadi apa-apa. Dia baru berarti saat proses naik ke produksinya serapi fiturnya.

Pelajaran yang saya bawa

Setelah proyek ini, ada beberapa hal yang saya bawa.

  • Offline-first bukan fitur bonus, tapi kebutuhan nyata di Indonesia. Mendesain aplikasi yang tetap bekerja saat koneksi putus adalah bagian dari memahami pengguna kita.
  • Desain peran menentukan aman tidaknya sistem. RBAC yang dipikirkan sejak awal jauh lebih murah daripada menambal akses di tengah jalan.
  • Workflow kecil berdampak besar. Stock request dan transfer yang tampak sepele ternyata menghilangkan kesalahan yang selama ini dianggap wajar.
  • Infrastruktur adalah fitur. Deploy yang mudah berarti perbaikan cepat, dan perbaikan cepat berarti sistem yang terus hidup.

Penutup

Cerita RMS ini adalah satu dari sekian banyak contoh bahwa masalah operasional retail di Indonesia tidak selalu butuh solusi yang besar. Kadang cukup satu sistem yang dirancang dengan memahami masalahnya dulu, baru teknologinya.

Kalau kamu sedang bergelut dengan masalah serupa, atau sekadar ingin diskusi soal arsitektur aplikasi retail, kabari saya. Kotak masuk email saya selalu terbuka.