Inilah bagian di mana database gudang benar-benar bernyawa: barang mengalir masuk dari supplier, keluar ke pelanggan, dan kadang kembali lewat retur. Setiap mutasi menyentuh dua tabel sekaligus — transaksi mencatat jejaknya sementara barang mengubah stoknya — dan di situlah transaksi MySQL menunjukkan fungsinya: kedua perubahan harus terjadi bersama-sama atau tidak sama sekali. Pada bagian ini kita jalankan alur lengkapnya, batalkan satu salah catat dengan ROLLBACK, lalu sengaja mencoba mengeluarkan barang melebihi stok untuk melihat CHECK constraint menahan garis batas terakhir.

Menyiapkan Ulang Database #

Sesuai kebiasaan proyek ini, kita mulai dari keadaan bersih: skema dan data awal dibangun ulang sehingga angka-angka pada bagian ini dapat diikuti ulang kapan pun. Dua blok berikut menjalankan pernyataan yang sama dengan database/skema.sql dan database/data-awal.sql:

sql
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:

text
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)
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);

Output yang Dihasilkan:

text
Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

Alur Barang Masuk #

Satu mutasi masuk menyentuh dua tabel: transaksi mendapat baris baru dan stok di barang bertambah. Diagram berikut memetakan urutannya:

Pembelian pertama tiba pada 3 Maret 2026: 40 pulpen (AT-001, id 1) masuk dari supplier dengan nomor purchase order. SELECT di awal blok mencatat stok sebelum, keempat pernyataan berikutnya berjalan sebagai satu unit kerja:

sql
SELECT sku, nama, stok FROM barang WHERE id = 1;
START TRANSACTION;
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan)
  VALUES ('masuk', 1, 40, '2026-03-03', 'Pembelian PO-2603-01');
UPDATE barang SET stok = stok + 40 WHERE id = 1;
COMMIT;

Output yang Dihasilkan:

text
+--------+---------------------------+------+
| sku    | nama                      | stok |
+--------+---------------------------+------+
| AT-001 | Pulpen Hitam Standard AE7 |  120 |
+--------+---------------------------+------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Query OK, 0 rows affected (0.00 sec)

Bukti setelah transaksi kita kumpulkan dalam satu blok pemeriksaan — stok barang dan baris transaksinya:

sql
SELECT sku, stok FROM barang WHERE id = 1;
SELECT id, tipe, barang_id, jumlah, tanggal, keterangan
FROM transaksi
ORDER BY id;

Output yang Dihasilkan:

text
+--------+------+
| sku    | stok |
+--------+------+
| AT-001 |  160 |
+--------+------+
1 row in set (0.00 sec)

+----+-------+-----------+--------+------------+----------------------+
| id | tipe  | barang_id | jumlah | tanggal    | keterangan           |
+----+-------+-----------+--------+------------+----------------------+
|  1 | masuk |         1 |     40 | 2026-03-03 | Pembelian PO-2603-01 |
+----+-------+-----------+--------+------------+----------------------+
1 row in set (0.00 sec)

Hasilnya konsisten satu sama lain: stok pulpen naik dari 120 menjadi 160, dan tabel transaksi menyimpan satu baris masuk berjumlah 40 tertanggal 2026-03-03. Pola stok = stok + 40 pada pernyataan UPDATE penting: penambahan dihitung dari nilai stok saat itu, bukan angka mutlak, sehingga pernyataan yang sama tetap benar kapan pun dijalankan.

Alur Barang Keluar dan Retur #

Barang keluar adalah cermin dari barang masuk: baris transaksi bertipe keluar dicatat, stok berkurang. Enam puluh pulpen terjual borongan ke sebuah kantor pada 6 Maret 2026:

sql
START TRANSACTION;
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan)
  VALUES ('keluar', 1, 60, '2026-03-06', 'Penjualan borongan PT Maju Jaya');
UPDATE barang SET stok = stok - 60 WHERE id = 1;
COMMIT;
SELECT sku, stok FROM barang WHERE id = 1;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Query OK, 0 rows affected (0.00 sec)

+--------+------+
| sku    | stok |
+--------+------+
| AT-001 |  100 |
+--------+------+
1 row in set (0.00 sec)

Stok pulpen turun dari 160 menjadi 100. Dua hari kemudian seorang pelanggan mengembalikan lima pensil 2B (AT-002, id 2) yang rusak — retur tercatat sebagai barang masuk, karena barangnya kembali ke gudang:

sql
START TRANSACTION;
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan)
  VALUES ('masuk', 2, 5, '2026-03-08', 'Retur pelanggan - barang rusak');
