Sistem manajemen inventori berbasis web yang dibangun dengan Laravel 13. Dirancang untuk mengelola produk, stok, gudang, supplier, dan kategori dalam satu platform yang aman dan mudah digunakan.
- Overview
- Untuk Siapa
- Fitur
- Tech Stack
- Development
- Production
- Kredensial Default
- Arsitektur & Struktur
- Keamanan
- Troubleshoot
Inventori adalah aplikasi web full-stack untuk manajemen stok barang. Sistem ini mampu melacak pergerakan stok secara real-time lintas gudang, memberikan peringatan stok rendah, dan menyimpan riwayat transaksi yang tidak dapat diubah (immutable).
| Komponen | Detail |
|---|---|
| Framework | Laravel 13 (PHP 8.3+) |
| Database | SQLite (dev) / MySQL/PostgreSQL (prod) |
| Session | Database-backed, terenkripsi |
| Auth | Session-based, single-role (Admin) |
| Testing | PHPUnit 12 β 43 tests, 92 assertions |
| Keamanan | CSRF, XSS-safe, rate limiting, bcrypt 12 rounds |
Sistem ini cocok untuk:
- Usaha kecil dan menengah (UKM) yang butuh sistem inventori sederhana namun andal
- Toko retail yang mengelola stok di satu atau beberapa gudang
- Developer Laravel yang ingin mempelajari arsitektur service layer, form request, transaksi database, dan feature testing
- Tim internal perusahaan yang membutuhkan sistem pencatatan stok dengan audit trail
β οΈ Sistem ini dirancang untuk satu role Admin. Multi-user dengan role berbeda (admin/staf) merupakan rencana fitur lanjutan.
- Kategori Produk β CRUD kategori dengan auto-slug, search, dan proteksi hapus (tidak bisa dihapus jika masih ada produk terkait)
- Supplier β CRUD supplier dengan multi-kolom search (nama, kontak, email, telepon) dan proteksi hapus
- Gudang β CRUD gudang dengan kode unik, search, dan proteksi hapus berlapis (ada stok / ada riwayat)
- CRUD Produk lengkap dengan harga beli & jual, SKU unik, satuan, dan status aktif/non-aktif
- Filter multi-dimensi: nama/SKU, kategori, supplier, status aktif, dan produk stok rendah
- Detail produk: tampilan stok per gudang dan 20 riwayat pergerakan stok terbaru
- Proteksi hapus: produk yang sudah memiliki riwayat stok tidak dapat dihapus
- Stok Masuk (
in) β Menambah stok pada gudang tertentu - Stok Keluar (
out) β Mengurangi stok (diblokir jika stok tidak mencukupi) - Penyesuaian (
adjustment) β Set stok ke nilai absolut baru (stock opname) - Riwayat immutable β Pergerakan stok yang sudah dicatat tidak dapat diubah atau dihapus
- Pencatatan
stock_before&stock_afterdi setiap transaksi untuk audit trail penuh - Filter riwayat: produk, gudang, tipe, rentang tanggal
- Ringkasan: total produk, kategori, supplier, gudang
- Tabel 10 pergerakan stok terbaru
- Tabel peringatan stok rendah (produk di bawah minimum stok)
- Login / Logout berbasis session
- Remember Me β session persisten
- Redirect-if-authenticated β user yang sudah login tidak bisa mengakses halaman login
- Semua halaman dilindungi middleware
auth - Rate limiting: maksimum 5 percobaan login per menit per email+IP
Backend: Laravel 13, PHP 8.3+
Database: SQLite (dev) / MySQL 8+ or PostgreSQL 14+ (prod)
Auth: Laravel built-in session auth
Testing: PHPUnit 12
Frontend: Bootstrap 5.3, Bootstrap Icons (via CDN)
Build: Vite
- PHP 8.3+
- Composer
- Node.js 20+ & npm
- SQLite extension aktif
# 1. Clone repository
git clone <repo-url>
cd laraproj
# 2. Install PHP dependencies
composer install
# 3. Copy environment file
cp .env.example .env
# 4. Generate application key
php artisan key:generate
# 5. Install Node dependencies
npm install
# 6. Siapkan database dan jalankan seeder
php artisan migrate:fresh --seed
# 7. Jalankan development server
npm run dev
# Di terminal lain:
php artisan serveAtau gunakan shortcut Composer:
composer setup # install + migrate + build composer dev # serve + queue + log watcher + vite
# Jalankan semua tests
php artisan test
# Atau via Composer
composer test
# Filter per kelas
php artisan test --filter=AuthTest
php artisan test --filter=CategoryTest
php artisan test --filter=ProductTest
php artisan test --filter=StockMovementTestExpected output: Tests: 43, Assertions: 92, Passed: 43
# Reset total + jalankan seeder ulang
php artisan migrate:fresh --seedAPP_NAME=Inventori
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost:8000
# Timezone WIB
APP_TIMEZONE=Asia/Jakarta
DB_CONNECTION=sqlite
# DB_DATABASE=database/database.sqlite # default
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=true
BCRYPT_ROUNDS=12- PHP 8.3+ dengan ekstensi:
pdo_mysql/pdo_pgsql,openssl,mbstring,tokenizer,xml,ctype,json,bcmath - MySQL 8+ atau PostgreSQL 14+
- Web server: Nginx / Apache
- Supervisor (untuk queue worker jika diperlukan)
# 1. Clone / pull kode ke server
git clone <repo-url> /var/www/inventori
cd /var/www/inventori
# 2. Install dependencies (tanpa dev)
composer install --no-dev --optimize-autoloader
# 3. Copy dan edit .env production
cp .env.example .env
# Edit sesuai konfigurasi production (lihat di bawah)
# 4. Generate app key
php artisan key:generate
# 5. Build frontend assets
npm ci
npm run build
# 6. Jalankan migration (TANPA --seed di production)
php artisan migrate --force
# 7. Buat user admin pertama via Tinker
php artisan tinker
>>> \App\Models\User::create(['name'=>'Admin','email'=>'admin@domain.com','password'=>bcrypt('your-strong-password')])->forceFill(['role'=>'admin'])->save();
# 8. Optimasi
php artisan config:cache
php artisan route:cache
php artisan view:cache
# 9. Set permission storage
chmod -R 775 storage bootstrap/cache
chown -R www-data:www-data storage bootstrap/cacheAPP_NAME=Inventori
APP_ENV=production
APP_DEBUG=false # β WAJIB false
APP_URL=https://yourdomain.com
APP_TIMEZONE=Asia/Jakarta
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=inventori_db
DB_USERNAME=inventori_user
DB_PASSWORD=your-strong-db-password # β Gunakan password kuat
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=true # β WAJIB true
SESSION_SECURE_COOKIE=true # β Aktifkan jika pakai HTTPS
BCRYPT_ROUNDS=12
LOG_CHANNEL=daily
LOG_LEVEL=warning
β οΈ PENTING:
- Jangan commit file
.envke Git- Ganti semua password default sebelum deploy
- Pastikan
APP_DEBUG=falsedi production- Gunakan HTTPS di production
server {
listen 80;
server_name yourdomain.com;
root /var/www/inventori/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
β οΈ Hanya untuk development/testing. Ganti sebelum production.
| Field | Value |
|---|---|
admin@inventori.test |
|
| Password | password |
app/
βββ Enums/
β βββ UserRole.php # Enum role user (Admin)
βββ Http/
β βββ Controllers/
β β βββ Auth/
β β β βββ LoginController.php
β β βββ DashboardController.php
β β βββ CategoryController.php
β β βββ SupplierController.php
β β βββ WarehouseController.php
β β βββ ProductController.php
β β βββ StockMovementController.php
β βββ Requests/
β βββ Store/UpdateCategoryRequest.php
β βββ Store/UpdateSupplierRequest.php
β βββ Store/UpdateWarehouseRequest.php
β βββ Store/UpdateProductRequest.php
β βββ StoreStockMovementRequest.php
βββ Models/
β βββ User.php
β βββ Category.php
β βββ Supplier.php
β βββ Warehouse.php
β βββ Product.php
β βββ ProductStock.php
β βββ StockMovement.php
βββ Providers/
β βββ AppServiceProvider.php # Rate limiter config
βββ Services/
βββ Inventory/
βββ StockMovementService.php # Core business logic
database/
βββ migrations/ # 9 tabel migration
βββ factories/ # User, Category, Supplier, Warehouse, Product factories
βββ seeders/
βββ DatabaseSeeder.php # 20 produk + riwayat stok realistis
routes/
βββ web.php # Guest routes + Auth-protected routes
tests/
βββ Feature/
βββ AuthTest.php # 12 tests
βββ CategoryTest.php # 8 tests
βββ ProductTest.php # 9 tests
βββ StockMovementTest.php # 11 tests
βββ ExampleTest.php # 3 tests
Request β StoreStockMovementRequest (validasi)
β StockMovementController::store()
β StockMovementService::createMovement()
βββ DB::transaction()
βββ ProductStock::lockForUpdate() β cegah race condition
βββ Hitung stock_before & stock_after
βββ Validasi: stok tidak boleh negatif
βββ Update ProductStock.quantity
βββ Create StockMovement (immutable record)
| Aspek | Implementasi |
|---|---|
| CSRF | Laravel default middleware, @csrf di semua form |
| XSS | Blade {{ }} escaping di semua output |
| SQL Injection | Eloquent parameterized queries β tidak ada raw query |
| Brute Force | Rate limit 5x/menit per email+IP via throttle:login |
| Session Fixation | session()->regenerate() saat login, invalidate() saat logout |
| Session Encryption | SESSION_ENCRYPT=true |
| Password | Bcrypt 12 rounds via model cast |
| Mass Assignment | $fillable eksplisit di semua model; role dikecualikan |
| Race Condition | DB::transaction() + lockForUpdate() pada transaksi stok |
Dashboard dan halaman lain memerlukan file layout utama.
# Cek apakah file ada
ls resources/views/layouts/app.blade.phpJika tidak ada, buat file resources/views/layouts/app.blade.php sebagai layout Bootstrap 5.
Session driver menggunakan database tapi tabel belum ada.
php artisan migrateAutoloader belum di-refresh setelah penambahan file.
composer dump-autoloadKemungkinan penyebab:
- Database belum di-seed
php artisan migrate:fresh --seed
- Cache config lama
php artisan config:clear && php artisan cache:clear - Sudah kena rate limit (5x/menit) β tunggu 1 menit
Pastikan product_id dan warehouse_id yang dipakai sesuai. Stok dihitung per gudang, bukan total semua gudang.
Test menggunakan in-memory SQLite terpisah dari database development.
# Pastikan SQLite extension aktif
php -m | findstr sqlite
# Clear config cache sebelum test
php artisan config:clear
php artisan testAsset Vite belum di-build.
# Development
npm run dev
# Production
npm run buildphp artisan key:generatephp artisan migrate:fresh --seed
php artisan config:clear
php artisan cache:clear
php artisan view:clearMIT License β bebas digunakan untuk keperluan pribadi, edukasi, maupun komersial.