Membangun Sistem Informasi Inventory Berbasis Web

Sistem informasi inventory adalah salah satu aplikasi yang paling banyak diminta klien: toko, gudang, apotek, hingga bengkel semua butuh tahu berapa stok tersisa saat ini. Kesalaha...

Membangun Sistem Informasi Inventory Berbasis Web

Sistem informasi inventory adalah salah satu aplikasi yang paling banyak diminta klien: toko, gudang, apotek, hingga bengkel semua butuh tahu berapa stok tersisa saat ini. Kesalahan paling umum pemula adalah menyimpan stok sebagai satu angka di tabel produk lalu menambah/mengurangi angka itu langsung. Cara itu tampak sederhana, tetapi begitu ada retur, koreksi, atau audit, Anda kehilangan riwayat dan sulit menjelaskan kenapa stok tiba-tiba salah. Tutorial ini membangun inventory yang benar di Laravel 11 menggunakan pola stock movement (buku besar stok).

1. Merancang Model Data

Kita butuh tiga entitas inti: suppliers, products, dan stock_movements. Kunci desainnya: stok saat ini bukan kolom yang diedit manual, melainkan hasil penjumlahan dari seluruh mutasi. Setiap barang masuk dicatat sebagai baris in dan setiap barang keluar sebagai baris out.

php artisan make:model Supplier -m
php artisan make:model Product -m
php artisan make:model StockMovement -m

Migration suppliers dan products

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

Schema::create('products', function (Blueprint $table) {
    $table->id();
    $table->string('sku')->unique();
    $table->string('name');
    $table->integer('price')->default(0);
    $table->integer('min_stock')->default(5); // ambang stok menipis
    $table->timestamps();
});

Migration stock_movements

Inilah jantung sistem. Kolom type membedakan masuk/keluar, qty selalu positif, dan reference menyimpan nomor faktur atau nota agar setiap mutasi bisa dilacak asalnya.

Schema::create('stock_movements', function (Blueprint $table) {
    $table->id();
    $table->foreignId('product_id')->constrained()->cascadeOnDelete();
    $table->foreignId('supplier_id')->nullable()->constrained();
    $table->enum('type', ['in', 'out']);
    $table->unsignedInteger('qty');
    $table->string('reference')->nullable();
    $table->text('note')->nullable();
    $table->timestamps();
});
php artisan migrate

2. Menyiapkan Model dan Relasi

Pada model Product, kita definisikan relasi ke mutasi dan sebuah accessor untuk menghitung stok berjalan. Accessor membuat $product->current_stock selalu akurat tanpa menyimpan angka yang bisa basi.

class Product extends Model
{
    protected $fillable = ['sku', 'name', 'price', 'min_stock'];

    public function movements()
    {
        return $this->hasMany(StockMovement::class);
    }

    public function getCurrentStockAttribute(): int
    {
        return (int) $this->movements()
            ->selectRaw("SUM(CASE WHEN type = 'in' THEN qty ELSE -qty END) as total")
            ->value('total');
    }

    public function getIsLowAttribute(): bool
    {
        return $this->current_stock <= $this->min_stock;
    }
}

Model StockMovement cukup ringkas:

class StockMovement extends Model
{
    protected $fillable = ['product_id', 'supplier_id', 'type', 'qty', 'reference', 'note'];

    public function product()
    {
        return $this->belongsTo(Product::class);
    }
}

3. Mencatat Stok Masuk dan Keluar

Semua perubahan stok harus lewat pencatatan mutasi. Untuk barang keluar, kita wajib memeriksa dulu apakah stok mencukupi supaya stok tidak jadi minus. Bungkus operasi dalam transaksi database agar aman dari kondisi balapan (race condition).

use Illuminate\Support\Facades\DB;

public function store(Request $request)
{
    $data = $request->validate([
        'product_id' => 'required|exists:products,id',
        'type'       => 'required|in:in,out',
        'qty'        => 'required|integer|min:1',
        'reference'  => 'nullable|string|max:100',
    ]);

    DB::transaction(function () use ($data) {
        $product = Product::lockForUpdate()->findOrFail($data['product_id']);

        if ($data['type'] === 'out' && $product->current_stock < $data['qty']) {
            abort(422, 'Stok tidak mencukupi. Sisa: ' . $product->current_stock);
        }

        $product->movements()->create($data);
    });

    return back()->with('success', 'Mutasi stok tercatat.');
}

