Schema hanya menjadi hidup ketika operasi harian mulai berjalan. Gudang kita membutuhkan empat rutinitas: petugas menerima barang baru dari pemasok, mencatat barang masuk dan keluar, menonaktifkan produk yang tidak dijual lagi, dan menghapus data produk lama dengan aman. Pada bagian ini keempat rutinitas tersebut menjadi fungsi dalam satu skrip, lalu kita jalankan skenarionya sekaligus.
Empat Fungsi Inti #
| Fungsi | Operasi MongoDB | Catatan |
|---|---|---|
tambahProduk() | findOne() + insertOne() | Cek sku ganda sebelum insert |
catatTransaksi() | findOneAndUpdate() dengan $inc + $push | Stok dan riwayat berubah dalam satu operasi atomik |
nonaktifkanProduk() | updateOne() dengan $set | Menandai aktif: false beserta waktunya |
hapusProdukAman() | findOne() + deleteOne() | Menolak menghapus produk yang masih aktif |
Dua fungsi pertama memegang kontrak terpenting proyek: field stok harus selalu cocok dengan jumlah riwayat_stok. Kontrak itu ditegakkan dengan menaruh $inc dan $push di dalam satu operasi update — keduanya berhasil atau keduanya tidak terjadi, tidak ada kondisi setengah jalan.
Batas Riwayat #
Konstanta BATAS_RIWAYAT menahan pertumbuhan document. Setiap entri baru didorong lewat $push dengan $each dan $slice: -20, sehingga MongoDB menyisipkan entri lalu memangkas array menjadi 20 entri terakhir. Produk dengan perputaran tinggi tidak akan membengkak tanpa batas — kekhawatiran utama chapter Document Growth kini diterjemahkan menjadi satu baris kode.
Skrip CRUD #
// inventaris-crud.js — operasi CRUD sehari-hari gudang
// Jalankan setelah inventaris-seed.js:
// mongosh "mongodb://127.0.0.1:27017/?directConnection=true" --file src/inventaris-crud.js
var inv = db.getSiblingDB("inventaris")
const BATAS_RIWAYAT = 20
function tambahProduk(sku, nama, kategori, harga, stok, pemasok, tanggal) {
const sudah = inv.produk.findOne({ sku: sku }, { _id: 1 })
if (sudah) {
print("SKU sudah terdaftar:", sku)
return
}
inv.produk.insertOne({
sku: sku, nama: nama, kategori: kategori, harga: harga, stok: stok,
aktif: true, pemasok: pemasok,
riwayat_stok: [{ tanggal: tanggal, jenis: "masuk", jumlah: stok }],
})
print("produk baru:", sku, "-", nama)
}
function catatTransaksi(sku, jenis, jumlah, tanggal) {
const produk = inv.produk.findOne({ sku: sku })
if (!produk) {
print("produk tidak ditemukan:", sku)
return
}
if (jenis === "keluar" && jumlah > produk.stok) {
print("ditolak:", sku, "stok", produk.stok, "tidak cukup untuk keluar", jumlah)
return
}
const sesudah = inv.produk.findOneAndUpdate(
{ sku: sku },
{
$inc: { stok: jenis === "masuk" ? jumlah : -jumlah },
$push: {
riwayat_stok: {
$each: [{ tanggal: tanggal, jenis: jenis, jumlah: jumlah }],
$slice: -BATAS_RIWAYAT,
},
},
},
{ returnDocument: "after", projection: { stok: 1, riwayat_stok: 1 } }
)
print("transaksi:", sku, jenis, jumlah, "-> stok", sesudah.stok,
"| panjang riwayat:", sesudah.riwayat_stok.length)
}
function nonaktifkanProduk(sku, tanggal) {
const hasil = inv.produk.updateOne(
{ sku: sku },
{ $set: { aktif: false, nonaktif_pada: tanggal } }
)
print("nonaktifkan:", sku, "| cocok:", hasil.matchedCount, "| berubah:", hasil.modifiedCount)
}
function hapusProdukAman(sku) {
const produk = inv.produk.findOne({ sku: sku })
if (!produk) {
print("produk tidak ditemukan:", sku)
return
}
if (produk.aktif) {
print("ditolak:", sku, "masih aktif, nonaktifkan dulu sebelum menghapus")
return
}
const hasil = inv.produk.deleteOne({ sku: sku })
print("hapus:", sku, "| terhapus:", hasil.deletedCount)
}
print("== demo operasi CRUD ==")
tambahProduk(
"KB-004", "Keyboard Tenkeyless Hot-swap", "keyboard", 950000, 10,
{ nama: "PT Sinar Teknik", email: "[email protected]", kota: "Bandung" },
ISODate("2026-05-12T00:00:00Z")
)
catatTransaksi("KB-003", "masuk", 5, ISODate("2026-05-12T00:00:00Z"))
catatTransaksi("MS-002", "keluar", 2, ISODate("2026-05-12T00:00:00Z"))
catatTransaksi("MS-002", "keluar", 10, ISODate("2026-05-12T00:00:00Z"))
nonaktifkanProduk("KB-002", ISODate("2026-05-12T00:00:00Z"))
hapusProdukAman("KB-002")
hapusProdukAman("MN-001")Jalankan setelah seed:
mongosh "mongodb://127.0.0.1:27017/?directConnection=true" --file src/inventaris-crud.jsOutput yang Dihasilkan:
== demo operasi CRUD ==
produk baru: KB-004 - Keyboard Tenkeyless Hot-swap
transaksi: KB-003 masuk 5 -> stok 8 | panjang riwayat: 3
transaksi: MS-002 keluar 2 -> stok 4 | panjang riwayat: 3
ditolak: MS-002 stok 4 tidak cukup untuk keluar 10
nonaktifkan: KB-002 | cocok: 1 | berubah: 1
hapus: KB-002 | terhapus: 1
ditolak: MN-001 masih aktif, nonaktifkan dulu sebelum menghapusBedah Fungsi demi Fungsi #
tambahProduk()memeriksaskudenganfindOne()sebelum insert. Penjaga sederhana ini mencegah produk ganda pada kondisi data apa pun; pada Bagian 3 kita tambahkan lapisan kedua berupa index unik yang menolak duplikat langsung dari sisi server.catatTransaksi()membaca produk lebih dulu untuk memvalidasi stok cukup, lalu mengeksekusifindOneAndUpdate(). OpsireturnDocument: "after"mengembalikan document setelah perubahan sehingga skrip langsung mencetak stok terbaru tanpa query ulang. Operator$incmengubahstok,$pushdengan$eachplus$slice: -20menambah entri riwayat sekaligus memangkas array, dan semuanya berlangsung atomik dalam satu operasi.nonaktifkanProduk()hanya mengubah dua field. Produk nonaktif tetap tersimpan — riwayat dan nilainya masih bisa diaudit, tetapi laporan persediaan pada Bagian 4 akan menyaringnya lewataktif: true.hapusProdukAman()menerapkan pengaman dua langkah: produk harus dinonaktifkan lebih dulu barulah boleh dihapus. Baris terakhir output membuktikan penjaga tersebut bekerja — MN-001 yang masih aktif ditolak, sementara KB-002 yang sudah nonaktif terhapus dengandeletedCount: 1.
Perhatikan baris ketiga output: percobaan mengeluarkan 10 unit MS-002 ditolak karena stok tersisa 4. Validasi ini terjadi sebelum update, sehingga $inc negatif tidak pernah membuat stok menjadi angka minus.
Apa Berikutnya #
Data sembilan produk kini bertambah satu, berkurang satu, dan bergerak lima transaksi. Ukurannya masih kecil sehingga setiap query terasa cepat — pada Bagian 3 kita menyiapkan tiga index sebelum data benar-benar tumbuh, lalu membuktikan efeknya lewat explain.