Day 16 โ Modul Peminjaman (Loan): Create & List โ
Fase: 2 โ Mini Project Asset Management | Minggu: 4 | Hari: Senin
๐ฏ Tujuan Hari Ini โ
Tim membangun modul peminjaman aset dari nol: migration tabel loans, model, controller, form peminjaman, dan list riwayat peminjaman. Termasuk logika pengurangan stok aset secara otomatis.
๐ Task Wajib โ
1. Standup & Briefing Minggu Terakhir (09:00 โ 09:20) โ
Ini adalah minggu terakhir! Bahas:
- Review hasil demo Day 15
- Pastikan tidak ada bug yang tertinggal dari minggu lalu
- Klarifikasi fitur yang akan dibangun hari ini
Aturan akhir minggu:
- Semua fitur wajib selesai sebelum Day 20
- QA wajib membuat test case riil setiap hari
- PM membuat to-do list harian di GitHub Issues
2. Buat Migration Tabel loans โ
php artisan make:migration create_loans_tableIsi method up() di file migration yang baru:
public function up(): void
{
Schema::create('loans', function (Blueprint $table) {
$table->id();
$table->foreignId('asset_id')
->constrained('assets')
->onDelete('restrict'); // Aset tidak bisa dihapus jika masih ada pinjaman
$table->string('borrower_name', 150)->comment('Nama peminjam');
$table->string('borrower_department', 100)->nullable()->comment('Divisi / kelas peminjam');
$table->date('borrow_date')->comment('Tanggal peminjaman');
$table->date('expected_return_date')->comment('Rencana tanggal pengembalian');
$table->date('actual_return_date')->nullable()->comment('Tanggal aktual dikembalikan');
$table->unsignedInteger('quantity')->default(1)->comment('Jumlah yang dipinjam');
$table->enum('status', ['dipinjam', 'dikembalikan'])->default('dipinjam');
$table->text('notes')->nullable()->comment('Catatan tambahan');
$table->timestamps();
});
}php artisan migrateVerifikasi di phpMyAdmin: tabel loans muncul dengan semua kolom dan foreign key ke assets.
3. Buat Model Loan โ
php artisan make:model LoanIsi app/Models/Loan.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Loan extends Model
{
protected $fillable = [
'asset_id',
'borrower_name',
'borrower_department',
'borrow_date',
'expected_return_date',
'actual_return_date',
'quantity',
'status',
'notes',
];
protected $casts = [
'borrow_date' => 'date',
'expected_return_date' => 'date',
'actual_return_date' => 'date',
];
// Relasi: 1 loan milik 1 aset
public function asset()
{
return $this->belongsTo(Asset::class);
}
// Helper: apakah pinjaman ini sudah terlambat dikembalikan?
public function isTerlambat(): bool
{
return $this->status === 'dipinjam'
&& $this->expected_return_date->isPast();
}
}4. Buat LoanController โ
php artisan make:controller LoanController --resourceIsi app/Http/Controllers/LoanController.php:
<?php
namespace App\Http\Controllers;
use App\Models\Loan;
use App\Models\Asset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class LoanController extends Controller
{
public function index()
{
$loans = Loan::with('asset.category')
->orderBy('created_at', 'desc')
->paginate(15);
return view('loans.index', compact('loans'));
}
public function create()
{
// Hanya tampilkan aset yang stoknya > 0
$assets = Asset::where('stock', '>', 0)
->with('category')
->orderBy('name')
->get();
return view('loans.create', compact('assets'));
}
public function store(Request $request)
{
$request->validate([
'asset_id' => 'required|exists:assets,id',
'borrower_name' => 'required|min:3|max:150',
'borrower_department' => 'nullable|max:100',
'borrow_date' => 'required|date',
'expected_return_date' => 'required|date|after_or_equal:borrow_date',
'quantity' => 'required|integer|min:1',
'notes' => 'nullable|max:500',
]);
$asset = Asset::findOrFail($request->asset_id);
// Validasi stok cukup
if ($request->quantity > $asset->stock) {
return back()
->withInput()
->withErrors([
'quantity' => "Stok tidak cukup! Stok tersedia: {$asset->stock} unit, kamu minta: {$request->quantity} unit."
]);
}
// Gunakan DB Transaction untuk keamanan
DB::transaction(function () use ($request, $asset) {
// 1. Buat record peminjaman
Loan::create([
'asset_id' => $request->asset_id,
'borrower_name' => $request->borrower_name,
'borrower_department' => $request->borrower_department,
'borrow_date' => $request->borrow_date,
'expected_return_date' => $request->expected_return_date,
'quantity' => $request->quantity,
'status' => 'dipinjam',
'notes' => $request->notes,
]);
// 2. Kurangi stok aset
$asset->decrement('stock', $request->quantity);
});
return redirect()->route('loans.index')
->with('success', "Peminjaman aset '{$asset->name}' berhasil dicatat!");
}
public function show(Loan $loan)
{
$loan->load('asset.category');
return view('loans.show', compact('loan'));
}
// edit, update, destroy akan diimplementasikan jika waktu memungkinkan
}DB::transaction() โ Ini sangat penting! Jika
Loan::create()berhasil tapi$asset->decrement()gagal (karena error), maka seluruh operasi akan di-rollback otomatis. Data tidak akan setengah tersimpan.
5. Daftarkan Route โ
Di routes/web.php:
use App\Http\Controllers\LoanController;
Route::resource('loans', LoanController::class)->only(['index', 'create', 'store', 'show']);6. Buat Views Peminjaman โ
resources/views/loans/index.blade.php:
@extends('layouts.app')
@section('title', 'Riwayat Peminjaman')
@section('page-title', 'Riwayat Peminjaman')
@section('content')
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
<div>
<h1>๐ Riwayat Peminjaman</h1>
<p style="color:#666;">Total: {{ $loans->total() }} transaksi</p>
</div>
<a href="{{ route('loans.create') }}" class="btn-primary">+ Catat Peminjaman Baru</a>
</div>
@include('partials.alert')
<div style="background:white; border-radius:10px; overflow:hidden; box-shadow:0 2px 8px rgba(0,0,0,0.07);">
<table style="width:100%; border-collapse:collapse;">
<thead>
<tr style="background:#1a1a2e; color:white;">
<th style="padding:12px 16px; text-align:left;">Peminjam</th>
<th style="padding:12px 16px; text-align:left;">Aset</th>
<th style="padding:12px 16px; text-align:center;">Qty</th>
<th style="padding:12px 16px; text-align:center;">Tgl Pinjam</th>
<th style="padding:12px 16px; text-align:center;">Rencana Kembali</th>
<th style="padding:12px 16px; text-align:center;">Status</th>
<th style="padding:12px 16px; text-align:center;">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($loans as $loan)
<tr style="border-bottom:1px solid #f0f0f0; {{ $loan->isTerlambat() ? 'background:#fff8f0;' : '' }}">
<td style="padding:12px 16px;">
<strong>{{ $loan->borrower_name }}</strong>
@if($loan->borrower_department)
<br><small style="color:#999;">{{ $loan->borrower_department }}</small>
@endif
</td>
<td style="padding:12px 16px;">
{{ $loan->asset->name }}
<br><small style="color:#4f46e5; font-family:monospace;">{{ $loan->asset->code }}</small>
</td>
<td style="padding:12px 16px; text-align:center; font-weight:bold;">{{ $loan->quantity }}</td>
<td style="padding:12px 16px; text-align:center;">{{ $loan->borrow_date->format('d M Y') }}</td>
<td style="padding:12px 16px; text-align:center;">
{{ $loan->expected_return_date->format('d M Y') }}
@if($loan->isTerlambat())
<br><small style="color:#dc3545;">โ ๏ธ Terlambat!</small>
@endif
</td>
<td style="padding:12px 16px; text-align:center;">
@if($loan->status === 'dipinjam')
<span style="background:#fff3cd; color:#856404; padding:4px 12px; border-radius:20px; font-size:12px;">
๐ Dipinjam
</span>
@else
<span style="background:#d4edda; color:#155724; padding:4px 12px; border-radius:20px; font-size:12px;">
โ
Dikembalikan
</span>
@endif
</td>
<td style="padding:12px 16px; text-align:center;">
<a href="{{ route('loans.show', $loan->id) }}" style="color:#0066cc; font-size:13px;">Detail</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" style="text-align:center; padding:40px; color:#999;">
Belum ada transaksi peminjaman.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div style="margin-top:20px;">{{ $loans->links() }}</div>
@endsectionresources/views/loans/create.blade.php:
@extends('layouts.app')
@section('title', 'Catat Peminjaman')
@section('page-title', 'Catat Peminjaman Baru')
@section('content')
<form method="POST" action="{{ route('loans.store') }}"
style="background:white; padding:30px; border-radius:10px; max-width:700px; box-shadow:0 2px 8px rgba(0,0,0,0.07);">
@csrf
<div style="display:grid; grid-template-columns:1fr 1fr; gap:20px;">
<div style="grid-column:1/-1; margin-bottom:4px;">
<label style="display:block; font-weight:bold; margin-bottom:6px;">Aset yang Dipinjam *</label>
<select name="asset_id" style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px;">
<option value="">-- Pilih Aset --</option>
@foreach($assets as $asset)
<option value="{{ $asset->id }}" {{ old('asset_id') == $asset->id ? 'selected' : '' }}>
[{{ $asset->code }}] {{ $asset->name }} โ {{ $asset->category->name }}
(Stok: {{ $asset->stock }})
</option>
@endforeach
</select>
@error('asset_id') <span style="color:red; font-size:13px;">{{ $message }}</span> @enderror
</div>
<div>
<label style="display:block; font-weight:bold; margin-bottom:6px;">Nama Peminjam *</label>
<input type="text" name="borrower_name" value="{{ old('borrower_name') }}"
style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px;"
placeholder="Nama lengkap peminjam">
@error('borrower_name') <span style="color:red; font-size:13px;">{{ $message }}</span> @enderror
</div>
<div>
<label style="display:block; font-weight:bold; margin-bottom:6px;">Divisi / Departemen</label>
<input type="text" name="borrower_department" value="{{ old('borrower_department') }}"
style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px;"
placeholder="Opsional">
</div>
<div>
<label style="display:block; font-weight:bold; margin-bottom:6px;">Jumlah Dipinjam *</label>
<input type="number" name="quantity" value="{{ old('quantity', 1) }}" min="1"
style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px;">
@error('quantity') <span style="color:red; font-size:13px;">{{ $message }}</span> @enderror
</div>
<div>
<label style="display:block; font-weight:bold; margin-bottom:6px;">Tanggal Pinjam *</label>
<input type="date" name="borrow_date" value="{{ old('borrow_date', date('Y-m-d')) }}"
style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px;">
@error('borrow_date') <span style="color:red; font-size:13px;">{{ $message }}</span> @enderror
</div>
<div>
<label style="display:block; font-weight:bold; margin-bottom:6px;">Rencana Tanggal Kembali *</label>
<input type="date" name="expected_return_date" value="{{ old('expected_return_date') }}"
style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px;">
@error('expected_return_date') <span style="color:red; font-size:13px;">{{ $message }}</span> @enderror
</div>
<div style="grid-column:1/-1;">
<label style="display:block; font-weight:bold; margin-bottom:6px;">Catatan Tambahan</label>
<textarea name="notes" rows="3"
style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px; resize:vertical;"
placeholder="Kondisi aset saat dipinjam, tujuan pemakaian, dll">{{ old('notes') }}</textarea>
</div>
</div>
<div style="display:flex; gap:10px; margin-top:20px;">
<button type="submit" style="background:#1a1a2e; color:white; padding:12px 30px; border:none; border-radius:6px; cursor:pointer; font-size:15px;">
๐ Simpan Peminjaman
</button>
<a href="{{ route('loans.index') }}" style="padding:12px 20px; border:1px solid #ddd; border-radius:6px; text-decoration:none; color:#333;">
Batal
</a>
</div>
</form>
@endsection7. Update Sidebar Layout โ
Update layouts/app.blade.php โ aktifkan link Peminjaman:
<a href="{{ route('loans.index') }}" class="{{ request()->is('loans*') ? 'active' : '' }}">
๐ Peminjaman
</a>๐ Laporan ke Mentor โ
๐ LAPORAN HARIAN โ Day 16
Nama : [Nama Lengkap]
Role : [Role Kamu]
โ
Yang saya kerjakan hari ini:
- [ ] Migration tabel loans berhasil dibuat dan dijalankan
- [ ] Model Loan dengan relasi belongsTo Asset selesai
- [ ] LoanController dengan method index(), create(), store() selesai
- [ ] Validasi stok (quantity > stock) berjalan
- [ ] DB::transaction() diterapkan di method store()
- [ ] Halaman list peminjaman menampilkan data
- [ ] Form peminjaman bisa submit dan mengurangi stok aset
๐ธ Screenshot Wajib:
1. phpMyAdmin: struktur tabel loans
2. Form peminjaman dengan dropdown aset (tampil stok tersedia)
3. Setelah submit: halaman list peminjaman + stok aset berkurang di halaman /assets
๐งช QA Test:
| Test Case | Expected | Pass/Fail |
|-----------|----------|-----------|
| Pinjam 2 unit, stok awal 5 | Stok jadi 3 | |
| Pinjam lebih dari stok | Muncul pesan error | |
| Pinjam aset stok 0 (tidak muncul di dropdown) | Tidak bisa dipilih | |
| Return date sebelum borrow date | Muncul validasi error | |
โ Kendala:
[Tulis jika ada, khususnya tentang DB Transaction]Catatan Mentor
DB::transaction() adalah salah satu konsep paling penting di programming backend. Pastikan peserta memahami mengapa ini diperlukan: jika server mati di tengah proses, kita tidak mau situasi di mana record Loan sudah terbuat tapi stok belum berkurang (atau sebaliknya). Transaction menjamin atomicity โ semua berhasil, atau tidak ada yang tersimpan.