UPDATE barang SET stok = stok + 5 WHERE id = 2;
COMMIT;
SELECT sku, stok FROM barang WHERE id = 2;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Query OK, 0 rows affected (0.00 sec)

+--------+------+
| sku    | stok |
+--------+------+
| AT-002 |   90 |
+--------+------+
1 row in set (0.00 sec)

Stok pensil naik dari 85 menjadi 90. Retur lewat jalur transaksi masuk menjaga prinsip gudang yang kita tetapkan sejak Bagian 1: setiap perubahan stok selalu punya baris jejak — pembelian, retur, penjualan, pengiriman, semuanya satu bahasa.

Membatalkan Salah Catat dengan ROLLBACK #

Petugas keliru: ia mencatat pengiriman 25 pulpen ke cabang Bandung, padahal pengiriman itu belum seharusnya jalan. Sebelum COMMIT terjadi, kesalahan masih bisa dibatalkan seluruhnya. Blok berikut menjalankan mutasi keliru itu lalu membatalkannya — perhatikan dua SELECT yang memperlihatkan keadaan di dalam transaksi dan sesudah pembatalan:

sql
START TRANSACTION;
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan)
  VALUES ('keluar', 1, 25, '2026-03-10', 'Pengiriman cabang Bandung');
UPDATE barang SET stok = stok - 25 WHERE id = 1;
SELECT sku, stok FROM barang WHERE id = 1;
ROLLBACK;
SELECT sku, stok FROM barang WHERE id = 1;
SELECT COUNT(*) AS jumlah_transaksi FROM transaksi;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

+--------+------+
| sku    | stok |
+--------+------+
| AT-001 |   75 |
+--------+------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

+--------+------+
| sku    | stok |
+--------+------+
| AT-001 |  100 |
+--------+------+
1 row in set (0.00 sec)

+------------------+
| jumlah_transaksi |
+------------------+
|                3 |
+------------------+
1 row in set (0.00 sec)

SELECT pertama melihat stok 75 — sesi kita sendiri membaca perubahan yang belum dikukuhkan. Setelah ROLLBACK, stok kembali ke 100 dan COUNT(*) masih mengembalikan 3: hanya tiga mutasi yang benar-benar terjadi (pembelian, penjualan borongan, retur), baris pengiriman Bandung terhapus seolah tidak pernah ditulis. Perubahan di luar transaksi — tiga mutasi yang sudah di-COMMIT sebelumnya — tidak tersentuh sama sekali.

Menolak Nilai Mutasi yang Tidak Sah #

Dua benteng terakhir kita uji dengan sengaja. Pertama, staf mencatat barang “rusak” dengan tipe transaksi rusak — nilai yang tidak ada dalam daftar ENUM:

sql
-- @error
INSERT INTO transaksi (tipe, barang_id, jumlah, tanggal, keterangan) VALUES ('rusak', 1, 3, '2026-03-12', 'Barang rusak di gudang');

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

ERROR 1265 (01000): Data truncated for column 'tipe' at row 1
Bye

MySQL menolak nilai di luar daftar ENUM. Barang rusak memang perlu dicatat — tetapi jalurnya adalah transaksi keluar dengan keterangan “barang rusak”, bukan tipe baru. Kedua, skenario penutup yang paling menekan: mencoba mengeluarkan 500 pulpen padahal stok tersisa 100. Kode aplikasi semestinya menolak lebih dulu, tetapi bila aplikasi punya bug, CHECK constraint menjadi benteng terakhir:

sql
-- @error
UPDATE barang SET stok = stok - 500 WHERE id = 1;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

ERROR 3819 (HY000): Check constraint 'chk_barang_stok' is violated.
Bye

Constraint chk_barang_stok menolak UPDATE itu sebelum satu baris pun berubah, sehingga stok tetap 100. Karena pernyataan ini berdiri sendiri di luar transaksi, penolakan terjadi total: mutasi bermasalah tidak pernah tercatat dan stok tidak pernah tersentuh.

Referensi #

Mutasi masuk-keluar kini berjalan dengan jaminan konsistensi: transaksi menjaga kesatuan perubahan, ROLLBACK menyelamatkan salah catat, dan CHECK menjaga batas stok. Satu hal yang belum kita miliki adalah pandangan menyeluruh — pada Bagian 4 kita menyusun laporan dari dua bulan riwayat: nilai persediaan, barang yang perlu dipesan ulang, kartu stok, dan ringkasan mutasi per kategori.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

database/data-awal.sql

Unduh berkas
sql
-- 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
sql
-- 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
sql
-- 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
sql
-- 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
sql
-- 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
sql
-- 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
sql
-- 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;