Membuat Sistem Kasir Modern dengan Laravel 12 dan Vue.js dari Nol

Di era digital saat ini, kebutuhan akan sistem kasir berbasis web (Point of Sale / POS) semakin meningkat, terutama bagi pelaku UMKM yang ingin mengelola transaksi secara cepat, akurat, dan real-time. Pada artikel ini, kita akan membahas secara lengkap cara membuat sistem kasir modern menggunakan Laravel 12 sebagai backend dan Vue.js sebagai frontend.

Laravel 12 menawarkan performa tinggi, struktur kode yang rapi, serta dukungan REST API yang powerful, sementara Vue.js memberikan pengalaman antarmuka yang interaktif dan responsif. Kombinasi keduanya sangat cocok untuk membangun aplikasi kasir yang scalable, ringan, dan mudah dikembangkan.

Tutorial ini akan membahas mulai dari setup project, desain database, pembuatan API transaksi, fitur keranjang belanja (cart system), manajemen produk, hingga proses checkout dan laporan penjualan. Dengan mengikuti panduan ini, Anda dapat membangun aplikasi kasir berbasis web yang siap digunakan untuk toko, café, minimarket, maupun bisnis retail lainnya.

STEP 1 — Setup Project Laravel + Vue

Install Laravel

composer create-project laravel/laravel smartpos<br>cd smartpos

Install Breeze + Vue

Schema::create('categories', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->timestamps();
});

Sekarang sudah ada:

  • Login
  • Register
  • Vue 3
  • Vite
  • Sanctum ready

STEP 2 — Struktur Database POS

Kita buat minimal dulu:

  • users
  • categories
  • products
  • transactions
  • transaction_items

Migration: Categories

php artisan make:model Category -m
Schema::create('products', function (Blueprint $table) {
    $table->id();
    $table->foreignId('category_id')->constrained()->cascadeOnDelete();
    $table->string('name');
    $table->string('barcode')->nullable();
    $table->decimal('cost_price', 12, 2)->default(0);
    $table->decimal('sell_price', 12, 2);
    $table->integer('stock')->default(0);
    $table->timestamps();
});

Migration: Products

php artisan make:model Product -m
Schema::create('transactions', function (Blueprint $table) {
    $table->id();
    $table->string('invoice_number')->unique();
    $table->decimal('total', 12, 2);
    $table->decimal('paid', 12, 2);
    $table->decimal('change', 12, 2);
    $table->string('payment_method');
    $table->foreignId('user_id')->constrained();
    $table->timestamps();
});

Migration: Transactions

php artisan make:model Transaction -m
Schema::create('transactions', function (Blueprint $table) {
    $table->id();
    $table->string('invoice_number')->unique();
    $table->decimal('total', 12, 2);
    $table->decimal('paid', 12, 2);
    $table->decimal('change', 12, 2);
    $table->string('payment_method');
    $table->foreignId('user_id')->constrained();
    $table->timestamps();
});

Migration: Transaction Items

php artisan make:model TransactionItem -m
Schema::create('transaction_items', function (Blueprint $table) {
    $table->id();
    $table->foreignId('transaction_id')->constrained()->cascadeOnDelete();
    $table->foreignId('product_id')->constrained();
    $table->integer('qty');
    $table->decimal('price', 12, 2);
    $table->decimal('subtotal', 12, 2);
    $table->timestamps();
});

Jalankan Migration

php artisan migrate

STEP 3 — Relasi Model

Product.php

public function category()
{
    return $this->belongsTo(Category::class);
}

Transaction.php

public function items()
{
    return $this->hasMany(TransactionItem::class);
}

public function user()
{
    return $this->belongsTo(User::class);
}

STEP 4 — API Endpoint

Edit routes/api.php

Route::middleware('auth:sanctum')->group(function () {

    Route::apiResource('products', ProductController::class);
    Route::post('transactions', [TransactionController::class, 'store']);

});

STEP 5 — Logic Simpan Transaksi

Buat controller:

php artisan make:controller TransactionController

Contoh method store:

public function store(Request $request)
{
    DB::beginTransaction();

    try {

        $transaction = Transaction::create([
            'invoice_number' => 'INV-'.time(),
            'total' => $request->total,
            'paid' => $request->paid,
            'change' => $request->paid - $request->total,
            'payment_method' => $request->payment_method,
            'user_id' => auth()->id(),
        ]);

        foreach ($request->items as $item) {

            TransactionItem::create([
                'transaction_id' => $transaction->id,
                'product_id' => $item['id'],
                'qty' => $item['qty'],
                'price' => $item['sell_price'],
                'subtotal' => $item['sell_price'] * $item['qty'],
            ]);

            Product::where('id', $item['id'])
                ->decrement('stock', $item['qty']);
        }

        DB::commit();
        return response()->json(['success' => true]);

    } catch (\Exception $e) {

        DB::rollBack();
        return response()->json(['error' => $e->getMessage()], 500);

    }
}

STEP 6 — Vue Cashier Page

Buat:

resources/js/Pages/Cashier.vue

Core logic:

import { ref, computed } from 'vue'
import axios from 'axios'

const products = ref([])
const cart = ref([])

const total = computed(() =>
    cart.value.reduce((sum, item) => sum + item.sell_price * item.qty, 0)
)

function addToCart(product) {
    const existing = cart.value.find(p => p.id === product.id)

    if (existing) {
        existing.qty++
    } else {
        cart.value.push({ ...product, qty: 1 })
    }
}

async function checkout() {
    await axios.post('/api/transactions', {
        items: cart.value,
        total: total.value,
        paid: total.value,
        payment_method: 'cash'
    })

    cart.value = []
};

Sampai Sini Kamu Sudah Punya:

✅ Login system
✅ CRUD produk
✅ Transaksi
✅ Pengurangan stok
✅ Total otomatis

Leave a Comment