Aplikasi Point of Sale (POS)/kasir UMKM sederhana berbasis Laravel 13. Ini adalah
demonstrasi dosen untuk mata kuliah Pemrograman Web Lanjut (RTI254007, Prodi D4 Sistem
Informasi Bisnis) — dibangun bertahap mengikuti RPS di
../RTI254007-pemrograman-web-lanjut-sib/, dan menjadi rujukan arsitektur bagi proyek PBL
mahasiswa.
Seluruh dokumentasi proyek ada di file ini — tidak ada file dokumentasi terpisah.
- Laravel 13 (PHP
^8.3, dijalankan dengan PHP 8.4 lewat Nix) - SQLite — zero-setup, file-based
- Tailwind CSS via CDN — tidak ada langkah build frontend (npm/Vite tidak diperlukan)
- Laravel Sanctum — token-based API authentication
Environment PHP disediakan lewat Nix (lihat shell.nix, dibuat dengan php-init 8.4).
# masuk shell (php + composer tersedia)
nix-shell shell.nix
# atau via direnv (sekali izinkan)
direnv allow
composer install
cp .env.example .env # jika belum ada .env
php artisan key:generate
touch database/database.sqlite
php artisan migrate:fresh --seed
php artisan serveBuka http://127.0.0.1:8000. Cukup php artisan serve — tidak ada langkah build frontend.
| Peran | Password | |
|---|---|---|
| Admin | admin@pos.test |
password |
| Kasir | kasir@pos.test |
password |
nix-shell shell.nix --run "php artisan test"13 test (27 assertion): login/logout, otorisasi berbasis peran (kasir diblokir dari halaman admin), transaksi POS (dekremen stok & perhitungan total), alur token API (login → transaksi).
database/seeders/DatabaseSeeder.php sengaja mengisi data dalam skala nyata, bukan
sekadar beberapa baris — supaya paginasi (paginate()) benar-benar terlihat, dan supaya cara
mengisinya sendiri jadi contoh sadar-performa, bukan cuma "lebih banyak data".
Volume yang di-seed (php artisan migrate:fresh --seed, ±1.8 detik):
| Tabel | Jumlah baris | Halaman paginasi |
|---|---|---|
categories |
8 | — |
products |
300 (12 kurasi manual + 288 sintetis, 15 sengaja stok < 10) | 30 halaman @ paginate(10) |
transactions |
2.500 (tersebar 120 hari terakhir) | ±167 halaman @ paginate(15) |
transaction_details |
6.250 (1–4 item per transaksi) | — |
Kenapa bulk insert, bukan Model::create() per baris. Dengan ribuan baris, memanggil
Product::create()/Transaction::create() satu per satu berarti ribuan query individual plus
overhead event Eloquent (observer, casting, dirty-tracking) per baris — lambat dan tidak
representatif untuk skala produksi. Seeder ini membangun array PHP dulu, lalu insert lewat
DB::table(...)->insert($chunk) dalam batch 100 baris/statement (dijaga di bawah batas
~999 parameter terikat SQLite per statement), dibungkus satu DB::transaction() untuk transaksi
- detail agar commit sekali, bukan per baris. Hasilnya: 300 produk + 2.500 transaksi + 6.250 baris detail selesai dalam ±2 detik.
Kenapa perlu index eksplisit — bug performa nyata yang ditemukan. Migrasi memakai
foreignId()->constrained() untuk semua foreign key. Di MySQL, kolom FK otomatis ter-index;
di SQLite, tidak — constrained() hanya menambah constraint, bukan index. Pada skala 12
baris hal ini tidak kelihatan; pada skala ribuan baris, query listing/laporan akan melakukan
full table scan. Migrasi di proyek ini menambahkan $table->index(...) eksplisit pada:
products.category_id, transactions.user_id, transactions.created_at (dipakai ORDER BY
di listing transaksi dan whereBetween di filter laporan), transaction_details.transaction_id,
transaction_details.product_id. Dikonfirmasi dengan EXPLAIN QUERY PLAN:
SEARCH transactions USING INDEX transactions_created_at_index (created_at>? AND created_at<?)
SEARCH transaction_details USING INDEX transaction_details_transaction_id_index (transaction_id=?)
tanpa index eksplisit, baris di atas akan berbunyi SCAN transactions (full table scan).
Membuktikan paginasi bekerja (bukan single-page no-op): GET /products?page=2 dan
GET /transactions?page=2 mengembalikan baris yang benar-benar berbeda dari halaman 1 (SKU
GEN-0146... vs AIR-001... pada products; invoice tanggal berbeda pada transactions) —
sudah diverifikasi manual lewat curl.
Setiap commit git dilabeli increment N dan berkorespondensi dengan minggu RPS di
RTI254007 Pemrograman Web Lanjut SIB - RPS.tex. Ini adalah artefak yang didemonstrasikan
dosen tiap minggu di kelas.
| Increment | Minggu RPS | Topik | Yang dibangun |
|---|---|---|---|
| 1 | 1 | Arsitektur web, framework, environment | Setup proyek Laravel, repo Git, SQLite |
| 2 | 2 | HTTP, MVC, routing, controller | Route & controller produk/transaksi |
| 3 | 3 | Templating, komponen, asset bundling | Layout Blade + Tailwind CDN, halaman POS |
| 4–5 | 4–5 | Desain DB, migrasi, ORM, relasi | Skema (produk, kategori, transaksi), model Eloquent, index FK |
| 6 | 6 | Validasi & sanitasi | FormRequest produk & transaksi |
| 7 | 7 | Autentikasi, RBAC | Login, middleware role:admin |
| — | 8 (UTS) | Milestone studi kasus | Demo aplikasi hasil increment 1–7 |
| 9 | 9 | Pengolahan data, import/export | Laporan penjualan (CSV export), import produk (CSV) |
| 10 | 10 | Arsitektur & desain API | Sanctum token auth, API produk/transaksi untuk kasir mobile |
Minggu 11–15 (PBL): tim mahasiswa membangun aplikasi pada domain bisnis pilihan sendiri, menjadikan struktur proyek ini (skema → model → controller/route → view → validasi → auth → laporan → API) sebagai referensi arsitektur.
- Kasir (POS) —
/pos: pilih produk dari grid, keranjang dinamis (JS), simpan transaksi (stok didekremen dalam DB transaction dengan row lock). - Admin —
/products,/categories: CRUD produk & kategori (middlewarerole:admin), dengan paginasi (30 halaman produk pada data seed). - Laporan —
/reports: filter tanggal, total penjualan, export CSV;/reports/import: import produk dari CSV. - API kasir mobile —
POST /api/login(token Sanctum),GET /api/products,POST /api/transactions(headerAuthorization: Bearer <token>).
categories 1──* products 1──* transaction_details *──1 transactions *──1 users
users.role:admin|kasirtransactions.total,transaction_details.subtotal: dihitung server-side, bukan dari input klien.- Index eksplisit pada semua kolom foreign key +
transactions.created_at(lihat bagian "Data seed, pagination, dan performa" di atas).