Files

547 lines
22 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\{Cat, User, Matting,Medicine};
use App\Traits\LogsActivity;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\View\View;
use Illuminate\Support\Facades\{Storage, DB, Gate,Cache}; // Tambahkan Gate
use Illuminate\Support\Carbon;
use Barryvdh\DomPDF\Facade\Pdf;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
use Intervention\Image\Laravel\Facades\Image;
use Illuminate\Validation\Rule;
use Illuminate\Support\Str;
use App\Imports\CatWeightImport;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\CatWeightTemplateExport;
use Maatwebsite\Excel\Validators\ValidationException;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class CatController extends Controller
{
use LogsActivity;
/**
* Menampilkan daftar kucing dengan filter cerdas.
*/
public function index(Request $request): View
{
Gate::authorize('check-access', ['cats', 'read']);
$now = now();
$listScope = $request->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'));
}
}