# DOKUMEN TEKNIS: STANDARISASI MEKANISME TRANSAKSI PENJUALAN
## Migrasi dari Direct INSERT ke Stored Procedure & Database Transaction (ACID Compliant)

### 1. Latar Belakang & Masalah (Urgensi)
Saat ini, mekanisme transaksi masih menggunakan `INSERT` langsung ke tabel fisik dari aplikasi kasir. Metode ini **dilarang dalam skala supermarket puluhan ribu item** karena memiliki risiko tinggi:
*   **Data Pincang (Data Inconsistency):** Jika koneksi putus saat proses input, stok barang bisa jadi sudah terpotong, namun jurnal keuangan (COA) belum terbentuk.
*   **Celah Keamanan:** Hak akses database di sisi aplikasi kasir terbuka terlalu bebas, rawan manipulasi data piutang atau harga modal.

### 2. Solusi Standar (Target Arsitektur)
Sesuai standar **ISO/IEC 9075 (SQL)** dan prinsip **ACID (Atomicity, Consistency, Isolation, Durability)**, seluruh proses transaksi wajib dibungkus dalam satu kesatuan menggunakan **Stored Procedure** dan dikunci oleh fitur **Database Transaction**. 

Aplikasi kasir di bagian depan *hanya boleh* memanggil satu fungsi tunggal, dan database di latar belakang yang akan mengeksekusi semua tabel terkait secara serentak. Jika satu tabel gagal di-input, maka seluruh rangkaian transaksi akan dibatalkan otomatis (*Rollback*).

---

### 3. Implementasi Kode SQL (Cetak Biru Database)

Programmer wajib menghapus query `INSERT` manual di aplikasi dan menanamkan *Stored Procedure* berikut di dalam DBMS (MySQL/PostgreSQL/SQL Server):

```sql
DELIMITER \[  CREATE PROCEDURE sp_ProsesTransaksiPenjualan (     IN p_sku_barang VARCHAR(20),     IN p_jumlah_beli INT,     IN p_id_pelanggan VARCHAR(20),     IN p_metode_bayar VARCHAR(10) -- 'TUNAI' atau 'PIUTANG' ) BEGIN     -- Deklarasi variabel internal     DECLARE v_harga_jual DECIMAL(15,2);     DECLARE v_total_bayar DECIMAL(15,2);     DECLARE v_stok_sekarang INT;     DECLARE v_no_nota VARCHAR(20);     DECLARE v_akun_piutang VARCHAR(15);          -- Handler jika terjadi error (Otomatis lakukan ROLLBACK)     DECLARE EXIT HANDLER FOR SQLEXCEPTION     BEGIN         ROLLBACK;         SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Transaksi Gagal! Terjadi kesalahan data, seluruh perubahan dibatalkan.';     END;      -- =================================================================     -- LANGKAH 1: Validasi Ketersediaan Barang & Ambil Harga Jual     -- =================================================================     SELECT harga_jual, stok INTO v_harga_jual, v_stok_sekarang     FROM t_master_barang     WHERE sku = p_sku_barang;      IF v_stok_sekarang < p_jumlah_beli THEN         SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Transaksi Ditolak! Stok barang di gudang tidak mencukupi.';     END IF;      -- Hitung total belanja     SET v_total_bayar = v_harga_jual * p_jumlah_beli;     -- Generate nomor nota otomatis secara sekuensial     SET v_no_nota = CONCAT('INV-', DATE_FORMAT(NOW(), '\%Y\%m\%d'), '-', LPAD(FLOOR(RAND() * 10000), 4, '0'));      -- =================================================================     -- AMAN: AKTIFKAN MEKANISME TRANSACTION BLOCK     -- =================================================================     START TRANSACTION;          -- Langkah A: Masukkan data ke Tabel Transaksi Utama         INSERT INTO t_penjualan_nota (no_nota, tanggal, id_pelanggan, total_transaksi, status_pembayaran)         VALUES (v_no_nota, NOW(), p_id_pelanggan, v_total_bayar, p_metode_bayar);          -- Langkah B: Masukkan data rincian ke Tabel Detail Item         INSERT INTO t_penjualan_detail (no_nota, sku, jumlah, harga_satuan, subtotal)         VALUES (v_no_nota, p_sku_barang, p_jumlah_beli, v_harga_jual, v_total_bayar);          -- Langkah C: Potong Stok secara Akurat di Tabel Gudang         UPDATE t_master_barang          SET stok = stok - p_jumlah_beli          WHERE sku = p_sku_barang;          -- Langkah D: Otomatisasi Jurnal Akuntansi Berderajat Level (COA)         IF p_metode_bayar = 'PIUTANG' THEN             -- Cari rekening pembantu piutang milik spesifik pelanggan tersebut             SELECT no_akun INTO v_akun_piutang FROM t_chart_of_accounts WHERE id_referensi = p_id_pelanggan AND level_akun = 4;                          -- Jurnal DEBIT: Rekening Pembantu Piutang Pelanggan (Level 4)             INSERT INTO t_jurnal_akuntansi (no_nota, tanggal, no_akun, posisi, nominal)             VALUES (v_no_nota, NOW(), v_akun_piutang, 'DEBIT', v_total_bayar);         ELSE             -- Jurnal DEBIT: Kas Toko (Jika Tunai)             INSERT INTO t_jurnal_akuntansi (no_nota, tanggal, no_akun, posisi, nominal)             VALUES (v_no_nota, NOW(), '11001', 'DEBIT', v_total_bayar);         END IF;          -- Jurnal KREDIT: Pendapatan Suku Cadang (Otomatis masuk kelompok laporan)         INSERT INTO t_jurnal_akuntansi (no_nota, tanggal, no_akun, posisi, nominal)         VALUES (v_no_nota, NOW(), '41010', 'KREDIT', v_total_bayar);      -- =================================================================     -- KUNCI DATA: SEMUA SUKSES, SIMPAN PERMANEN KE HARDDISK     -- =================================================================     COMMIT;      -- Kembalikan respons sukses ke aplikasi kasir     SELECT v_no_nota AS 'Nomor Nota Baru', 'SUKSES' AS 'Status';  END\]

DELIMITER ;
```

