Cara Membuat Sistem Multi-Cabang Menggunakan Laravel
Apa Itu Sistem Multi-Cabang?
Sistem multi-cabang adalah aplikasi terpusat yang digunakan oleh beberapa lokasi usaha.
Setiap cabang dapat mengelola data operasionalnya sendiri, seperti:
Produk dan stok.
Penjualan.
Pembelian.
Pelanggan.
Pengguna.
Laporan.
Sementara itu, kantor pusat dapat memantau seluruh cabang melalui satu dashboard.
Sistem seperti ini cocok digunakan untuk minimarket, apotek, klinik, restoran, gudang, koperasi, dan perusahaan dengan banyak lokasi.
Struktur Dasar Multi-Cabang
Pendekatan paling sederhana adalah menggunakan satu database dan menambahkan kolom:
branch_id
pada setiap tabel operasional.
Contohnya:
branches
users
branch_user
products
transactions
customers
purchases
Setiap produk dan transaksi akan terhubung dengan cabang tertentu.
1. Membuat Tabel Cabang
Jalankan:
php artisan make:model Branch -m
Isi migration:
Schema::create('branches', function (Blueprint $table) {
$table->id();
$table->string('code')->unique();
$table->string('name');
$table->string('city')->nullable();
$table->text('address')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
Kemudian jalankan:
php artisan migrate
2. Menghubungkan Pengguna dengan Cabang
Jika satu pengguna dapat mengakses beberapa cabang, buat tabel penghubung:
php artisan make:migration create_branch_user_table
Isi migration:
Schema::create('branch_user', function (Blueprint $table) {
$table->id();
$table->foreignId('branch_id')
->constrained()
->cascadeOnDelete();
$table->foreignId('user_id')
->constrained()
->cascadeOnDelete();
$table->boolean('is_default')->default(false);
$table->timestamps();
$table->unique(['branch_id', 'user_id']);
});
Relasi pada model User:
public function branches()
{
return $this->belongsToMany(Branch::class)
->withPivot('is_default')
->withTimestamps();
}
Relasi pada model Branch:
public function users()
{
return $this->belongsToMany(User::class)
->withPivot('is_default')
->withTimestamps();
}
3. Menambahkan branch_id pada Tabel Operasional
Setiap tabel yang datanya berbeda per cabang harus memiliki branch_id.
Contoh pada tabel produk:
Schema::table('products', function (Blueprint $table) {
$table->foreignId('branch_id')
->after('id')
->constrained()
->cascadeOnDelete();
$table->unique(['branch_id', 'sku']);
});
Tambahkan juga pada tabel lain, seperti:
transactions
customers
purchases
stock_movements
expenses
Jika aplikasi sudah memiliki data, tambahkan branch_id sebagai nullable terlebih dahulu. Setelah seluruh data lama memiliki cabang, barulah kolom dibuat wajib diisi.
4. Menentukan Cabang Aktif
Cabang aktif dapat disimpan pada session setelah pengguna login atau memilih cabang.
Contoh:
session([
'active_branch_id' => $branchId,
]);
Sebelum menyimpan cabang, pastikan pengguna memang mempunyai akses:
$hasAccess = auth()->user()
->branches()
->whereKey($branchId)
->exists();
abort_unless($hasAccess, 403);
session([
'active_branch_id' => $branchId,
]);
Jangan langsung mempercayai branch_id dari form tanpa memeriksa hak akses pengguna.
5. Membuat Middleware Cabang
Buat middleware:
php artisan make:middleware EnsureActiveBranch
Isi:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class EnsureActiveBranch
{
public function handle(Request $request, Closure $next)
{
if (! $request->user()) {
abort(401);
}
$branchId = session('active_branch_id');
if (! $branchId) {
abort(403, 'Silakan pilih cabang terlebih dahulu.');
}
$hasAccess = $request->user()
->branches()
->whereKey($branchId)
->exists();
abort_unless(
$hasAccess,
403,
'Anda tidak memiliki akses ke cabang ini.'
);
return $next($request);
}
}
Gunakan middleware pada route:
Route::middleware([
'auth',
'branch',
])->group(function () {
Route::resource('products', ProductController::class);
Route::resource('transactions', TransactionController::class);
});
6. Membatasi Query Berdasarkan Cabang
Cara paling sederhana adalah menambahkan filter pada setiap query:
$products = Product::query()
->where('branch_id', session('active_branch_id'))
->get();
Ketika menambahkan data:
Product::create([
'branch_id' => session('active_branch_id'),
'sku' => $request->sku,
'name' => $request->name,
'price' => $request->price,
]);
Namun, hindari mengambil branch_id langsung dari request pengguna.
Gunakan cabang aktif yang sudah diverifikasi oleh middleware.
7. Menggunakan Global Scope
Agar tidak menulis filter cabang berulang kali, Anda dapat menggunakan global scope.
Contoh pada model Product:
protected static function booted(): void
{
static::addGlobalScope('branch', function ($query) {
if (session()->has('active_branch_id')) {
$query->where(
'branch_id',
session('active_branch_id')
);
}
});
static::creating(function ($product) {
$product->branch_id = session(
'active_branch_id'
);
});
}
Dengan begitu, query berikut:
Product::all();
hanya menampilkan produk dari cabang aktif.
Global scope sebaiknya digunakan dengan hati-hati, terutama pada queue, scheduler, command, atau laporan semua cabang karena proses tersebut mungkin tidak memiliki session.
8. Membuat Fitur Ganti Cabang
Contoh controller sederhana:
public function switch(Request $request)
{
$validated = $request->validate([
'branch_id' => [
'required',
'integer',
'exists:branches,id',
],
]);
$branchId = (int) $validated['branch_id'];
$hasAccess = $request->user()
->branches()
->whereKey($branchId)
->exists();
abort_unless($hasAccess, 403);
session([
'active_branch_id' => $branchId,
]);
return back()->with(
'success',
'Cabang berhasil diubah.'
);
}
Route:
Route::post(
'/switch-branch',
[BranchController::class, 'switch']
)->middleware('auth');
9. Laporan Semua Cabang
Pengguna kantor pusat dapat diberikan akses untuk melihat seluruh cabang.
Contoh laporan penjualan:
$report = Transaction::query()
->select('branch_id')
->selectRaw('COUNT(*) as total_transactions')
->selectRaw('SUM(grand_total) as total_sales')
->groupBy('branch_id')
->with('branch')
->get();
Akses laporan semua cabang harus dibatasi menggunakan role atau permission.
10. Transfer Stok Antar-Cabang
Transfer stok harus mencatat:
Cabang asal.
Cabang tujuan.
Produk.
Jumlah.
Pengirim.
Penerima.
Status transfer.
Tanggal pengiriman dan penerimaan.
Gunakan database transaction agar pengurangan dan penambahan stok diproses sebagai satu kesatuan:
DB::transaction(function () {
// Kurangi stok cabang asal.
// Tambahkan stok cabang tujuan.
// Simpan riwayat transfer.
});
Jika salah satu proses gagal, seluruh perubahan akan dibatalkan.
Keamanan Sistem Multi-Cabang
Beberapa hal yang perlu diperhatikan:
Jangan mempercayai
branch_iddari request.Periksa akses pengguna terhadap cabang.
Lindungi route menggunakan middleware.
Tambahkan filter cabang pada setiap query.
Batasi laporan semua cabang.
Gunakan transaksi database untuk transfer stok.
Sertakan
branch_idpada job queue.Uji sistem menggunakan beberapa akun dan cabang.
Jangan hanya menyembunyikan menu. Pengguna masih dapat mencoba membuka URL secara langsung.
Kesalahan yang Perlu Dihindari
Lupa Memfilter Data
Query tanpa branch_id dapat menampilkan data milik cabang lain.
Membiarkan Pengguna Mengirim branch_id
Pengguna dapat memanipulasi request untuk memasukkan data ke cabang yang tidak dimilikinya.
Menghapus Cabang Secara Langsung
Lebih aman menonaktifkan cabang menggunakan:
is_active = false
agar riwayat transaksi tetap tersimpan.
Mengandalkan Session pada Queue
Queue Worker tidak selalu memiliki session pengguna. Sertakan branch_id langsung ketika job dibuat.
Pengembangan Sistem Multi-Cabang Bersama Ovla Media
Ovla Media dapat membantu mengembangkan sistem multi-cabang untuk kebutuhan:
Point of Sales.
Inventory dan gudang.
Klinik.
Koperasi.
Retail dan minimarket.
Restoran dan kafe.
Distributor.
Dashboard kantor pusat.
Transfer stok.
Laporan operasional.
Sistem dapat dilengkapi dengan role dan permission, pembatasan akses cabang, monitoring stok, integrasi pembayaran, serta laporan terpusat.
Kesimpulan
Sistem multi-cabang Laravel dapat dibuat menggunakan satu database dengan menambahkan branch_id pada tabel operasional.
Pengguna dihubungkan dengan cabang melalui tabel branch_user, sedangkan middleware digunakan untuk memastikan pengguna hanya mengakses cabang yang diizinkan.
Query data harus selalu dibatasi berdasarkan cabang aktif:
Product::where(
'branch_id',
session('active_branch_id')
)->get();
Untuk sistem yang lebih besar, gunakan global scope, policy, database transaction, dan role permission agar data antar-cabang tetap aman dan tidak tercampur.
Artikel Terkait
Cara Menjalankan Queue Worker Laravel di cPanel Tanpa Supervisor
Queue Worker Laravel digunakan untuk memproses pekerjaan di belakang layar, seperti pengiriman email, notifikasi, pembuatan laporan, sinkronisasi API, dan pemrosesan data. Pada VPS, worker biasanya dijaga menggunakan Supervisor. Namun, pada shared hosting cPanel yang tidak menyediakan Supervisor, queue tetap dapat dijalankan menggunakan Cron Job dan perintah queue:work --stop-when-empty.
Cara Integrasi WhatsApp Cloud API dengan Laravel
WhatsApp Cloud API memungkinkan aplikasi Laravel mengirim pesan, notifikasi, dan template WhatsApp melalui layanan resmi Meta. Integrasi ini dapat digunakan untuk konfirmasi pesanan, invoice, pengingat pembayaran, customer service, hingga chatbot. Artikel ini membahas konfigurasi token, pengiriman pesan, serta penerimaan webhook menggunakan Laravel.
Cara Membuat Role dan Permission Laravel Tanpa Package
Role dan permission digunakan untuk membatasi akses pengguna berdasarkan tugas dan kewenangannya. Tanpa memasang package tambahan, Laravel dapat membuat sistem hak akses menggunakan migration, relasi Eloquent, middleware, Gate, Policy, dan Blade Directive. Artikel ini membahas pembuatan role dan permission dari awal, mulai dari struktur database, model, seeder, middleware, hingga penerapannya pada route dan tampilan.