Karena stok dihitung dari mutasi, koreksi menjadi mudah: salah input tinggal dibuat mutasi lawannya, dan riwayat tetap utuh untuk audit.

4. Alert Stok Menipis

Pemilik toko ingin tahu barang apa yang harus segera dipesan ulang. Karena stok dihitung dari SUM mutasi, kita filter di level query agar cepat meski produk ribuan:

$lowStock = Product::query()
    ->withSum(['movements as stock' => function ($q) {
        $q->selectRaw("SUM(CASE WHEN type = 'in' THEN qty ELSE -qty END)");
    }], 'qty')
    ->get()
    ->filter(fn ($p) => $p->current_stock <= $p->min_stock);

Untuk dataset besar, lebih efisien menghitung agregat langsung di database dengan selectRaw dan havingRaw:

$lowStock = Product::query()
    ->leftJoin('stock_movements', 'stock_movements.product_id', '=', 'products.id')
    ->selectRaw('products.*, COALESCE(SUM(CASE WHEN type = "in" THEN qty ELSE -qty END), 0) as stock')
    ->groupBy('products.id')
    ->havingRaw('stock <= products.min_stock')
    ->get();

5. Laporan Sederhana

Laporan mutasi per periode cukup memfilter tabel stock_movements berdasarkan tanggal, lalu meringkas total masuk dan keluar:

$report = StockMovement::query()
    ->whereBetween('created_at', [$start, $end])
    ->selectRaw("type, SUM(qty) as total")
    ->groupBy('type')
    ->pluck('total', 'type');

// $report['in'] = total masuk, $report['out'] = total keluar

Kesalahan Umum yang Perlu Dihindari

  • Menyimpan stok sebagai satu kolom yang diedit langsung: Anda kehilangan riwayat dan rawan stok jadi salah tanpa jejak. Pakai buku besar mutasi.
  • Tidak memakai transaksi + lockForUpdate: dua permintaan bersamaan bisa membuat stok jadi minus.
  • Menyimpan qty negatif untuk barang keluar: lebih rapi memakai kolom type dan qty selalu positif.

Dengan pola stock movement ini, aplikasi inventory Anda akurat, mudah diaudit, dan siap dikembangkan menjadi fitur retur, transfer antar-gudang, atau stok opname tanpa merombak struktur data.

Yudhi
Ditulis oleh
Yudhi
Founder & Lead Developer, GudangCode

Yudhi adalah founder GudangCode dan developer Laravel dengan pengalaman membangun puluhan sistem informasi bisnis siap pakai — mulai dari POS, HRIS, hingga aplikasi manajemen. Ia menulis panduan dan artikel di GudangCode untuk membantu developer Indonesia menjalankan, memahami, dan men-deploy source code Laravel dengan benar.

Laravel PHP MySQL Sistem Informasi Bisnis
Lihat semua artikel Yudhi
Mau source code & aplikasi lengkapnya?

Daftar gratis untuk mengunduh aplikasi bisnis, sistem informasi, dan source code Laravel siap pakai.

Daftar Gratis & Download
Sistem Informasi Inventory Web Stok
📚 Free Learning Hub

Learn Coding for Free at DhieCoderWeb

Explore Laravel, PHP, JavaScript tutorials, source code, web development guides, and practical programming tips.

DhieCoderWeb
100+
Tutorials
Free
Learning
SEO
Tips
Visit Dhiecoderweb.com →

Dapatkan Akses Penuh Sekarang!

Bergabunglah menjadi member kami dan dapatkan akses eksklusif ke seluruh fitur unggulan aplikasi ini. Proses cepat, mudah, dan langsung bisa Anda gunakan.

Daftar Members Sekarang
Tim Support
Online
Isi data dulu untuk mulai chat:
Beri rating & testimoni sebelum menutup:
Live chat by gudangcode.com