Perusahaan yang mengimplementasikan pelacakan inventaris real-time melalui sistem ERP melaporkan peningkatan akurasi inventaris sebesar 35% dan peningkatan efisiensi operasional sebesar 40%. Namun hanya 23% UKM yang telah berinvestasi pada alat inventaris real-time. Di Indonesia, di mana rantai pasokan kompleks dan operasi gudang sering mencakup beberapa lokasi, visibilitas real-time adalah perbedaan antara keputusan pengadaan yang percaya diri dan kehabisan stok yang mahal. Di Commsult, saya membangun modul inventaris yang melacak setiap pergerakan stok sebagai event yang tidak dapat diubah dan menampilkan data langsung ke staf gudang dan dashboard manajemen.
Keputusan desain inti: lacak inventaris sebagai aliran event, bukan kuantitas yang dapat diubah. Setiap penerimaan, pengeluaran, transfer, penyesuaian, atau pengembalian membuat baris stock_movements yang tidak dapat diubah. Kuantitas saat ini untuk item apa pun di lokasi mana pun selalu dihitung sebagai jumlah pergerakan. Ini memberikan audit trail bawaan, membuat rekonstruksi historis menjadi mudah, dan menghilangkan masalah 'ghost stock'.
Kami mendukung enam jenis pergerakan: GR (Penerimaan Barang dari PO), GI (Pengeluaran Barang ke produksi atau pelanggan), TR (Transfer antar lokasi), ADJ+ (Penyesuaian Positif), ADJ- (Penyesuaian Negatif), dan RTN (Pengembalian dari pelanggan atau ke vendor). Setiap pergerakan mencatat: item_id, location_id, quantity, movement_type, reference_id, unit_cost, dan created_by.
Inventory Event Ledger Architecture (NestJS + PostgreSQL)
┌─────────────────────────────────────────────────────┐
│ TRANSACTION SOURCES │
│ Purchase Receipt │ Sales Issue │ Transfer │ Adjust │
└──────────────────────┬──────────────────────────────┘
│ create_movement()
▼
┌─────────────────────────────────────────────────────┐
│ stock_movements (append-only ledger) │
│──────────────────────────────────────────────────── │
│ id UUID PRIMARY KEY │
│ item_id UUID REFERENCES items(id) │
│ location_id UUID REFERENCES locations(id) │
│ quantity NUMERIC (+ve always) │
│ movement_type VARCHAR GR|GI|TR|ADJ+|ADJ-|RTN │
│ reference_id UUID (PO, SO, or transfer_order) │
│ unit_cost NUMERIC │
│ created_by UUID REFERENCES users(id) │
│ created_at TIMESTAMPTZ DEFAULT NOW() │
└──────────────────────┬──────────────────────────────┘
│
┌──────────────┴──────────────┐
▼ ▼
┌──────────────────┐ ┌─────────────────────────┐
│ Materialized View│ │ WebSocket NOTIFY │
│ current_stock │ │ → React UI updates live │
│ (refresh 5min) │ │ → No polling needed │
└──────────────────┘ └─────────────────────────┘Dari pengalaman saya membangun sistem ERP di Commsult: jangan pernah izinkan pengeditan langsung kuantitas inventaris melalui UI. Semua perubahan stok harus melalui movement engine, bahkan penyesuaian manual. Ini terlihat membatasi sampai audit pertama — lalu klien bersyukur dapat menelusuri setiap perubahan kuantitas ke tindakan pengguna dan timestamp.
Skema kami mendukung model lokasi hierarkis: Gudang → Zona → Lorong → Bin. Item dapat berada di beberapa bin secara bersamaan. Transfer order memindahkan kuantitas dari satu bin ke bin lain, membuat dua baris pergerakan (GI dari sumber dan GR di tujuan). Transfer bersifat atomik — kedua baris di-commit dalam transaksi PostgreSQL yang sama.
Setiap item memiliki reorder_point dan reorder_qty yang dapat dikonfigurasi. Job cron NestJS berjalan setiap malam dan memeriksa semua item di mana stok saat ini telah turun di bawah titik reorder. Untuk setiap kecocokan, sistem membuat draft Purchase Request di modul pengadaan, sudah diisi dengan vendor pilihan dan kuantitas reorder. Manajer gudang meninjau dan menyetujui PR draft ini setiap pagi.
-- PostgreSQL: Validated stock movement function
CREATE OR REPLACE FUNCTION create_movement(
p_item_id UUID,
p_location_id UUID,
p_quantity NUMERIC,
p_type VARCHAR,
p_reference_id UUID,
p_unit_cost NUMERIC,
p_user_id UUID
) RETURNS UUID AS $$
DECLARE
v_current_stock NUMERIC;
v_movement_id UUID;
BEGIN
-- Validate sufficient stock for outbound movements
IF p_type IN ('GI', 'TR') THEN
SELECT COALESCE(SUM(
CASE WHEN movement_type IN ('GR','ADJ+','RTN') THEN quantity
ELSE -quantity END
), 0)
INTO v_current_stock
FROM stock_movements
WHERE item_id = p_item_id AND location_id = p_location_id;
IF v_current_stock < p_quantity THEN
RAISE EXCEPTION 'Insufficient stock: % available, % requested',
v_current_stock, p_quantity;
END IF;
END IF;
INSERT INTO stock_movements
(item_id, location_id, quantity, movement_type,
reference_id, unit_cost, created_by)
VALUES
(p_item_id, p_location_id, p_quantity, p_type,
p_reference_id, p_unit_cost, p_user_id)
RETURNING id INTO v_movement_id;
-- Notify connected WebSocket clients
PERFORM pg_notify('inventory_update',
json_build_object('item_id', p_item_id,
'location_id', p_location_id)::text);
RETURN v_movement_id;
END;
$$ LANGUAGE plpgsql;Modul inventaris berjalan di NestJS dengan TypeORM. Kami menggunakan fungsi PostgreSQL create_movement() yang memvalidasi pergerakan, memasukkan baris, dan memicu NOTIFY untuk me-refresh klien yang terhubung via WebSocket. UI gudang React berlangganan WebSocket ini dan memperbarui kuantitas yang ditampilkan secara real-time — tidak perlu polling.
Jika Anda menggunakan materialized view untuk total inventaris, Anda harus menangani jendela ketidaktepatan dengan hati-hati. Selama sesi penerimaan volume tinggi, kuantitas langsung dalam materialized view mungkin tertinggal hingga 5 menit. Kami menambahkan endpoint 'live balance' yang melakukan query agregasi real-time untuk item spesifik yang sedang dilihat. Jangan pernah tampilkan total yang basi di layar picking — staf gudang yang bertindak berdasarkan data basi menyebabkan masalah operasional nyata.
Manajemen mendapatkan dashboard yang menampilkan: total nilai inventaris berdasarkan kategori, item yang bergerak lambat (tidak ada pergerakan dalam 90 hari), item di bawah titik reorder, item dengan stok negatif (tanda masalah integritas data), dan perputaran inventaris bulanan berdasarkan kategori.
Penghitungan fisik tahunan atau kuartalan adalah kenyataan di gudang Indonesia, terutama untuk bisnis dengan persyaratan audit PKWT. Sistem kami mendukung workflow 'freeze and count': kunci lokasi (tidak ada pergerakan yang diizinkan selama penghitungan), rekam kuantitas penghitungan fisik item per item, hitung varians terhadap kuantitas sistem, buat pergerakan ADJ untuk setiap varians, dan buka kunci lokasi.