input('search'); $query = Peminjaman::with(['anggota', 'user', 'buku'])->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}%"]); }); }); } $peminjaman = $query->paginate(15)->appends(['search' => $search]); $buku = Buku::where('eksemplar', '>', 0)->get(); $anggota = Anggota::all(); return view('admin.peminjaman.index', compact('peminjaman', 'buku', 'anggota', 'search')); } public function create() { $anggota = Anggota::all(); $bukus = Buku::all(); return view('admin.peminjaman.create', compact('anggota', 'bukus')); } public function store(Request $request) { // 1. Validasi input $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', ]); // 2. Cek dan kurangi stok buku $buku = Buku::findOrFail($request->id_buku); if ($buku->eksemplar <= 0) { return back()->withErrors(['id_buku' => 'Stok buku habis!'])->withInput(); } $buku->decrement('eksemplar'); // 3. Simpan data peminjaman ke database $peminjaman = Peminjaman::create([ 'id_anggota' => $request->id_anggota, 'id_buku' => $request->id_buku, 'tanggal_pinjam' => $request->tanggal_pinjam, 'tanggal_kembali' => $request->tanggal_kembali, 'status_peminjaman' => 'Dipinjam', ]); // Muat relasi untuk mendapatkan data anggota (seperti no_hp) dan buku untuk keperluan struk PDF $peminjaman->load(['anggota', 'buku']); $waSuccess = false; // 4. Proses render PDF & Kirim Fonnte dengan Exception Handling try { // Tentukan direktori penyimpanan sementara (temp) $tempDir = storage_path('app/public/temp'); // Jika foldernya belum ada, buat foldernya terlebih dahulu. if (!file_exists($tempDir)) { mkdir($tempDir, 0755, true); } // Buat nama file unik (agar tidak masalah saat request bersamaan) $fileName = 'struk_peminjaman_' . $peminjaman->id . '_' . time() . '.pdf'; $filePath = $tempDir . '/' . $fileName; // Generate dokumen PDF dari view yang sudah ada $pdf = Pdf::loadView('admin.peminjaman.struk', compact('peminjaman')); // Simpan PDF sementara $pdf->save($filePath); // Dapatkan nomor handphone pendaftar & Fonnte Token $targetNum = $peminjaman->anggota->no_hp ?? ''; $fonnteToken = 'vpzqxF2ZGgTGz9F5UbUS'; // Token Fonnte // Standarisasi Format Nomor ke awalan 62 (Kode Internasional Indonesia) standar yang dianjurkan Fonnte if (!empty($targetNum)) { $targetNum = preg_replace('/^0/', '62', trim($targetNum)); } // Jika token terisi & nomor HP valid, jalankan request ke service API Fonnte if (!empty($targetNum) && !empty($fonnteToken)) { // Gunakan attach() untuk menyisipkan file PDF dari folder temp $response = Http::withoutVerifying()->timeout(15)->withHeaders([ 'Authorization' => $fonnteToken, ])->attach('file', file_get_contents($filePath), $fileName)->post('https://api.fonnte.com/send', [ 'target' => $targetNum, 'message' => "Halo Kak *{$peminjaman->anggota->nama}*! šŸ“šāœØ\n\nTerima kasih telah meminjam buku di *Perpustakaan Daerah Jember*.\n\nBersama pesan ini, kami lampirkan file e-Struk (PDF) untuk peminjaman buku:\nšŸ“– Judul: _{$peminjaman->buku->judul}_\nšŸ“… Batas Kembali: *{$peminjaman->tanggal_kembali}*\n\nMohon simpan dokumen PDF ini sebagai bukti transaksi yang sah. Harap kembalikan buku tepat waktu untuk menghindari denda keterlambatan.\n\nSelamat menikmati waktu membaca Anda!\n\nSalam Literasi,\n*Sistem Sarakata Jember*" ]); // Cek respon resmi fonnte API if ($response->successful() && ($response->json('status') == true)) { $waSuccess = true; } else { \Illuminate\Support\Facades\Log::warning("Fonnte Log: Gagal Terkirim", ['body' => $response->body()]); } } else { Log::warning('Fonnte Log: Token / No HP kosong (Pesan tidak diproses).'); } // 5. Cleanup Darurat File PDF Secara Otomatis if (file_exists($filePath)) { unlink($filePath); } } catch (\Exception $e) { // Bila error (misal: memori PDF kurang, library dompdf crash, timeout dari Fonnte) // Error ditangkap agar sistem tidak langsung menampilkan layar error 500 ke User. Log::error("Error PDF/WA Pengiriman: " . $e->getMessage()); // Pembersihan ekstra bila error terjadi saat file sudah diciptakan di temp folder, tapi belum dieksekusi unlink di dalam block atas. if (isset($filePath) && file_exists($filePath)) { unlink($filePath); } } // Notifikasi Balikan Final ke User $alertSuccessMessage = 'Transaksi peminjaman berhasil direkap.'; $alertSuccessMessage .= $waSuccess ? ' Struk WA berhasil terkirim kepada Anggota.' : ' (Namun e-Struk belum sempat terkirim via WA, silakan cek Logs).'; return redirect()->route('admin.peminjaman.index')->with('success', $alertSuccessMessage); } public function kembali(Peminjaman $peminjaman) { $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 back()->with('success', $pesan); } public function indexPengembalian() { $pengembalian = Peminjaman::where('status_peminjaman', 'Dikembalikan') ->with(['anggota', 'user', 'buku']) ->orderBy('tanggal_kembali', 'desc') ->paginate(10); return view('admin.peminjaman.pengembalian', compact('pengembalian')); } }