Skema yang kita rancang di Bagian 1 baru berupa rak kosong; kegunaannya baru terasa setelah diisi master barang dan dioperasikan. Pada bagian ini kita memegang peran staf gudang dalam pekerjaan hariannya: mendata seluruh barang beserta stok dan batas minimumnya, menerima barang baru, menyesuaikan harga, melakukan stock opname, sampai menghapus data yang keliru. Di sepanjang jalan kita sengaja mencoba memasukkan data yang tidak valid — SKU ganda dan transaksi atas barang yang tidak ada — untuk membuktikan constraint pada skema benar-benar bekerja, bukan sekadar hiasan deklarasi.
Menyiapkan Ulang Database #
Setiap bagian proyek ini mandiri: kita mulai dari database bersih yang dibangun ulang lewat database/skema.sql, sehingga hasil tiap bagian selalu bisa direproduksi tanpa bergantung pada bagian sebelumnya. Blok berikut menjalankan pernyataan yang sama dengan berkas skema dari Bagian 1:
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)Mengisi Data Awal #
Data contoh kita simpan di berkas database/data-awal.sql supaya seluruh bagian berikutnya memakai titik awal yang sama. Sepuluh barang gudang toko semuanya fiktif, terbagi lima kategori dengan satuan hitung yang berbeda-beda — justru satuan itulah yang membuat inventaris gudang lebih menuntut daripada katalog biasa:
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: 0Kolom stok_minimum adalah kunci kebiasaan gudang yang baik: angka ini menjadi pemicu pemesanan ulang sehingga barang tidak habis diam-diam. Perhatikan EL-001 Kalkulator — stok 8 dengan minimum 3 berarti gudang hanya boleh kehilangan 5 unit sebelum harus memesan lagi. Nilai minimum yang berbeda-beda per barang wajar terjadi: pensil laku cepat (minimum 25), kalkulator lambat (minimum 3).
Isi yang baru ditulis kita baca kembali untuk memastikan apa yang tersimpan memang sesuai dugaan. Query diurutkan eksplisit lewat ORDER BY sku supaya urutan barisnya pasti:
SELECT sku, nama, kategori, satuan, stok, stok_minimum, harga
FROM barang
ORDER BY sku;Output yang Dihasilkan:
+--------+------------------------------+------------+--------+------+--------------+----------+
| sku | nama | kategori | satuan | stok | stok_minimum | harga |
+--------+------------------------------+------------+--------+------+--------------+----------+
| 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 |
| 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 |
| KR-001 | Kertas HVS A4 80gsm | Kertas | rim | 25 | 10 | 55000.00 |
| KR-002 | Amplop Cokelat Ukuran F4 | Kertas | pak | 18 | 12 | 15000.00 |
+--------+------------------------------+------------+--------+------+--------------+----------+
10 rows in set (0.00 sec)Menambah dan Menyesuaikan Barang #
Kehidupan gudang tidak berhenti di data awal. Supplier mengirim jenis barang baru, harga kertas naik, dan hasil hitung fisik berbeda dengan catatan. Empat pernyataan berikut mensimulasikan ketiganya. Pertama, kabel data USB-C masuk sebagai barang baru:
INSERT INTO barang (sku, nama, kategori, satuan, stok, stok_minimum, harga)
VALUES ('EL-003', 'Kabel Data USB-C 1m', 'Elektronik', 'pcs', 25, 10, 28000.00);Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)Kedua, harga kertas HVS naik karena kebijakan supplier. Kolom harga menyimpan harga satuan saat ini — saat harga berubah, nilai persediaan dihitung dengan harga baru:
UPDATE barang SET harga = 58000.00 WHERE sku = 'KR-001';
SELECT sku, harga FROM barang WHERE sku = 'KR-001';Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
+--------+----------+
| sku | harga |
+--------+----------+
| KR-001 | 58000.00 |
+--------+----------+
1 row in set (0.00 sec)Ketiga, stock opname menemukan lima pensil 2B hilang dari rak. Penyesuaian turun dilakukan dengan ekspresi stok = stok - 5, bukan angka tetap, supaya perubahan tetap benar berapapun nilai stok sebelumnya:
UPDATE barang SET stok = stok - 5 WHERE sku = 'AT-002';
SELECT sku, nama, stok FROM barang WHERE sku = 'AT-002';Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
+--------+---------------------+------+
| sku | nama | stok |
+--------+---------------------+------+
| AT-002 | Pensil 2B Nusantara | 80 |
+--------+---------------------+------+
1 row in set (0.00 sec)Stok pensil kini tercatat 80. Penyesuaian semacam ini selayaknya jarang terjadi — pada Bagian 3 kita akan melihat bahwa perubahan stok yang benar selalu disertai baris transaksi; opname hanya untuk mengoreksi selisih fisik.
Menghapus baris memang layak diperlakukan hati-hati. Contoh yang aman: staf salah menambahkan spidol yang ternyata sudah diwakili barang lain, dan barang itu belum pernah bertransaksi:
INSERT INTO barang (sku, nama, kategori, satuan, stok, stok_minimum, harga)
VALUES ('AT-004', 'Spidol Whiteboard Hitam', 'Alat Tulis', 'pcs', 30, 10, 8000.00);
DELETE FROM barang WHERE sku = 'AT-004';
SELECT COUNT(*) AS jumlah_barang FROM barang;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
+---------------+
| jumlah_barang |
+---------------+
| 11 |
+---------------+
1 row in set (0.00 sec)COUNT(*) mengembalikan 11: sepuluh barang awal ditambah kabel USB-C; spidol hilang dari hitungan karena memang dihapus sebelum sempat dipakai transaksi mana pun.
Membaca Stok sebagai Nilai #
Stok bukan sekadar jumlah — tiap unit menahan uang. Query berikut mengalikan stok dengan harga lalu menyortir item dengan nilai terbesar, siap dipakai supervisor menilai barang mana yang menumpuk modal:
SELECT sku, nama, harga, stok, stok * harga AS nilai
FROM barang
ORDER BY nilai DESC
LIMIT 3;Output yang Dihasilkan:
+--------+--------------------------+----------+------+------------+
| sku | nama | harga | stok | nilai |
+--------+--------------------------+----------+------+------------+
| KR-001 | Kertas HVS A4 80gsm | 58000.00 | 25 | 1450000.00 |
| EL-002 | Baterai AA Isi 4 | 18000.00 | 60 | 1080000.00 |
| KP-001 | Kantong Plastik Kresek M | 5000.00 | 200 | 1000000.00 |
+--------+--------------------------+----------+------+------------+
3 rows in set (0.00 sec)Tiga item teratas menahan nilai terbesar: kertas HVS (stok 25 dengan harga yang baru dinaikkan), baterai, dan kantong plastik. LIMIT 3 memotong hasil ke tiga baris pertama setelah pengurutan — pola yang pas untuk pertanyaan “top N”.
Menguji Constraint dengan Data Tidak Valid #
Bagian terpenting bagian ini adalah membuktikan constraint bekerja. Pertama kita coba mendaftarkan barang baru dengan SKU yang sudah dipakai — AT-001 sudah dimiliki pulpen:
-- @error
INSERT INTO barang (sku, nama, kategori, satuan, stok, stok_minimum, harga) VALUES ('AT-001', 'Pulpen Hitam Standard AE7 (salinan)', 'Alat Tulis', 'pcs', 50, 30, 3500.00);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 1062 (23000): Duplicate entry 'AT-001' for key 'barang.sku'
ByeMySQL menolak pernyataan itu dengan error duplikat pada barang.sku — nama constraint unique inilah yang memudahkan kita tahu kolom mana yang dilanggar tanpa membuka skema. Kedua, kita coba mencatat mutasi masuk atas barang bernomor 99 yang tidak pernah terdaftar:
-- @error
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan) VALUES ('masuk', 99, 10, '2026-03-01', 'Barang titipan supplier');Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`inventaris_db`.`transaksi`, CONSTRAINT `fk_transaksi_barang` FOREIGN KEY (`barang_id`) REFERENCES `barang` (`id`))
ByeError kedua datang dari foreign key fk_transaksi_barang: baris anak (transaksi) menuntut nilai barang_id yang benar-benar ada di tabel induk (barang). Dua penolakan ini menegaskan pola penting: constraint adalah penjaga terakhir yang tetap bekerja meski aplikasi punya bug — data tidak valid tidak akan pernah lolos ke penyimpanan.
Referensi #
Master barang kini hidup dengan stok, batas minimum, dan harga yang terjaga constraint-nya. Namun gudang yang sejati diukur dari gerak barangnya — pada Bagian 3 kita menjalankan alur intinya: barang masuk yang menambah stok dan barang keluar yang menguranginya, semuanya dalam transaksi database yang atomik.
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;