Ada tiga pilihan utama: scanner genggam khusus (Zebra, Honeywell — tahan banting tapi ~Rp 3–8 juta per unit), ponsel dengan pemindaian berbasis kamera (hardware gratis tapi lebih lambat), dan pembaca tetap di pintu dok (Rp 15–30 juta, ideal untuk penerimaan volume tinggi). Untuk klien Commsult dipilih pendekatan hybrid — pembaca tetap di dok penerimaan dan ponsel yang menjalankan aplikasi scanning PWA — untuk menyeimbangkan biaya dan performa.
Barcode produsen (GS1) sering tidak terdaftar dalam master item ERP, sehingga pemindaiannya tidak menghasilkan kecocokan dan membuang waktu. Mencetak label Code 128 yang mengkodekan SKU internal menggunakan ZPL yang dikirim ke printer label Zebra memastikan setiap pemindaian langsung teridentifikasi. Setiap label memuat barcode, kode item, nama item, dan nomor lot — setup awalnya hanya membutuhkan sekitar satu hari tetapi menghemat berjam-jam waktu pencarian setiap minggu.
PWA mengimplementasikan pendekatan offline-first: PO hari ini dan master item di-cache secara lokal menggunakan IndexedDB sehingga pemindaian tetap berjalan meski koneksi terputus. Pemindaian yang dilakukan saat offline akan masuk antrean lokal dan otomatis disinkronkan ke backend NestJS begitu konektivitas pulih. Ini mencegah kegagalan di tengah proses penerimaan yang bisa membuat frustasi staf gudang yang bekerja di sudut atau dekat rak logam besar.
Saat staf gudang memindai item dalam pick order, sistem memvalidasi bahwa barcode yang dipindai cocok dengan baris pick order yang dimaksud. Jika item yang salah dipindai, suara error langsung diputar dan pesan peringatan ditampilkan saat itu juga. Pengecekan real-time ini menghentikan produk yang salah sebelum sampai ke pelanggan, mencegah kesalahan menyebar lebih jauh.
Setiap penerimaan barang masuk mencatat nomor lot dan tanggal produksi yang dikodekan dalam barcode GS1-128 menggunakan Application Identifier 10 (lot) dan 17 (tanggal kedaluwarsa). Stok dilacak berdasarkan item, lot, dan lokasi, dan picking keluar menggunakan FEFO (First Expired First Out) untuk otomatis memilih lot dengan kedaluwarsa paling awal. ERP juga memperingatkan manajer gudang 30 hari sebelum lot mana pun kedaluwarsa, memberikan waktu untuk menggunakan atau mengembalikan stok tersebut.
Sistem barcode mengurangi kesalahan manusia dengan mengotomatisasi entri data, dan pemindaian barcode lebih cepat dari entri manual untuk proses penerimaan dan picking, memberikan pembaruan inventaris real-time. Untuk gudang Indonesia yang masih mengandalkan lembar penerimaan tulisan tangan dan kartu inventaris manual, integrasi barcode scanning merupakan perubahan signifikan dalam akurasi dan kecepatan.
Ada tiga pilihan hardware untuk barcode scanning dalam integrasi gudang ERP: scanner barcode genggam khusus (Zebra, Honeywell — tahan banting, cepat, mahal ~Rp 3-8 juta per unit), ponsel dengan pemindaian berbasis kamera (gunakan smartphone perusahaan yang ada — hardware gratis tapi pemindaian lebih lambat), dan pembaca barcode tetap di pintu dok untuk penerimaan (Zebra fixed reader — ideal untuk penerimaan volume tinggi, Rp 15-30 juta). Untuk klien ini, saya menggunakan pendekatan hybrid: pembaca tetap di dok penerimaan dan ponsel dengan aplikasi scanning PWA untuk staf gudang.
Saya membangun Progressive Web App khusus untuk operasi gudang — aplikasi React mobile-first yang berjalan di browser tanpa instalasi app store. Pemindaian barcode diimplementasikan menggunakan QuaggaJS (barcode 1D seperti Code 128, EAN-13) dan @zxing/browser (kode QR dan barcode 2D). Pemindaian yang berhasil memicu getaran haptic dan bunyi bip audio, kemudian memuat detail item yang dipindai.
Warehouse Barcode Scanning System
Hardware Layer:
┌───────────────────┐ ┌─────────────────────┐
│ Smartphone Camera │ │ Zebra ZT410 Printer │
│ (PWA scanning) │ │ → prints Code 128 │
│ QuaggaJS + zxing │ │ labels via ZPL │
└─────────┬─────────┘ └─────────────────────┘
│
▼ (offline-first via IndexedDB)
┌───────────────────────────────────────────────┐
│ React PWA (Cloudflare Pages) │
│ │
│ Scan → Lookup item → Confirm qty → Submit │
│ │
│ Offline mode: queue scans in IndexedDB │
│ Sync when online: POST /api/movements/batch │
└─────────────────────┬─────────────────────────┘
│ (online sync)
▼
┌───────────────────────────────────────────────┐
│ NestJS WarehouseModule │
│ │
│ POST /goods-receipts (GR scanning) │
│ POST /pick-orders/scan (picking) │
│ POST /cycle-count/submit (stocktake) │
│ │
│ → optimistic locking for concurrent scans │
│ → create_movement() PostgreSQL function │
└───────────────────────────────────────────────┘
Label format (Code 128 + GS1-128 for lots):
┌─────────────────────────────────────────────┐
│ ▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌ │
│ SKU: MAT-001234 Lot: L2025-0412 │
│ Bahan Baku A — 25kg │
│ Exp: 2026-04-12 │
└─────────────────────────────────────────────┘Dari pengalaman saya dalam pekerjaan konsultasi ERP lepas: cetak barcode Anda sendiri di label dan tempelkan pada kemasan produk Anda — jangan mengandalkan barcode produsen. Barcode produsen (GS1) sering tidak ada dalam master item ERP Anda, dan pemindaiannya tidak menghasilkan kecocokan. Saya mencetak barcode Code 128 yang mengkodekan kode item internal (SKU) menggunakan ZPL (Zebra Programming Language) yang dikirim ke printer label Zebra.
Alur penerimaan barang (GR): staf gudang menerima pengiriman fisik, membuka layar GR di PWA, memilih Purchase Order yang cocok dari daftar pengiriman yang diharapkan, dan memindai setiap barcode item. Setiap pemindaian: mencari item di ERP, menampilkan nama item dan kuantitas yang diharapkan dari PO, meminta kuantitas yang diterima (default 1, dapat disesuaikan), dan menambahkan ke daftar GR.
Untuk picking keluar, alur picking adalah: staf gudang membuka Pick Order, memindai setiap item saat dipilih dari rak, mengkonfirmasi lokasi bin dengan memindai barcode rak, dan menandai item sebagai dipilih. Sistem memvalidasi item yang dipindai cocok dengan baris pick order — jika item yang salah dipindai, suara error diputar dan pesan peringatan ditampilkan. Pendekatan scan-to-verify ini mencegah kesalahan pick.
// React PWA: Barcode scanning with QuaggaJS
import Quagga from 'quagga';
import { openDB } from 'idb'; // IndexedDB for offline storage
function BarcodeScanner({ onScan }: { onScan: (code: string) => void }) {
useEffect(() => {
Quagga.init({
inputStream: {
type: 'LiveStream',
target: document.querySelector('#scanner-container'),
constraints: { facingMode: 'environment' },
},
decoder: { readers: ['code_128_reader', 'ean_reader'] },
}, (err) => {
if (err) { console.error(err); return; }
Quagga.start();
});
Quagga.onDetected((result) => {
const code = result.codeResult.code;
if (code) {
navigator.vibrate?.(100); // haptic feedback
onScan(code);
Quagga.stop();
}
});
return () => Quagga.stop();
}, [onScan]);
return <div id="scanner-container" className="w-full h-64" />;
}
// Offline-first: queue scan when offline, sync when online
const db = await openDB('warehouse-offline', 1, {
upgrade(db) {
db.createObjectStore('pending-scans', { keyPath: 'id', autoIncrement: true });
},
});
async function submitScan(scan: WarehouseScan) {
if (navigator.onLine) {
await fetch('/api/movements', { method: 'POST', body: JSON.stringify(scan) });
} else {
await db.add('pending-scans', { ...scan, timestamp: Date.now() });
}
}
// Sync on reconnect
window.addEventListener('online', async () => {
const pending = await db.getAll('pending-scans');
for (const scan of pending) {
await fetch('/api/movements/batch', {
method: 'POST', body: JSON.stringify(scan)
});
await db.delete('pending-scans', scan.id);
}
});Sistem scanning terdiri dari: NestJS WarehouseModule (API penerimaan barang, pick order, dan manajemen bin), React PWA yang di-deploy ke Cloudflare Pages (untuk CDN global dan HTTPS tanpa konfigurasi), dan printer label Zebra ZT410 untuk pembuatan label barcode. API NestJS menggunakan optimistic locking saat menulis pergerakan stok.
Gudang sering memiliki cakupan WiFi yang buruk di sudut, di belakang rak, dan dekat struktur logam besar. Aplikasi scanning yang memerlukan konektivitas internet terus-menerus akan membuat frustasi staf gudang ketika koneksi terputus di tengah penerimaan. Saya mengimplementasikan pemindaian offline-first: PWA meng-cache PO hari ini dan master item secara lokal menggunakan IndexedDB. Pemindaian bekerja offline — pemindaian mengantri secara lokal. Ketika konektivitas dipulihkan, antrian disinkronkan ke server secara otomatis.
Untuk klien manufaktur ini yang menangani material yang mudah rusak, pelacakan lot sangat penting. Setiap GR masuk menangkap nomor lot dan tanggal produksi. Barcode mengkodekan nomor lot (menggunakan GS1-128 dengan Application Identifier 10 untuk lot dan 17 untuk tanggal kedaluwarsa). Sistem inventaris melacak stok berdasarkan item + lot + lokasi. Picking FEFO (First Expired First Out) secara otomatis memilih lot dengan tanggal kedaluwarsa paling awal.
Cycle count reguler (alih-alih stocktake penuh tahunan) lebih akurat dan tidak terlalu mengganggu. Sistem ini mendukung tugas cycle count terjadwal: staf gudang menerima tugas penghitungan di PWA mereka yang menunjukkan bin mana yang harus dihitung. Mereka pergi ke setiap bin, memindai barcode bin (untuk mengkonfirmasi lokasi), kemudian memindai setiap item dan memasukkan hitungan fisik.