input('search'); $query = Peminjaman::with(['buku', 'anggota', 'user'])->latest(); if ($search) { $searchTerm = strtolower($search); $query->where(function ($q) use ($searchTerm) { $q->whereHas('anggota', function ($qMember) use ($searchTerm) { $qMember->whereRaw('LOWER(nama) LIKE ?', ["%{$searchTerm}%"]); })->orWhereHas('buku', function ($qBuku) use ($searchTerm) { $qBuku->whereRaw('LOWER(judul) LIKE ?', ["%{$searchTerm}%"]); })->orWhereHas('user', function ($qUser) use ($searchTerm) { $qUser->whereRaw('LOWER(name) LIKE ?', ["%{$searchTerm}%"]); }); }); } $peminjaman = $query->paginate(15)->appends(['search' => $search]); $buku = Buku::orderBy('judul', 'asc')->get(); $anggota = Anggota::orderBy('nama', 'asc')->get(); return view('admin.peminjaman.index', compact('peminjaman', 'buku', 'anggota', 'search')); } public function create() { $buku = Buku::where('eksemplar', '>', 0)->orderBy('judul', 'asc')->get(); $anggota = Anggota::orderBy('nama', 'asc')->get(); return view('admin.peminjaman.create', compact('buku', 'anggota')); } public function edit($id) { $peminjaman = Peminjaman::findOrFail($id); $buku = Buku::all(); $anggota = Anggota::orderBy('nama', 'asc')->get(); return view('admin.peminjaman.edit', compact('peminjaman', 'buku', 'anggota')); } public function store(Request $request) { // 1. Validasi Input $validated = $request->validate([ 'id_anggota' => 'required|exists:anggotas,id', 'id_buku' => 'required|exists:buku,id_buku', 'tanggal_pinjam' => 'required|date', 'tanggal_kembali' => 'required|date|after_or_equal:tanggal_pinjam', ]); $validated['status_peminjaman'] = 'Dipinjam'; // 2. Kurangi Stok Buku $buku = Buku::findOrFail($validated['id_buku']); $buku->decrement('eksemplar'); // 3. Simpan ke Database $peminjaman = Peminjaman::create($validated); $peminjaman->load(['buku', 'anggota']); $waSuccess = false; // 4. Proses Kirim WA (Format Struk Teks Resmi) try { $targetNum = $peminjaman->anggota->no_hp ?? ''; $fonnteToken = 'vpzqxF2ZGgTGz9F5UbUS'; // Token Fonnte // Standarisasi Format Nomor ke awalan 62 if (!empty($targetNum)) { $targetNum = preg_replace('/^0/', '62', trim($targetNum)); } if (!empty($targetNum) && !empty($fonnteToken)) { // Merangkai Teks Menyerupai Struk Kertas $pesanStruk = "🏢 *PERPUSTAKAAN DAERAH JEMBER*\n"; $pesanStruk .= "Jl. Mastrip No. 1, Kabupaten Jember\n"; $pesanStruk .= "===============================\n\n"; $pesanStruk .= "📄 *BUKTI PEMINJAMAN BUKU*\n"; $pesanStruk .= "No. Transaksi : PMJ-{$peminjaman->id_peminjaman}\n"; $pesanStruk .= "Tanggal Cetak : " . \Carbon\Carbon::now()->format('d-m-Y H:i') . "\n\n"; $pesanStruk .= "*DATA PEMINJAM*\n"; $pesanStruk .= "Nama : {$peminjaman->anggota->nama}\n"; $pesanStruk .= "No. HP : {$peminjaman->anggota->no_hp}\n\n"; $pesanStruk .= "*DETAIL BUKU*\n"; $pesanStruk .= "Judul : {$peminjaman->buku->judul}\n"; $pesanStruk .= "Kode Pengembalian : {$peminjaman->buku->bibid}\n"; $pesanStruk .= "Pinjam : " . \Carbon\Carbon::parse($peminjaman->tanggal_pinjam)->format('d F Y') . "\n"; $pesanStruk .= "Kembali: *" . \Carbon\Carbon::parse($peminjaman->tanggal_kembali)->format('d F Y') . "*\n\n"; $pesanStruk .= "===============================\n"; $pesanStruk .= "⚠️ *Catatan:*\n"; $pesanStruk .= "Tunjukkan Kode Pengembalian ke Admin saat pengembalian buku.\n"; $pesanStruk .= "Harap kembalikan buku tepat waktu.\n"; $pesanStruk .= "Denda keterlambatan: Rp 1.000/hari.\n\n"; $pesanStruk .= "Terima kasih atas kunjungan Anda!\n"; $pesanStruk .= "_Sistem Sarakata - TA 2026_"; // Eksekusi Pengiriman via Http Laravel $response = \Illuminate\Support\Facades\Http::withoutVerifying()->timeout(15)->withHeaders([ 'Authorization' => $fonnteToken, ])->post('https://api.fonnte.com/send', [ 'target' => $targetNum, 'message' => $pesanStruk, ]); if ($response->successful() && ($response->json('status') == true)) { $waSuccess = true; } else { \Illuminate\Support\Facades\Log::warning("Fonnte Log: Gagal Terkirim", [ 'target' => $targetNum, 'http_status' => $response->status(), 'body' => $response->body() ]); } } else { \Illuminate\Support\Facades\Log::warning('Fonnte Log: Token / No HP kosong', [ 'target' => $targetNum ?? 'kosong', 'token_set' => !empty($fonnteToken), ]); } } catch (\Exception $e) { \Illuminate\Support\Facades\Log::error("Error WA Pengiriman: " . $e->getMessage()); } // 5. Notifikasi Kembali ke Layar Admin $msg = 'Transaksi peminjaman berhasil dicatat.'; if ($waSuccess) { $msg .= ' Struk WA berhasil terkirim kepada Anggota.'; } return redirect()->route('admin.peminjaman.index')->with('success', $msg); } public function cetakStruk($id) { $peminjaman = Peminjaman::with(['buku', 'anggota', 'user'])->findOrFail($id); return view('admin.peminjaman.struk', compact('peminjaman')); } public function scan() { return view('admin.peminjaman.scan'); } public function prosesScan(Request $request) { $request->validate(['bibid' => 'required|string']); $buku = Buku::where('bibid', $request->bibid)->first(); if (!$buku) { return back()->with('error', 'Aset buku tidak ditemukan di dalam database.'); } $peminjaman = Peminjaman::with(['user', 'anggota']) ->where('id_buku', $buku->id_buku) ->where('status_peminjaman', 'Dipinjam') ->first(); if (!$peminjaman) { return back()->with('error', 'Buku ini tidak dalam status dipinjam oleh siapapun.'); } $tanggal_kembali_seharusnya = \Carbon\Carbon::parse($peminjaman->tanggal_kembali); $tanggal_dikembalikan_aktual = \Carbon\Carbon::now(); $denda = 0; if ($tanggal_dikembalikan_aktual->gt($tanggal_kembali_seharusnya)) { $selisih_hari = $tanggal_dikembalikan_aktual->diffInDays($tanggal_kembali_seharusnya); $denda = $selisih_hari * 1000; } $lokasi = $this->prediksiLokasiRakUntukAdmin($buku->nomor_panggil); return view('admin.peminjaman.scan', compact('buku', 'peminjaman', 'denda', 'lokasi')); } private function prediksiLokasiRakUntukAdmin($nomor_panggil) { if (empty($nomor_panggil)) return ['rak' => 'Tidak Diketahui', 'area' => '-']; $kode_utama = (int) substr(trim($nomor_panggil), 0, 3); return match (true) { $kode_utama >= 0 && $kode_utama <= 99 => match (true) { $kode_utama <= 19 => ['rak' => 'Rak 01', 'area' => 'Karya Umum'], $kode_utama <= 50 => ['rak' => 'Rak 02', 'area' => 'Karya Umum'], default => ['rak' => 'Rak 03-05', 'area' => 'Karya Umum Lainnya'], }, $kode_utama >= 100 && $kode_utama <= 199 => match (true) { $kode_utama <= 150 => ['rak' => 'Rak 06-10', 'area' => 'Filsafat'], default => ['rak' => 'Rak 11-14', 'area' => 'Psikologi'], }, $kode_utama >= 200 && $kode_utama <= 299 => match (true) { $kode_utama == 297 => ['rak' => 'Rak 25-32', 'area' => 'Agama Islam'], default => ['rak' => 'Rak 15-24', 'area' => 'Agama Umum'], }, $kode_utama >= 300 && $kode_utama <= 399 => match (true) { $kode_utama <= 330 => ['rak' => 'Rak 33-36', 'area' => 'Sosiologi & Politik'], $kode_utama <= 360 => ['rak' => 'Rak 37-40', 'area' => 'Ekonomi & Hukum'], default => ['rak' => 'Rak 41-44', 'area' => 'Pendidikan & Adat'], }, $kode_utama >= 400 && $kode_utama <= 499 => ['rak' => 'Rak 45', 'area' => 'Bahasa'], $kode_utama >= 500 && $kode_utama <= 599 => ['rak' => 'Rak 46-48', 'area' => 'Ilmu Murni'], $kode_utama >= 600 && $kode_utama <= 699 => match (true) { $kode_utama <= 610 => ['rak' => 'Rak 49-53', 'area' => 'Kedokteran'], $kode_utama <= 630 => ['rak' => 'Rak 54-58', 'area' => 'Teknik'], $kode_utama <= 650 => ['rak' => 'Rak 59-63', 'area' => 'Pertanian'], default => ['rak' => 'Rak 64-68', 'area' => 'Manajemen Bisnis'], }, $kode_utama >= 700 && $kode_utama <= 799 => match (true) { $kode_utama <= 739 => ['rak' => 'Rak 71', 'area' => 'Kesenian'], $kode_utama <= 769 => ['rak' => 'Rak 72', 'area' => 'Seni Rupa'], $kode_utama <= 789 => ['rak' => 'Rak 73', 'area' => 'Fotografi/Musik'], default => ['rak' => 'Rak 74', 'area' => 'Olahraga'], }, $kode_utama >= 800 && $kode_utama <= 899 => ['rak' => 'Rak 77-79', 'area' => 'Sastra'], $kode_utama >= 900 && $kode_utama <= 999 => match (true) { $kode_utama <= 919 => ['rak' => 'Rak 69, 70', 'area' => 'Geografi'], default => ['rak' => 'Rak 80-84', 'area' => 'Sejarah Umum'], }, default => ['rak' => 'Rak 75-76', 'area' => 'Koleksi Terbaru'], }; } public function update(Request $request, $id) { $validated = $request->validate([ 'id_anggota' => 'required|exists:anggotas,id', 'id_buku' => 'required|exists:buku,id_buku', 'tanggal_pinjam' => 'required|date', 'tanggal_kembali' => 'required|date|after_or_equal:tanggal_pinjam', ]); $peminjaman = Peminjaman::findOrFail($id); // If the book changed, adjust stock if ($peminjaman->id_buku != $validated['id_buku']) { if ($peminjaman->status_peminjaman == 'Dipinjam') { $oldBuku = Buku::find($peminjaman->id_buku); if ($oldBuku) $oldBuku->increment('eksemplar'); $newBuku = Buku::findOrFail($validated['id_buku']); $newBuku->decrement('eksemplar'); } } $peminjaman->update($validated); return redirect()->route('admin.peminjaman.index')->with('success', 'Data peminjaman berhasil diperbarui.'); } public function destroy($id) { $peminjaman = Peminjaman::findOrFail($id); if ($peminjaman->status_peminjaman == 'Dipinjam' && $peminjaman->buku) { $peminjaman->buku->increment('eksemplar'); } $peminjaman->delete(); return redirect()->route('admin.peminjaman.index')->with('success', 'Data peminjaman berhasil dihapus.'); } public function kembalikan($id) { $peminjaman = Peminjaman::findOrFail($id); $tglTenggat = \Carbon\Carbon::parse($peminjaman->tanggal_kembali)->startOfDay(); $tglSekarang = \Carbon\Carbon::now()->startOfDay(); $denda = 0; if ($tglSekarang->gt($tglTenggat)) { $selisihHari = $tglSekarang->diffInDays($tglTenggat); $denda = $selisihHari * 1000; } $peminjaman->update([ 'status_peminjaman' => 'Dikembalikan', 'tanggal_dikembalikan' => now(), 'denda' => $denda ]); if ($peminjaman->buku) { $peminjaman->buku->increment('eksemplar'); } $pesan = 'Buku berhasil dikembalikan.'; if ($denda > 0) { $pesan .= ' Denda keterlambatan Rp ' . number_format($denda, 0, ',', '.'); } return redirect()->back()->with('success', $pesan); } public function resendWa($id) { $peminjaman = Peminjaman::with(['buku', 'anggota'])->findOrFail($id); try { $targetNum = $peminjaman->anggota->no_hp ?? ''; $fonnteToken = env('FONNTE_TOKEN', 'vpzqxF2ZGgTGz9F5UbUS'); if (!empty($targetNum)) { $targetNum = preg_replace('/^0/', '62', trim($targetNum)); } if (!empty($targetNum) && !empty($fonnteToken)) { $pesanStruk = "🏢 *PERPUSTAKAAN DAERAH JEMBER*\n"; $pesanStruk .= "Jl. Mastrip No. 1, Kabupaten Jember\n"; $pesanStruk .= "===============================\n\n"; $pesanStruk .= "📄 *SALINAN BUKTI PEMINJAMAN*\n"; $pesanStruk .= "No. Transaksi : PMJ-{$peminjaman->id_peminjaman}\n"; $pesanStruk .= "Tanggal Cetak : " . \Carbon\Carbon::now()->format('d-m-Y H:i') . "\n\n"; $pesanStruk .= "*DATA PEMINJAM*\n"; $pesanStruk .= "Nama : {$peminjaman->anggota->nama}\n"; $pesanStruk .= "No. HP : {$peminjaman->anggota->no_hp}\n\n"; $pesanStruk .= "*DETAIL BUKU*\n"; $pesanStruk .= "Judul : {$peminjaman->buku->judul}\n"; $pesanStruk .= "Kode Panggil : {$peminjaman->buku->bibid}\n"; $pesanStruk .= "Pinjam : " . \Carbon\Carbon::parse($peminjaman->tanggal_pinjam)->format('d F Y') . "\n"; $pesanStruk .= "Kembali: *" . \Carbon\Carbon::parse($peminjaman->tanggal_kembali)->format('d F Y') . "*\n\n"; $pesanStruk .= "===============================\n"; $pesanStruk .= "⚠️ *Catatan:*\n"; $pesanStruk .= "Tunjukkan Kode Panggil ke Admin saat pengembalian buku.\n"; $pesanStruk .= "Harap kembalikan buku tepat waktu.\n"; $pesanStruk .= "Denda keterlambatan: Rp 1.000/hari.\n\n"; $pesanStruk .= "Terima kasih atas kunjungan Anda!\n"; $pesanStruk .= "_Sistem Sarakata - TA 2026_"; $response = \Illuminate\Support\Facades\Http::withoutVerifying()->timeout(15)->withHeaders([ 'Authorization' => $fonnteToken, ])->post('https://api.fonnte.com/send', [ 'target' => $targetNum, 'message' => $pesanStruk, ]); if ($response->successful() && ($response->json('status') == true)) { return back()->with('success', 'Struk WhatsApp berhasil dikirim ulang ke nomor ' . $targetNum); } else { \Illuminate\Support\Facades\Log::warning("Fonnte Log: Gagal Terkirim Ulang", ['body' => $response->body()]); return back()->with('error', 'Gagal mengirim ulang WA. API Fonnte menolak koneksi (Status: '.$response->status().').'); } } else { return back()->with('error', 'Nomor HP anggota kosong atau Token Fonnte belum dikonfigurasi.'); } } catch (\Exception $e) { \Illuminate\Support\Facades\Log::error("Error WA Pengiriman Ulang: " . $e->getMessage()); return back()->with('error', 'Terjadi kesalahan sistem saat menghubungi server WhatsApp.'); } } }