WebTech Interaktif XI RPL

Laravel · Bab 8

Lab 5: Transaksi Kasir & Potong Stok

Membangun modul kasir yang memeriksa stok, mencatat transaksi dengan nomor invoice, dan memotong stok produk dalam satu Database Transaction yang aman (ACID).

Di lab ini Smart Retail App menjalankan logika bisnis sungguhan: kasir memilih produk dan jumlah, sistem memeriksa stok, lalu mencatat transaksi sekaligus memotong stok. Kedua perubahan itu harus berhasil bersama atau gagal bersama. Contoh kode memakai studi kasus peminjaman buku (loans dan books); terapkan polanya ke transaksi dan produk.

Alur Langkah

  1. Jalankan php artisan make:controller <NamaController> untuk controller transaksi.
  2. Isi method index() yang menampilkan riwayat transaksi beserta produknya (eager loading, urut terbaru).
  3. Isi method create() yang hanya mengirim produk dengan stok lebih dari 0 ke form kasir.
  4. Isi method store(): validasi product_id, quantity (min. 1), dan transaction_date; ambil produknya; tolak bila stok kurang; lalu di dalam Database Transaction buat nomor invoice, simpan transaksi dengan total harga, dan potong stok. Setelah itu redirect ke riwayat dengan pesan sukses.
  5. Tambahkan tiga rute bernama di routes/web.php: daftar transaksi (GET), form transaksi (GET), dan simpan transaksi (POST), beserta use controllernya.
  6. Buat folder view transaksi, lalu buat index.blade.php (tabel riwayat: invoice, tanggal, produk, harga satuan, qty, total) dan create.blade.php (form kasir: pilih produk, jumlah, tanggal).
  7. Uji transaksi normal, lalu uji jumlah yang melebihi stok.

Hanya Tampilkan yang Tersedia

Method where menyaring data sebelum diambil. Buku yang stoknya habis tidak perlu muncul di form peminjaman:

public function create()
{
    $books = Book::where('stock', '>', 0)->get();
    return view('loans.create', compact('books'));
}

Validasi Aturan Bisnis

Aturan validasi (required, min) memeriksa format input, tetapi tidak tahu sisa stok. Aturan bisnis seperti ini diperiksa manual setelah validasi:

$book = Book::findOrFail($request->book_id);

if ($book->stock < $request->quantity) {
    return back()->withInput()->with('error', 'Stok tidak cukup (sisa: ' . $book->stock . ').');
}
  • findOrFail mengambil data berdasarkan ID, atau langsung menampilkan halaman 404 bila tidak ditemukan.
  • back()->withInput() mengembalikan pengguna ke form dengan isian sebelumnya.
  • ->with('error', ...) mengirim pesan yang ditampilkan kotak merah di layout (Lab 3).

Database Transaction (ACID)

Menyimpan transaksi dan memotong stok adalah dua perintah terpisah. Jika perintah pertama berhasil tetapi yang kedua gagal (misalnya listrik padam), data menjadi tidak konsisten: transaksi tercatat, stok tidak berkurang. DB::transaction mengikat keduanya sesuai prinsip ACID:

  • Atomicity — semua perintah berhasil, atau semuanya dibatalkan.
  • Consistency — database selalu berpindah dari satu keadaan valid ke keadaan valid lain.
  • Isolation — transaksi yang berjalan bersamaan tidak saling mengganggu.
  • Durability — perubahan yang sudah berhasil tetap tersimpan.
use Illuminate\Support\Facades\DB;

DB::transaction(function () use ($request, $book) {
    Loan::create([
        'book_id'   => $book->id,
        'loan_code' => 'LN-' . strtoupper(uniqid()),
        'quantity'  => $request->quantity,
        'loan_date' => $request->loan_date,
    ]);

    $book->decrement('stock', $request->quantity);
});
  • use ($request, $book) membuat variabel dari luar tersedia di dalam fungsi.
  • uniqid() menghasilkan kode unik berbasis waktu; strtoupper membuatnya huruf kapital sehingga nomor nota terlihat seperti LN-66F9A1B2C3D4E.
  • decrement('stock', n) mengurangi kolom stok sebanyak n langsung di database.

Pada transaksi retail, total bayar dihitung dari harga produk dikali jumlah sebelum disimpan.

Rute Manual Bernama

Modul kasir tidak memerlukan ketujuh rute resource, jadi rutenya didaftarkan satu per satu dengan nama:

use App\Http\Controllers\LoanController;

Route::get('/loans', [LoanController::class, 'index'])->name('loans.index');
Route::get('/loans/create', [LoanController::class, 'create'])->name('loans.create');
Route::post('/loans', [LoanController::class, 'store'])->name('loans.store');

Nama dari ->name('...') membuat helper route('loans.index') bisa dipakai di view dan redirect. Tautan menu transaksi di navbar layout (yang dibungkus Route::has di Lab 3) otomatis aktif begitu rute ini terdaftar.

View Riwayat & Form Kasir

  • Tabel riwayat menampilkan nomor invoice, tanggal (date('d/m/Y', strtotime(...))), nama produk lewat relasi, harga satuan dan total dengan number_format, serta qty.
  • Form kasir berisi dropdown produk berstok (nama, harga, dan sisa stok ditampilkan di teks opsi), input jumlah dengan old('quantity', 1), dan input tanggal dengan nilai awal date('Y-m-d'). Setiap input memakai pola @error dari Lab 4, dan form wajib memakai @csrf.
  • Pesan session('error') dari pengecekan stok tampil lewat kotak merah di layout.

Hasil yang Diharapkan

Transaksi yang valid muncul di riwayat dengan nomor invoice unik, dan stok produknya di katalog berkurang sesuai jumlah terjual. Jumlah yang melebihi stok ditolak dengan pesan merah, dan tidak ada data yang berubah.