get('list_scope', 'active'); if (!in_array($listScope, ['active', 'adopted', 'external_parent', 'all'], true)) { $listScope = 'active'; } $query = Cat::query()->with(['pic:id,name', 'sire', 'dam', 'registrations.association']); if ($listScope === 'active') { $query->activeInCattery(); } elseif ($listScope === 'adopted') { $query->adopted(); } elseif ($listScope === 'external_parent') { $query->externalParents(); } if ($request->filled('search')) { $search = $request->search; $query->where(function($q) use ($search) { $q->where('nama_kucing', 'like', "%{$search}%") ->orWhere('ras', 'like', "%{$search}%") ->orWhere('microchip', 'like', "%{$search}%") ->orWhere('pedigree', 'like', "%{$search}%") ->orWhere('kode_ems', 'like', "%{$search}%") ->orWhere('lokasi_kandang', 'like', "%{$search}%") ->orWhereHas('pic', fn($sub) => $sub->where('name', 'like', "%{$search}%")); }); } if ($request->filled('filter_overdue')) { $filter = $request->get('filter_overdue'); $query->where(function($q) use ($filter, $now) { if ($filter === 'vaccine') { $q->whereNotNull('jadwal_vaksin')->where('jadwal_vaksin', '<', $now); } elseif (in_array($filter, ['flea', 'obat_kutu'], true)) { $q->whereNotNull('jadwal_obat_kutu')->where('jadwal_obat_kutu', '<', $now); } elseif (in_array($filter, ['deworming', 'obat_cacing'], true)) { $q->whereNotNull('jadwal_obat_cacing')->where('jadwal_obat_cacing', '<', $now); } elseif ($filter === 'grooming') { $q->whereNotNull('jadwal_grooming')->where('jadwal_grooming', '<', $now); } }); } if ($request->get('filter_health') === 'treatment') { $query->where('treatment_status', 1); } if ($request->filled('status')) { $status = $request->status; if (in_array(strtolower($status), ['adult', 'dewasa'], true)) { $query->where('tanggal_lahir', '<=', $now->copy()->subMonths(12)); } elseif ($status === 'Junior') { $query->whereBetween('tanggal_lahir', [$now->copy()->subMonths(12)->addDay(), $now->copy()->subMonths(7)]); } elseif ($status === 'Kitten') { $query->where('tanggal_lahir', '>', $now->copy()->subMonths(7)); } else { $query->where('status', $status); } } if ($request->filled('ras')) $query->where('ras', $request->ras); if ($request->filled('gender')) $query->where('jenis_kelamin', $request->gender); $allowedSorts = ['nama_kucing', 'lokasi_kandang', 'berat_badan', 'created_at', 'pic_name']; $sort = in_array($request->get('sort'), $allowedSorts, true) ? $request->get('sort') : 'created_at'; $direction = strtolower($request->get('direction')) === 'asc' ? 'asc' : 'desc'; if ($sort === 'pic_name') { $query->leftJoin('users', 'cats.pic_id', '=', 'users.id')->select('cats.*')->orderBy('users.name', $direction); } else { $query->orderBy("cats.$sort", $direction); } $cats = $query->paginate(15)->withQueryString(); $activeCats = Cat::activeInCattery()->count(); $adoptedCats = Cat::adopted()->count(); $externalParentCats = Cat::externalParents()->count(); $sickCats = Cat::activeInCattery()->where('treatment_status', 1)->count(); $breeds = Cat::activeInCattery()->distinct()->whereNotNull('ras')->pluck('ras'); return view('pages.cats.index', compact( 'cats', 'activeCats', 'adoptedCats', 'externalParentCats', 'sickCats', 'breeds', 'listScope' )); } public function create(): View { // Proteksi Akses: Create Gate::authorize('check-access', ['cats', 'create']); $sires = Cat::where('jenis_kelamin', 'Jantan')->get(['id', 'nama_kucing']); $dams = Cat::where('jenis_kelamin', 'Betina')->get(['id', 'nama_kucing']); $users = User::all(['id', 'name']); return view('pages.cats.create', compact('sires', 'dams', 'users')); } public function store(Request $request): RedirectResponse { Gate::authorize('check-access', ['cats', 'create']); // 1. Validasi $validated = $this->validateCat($request); return DB::transaction(function () use ($request, $validated) { // 2. Handling Upload Foto (Fix: Encoding Intervention v3) if ($request->hasFile('foto')) { $validated['foto'] = $this->handleImageUpload($request->file('foto')); } // 3. Handling Berat Badan (Sederhanakan) // Cukup isi berat_badan, Model Cat akan otomatis mengisi berat_terakhir (via static::saving) if ($request->filled('berat_badan')) { $validated['berat_badan'] = $request->berat_badan; } // 4. Handling Status Otomatis if (empty($validated['status'])) { $validated['status'] = ($request->induk_jantan_id || $request->induk_betina_id) ? 'Kitten' : 'Active'; } // 5. Simpan Data // Pastikan kolom-kolom induk juga masuk ke validated jika ada di request $validated['induk_jantan_id'] = $request->induk_jantan_id; $validated['induk_betina_id'] = $request->induk_betina_id; $validated['pic_id'] = $request->pic_id ?? auth()->id(); $validated['cattery_status'] = $request->boolean('is_external_parent') ? 'external_parent' : ($request->boolean('is_adopted') ? 'adopted' : 'active'); $validated['adopted_at'] = $validated['cattery_status'] === 'adopted' ? ($request->input('adopted_at') ?: now()->toDateString()) : null; $validated['adoption_notes'] = $request->input('adoption_notes'); $cat = Cat::create($validated); // 6. Logging (Pastikan Trait LogsActivity & Tabel Log sudah punya kolom 'model') try { $this->logAction('CREATED', $cat, "Mendaftarkan kucing baru: {$cat->nama_kucing}", 'Cat'); } catch (\Exception $e) { \Illuminate\Support\Facades\Log::warning("Log gagal dicatat: " . $e->getMessage()); } return redirect()->route('cats.index')->with('success', "{$cat->nama_kucing} berhasil didaftarkan."); }); } /** * Fix: Perbaikan cara simpan gambar pada Intervention Image v3 */ private function handleImageUpload($file): string { $filename = 'cat_' . uniqid() . '.png'; $path = 'cats/' . $filename; // Intervention v3: Gunakan encode() atau toPng() lalu simpan sebagai string $img = Image::read($file)->cover(500, 500, 'center'); // Simpan ke disk public Storage::disk('public')->put($path, $img->toPng()->toString()); return $path; } public function show(Cat $cat): View { // 1. Otorisasi (TETAP ADA) Gate::authorize('check-access', ['cats', 'read']); // 2. Eager Loading (DITAMBAHKAN medicalRecords & performer) // Kita memuat medicalRecords agar tidak terjadi query berulang di dalam Loop Blade (N+1 Problem) $cat->load([ 'treatments' => fn($q) => $q->latest(), 'medicalRecords' => fn($q) => $q->latest(), // Baru: Riwayat Vaksin/Obat Cacing 'medicalRecords.performer', // Baru: Siapa yang melakukan tindakan 'sire', 'dam', 'pic', 'weightHistories' => fn($q) => $q->latest()->limit(12), 'registrations.association', 'registrations.documents' ]); // 3. Ambil Data Inventori (Baru: Untuk Datalist di Modal Input) // Menampilkan semua obat yang stoknya masih tersedia $availableMedicines = Medicine::where('qty', '>', 0) ->orderBy('nama_obat', 'asc') ->get(['nama_obat', 'qty', 'unit']); // 4. Return View dengan Compact (DITAMBAHKAN availableMedicines) return view('pages.cats.show', compact('cat', 'availableMedicines')); } public function edit(Cat $cat): View { // Proteksi: Akses Update Gate::authorize('check-access', ['cats', 'update']); $sires = Cat::where('jenis_kelamin', 'Jantan')->where('id', '!=', $cat->id)->get(['id', 'nama_kucing']); $dams = Cat::where('jenis_kelamin', 'Betina')->where('id', '!=', $cat->id)->get(['id', 'nama_kucing']); $users = User::all(['id', 'name']); return view('pages.cats.edit', compact('cat', 'sires', 'dams', 'users')); } public function update(Request $request, Cat $cat): RedirectResponse { // 1. Otorisasi Akses Gate::authorize('check-access', ['cats', 'update']); // 2. Validasi (Mengirimkan $cat->id agar Rule:unique mengabaikan record ini sendiri) $validated = $this->validateCat($request, $cat->id); return DB::transaction(function () use ($request, $validated, $cat) { // 3. Handling Foto if ($request->hasFile('foto')) { // Hapus foto lama jika ada if ($cat->foto && Storage::disk('public')->exists($cat->foto)) { Storage::disk('public')->delete($cat->foto); } // Upload foto baru menggunakan helper yang sudah diperbaiki $validated['foto'] = $this->handleImageUpload($request->file('foto')); } /** * 4. PENYEMPURNAAN REGISTRY (FIX UPDATE) * Jika di form semua checkbox tidak dicentang, $request->registry akan kosong. * Kita paksa nilainya menjadi ['NON-PED'] agar data lama di DB terhapus/terganti. */ $validated['registry'] = $request->input('registry', ['NON-PED']); $validated['cattery_status'] = $request->boolean('is_external_parent') ? 'external_parent' : ($request->boolean('is_adopted') ? 'adopted' : 'active'); $validated['adopted_at'] = $validated['cattery_status'] === 'adopted' ? ($request->input('adopted_at') ?: ($cat->adopted_at?->format('Y-m-d') ?? now()->toDateString())) : null; $validated['adoption_notes'] = $request->input('adoption_notes'); // 5. Update Data Utama // Logika berat_badan sudah otomatis dihandle oleh Model (static::saving/updated) $cat->update($validated); // 6. Activity Log (Dibalut Try-Catch agar proses update tidak gagal hanya karena log) try { $this->logAction('UPDATED', $cat, "Memperbarui profil medis & identitas: {$cat->nama_kucing}", 'Cat'); } catch (\Exception $e) { \Illuminate\Support\Facades\Log::warning("Activity Log gagal dicatat: " . $e->getMessage()); } return redirect()->route('cats.index')->with('success', "Data {$cat->nama_kucing} berhasil diperbarui."); }); } public function destroy(Cat $cat): RedirectResponse { Gate::authorize('check-access', ['cats', 'delete']); $isParent = Matting::where('sire_id', $cat->id)->orWhere('dam_id', $cat->id)->exists(); if ($isParent) { return back()->with('error', "Gagal! {$cat->nama_kucing} tidak bisa dihapus karena sudah menjadi Induk."); } return DB::transaction(function () use ($cat) { $name = $cat->nama_kucing; if ($cat->foto) Storage::disk('public')->delete($cat->foto); $this->logAction('DELETED', $cat, "Menghapus data permanen: {$name}", 'Cat'); $cat->delete(); return redirect()->route('cats.index')->with('success', "Data {$name} telah dihapus."); }); } public function getCatData($id): JsonResponse { $cat = Cat::find($id); if (!$cat) return response()->json(['error' => 'Not found'], 404); return response()->json([ 'tgl_lahir' => $cat->tanggal_lahir ? $cat->tanggal_lahir->format('Y-m-d') : '', 'pedigree' => $cat->pedigree ?? '', 'microchip' => $cat->microchip ?? '', 'kode_ems' => $cat->kode_ems ?? '', ]); } //cetak download health ledger pdf public function downloadReport(Cat $cat) { Gate::authorize('check-access', ['cats', 'read']); $cat->load([ 'medicalRecords' => fn($q) => $q->orderBy('date_given', 'desc'), 'medicalRecords.performer', 'treatments' => fn($q) => $q->orderBy('tanggal', 'desc'), // <--- Pastikan ini ada 'sire', 'dam', 'weightHistories' => fn($q) => $q->orderBy('created_at', 'desc') ]); $groupedHistory = $cat->medicalRecords->groupBy('type'); $pdf = Pdf::loadView('pages.reports.medical_history', compact('cat', 'groupedHistory')) ->setPaper('a4', 'portrait'); $filename = "Medical_Report_" . Str::slug($cat->nama_kucing) . "_" . date('Ymd') . ".pdf"; return $pdf->download($filename); } public function bulkWeightPage(): View { Gate::authorize('check-access', ['cats', 'update']); return view('pages.cats.bulk-weight'); } public function downloadWeightTemplate(): BinaryFileResponse { Gate::authorize('check-access', ['cats', 'update']); // Menambahkan Jam & Menit agar nama file lebih spesifik $fileName = "Template_Bulk_BB_" . date('Ymd_Hi') . ".xlsx"; return Excel::download(new CatWeightTemplateExport, $fileName); } public function uploadWeight(Request $request): RedirectResponse { Gate::authorize('check-access', ['cats', 'update']); $request->validate([ // Tambahkan rule 'file' untuk memastikan inputan benar-benar file 'file_excel' => 'required|file|mimes:xlsx,xls,csv|max:5120', ], [ 'file_excel.required' => 'File Excel wajib diunggah.', 'file_excel.mimes' => 'Format file harus berupa Excel (.xlsx, .xls) atau CSV.', 'file_excel.max' => 'Ukuran file maksimal adalah 5MB.', ]); try { // 1. Masukkan class import ke dalam variabel $importHelper = new CatWeightImport(); // 2. Jalankan proses import menggunakan variabel tersebut Excel::import($importHelper, $request->file('file_excel')); // 3. Ambil jumlah data yang berhasil diproses $totalBerhasil = $importHelper->getImportedCount(); // 4. Sisipkan jumlahnya ke dalam pesan sukses return redirect()->route('cats.index') ->with('success', "Data berat badan massal berhasil diproses! Sebanyak {$totalBerhasil} record berhasil disinkronisasi ke dalam database."); } catch (\Maatwebsite\Excel\Validators\ValidationException $e) { return back()->with('error', 'Tipe file tidak dikenali. Pastikan file tidak rusak (corrupt).'); } catch (\Exception $e) { // MENANGKAP ERROR SISTEM / DATABASE return back()->with('error', 'Gagal memproses file Excel. Pastikan format kolom sesuai template. Error: ' . $e->getMessage()); } } /** * Simpan/Update Sertifikat Pedigree */ public function uploadPedigree(Request $request, Cat $cat) { Gate::authorize('check-access', ['cats', 'update']); $request->validate(['sertifikat_ped_file' => 'required|file|mimes:pdf,jpg,jpeg,png|max:10240']); $registration = $cat->registrations()->where('is_primary', true)->first() ?: $cat->registrations()->first(); if (!$registration) { return redirect()->route('cats.pedigree', $cat)->with('error', 'Tambahkan registrasi asosiasi terlebih dahulu sebelum mengunggah sertifikat.'); } $path = $request->file('sertifikat_ped_file')->store('cat-registrations/' . $cat->id, 'public'); $registration->documents()->create([ 'label' => 'Sertifikat Pedigree', 'document_type' => 'pedigree', 'file_path' => $path, ]); return redirect()->route('cats.pedigree', $cat)->with('success', 'Sertifikat pedigree berhasil ditambahkan.'); } /* -------------------------------------------------------------------------- */ /* HELPERS (TETAP UTUH) */ /* -------------------------------------------------------------------------- */ private function validateCat(Request $request, $id = null) { return $request->validate([ // Identitas Dasar 'nama_kucing' => 'required|string|max:255', 'ras' => 'required|string|max:100', 'jenis_kelamin' => 'required|in:Jantan,Betina', 'tanggal_lahir' => 'nullable|date|before_or_equal:today', // Media 'foto' => 'nullable|image|mimes:jpg,jpeg,png|max:3072', // Registry & Legalitas (Ditambahkan NON-PED & Unik) 'registry' => 'nullable|array', 'registry.*' => ['string', \Illuminate\Validation\Rule::in(['ICA', 'CFA', 'WCF', 'FFF', 'NON-PED'])], 'pedigree' => [ 'nullable', 'string', 'max:255', Rule::unique('cats', 'pedigree')->ignore($id) ], 'microchip' => [ 'nullable', 'string', 'max:255', Rule::unique('cats', 'microchip')->ignore($id) ], 'kode_ems' => 'nullable|string|max:50', // Status & Lokasi 'status' => 'nullable|string|max:50', 'adopted_at' => 'nullable|date', 'adoption_notes'=> 'nullable|string|max:1000', 'lokasi_kandang'=> 'nullable|string|max:255', 'pic_id' => 'nullable|exists:users,id', // Silsilah (Validasi Induk harus sesuai Gender) 'induk_jantan_id' => [ 'nullable', Rule::exists('cats', 'id')->where(function ($query) { $query->where('jenis_kelamin', 'Jantan'); }), ], 'induk_betina_id' => [ 'nullable', Rule::exists('cats', 'id')->where(function ($query) { $query->where('jenis_kelamin', 'Betina'); }), ], // Medis & Monitoring 'berat_badan' => 'nullable|numeric|min:0|max:20', 'riwayat_sakit_terakhir' => 'nullable|string', 'jadwal_vaksin' => 'nullable|date', 'jadwal_obat_cacing' => 'nullable|date', 'jadwal_obat_kutu' => 'nullable|date', 'jadwal_grooming' => 'nullable|date', ], [ // Custom Message (Opsional agar user paham kenapa gagal) 'induk_jantan_id.exists' => 'Induk jantan harus kucing dengan jenis kelamin Jantan.', 'induk_betina_id.exists' => 'Induk betina harus kucing dengan jenis kelamin Betina.', 'microchip.unique' => 'Nomor Microchip sudah terdaftar di sistem.', 'tanggal_lahir.before_or_equal' => 'Tanggal lahir tidak boleh melebihi hari ini.', ]); } public function completeTreatment(Request $request, Cat $cat) { Gate::authorize('check-access', ['cats', 'update']); $request->validate([ 'treatment_id' => 'required|exists:treatments,id', 'remark_pasca_sakit' => 'required|string|max:500', ]); // ======================================================== // 1. EKSEKUSI PAKSA TABEL TREATMENTS (JALUR KERAS) // DB::table akan menembus langsung ke database MySQL Anda. // ======================================================== DB::table('treatments') ->where('id', $request->treatment_id) ->update([ 'status_perawatan' => 2, 'remark_pasca_sakit' => $request->remark_pasca_sakit, 'updated_at' => now(), ]); // Ambil nama keluhan untuk dimunculkan di notifikasi $namaKeluhan = DB::table('treatments')->where('id', $request->treatment_id)->value('keluhan'); // ======================================================== // 2. CEK SISA PENYAKIT (Adakah penyakit lain di kucing ini yang masih = 1?) // ======================================================== $sisaPenyakitAktif = DB::table('treatments') ->where('cat_id', $cat->id) ->where('status_perawatan', 1) ->exists(); // ======================================================== // 3. EKSEKUSI TABEL CATS BERDASARKAN HASIL CEK // ======================================================== if (!$sisaPenyakitAktif) { // JIKA SUDAH TIDAK ADA PENYAKIT LAIN -> Kucing Full Sehat (2) $cat->treatment_status = 2; $cat->save(); return redirect()->back()->with('success', "Penyakit '{$namaKeluhan}' disembuhkan. Kucing sekarang berstatus SEHAT sepenuhnya!"); } // JIKA MASIH ADA PENYAKIT LAIN -> Pastikan kucing TETAP berstatus Treatment (1) $cat->treatment_status = 1; $cat->save(); return redirect()->back()->with('warning', "Penyakit '{$namaKeluhan}' disembuhkan, namun kucing MASIH DALAM PERAWATAN untuk keluhan lainnya."); } public function pedigree(Cat $cat) { return view('pages.cats.pedigree', compact('cat')); } }