---

### 4. Cara Aplikasi Kasir Memanggil Data (Sisi Frontend/API)

Dengan diubahnya mekanisme di atas, tim *programmer* di sisi aplikasi depan (Back-End API / Desktop Application) **TIDAK BOLEH LAGI** menulis kode insert yang panjang. Mereka cukup memanggil 1 baris perintah eksekusi ini:

#### Contoh Perintah di Database (SQL):
```sql
CALL sp_ProsesTransaksiPenjualan('SKU-001021', 2, 'CUST-0089', 'PIUTANG');
```

#### Contoh Implementasi Backend (Node.js / Express API):
```javascript
// Tim IT cukup hit endpoint ini saat kasir klik tombol "Selesai"
app.post('/api/transaksi', async (req, res) => {
    const { sku, jumlah, id_pelanggan, metode_bayar } = req.body;
    try {
        // Memanggil Stored Procedure, bukan query insert tabel langsung
        const [result] = await db.query('CALL sp_ProsesTransaksiPenjualan(?, ?, ?, ?)', [sku, jumlah, id_pelanggan, metode_bayar]);
        res.status(200).json({ success: true, data: result[0] });
    } catch (error) {
        res.status(500).json({ success: false, message: error.message });
    }
});
```

---

### 5. Parameter Pengujian Sukses (Untuk Tim QA/Tester)
Mekanisme baru ini dinyatakan berhasil lolos uji jika memenuhi skenario berikut:
1.  **Uji Stok Kosong:** Jika jumlah beli melebihi stok, sistem harus menolak di awal dan tidak boleh ada baris baru di tabel nota maupun akuntansi.
2.  **Uji Interupsi Jaringan (Simulasi Mati Lampu):** Matikan koneksi database tepat setelah langkah *Langkah B (Detail)* dieksekusi. Saat database dinyalakan kembali, **stok barang tidak boleh terpotong dan jurnal tidak boleh bocor** (*Rollback* berhasil).
