Gudang yang hanya bisa mencatat mutasi baru setengah jadi; nilai sejatinya muncul saat data ditanya kembali pada akhir bulan. Pada bagian penutup proyek ini kita mengisi inventaris_db dengan dua bulan riwayat mutasi, membangun ulang stok dari buku mutasinya, lalu menyusun empat laporan yang diminta setiap pemilik toko: berapa nilai uang yang terkunci di rak, barang mana yang harus segera dipesan ulang, bagaimana perjalanan stok satu barang dari hari ke hari, dan ke mana arah gerak barang tiap kategori. Seluruh query laporan disimpan sebagai berkas siap pakai di folder queries/, dan proyek ditutup dengan satu berkas pemeriksaan yang menguji ulang seluruh fitur secara otomatis.
Menyiapkan Riwayat Dua Bulan #
Kita mulai dari keadaan bersih seperti biasa: dua blok pertama menjalankan pernyataan yang sama dengan database/skema.sql dan database/data-awal.sql (berkas aslinya menyertakan kepala komentar berisi tujuan dan cara pakai):
DROP DATABASE IF EXISTS inventaris_db;
CREATE DATABASE inventaris_db;
USE inventaris_db;
CREATE TABLE barang (
id INT AUTO_INCREMENT PRIMARY KEY,
sku VARCHAR(15) NOT NULL UNIQUE,
nama VARCHAR(100) NOT NULL,
kategori VARCHAR(30) NOT NULL,
satuan VARCHAR(10) NOT NULL,
stok INT NOT NULL DEFAULT 0,
stok_minimum INT NOT NULL DEFAULT 0,
harga DECIMAL(10,2) NOT NULL DEFAULT 0.00,
CONSTRAINT chk_barang_stok CHECK (stok >= 0)
);
CREATE TABLE transaksi (
id INT AUTO_INCREMENT PRIMARY KEY,
tipe ENUM('masuk','keluar') NOT NULL,
barang_id INT NOT NULL,
jumlah INT NOT NULL,
tanggal DATE NOT NULL,
keterangan VARCHAR(100) NOT NULL,
CONSTRAINT chk_transaksi_jumlah CHECK (jumlah > 0),
CONSTRAINT fk_transaksi_barang
FOREIGN KEY (barang_id) REFERENCES barang(id)
);Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changed
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)INSERT INTO barang (sku, nama, kategori, satuan, stok, stok_minimum, harga) VALUES
('AT-001', 'Pulpen Hitam Standard AE7', 'Alat Tulis', 'pcs', 120, 30, 3500.00),
('AT-002', 'Pensil 2B Nusantara', 'Alat Tulis', 'pcs', 85, 25, 2500.00),
('AT-003', 'Buku Tulis 38 Lembar Sekolah', 'Alat Tulis', 'buku', 40, 12, 4500.00),
('KR-001', 'Kertas HVS A4 80gsm', 'Kertas', 'rim', 25, 10, 55000.00),
('KR-002', 'Amplop Cokelat Ukuran F4', 'Kertas', 'pak', 18, 12, 15000.00),
('EL-001', 'Kalkulator Desktop CD-8', 'Elektronik', 'unit', 8, 3, 95000.00),
('EL-002', 'Baterai AA Isi 4', 'Elektronik', 'pack', 60, 20, 18000.00),
('KB-001', 'Sabun Cuci Tangan Lavender', 'Kebersihan', 'botol', 45, 15, 12000.00),
('KB-002', 'Tisu Wajah 250 Sheet', 'Kebersihan', 'pak', 10, 8, 13500.00),
('KP-001', 'Kantong Plastik Kresek M', 'Kemasan', 'pak', 200, 50, 5000.00);Output yang Dihasilkan:
Query OK, 10 rows affected (0.00 sec)
Records: 10 Duplicates: 0 Warnings: 0Di atas titik awal itu kita tumpahkan 40 baris riwayat: sepuluh baris saldo awal per 31 Desember 2025 — stok tiap barang dari data awal dibukukan sebagai transaksi masuk — disusul 30 mutasi Januari–Februari 2026 yang terdiri dari pembelian, penjualan, dan pengiriman cabang. Baris disusun urut tanggal sehingga id yang dihasilkan AUTO_INCREMENT juga menaik mengikuti waktu:
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan) VALUES
('masuk', 1, 120, '2025-12-31', 'Saldo awal periode'),
('masuk', 2, 85, '2025-12-31', 'Saldo awal periode'),
('masuk', 3, 40, '2025-12-31', 'Saldo awal periode'),
('masuk', 4, 25, '2025-12-31', 'Saldo awal periode'),
('masuk', 5, 18, '2025-12-31', 'Saldo awal periode'),
('masuk', 6, 8, '2025-12-31', 'Saldo awal periode'),
('masuk', 7, 60, '2025-12-31', 'Saldo awal periode'),
('masuk', 8, 45, '2025-12-31', 'Saldo awal periode'),
('masuk', 9, 10, '2025-12-31', 'Saldo awal periode'),
('masuk', 10, 200, '2025-12-31', 'Saldo awal periode'),
('keluar', 2, 20, '2026-01-08', 'Penjualan tunai'),
('keluar', 9, 5, '2026-01-09', 'Penjualan tunai'),
('masuk', 1, 50, '2026-01-10', 'Pembelian PO-2601-02'),
('keluar', 3, 12, '2026-01-12', 'Penjualan tunai'),
('keluar', 6, 3, '2026-01-14', 'Pengiriman cabang Bandung'),
('keluar', 1, 60, '2026-01-15', 'Penjualan borongan PT Maju Jaya'),
('keluar', 8, 15, '2026-01-16', 'Penjualan tunai'),
('masuk', 7, 20, '2026-01-19', 'Pembelian PO-2601-04'),
('masuk', 2, 40, '2026-01-20', 'Pembelian PO-2601-05'),
('masuk', 5, 6, '2026-01-22', 'Pembelian PO-2601-06'),
('masuk', 4, 10, '2026-01-25', 'Pembelian PO-2601-07'),
('masuk', 9, 24, '2026-01-28', 'Pembelian PO-2601-08'),
('masuk', 10, 100, '2026-01-30', 'Pembelian PO-2601-09'),
('keluar', 7, 25, '2026-02-02', 'Penjualan tunai'),
('keluar', 1, 40, '2026-02-05', 'Penjualan borongan CV Sinar'),
('keluar', 4, 12, '2026-02-06', 'Pengiriman cabang Bandung'),
('keluar', 8, 20, '2026-02-07', 'Penjualan tunai'),
('keluar', 3, 14, '2026-02-08', 'Penjualan tunai'),
('keluar', 6, 2, '2026-02-09', 'Pengiriman cabang Surabaya'),
('keluar', 2, 55, '2026-02-10', 'Penjualan borongan toko sekolah'),
('keluar', 5, 12, '2026-02-11', 'Penjualan tunai'),
('masuk', 1, 30, '2026-02-12', 'Pembelian PO-2602-05'),
('keluar', 9, 22, '2026-02-13', 'Penjualan tunai'),
('masuk', 8, 12, '2026-02-14', 'Pembelian PO-2602-06'),
('masuk', 3, 10, '2026-02-15', 'Pembelian PO-2602-07'),
('keluar', 7, 18, '2026-02-16', 'Penjualan tunai'),
('keluar', 10, 150, '2026-02-17', 'Penggunaan pengemasan internal'),
('keluar', 4, 10, '2026-02-18', 'Penjualan tunai'),
('keluar', 10, 90, '2026-02-24', 'Penggunaan pengemasan internal'),
('keluar', 6, 1, '2026-02-25', 'Pengiriman cabang Surabaya');Output yang Dihasilkan:
Query OK, 40 rows affected (0.00 sec)
Records: 40 Duplicates: 0 Warnings: 0Karena saldo awal ikut tercatat sebagai transaksi, stok tiap barang kini bisa dibangun ulang sepenuhnya dari tabel transaksi — pola buku besar (ledger) yang membuat angka stok selalu bisa diaudit. Kita gunakan UPDATE ... JOIN dengan derived table untuk menimpa stok dengan hasil rekonstruksi:
UPDATE barang b
JOIN (
SELECT barang_id,
SUM(CASE WHEN tipe = 'masuk' THEN jumlah ELSE -jumlah END) AS stok_hitung
FROM transaksi
GROUP BY barang_id
) mutasi ON mutasi.barang_id = b.id
SET b.stok = mutasi.stok_hitung;Output yang Dihasilkan:
Query OK, 10 rows affected (0.00 sec)
Rows matched: 10 Changed: 10 Warnings: 0Konsistensi stok dengan buku mutasi kita buktikan dalam satu query: kolom stok disandingkan dengan saldo_buku yang dihitung langsung dari transaksi:
SELECT b.sku, b.nama, b.stok,
COALESCE(SUM(CASE WHEN t.tipe = 'masuk' THEN t.jumlah ELSE -t.jumlah END), 0) AS saldo_buku
FROM barang b
LEFT JOIN transaksi t ON t.barang_id = b.id
GROUP BY b.id, b.sku, b.nama, b.stok
ORDER BY b.sku;Output yang Dihasilkan:
+--------+------------------------------+------+------------+
| sku | nama | stok | saldo_buku |
+--------+------------------------------+------+------------+
| AT-001 | Pulpen Hitam Standard AE7 | 100 | 100 |
| AT-002 | Pensil 2B Nusantara | 50 | 50 |
| AT-003 | Buku Tulis 38 Lembar Sekolah | 24 | 24 |
| EL-001 | Kalkulator Desktop CD-8 | 2 | 2 |
| EL-002 | Baterai AA Isi 4 | 37 | 37 |
| KB-001 | Sabun Cuci Tangan Lavender | 22 | 22 |
| KB-002 | Tisu Wajah 250 Sheet | 7 | 7 |
| KP-001 | Kantong Plastik Kresek M | 60 | 60 |
| KR-001 | Kertas HVS A4 80gsm | 13 | 13 |
| KR-002 | Amplop Cokelat Ukuran F4 | 12 | 12 |
+--------+------------------------------+------+------------+
10 rows in set (0.00 sec)Sepuluh baris hasil menunjukkan angka yang sama pada kedua kolom — stok pulpen 100, pensil 50, buku tulis 24, dan seterusnya. Kalau suatu saat muncul selisih antara stok dan saldo_buku, kita tahu pasti ada mutasi yang dicatat tanpa UPDATE stok atau sebaliknya; query ini menjadi alat audit paling sederhana yang bisa dijalankan kapan pun.
Laporan Nilai Persediaan #
Pertanyaan pertama pemilik toko: berapa uang yang terkunci di rak? Nilai persediaan dihitung stok * harga dijumlahkan. Versi ringkasnya dulu — total seluruh gudang:
SELECT COUNT(*) AS jumlah_item, SUM(stok * harga) AS total_nilai
FROM barang;Output yang Dihasilkan:
+-------------+-------------+
| jumlah_item | total_nilai |
+-------------+-------------+
| 10 | 2992500.00 |
+-------------+-------------+
1 row in set (0.00 sec)Sepuluh item menahan nilai hampir tiga juta rupiah. Versi terperinci per kategori kita simpan sebagai queries/nilai-persediaan.sql:
SELECT b.kategori,
COUNT(*) AS jumlah_item,
SUM(b.stok * b.harga) AS nilai_persediaan
FROM barang b
GROUP BY b.kategori
ORDER BY nilai_persediaan DESC;Output yang Dihasilkan:
+------------+-------------+------------------+
| kategori | jumlah_item | nilai_persediaan |
+------------+-------------+------------------+
| Kertas | 2 | 895000.00 |
| Elektronik | 2 | 856000.00 |
| Alat Tulis | 3 | 583000.00 |
| Kebersihan | 2 | 358500.00 |
| Kemasan | 1 | 300000.00 |
+------------+-------------+------------------+
5 rows in set (0.00 sec)Kategori Kertas menahan nilai terbesar (Rp895.000,00 dari dua item) karena harga per rimnya tinggi, disusul Elektronik. Kemasan tampak kecil nilainya meski jumlahnya terbanyak — 60 pak kantong plastik hanya bernilai Rp300.000,00. Laporan semacam ini menuntun keputusan belanja: kategori bernilai besar layak diperhatikan lebih ketat karena selisih sedikit berarti uang banyak.
Laporan Barang di Bawah Stok Minimum #
Pertanyaan kedua: barang mana yang harus dipesan ulang sekarang? Jawabannya menyaring stok <= stok_minimum — tanda “sama dengan” disertakan karena barang yang tepat menyentuh batas minimum sama urgensinya dengan yang sudah menembusnya. Isi queries/barang-stok-minimum.sql:
SELECT b.sku, b.nama, b.stok, b.stok_minimum,
b.stok_minimum - b.stok AS kekurangan
FROM barang b
WHERE b.stok <= b.stok_minimum
ORDER BY kekurangan DESC, b.sku;Output yang Dihasilkan:
+--------+--------------------------+------+--------------+------------+
| sku | nama | stok | stok_minimum | kekurangan |
+--------+--------------------------+------+--------------+------------+
| EL-001 | Kalkulator Desktop CD-8 | 2 | 3 | 1 |
| KB-002 | Tisu Wajah 250 Sheet | 7 | 8 | 1 |
| KR-002 | Amplop Cokelat Ukuran F4 | 12 | 12 | 0 |
+--------+--------------------------+------+--------------+------------+
3 rows in set (0.00 sec)Tiga barang masuk daftar: kalkulator tersisa 2 dari batas 3, tisu tersisa 7 dari batas 8, dan amplop tepat menyentuh batas 12. Kolom kekurangan sekaligus menunjukkan jarak ke batas sehingga daftar ini bisa langsung diterjemahkan menjadi jumlah pemesanan. Perhatikan bahwa KR-002 hanya muncul karena kita memakai <= — bila memakai < ketat, amplop yang tepat di batas akan terlewat dan baru muncul di laporan berikutnya setelah stoknya benar-benar jebol.
Laporan Kartu Stok #
Pertanyaan ketiga: bagaimana perjalanan stok satu barang dari waktu ke waktu? Kartu stok menjawabnya — riwayat transaksi satu barang berurutan waktu, lengkap dengan stok berjalan (running balance) hasil penjumlahan berjalan mutasi bertanda. Isi queries/kartu-stok.sql:
SELECT t.tanggal, t.tipe, t.jumlah, t.keterangan,
SUM(CASE WHEN t.tipe = 'masuk' THEN t.jumlah ELSE -t.jumlah END)
OVER (ORDER BY t.tanggal, t.id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS stok_berjalan
FROM transaksi t
JOIN barang b ON b.id = t.barang_id
WHERE b.sku = 'AT-001'
ORDER BY t.tanggal, t.id;Output yang Dihasilkan:
+------------+--------+--------+---------------------------------+---------------+
| tanggal | tipe | jumlah | keterangan | stok_berjalan |
+------------+--------+--------+---------------------------------+---------------+
| 2025-12-31 | masuk | 120 | Saldo awal periode | 120 |
| 2026-01-10 | masuk | 50 | Pembelian PO-2601-02 | 170 |
| 2026-01-15 | keluar | 60 | Penjualan borongan PT Maju Jaya | 110 |
| 2026-02-05 | keluar | 40 | Penjualan borongan CV Sinar | 70 |
| 2026-02-12 | masuk | 30 | Pembelian PO-2602-05 | 100 |
+------------+--------+--------+---------------------------------+---------------+
5 rows in set (0.00 sec)Mari kita bedah bagian pentingnya:
CASE WHEN t.tipe = 'masuk' THEN t.jumlah ELSE -t.jumlah ENDmengubah arah mutasi menjadi tanda aritmetika: masuk bernilai positif, keluar negatif.SUMberjalan atas nilai bertanda inilah yang menghasilkan saldo.OVER (ORDER BY t.tanggal, t.id ...)mendefinisikan jendela perhitungan: baris diurutkan tanggal, denganidsebagai pemecah seri untuk transaksi di tanggal yang sama.ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWmenyatakan frame secara eksplisit: jumlahkan dari baris pertama sampai baris sekarang. Tanpa klausa ini, frame bawaanRANGEakan menjumlahkan semua baris peer bertanggal sama sekaligus — hasil masih benar di data kita karena tiap tanggal unik per barang, tetapi frameROWSmembuat perilakunya pasti di data apa pun.WHERE b.sku = 'AT-001'memfilter kartu stok hanya untuk pulpen; ganti SKU ini untuk mencetak kartu stok barang lain.
Perjalanan pulpen terbaca sekali jalan: saldo awal 120, naik ke 170 setelah pembelian, tergerus ke 110 oleh penjualan borongan, turun lagi ke 70, lalu pulih ke 100 setelah pembelian Februari — dan angka terakhir itulah stok saat ini di tabel barang.
Laporan Ringkasan Masuk-Keluar #
Pertanyaan keempat: ke mana arah gerak barang tiap kategori tiap bulan? Agregasi bersyarat menjawabnya — satu baris per pasangan kategori-bulan dengan dua kolom jumlahan yang berbeda arah. Isi queries/ringkasan-masuk-keluar.sql:
SELECT b.kategori,
DATE_FORMAT(t.tanggal, '%Y-%m') AS bulan,
SUM(CASE WHEN t.tipe = 'masuk' THEN t.jumlah ELSE 0 END) AS total_masuk,
SUM(CASE WHEN t.tipe = 'keluar' THEN t.jumlah ELSE 0 END) AS total_keluar
FROM transaksi t
JOIN barang b ON b.id = t.barang_id
WHERE t.tanggal BETWEEN '2026-01-01' AND '2026-02-28'
GROUP BY b.kategori, DATE_FORMAT(t.tanggal, '%Y-%m')
ORDER BY b.kategori, bulan;Output yang Dihasilkan:
+------------+---------+-------------+--------------+
| kategori | bulan | total_masuk | total_keluar |
+------------+---------+-------------+--------------+
| Alat Tulis | 2026-01 | 90 | 92 |
| Alat Tulis | 2026-02 | 40 | 109 |
| Elektronik | 2026-01 | 20 | 3 |
| Elektronik | 2026-02 | 0 | 46 |
| Kebersihan | 2026-01 | 24 | 20 |
| Kebersihan | 2026-02 | 12 | 42 |
| Kemasan | 2026-01 | 100 | 0 |
| Kemasan | 2026-02 | 0 | 240 |
| Kertas | 2026-01 | 16 | 0 |
| Kertas | 2026-02 | 0 | 34 |
+------------+---------+-------------+--------------+
10 rows in set (0.00 sec)Tiga keputusan pada query ini menentukan mutu laporannya:
DATE_FORMAT(t.tanggal, '%Y-%m')memampatkan tanggal menjadi label bulan2026-01atau2026-02sehinggaGROUP BYmengelompokkan per bulan, bukan per hari.SUM(CASE WHEN ... THEN jumlah ELSE 0 END)menghitung dua jumlahan dari baris yang sama tanpa join tambahan — pola agregasi bersyarat yang jauh lebih ringkas daripada dua query terpisah.WHERE t.tanggal BETWEEN ...membatasi periode laporan ke Januari–Februari 2026, sehingga baris saldo awal per 31 Desember 2025 tidak ikut menggembungkan angka masuk bulan Januari. Tanggal batas ditulis literal agar laporan dapat dijalankan ulang dengan hasil identik.
Pembacaan hasilnya lugas: Alat Tulis bergerak paling ramai (masuk 90 keluar 92 pada Januari), Kemasan habis terpakai internal 240 pak di Februari tanpa pembelian lanjutan — sinyal untuk mengevaluasi konsumsi plastik, dan Kebersihan hanya menerima pasokan 12 botol di Februari untuk mengejar keluaran 42.
Pemeriksaan Akhir Proyek #
Seluruh fitur proyek — skema, mutasi, konsistensi stok, dan laporan — kini terkumpul dalam satu berkas uji: tests/pemeriksaan.sql. Berkas ini mandiri: membangun skenario uji kecilnya sendiri, memverifikasi tiap fitur lewat query berlabel ekspektasi, lalu membersihkan database latihan. Kita jalankan dari direktori akar proyek:
-- @test-bash
mysql -h 127.0.0.1 -P 3306 -u root < content/mysql/proyek/database-inventaris-barang/tests/pemeriksaan.sqlOutput yang Dihasilkan:
sku stok
AT-001 150
sku stok
AT-001 120
sku stok
AT-001 120
KB-001 12
KR-001 10
sku stok saldo_buku
AT-001 120 120
KB-001 12 12
KR-001 10 10
kategori bulan total_masuk total_keluar
Alat Tulis 2026-01 50 30
Kebersihan 2026-02 0 18
Kertas 2026-01 0 10
nilai_persediaan
1114000.00
sku stok stok_minimum kekurangan
KB-001 12 15 3
KR-001 10 10 0
tanggal tipe jumlah stok_berjalan
2025-12-31 masuk 100 100
2026-01-10 masuk 50 150
2026-01-15 keluar 30 120Hasil pada output tersebut adalah delapan pemeriksaan berurutan: barang masuk menambah stok tepat 50 (1), barang keluar menguranginya tepat 30 (2), mutasi kertas dan sabun tercatat benar (3), stok sama dengan saldo buku untuk ketiga barang (4), ringkasan bulanan memuat tiga pasangan kategori-bulan dengan angka masuk-keluar yang tepat (5), nilai persediaan total 1114000.00 sesuai hitungan tangan (6), dua barang terdeteksi menyentuh atau melampaui batas minimum (7), dan kartu stok pulpen berakhir persis di angka stok terakhirnya (8). Karena berkas ini membangun skenarionya sendiri, pemeriksaan bisa dijalankan kapan pun — bahkan setelah database utama dibersihkan.
Membersihkan Database Latihan #
Proyek selesai dan database latihan tidak lagi diperlukan. Blok terakhir proyek ini membuangnya dengan sopan:
DROP DATABASE IF EXISTS inventaris_db;Output yang Dihasilkan:
Query OK, 2 rows affected (0.00 sec)IF EXISTS membuat pernyataan ini aman dijalankan ulang — bila database sudah tidak ada, MySQL hanya mengeluarkan peringatan, bukan error.
Ringkasan Proyek #
Empat bagian perjalanan kita menghasilkan sistem yang utuh dan teruji:
- Bagian 1 — kebutuhan gudang dianalisis menjadi dua tabel:
barangdengan SKU unik, stok, stok minimum, dan hargaDECIMAL, sertatransaksidengan ENUM arah mutasi, dijaga foreign key dan dua check constraint. - Bagian 2 — master barang terisi dari
database/data-awal.sql; operasi CRUD harian berjalan dari barang baru sampai stock opname, dan constraint terbukti menolak SKU ganda serta mutasi barang fiktif. - Bagian 3 — mutasi masuk-keluar-retur berjalan atomik dalam
START TRANSACTION ... COMMIT;ROLLBACKmembatalkan salah catat, danCHECKmenahan stok negatif serta tipe ENUM di luar daftar. - Bagian 4 — stok direkonstruksi dari buku mutasi dan diaudit dalam satu query, lalu empat laporan siap pakai di
queries/memakai agregat,DATE_FORMAT, dan window function, plus berkas pemeriksaan mandiri ditests/pemeriksaan.sql.
Pola yang kita tempuh — rancang, isi, operasikan, laporkan, uji — kini terulang untuk kedua kalinya. Proyek berikutnya, Database Penjualan, membawa pola yang sama ke sisi lain toko: pelanggan membuat pesanan multi-produk, stok produk berkurang otomatis dalam satu transaksi, dan harga saat pemesanan di-snapshot agar omzet historis tidak terdistorsi oleh perubahan harga.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
database/data-awal.sql
Unduh berkas-- data-awal.sql — Proyek Database Inventaris Barang
-- Tujuan: mengisi data contoh deterministik (10 barang gudang fiktif)
-- sebagai titik awal konsisten untuk seluruh bagian proyek.
-- Cara pakai: jalankan setelah skema.sql:
-- mysql -u root -p inventaris_db < database/data-awal.sql
INSERT INTO barang (sku, nama, kategori, satuan, stok, stok_minimum, harga) VALUES
('AT-001', 'Pulpen Hitam Standard AE7', 'Alat Tulis', 'pcs', 120, 30, 3500.00),
('AT-002', 'Pensil 2B Nusantara', 'Alat Tulis', 'pcs', 85, 25, 2500.00),
('AT-003', 'Buku Tulis 38 Lembar Sekolah', 'Alat Tulis', 'buku', 40, 12, 4500.00),
('KR-001', 'Kertas HVS A4 80gsm', 'Kertas', 'rim', 25, 10, 55000.00),
('KR-002', 'Amplop Cokelat Ukuran F4', 'Kertas', 'pak', 18, 12, 15000.00),
('EL-001', 'Kalkulator Desktop CD-8', 'Elektronik', 'unit', 8, 3, 95000.00),
('EL-002', 'Baterai AA Isi 4', 'Elektronik', 'pack', 60, 20, 18000.00),
('KB-001', 'Sabun Cuci Tangan Lavender', 'Kebersihan', 'botol', 45, 15, 12000.00),
('KB-002', 'Tisu Wajah 250 Sheet', 'Kebersihan', 'pak', 10, 8, 13500.00),
('KP-001', 'Kantong Plastik Kresek M', 'Kemasan', 'pak', 200, 50, 5000.00);
database/skema.sql
Unduh berkas-- skema.sql — Proyek Database Inventaris Barang
-- Tujuan: membuat database inventaris_db beserta kedua tabelnya
-- (barang, transaksi) lengkap dengan primary key, kolom sku yang unik,
-- foreign key bernama, dan check constraint stok/jumlah.
-- Cara pakai: mysql -u root -p < database/skema.sql
DROP DATABASE IF EXISTS inventaris_db;
CREATE DATABASE inventaris_db;
USE inventaris_db;
-- Master barang gudang; sku unik per produk, stok tidak boleh negatif.
CREATE TABLE barang (
id INT AUTO_INCREMENT PRIMARY KEY,
sku VARCHAR(15) NOT NULL UNIQUE,
nama VARCHAR(100) NOT NULL,
kategori VARCHAR(30) NOT NULL,
satuan VARCHAR(10) NOT NULL,
stok INT NOT NULL DEFAULT 0,
stok_minimum INT NOT NULL DEFAULT 0,
harga DECIMAL(10,2) NOT NULL DEFAULT 0.00,
CONSTRAINT chk_barang_stok CHECK (stok >= 0)
);
-- Buku mutasi stok; ENUM tipe membedakan arah gerak barang.
CREATE TABLE transaksi (
id INT AUTO_INCREMENT PRIMARY KEY,
tipe ENUM('masuk','keluar') NOT NULL,
barang_id INT NOT NULL,
jumlah INT NOT NULL,
tanggal DATE NOT NULL,
keterangan VARCHAR(100) NOT NULL,
CONSTRAINT chk_transaksi_jumlah CHECK (jumlah > 0),
CONSTRAINT fk_transaksi_barang
FOREIGN KEY (barang_id) REFERENCES barang(id)
);
queries/barang-stok-minimum.sql
Unduh berkas-- barang-stok-minimum.sql — Proyek Database Inventaris Barang
-- Tujuan: menampilkan barang yang stoknya sudah menyentuh atau turun di
-- bawah stok_minimum beserta kekurangannya, sebagai dasar pemesanan ulang.
-- Cara pakai: mysql -u root -p inventaris_db < queries/barang-stok-minimum.sql
SELECT b.sku, b.nama, b.stok, b.stok_minimum,
b.stok_minimum - b.stok AS kekurangan
FROM barang b
WHERE b.stok <= b.stok_minimum
ORDER BY kekurangan DESC, b.sku;
queries/kartu-stok.sql
Unduh berkas-- kartu-stok.sql — Proyek Database Inventaris Barang
-- Tujuan: menyusun kartu stok satu barang: riwayat transaksi berurutan
-- waktu beserta stok berjalan (running total) memakai window function
-- SUM() OVER dengan frame ROWS eksplisit.
-- Cara pakai: mysql -u root -p inventaris_db < queries/kartu-stok.sql
SELECT t.tanggal, t.tipe, t.jumlah, t.keterangan,
SUM(CASE WHEN t.tipe = 'masuk' THEN t.jumlah ELSE -t.jumlah END)
OVER (ORDER BY t.tanggal, t.id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS stok_berjalan
FROM transaksi t
JOIN barang b ON b.id = t.barang_id
WHERE b.sku = 'AT-001'
ORDER BY t.tanggal, t.id;
queries/nilai-persediaan.sql
Unduh berkas-- nilai-persediaan.sql — Proyek Database Inventaris Barang
-- Tujuan: menghitung nilai persediaan per kategori (stok x harga),
-- diurutkan dari kategori dengan nilai terbesar.
-- Cara pakai: mysql -u root -p inventaris_db < queries/nilai-persediaan.sql
SELECT b.kategori,
COUNT(*) AS jumlah_item,
SUM(b.stok * b.harga) AS nilai_persediaan
FROM barang b
GROUP BY b.kategori
ORDER BY nilai_persediaan DESC;
queries/ringkasan-masuk-keluar.sql
Unduh berkas-- ringkasan-masuk-keluar.sql — Proyek Database Inventaris Barang
-- Tujuan: merangkum total barang masuk dan keluar per kategori per bulan
-- dengan agregasi bersyarat (SUM + CASE) dan DATE_FORMAT.
-- Cara pakai: mysql -u root -p inventaris_db < queries/ringkasan-masuk-keluar.sql
SELECT b.kategori,
DATE_FORMAT(t.tanggal, '%Y-%m') AS bulan,
SUM(CASE WHEN t.tipe = 'masuk' THEN t.jumlah ELSE 0 END) AS total_masuk,
SUM(CASE WHEN t.tipe = 'keluar' THEN t.jumlah ELSE 0 END) AS total_keluar
FROM transaksi t
JOIN barang b ON b.id = t.barang_id
WHERE t.tanggal BETWEEN '2026-01-01' AND '2026-02-28'
GROUP BY b.kategori, DATE_FORMAT(t.tanggal, '%Y-%m')
ORDER BY b.kategori, bulan;
tests/pemeriksaan.sql
Unduh berkas-- pemeriksaan.sql — Proyek Database Inventaris Barang
-- Tujuan: pemeriksaan akhir seluruh fitur proyek (mutasi masuk-keluar,
-- konsistensi stok dengan buku mutasi, ringkasan bulanan, nilai
-- persediaan, batas stok minimum, dan kartu stok).
-- Berkas ini MANDIRI: membangun skenario uji kecilnya sendiri,
-- lalu membersihkan database setelah selesai.
-- Cara pakai: mysql -u root -p < tests/pemeriksaan.sql
-- Ekspektasi tiap hasil ditulis sebagai komentar di atas query terkait.
DROP DATABASE IF EXISTS inventaris_db;
CREATE DATABASE inventaris_db;
USE inventaris_db;
CREATE TABLE barang (
id INT AUTO_INCREMENT PRIMARY KEY,
sku VARCHAR(15) NOT NULL UNIQUE,
nama VARCHAR(100) NOT NULL,
kategori VARCHAR(30) NOT NULL,
satuan VARCHAR(10) NOT NULL,
stok INT NOT NULL DEFAULT 0,
stok_minimum INT NOT NULL DEFAULT 0,
harga DECIMAL(10,2) NOT NULL DEFAULT 0.00,
CONSTRAINT chk_barang_stok CHECK (stok >= 0)
);
CREATE TABLE transaksi (
id INT AUTO_INCREMENT PRIMARY KEY,
tipe ENUM('masuk','keluar') NOT NULL,
barang_id INT NOT NULL,
jumlah INT NOT NULL,
tanggal DATE NOT NULL,
keterangan VARCHAR(100) NOT NULL,
CONSTRAINT chk_transaksi_jumlah CHECK (jumlah > 0),
CONSTRAINT fk_transaksi_barang
FOREIGN KEY (barang_id) REFERENCES barang(id)
);
INSERT INTO barang (sku, nama, kategori, satuan, stok, stok_minimum, harga) VALUES
('AT-001', 'Pulpen Hitam Standard AE7', 'Alat Tulis', 'pcs', 100, 30, 3500.00),
('KR-001', 'Kertas HVS A4 80gsm', 'Kertas', 'rim', 20, 10, 55000.00),
('KB-001', 'Sabun Cuci Tangan Lavender', 'Kebersihan', 'botol', 30, 15, 12000.00);
-- Baris saldo awal: stok data awal dibukukan sebagai transaksi masuk
-- sehingga seluruh mutasi bisa direkonstruksi dari tabel transaksi.
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan) VALUES
('masuk', 1, 100, '2025-12-31', 'Saldo awal periode'),
('masuk', 2, 20, '2025-12-31', 'Saldo awal periode'),
('masuk', 3, 30, '2025-12-31', 'Saldo awal periode');
-- Pemeriksaan 1: barang masuk menambah stok secara atomik.
START TRANSACTION;
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan)
VALUES ('masuk', 1, 50, '2026-01-10', 'Pembelian PO-2601-01');
UPDATE barang SET stok = stok + 50 WHERE id = 1;
COMMIT;
-- Ekspektasi: AT-001 stok 150
SELECT sku, stok FROM barang WHERE id = 1;
-- Pemeriksaan 2: barang keluar mengurangi stok secara atomik.
START TRANSACTION;
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan)
VALUES ('keluar', 1, 30, '2026-01-15', 'Penjualan borongan');
UPDATE barang SET stok = stok - 30 WHERE id = 1;
COMMIT;
-- Ekspektasi: AT-001 stok 120
SELECT sku, stok FROM barang WHERE id = 1;
-- Pemeriksaan 3: mutasi barang lain (kertas turun ke batas minimum).
START TRANSACTION;
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan)
VALUES ('keluar', 2, 10, '2026-01-18', 'Pengiriman cabang Bandung');
UPDATE barang SET stok = stok - 10 WHERE id = 2;
COMMIT;
START TRANSACTION;
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan)
VALUES ('keluar', 3, 18, '2026-02-05', 'Pengiriman cabang Surabaya');
UPDATE barang SET stok = stok - 18 WHERE id = 3;
COMMIT;
-- Ekspektasi: KR-001 stok 10, KB-001 stok 12
SELECT sku, stok FROM barang ORDER BY sku;
-- Pemeriksaan 4: stok sama dengan saldo buku mutasi (konsistensi ledger).
-- Ekspektasi: 3 baris - stok dan saldo_buku sama: 120, 10, 12
SELECT b.sku, b.stok,
COALESCE(SUM(CASE WHEN t.tipe = 'masuk' THEN t.jumlah ELSE -t.jumlah END), 0) AS saldo_buku
FROM barang b
LEFT JOIN transaksi t ON t.barang_id = b.id
GROUP BY b.id, b.sku, b.stok
ORDER BY b.sku;
-- Pemeriksaan 5: ringkasan masuk/keluar per kategori per bulan.
-- Ekspektasi: 3 baris - Alat Tulis 2026-01 (50, 30), Kertas 2026-01 (0, 10),
-- Kebersihan 2026-02 (0, 18)
SELECT b.kategori,
DATE_FORMAT(t.tanggal, '%Y-%m') AS bulan,
SUM(CASE WHEN t.tipe = 'masuk' THEN t.jumlah ELSE 0 END) AS total_masuk,
SUM(CASE WHEN t.tipe = 'keluar' THEN t.jumlah ELSE 0 END) AS total_keluar
FROM transaksi t
JOIN barang b ON b.id = t.barang_id
WHERE t.tanggal BETWEEN '2026-01-01' AND '2026-02-28'
GROUP BY b.kategori, DATE_FORMAT(t.tanggal, '%Y-%m')
ORDER BY b.kategori, bulan;
-- Pemeriksaan 6: nilai persediaan total (stok x harga).
-- Ekspektasi: 1114000.00
SELECT SUM(stok * harga) AS nilai_persediaan FROM barang;
-- Pemeriksaan 7: barang yang menyentuh atau di bawah stok_minimum.
-- Ekspektasi: 2 baris - KB-001 (kekurangan 3), KR-001 (kekurangan 0)
SELECT sku, stok, stok_minimum, stok_minimum - stok AS kekurangan
FROM barang
WHERE stok <= stok_minimum
ORDER BY kekurangan DESC, sku;
-- Pemeriksaan 8: kartu stok AT-001; stok berjalan berakhir di angka stok.
-- Ekspektasi: 3 baris - 100, 150, 120
SELECT t.tanggal, t.tipe, t.jumlah,
SUM(CASE WHEN t.tipe = 'masuk' THEN t.jumlah ELSE -t.jumlah END)
OVER (ORDER BY t.tanggal, t.id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS stok_berjalan
FROM transaksi t
JOIN barang b ON b.id = t.barang_id
WHERE b.sku = 'AT-001'
ORDER BY t.tanggal, t.id;
DROP DATABASE inventaris_db;