2026-02-08 15:16:35 +07:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
|
|
|
|
use App\Models\Cat;
|
|
|
|
|
use App\Models\Treatment;
|
|
|
|
|
use App\Models\Medicine;
|
2026-09-23 12:45:02 +07:00
|
|
|
use App\Models\TreatmentDetail; // Menambahkan model Detail
|
2026-02-08 15:16:35 +07:00
|
|
|
use App\Traits\LogsActivity;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\View\View;
|
|
|
|
|
use Illuminate\Http\RedirectResponse;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
|
use Exception;
|
|
|
|
|
|
|
|
|
|
class TreatmentController extends Controller
|
|
|
|
|
{
|
|
|
|
|
use LogsActivity;
|
|
|
|
|
|
|
|
|
|
/**
|
2026-09-23 12:45:02 +07:00
|
|
|
* Menampilkan daftar rekam medis dengan optimasi Eager Loading & Filtering.
|
2026-02-08 15:16:35 +07:00
|
|
|
*/
|
2026-09-23 12:45:02 +07:00
|
|
|
public function index(Request $request): View|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
|
|
|
|
{
|
|
|
|
|
// 1. QUERY DASAR DENGAN OPTIMASI EAGER LOADING
|
|
|
|
|
$query = Treatment::with([
|
|
|
|
|
'cat:id,nama_kucing,foto,treatment_status',
|
|
|
|
|
'details.medicine:id,nama_obat,unit',
|
|
|
|
|
'user:id,name'
|
|
|
|
|
])
|
|
|
|
|
->whereHas('cat', function($q) {
|
|
|
|
|
$q->where('treatment_status', 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 2. LOGIKA FILTERING DINAMIS
|
|
|
|
|
if ($request->filled('search')) {
|
|
|
|
|
$query->whereHas('cat', function($q) use ($request) {
|
|
|
|
|
$q->where('nama_kucing', 'like', '%' . $request->search . '%');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($request->filled('pic_id')) {
|
|
|
|
|
$query->where('user_id', $request->pic_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($request->filled('start_date') && $request->filled('end_date')) {
|
|
|
|
|
$query->whereBetween('tanggal', [$request->start_date, $request->end_date]);
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// 3. LOGIKA EXPORT (Pastikan blok IF ditutup dengan benar)
|
|
|
|
|
if ($request->filled('export')) {
|
|
|
|
|
$exportData = new \App\Exports\TreatmentExport($request);
|
|
|
|
|
$fileName = 'Report_Active_Treatment_' . now()->format('Ymd_His');
|
|
|
|
|
|
|
|
|
|
if ($request->export === 'excel') {
|
|
|
|
|
return \Maatwebsite\Excel\Facades\Excel::download($exportData, "{$fileName}.xlsx");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($request->export === 'pdf') {
|
|
|
|
|
return \Maatwebsite\Excel\Facades\Excel::download($exportData, "{$fileName}.pdf", \Maatwebsite\Excel\Excel::DOMPDF);
|
|
|
|
|
}
|
|
|
|
|
} // <--- Pastikan kurung ini menutup blok export, bukan menutup fungsi index
|
|
|
|
|
|
|
|
|
|
// 4. EKSEKUSI DATA UNTUK VIEW (Line 67)
|
|
|
|
|
$treatments = $query->latest('tanggal')->paginate(20)->withQueryString();
|
|
|
|
|
|
|
|
|
|
$pics = \App\Models\User::select('id', 'name')->orderBy('name')->get();
|
|
|
|
|
|
|
|
|
|
return view('pages.treatments.index', compact('treatments', 'pics'));
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 15:16:35 +07:00
|
|
|
/**
|
|
|
|
|
* Form tambah rekam medis.
|
|
|
|
|
*/
|
|
|
|
|
public function create(Request $request): View
|
|
|
|
|
{
|
2026-09-23 12:45:02 +07:00
|
|
|
$selectedCat = null;
|
|
|
|
|
if ($request->filled('cat_id')) {
|
|
|
|
|
$selectedCat = Cat::select('id', 'nama_kucing', 'ras')->findOrFail($request->cat_id);
|
|
|
|
|
$cats = collect([$selectedCat]);
|
|
|
|
|
} else {
|
|
|
|
|
$cats = Cat::select('id', 'nama_kucing', 'ras')->get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Menampilkan semua obat (termasuk stok 0 untuk kebutuhan edit/view,
|
|
|
|
|
// namun di filter di frontend untuk input baru)
|
|
|
|
|
$medicines = Medicine::orderBy('nama_obat', 'asc')->get();
|
2026-02-08 15:16:35 +07:00
|
|
|
|
|
|
|
|
return view('pages.treatments.create', compact('selectedCat', 'cats', 'medicines'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-09-23 12:45:02 +07:00
|
|
|
* Simpan rekam medis baru & potong stok (Mendukung Multi-Obat).
|
2026-02-08 15:16:35 +07:00
|
|
|
*/
|
2026-09-23 12:45:02 +07:00
|
|
|
public function store(Request $request): \Illuminate\Http\RedirectResponse
|
2026-02-08 15:16:35 +07:00
|
|
|
{
|
|
|
|
|
$validated = $this->validateTreatment($request);
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
$fotoPath = null;
|
|
|
|
|
if ($request->hasFile('foto_bukti')) {
|
|
|
|
|
$fotoPath = $request->file('foto_bukti')->store('treatments', 'public');
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 15:16:35 +07:00
|
|
|
try {
|
2026-09-23 12:45:02 +07:00
|
|
|
return \Illuminate\Support\Facades\DB::transaction(function () use ($request, $fotoPath) {
|
|
|
|
|
|
|
|
|
|
$userId = auth()->id() ?? 1;
|
|
|
|
|
$firstTreatment = null;
|
2026-02-08 15:16:35 +07:00
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
if ($request->filled('items')) {
|
|
|
|
|
foreach ($request->items as $item) {
|
|
|
|
|
$medicine = \App\Models\Medicine::findOrFail($item['medicine_id']);
|
2026-02-08 15:16:35 +07:00
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
if ($medicine->qty < $item['qty_used']) {
|
|
|
|
|
throw new \Exception("Stok {$medicine->nama_obat} tidak mencukupi (Sisa: {$medicine->qty}).");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$treatment = \App\Models\Treatment::create([
|
|
|
|
|
'cat_id' => $request->cat_id,
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
'tanggal' => $request->tanggal,
|
|
|
|
|
'keluhan' => $request->keluhan,
|
|
|
|
|
|
|
|
|
|
// Langsung masukkan diagnosa tanpa embel-embel
|
|
|
|
|
'diagnosa' => $request->diagnosa,
|
|
|
|
|
|
|
|
|
|
'foto_bukti' => $fotoPath,
|
|
|
|
|
'status_perawatan' => 1,
|
|
|
|
|
'medicine_id' => $item['medicine_id'],
|
|
|
|
|
'qty_used' => $item['qty_used'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (!$firstTreatment) {
|
|
|
|
|
$firstTreatment = $treatment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$medicine->decrement('qty', $item['qty_used']);
|
|
|
|
|
}
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
\App\Models\Cat::where('id', $request->cat_id)->update(['treatment_status' => 1]);
|
2026-02-08 15:16:35 +07:00
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
if ($firstTreatment) {
|
|
|
|
|
$this->logAction('TREATED', $firstTreatment, "Mencatat pengobatan klinis untuk " . $firstTreatment->cat->nama_kucing);
|
|
|
|
|
}
|
2026-02-08 15:16:35 +07:00
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
return redirect()->route('cats.show', $request->cat_id)
|
|
|
|
|
->with('success', 'Rekam medis klinis berhasil dicatat dan stok dipotong otomatis.');
|
2026-02-08 15:16:35 +07:00
|
|
|
});
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
if ($fotoPath && \Illuminate\Support\Facades\Storage::disk('public')->exists($fotoPath)) {
|
|
|
|
|
\Illuminate\Support\Facades\Storage::disk('public')->delete($fotoPath);
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|
|
|
|
|
return back()->withInput()->with('error', 'Gagal menyimpan: ' . $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-23 12:45:02 +07:00
|
|
|
/**
|
|
|
|
|
* Menampilkan detail spesifik satu rekam medis.
|
|
|
|
|
*/
|
|
|
|
|
public function show(Treatment $treatment): View
|
|
|
|
|
{
|
|
|
|
|
// Load details untuk menampilkan list obat
|
|
|
|
|
$treatment->load(['cat', 'medicine', 'user', 'details.medicine']);
|
|
|
|
|
return view('pages.treatments.show', compact('treatment'));
|
|
|
|
|
}
|
2026-02-08 15:16:35 +07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Form edit rekam medis.
|
|
|
|
|
*/
|
|
|
|
|
public function edit(Treatment $treatment): View
|
|
|
|
|
{
|
2026-09-23 12:45:02 +07:00
|
|
|
$treatment->load('details');
|
2026-02-08 15:16:35 +07:00
|
|
|
$cats = Cat::select('id', 'nama_kucing', 'ras')->get();
|
2026-09-23 12:45:02 +07:00
|
|
|
$medicines = Medicine::orderBy('nama_obat', 'asc')->get();
|
|
|
|
|
|
2026-02-08 15:16:35 +07:00
|
|
|
return view('pages.treatments.edit', compact('treatment', 'cats', 'medicines'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-09-23 12:45:02 +07:00
|
|
|
* Update rekam medis & sinkronisasi ulang stok desimal (Multi-Obat).
|
2026-02-08 15:16:35 +07:00
|
|
|
*/
|
2026-09-23 12:45:02 +07:00
|
|
|
public function update(Request $request, Treatment $treatment)
|
2026-02-08 15:16:35 +07:00
|
|
|
{
|
2026-09-23 12:45:02 +07:00
|
|
|
// 1. Validasi Input
|
2026-02-08 15:16:35 +07:00
|
|
|
$validated = $this->validateTreatment($request);
|
|
|
|
|
|
|
|
|
|
try {
|
2026-09-23 12:45:02 +07:00
|
|
|
return DB::transaction(function () use ($request, $treatment) {
|
|
|
|
|
|
|
|
|
|
// 2. Kelola Foto Bukti (Hapus yang lama jika ada foto baru)
|
|
|
|
|
$fotoPath = $treatment->foto_bukti;
|
|
|
|
|
if ($request->hasFile('foto_bukti')) {
|
|
|
|
|
if ($fotoPath && \Illuminate\Support\Facades\Storage::disk('public')->exists($fotoPath)) {
|
|
|
|
|
\Illuminate\Support\Facades\Storage::disk('public')->delete($fotoPath);
|
|
|
|
|
}
|
|
|
|
|
$fotoPath = $request->file('foto_bukti')->store('treatments', 'public');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. KEMBALIKAN STOK OBAT LAMA (Penting agar stok tidak bocor)
|
|
|
|
|
$oldMedicine = \App\Models\Medicine::find($treatment->medicine_id);
|
2026-02-08 15:16:35 +07:00
|
|
|
if ($oldMedicine) {
|
|
|
|
|
$oldMedicine->increment('qty', $treatment->qty_used);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// 4. Ambil data obat PERTAMA dari form untuk memperbarui data saat ini (ID 9)
|
|
|
|
|
$firstItem = $request->items[0];
|
|
|
|
|
$newMedicine = \App\Models\Medicine::findOrFail($firstItem['medicine_id']);
|
|
|
|
|
|
|
|
|
|
if ($newMedicine->qty < $firstItem['qty_used']) {
|
|
|
|
|
throw new Exception("Stok {$newMedicine->nama_obat} tidak mencukupi (Sisa: {$newMedicine->qty}).");
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// Format Diagnosa (Gabungan Diagnosa Utama + Dosis/Ket)
|
|
|
|
|
$catatanDosis = isset($firstItem['dosis']) ? " | Dosis: " . $firstItem['dosis'] : "";
|
|
|
|
|
$catatanKet = isset($firstItem['keterangan']) && $firstItem['keterangan'] != '' ? " (" . $firstItem['keterangan'] . ")" : "";
|
|
|
|
|
$diagnosaFinal = $request->diagnosa . $catatanDosis . $catatanKet;
|
|
|
|
|
|
|
|
|
|
// EKSEKUSI UPDATE REKAM MEDIS (Tanpa kolom nama_obat!)
|
|
|
|
|
$treatment->update([
|
|
|
|
|
'cat_id' => $request->cat_id,
|
|
|
|
|
'tanggal' => $request->tanggal,
|
|
|
|
|
'keluhan' => $request->keluhan,
|
|
|
|
|
'diagnosa' => $diagnosaFinal,
|
|
|
|
|
'foto_bukti' => $fotoPath,
|
|
|
|
|
'medicine_id' => $firstItem['medicine_id'],
|
|
|
|
|
'qty_used' => $firstItem['qty_used'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Potong stok obat pertama
|
|
|
|
|
$newMedicine->decrement('qty', $firstItem['qty_used']);
|
|
|
|
|
|
|
|
|
|
// 5. JIKA STAF MENAMBAHKAN OBAT EKSTRA DI HALAMAN EDIT
|
|
|
|
|
// Buat sebagai record baru agar data tidak hilang
|
|
|
|
|
if (count($request->items) > 1) {
|
|
|
|
|
for ($i = 1; $i < count($request->items); $i++) {
|
|
|
|
|
$extraItem = $request->items[$i];
|
|
|
|
|
$extraMed = \App\Models\Medicine::findOrFail($extraItem['medicine_id']);
|
|
|
|
|
|
|
|
|
|
if ($extraMed->qty < $extraItem['qty_used']) {
|
|
|
|
|
throw new Exception("Stok {$extraMed->nama_obat} tidak mencukupi.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$catatanDosisEx = isset($extraItem['dosis']) ? " | Dosis: " . $extraItem['dosis'] : "";
|
|
|
|
|
$catatanKetEx = isset($extraItem['keterangan']) && $extraItem['keterangan'] != '' ? " (" . $extraItem['keterangan'] . ")" : "";
|
|
|
|
|
$diagnosaFinalEx = $request->diagnosa . $catatanDosisEx . $catatanKetEx;
|
|
|
|
|
|
|
|
|
|
\App\Models\Treatment::create([
|
|
|
|
|
'cat_id' => $request->cat_id,
|
|
|
|
|
'user_id' => auth()->id() ?? 1,
|
|
|
|
|
'tanggal' => $request->tanggal,
|
|
|
|
|
'keluhan' => $request->keluhan,
|
|
|
|
|
'diagnosa' => $diagnosaFinalEx,
|
|
|
|
|
'foto_bukti' => $fotoPath,
|
|
|
|
|
'status_perawatan' => $treatment->status_perawatan, // Ikuti status (Misal: 1 / Aktif)
|
|
|
|
|
'medicine_id' => $extraItem['medicine_id'],
|
|
|
|
|
'qty_used' => $extraItem['qty_used'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$extraMed->decrement('qty', $extraItem['qty_used']);
|
|
|
|
|
}
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// 6. Sinkronisasi Kucing & Log Activity
|
|
|
|
|
\App\Models\Cat::where('id', $request->cat_id)->update(['treatment_status' => 1]);
|
|
|
|
|
$this->logAction('UPDATED', $treatment, "Memperbarui rekam medis klinis untuk " . $treatment->cat->nama_kucing);
|
2026-02-08 15:16:35 +07:00
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
return redirect()->route('cats.show', $request->cat_id)
|
|
|
|
|
->with('success', 'Rekam medis berhasil diperbarui dan stok obat disesuaikan ulang secara otomatis!');
|
2026-02-08 15:16:35 +07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
return back()->withInput()->with('error', 'Gagal memperbarui: ' . $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-09-23 12:45:02 +07:00
|
|
|
* Hapus rekam medis & kembalikan semua stok obat terkait.
|
2026-02-08 15:16:35 +07:00
|
|
|
*/
|
|
|
|
|
public function destroy(Treatment $treatment): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
DB::transaction(function () use ($treatment) {
|
2026-09-23 12:45:02 +07:00
|
|
|
// Kembalikan semua stok dari tabel detail
|
|
|
|
|
foreach ($treatment->details as $detail) {
|
|
|
|
|
Medicine::where('id', $detail->medicine_id)->increment('qty', $detail->qty_used);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($treatment->foto_bukti) {
|
|
|
|
|
Storage::disk('public')->delete($treatment->foto_bukti);
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->logAction('DELETED_TREATMENT', $treatment, "Menghapus catatan medis kucing: {$treatment->cat->nama_kucing}");
|
2026-09-23 12:45:02 +07:00
|
|
|
|
|
|
|
|
// Delete otomatis menghapus details karena cascade di migration
|
2026-02-08 15:16:35 +07:00
|
|
|
$treatment->delete();
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
return back()->with('success', 'Data rekam medis dihapus dan semua stok obat dikembalikan.');
|
2026-02-08 15:16:35 +07:00
|
|
|
} catch (Exception $e) {
|
2026-09-23 12:45:02 +07:00
|
|
|
return back()->with('error', 'Gagal menghapus data: ' . $e->getMessage());
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|
|
|
|
|
}
|
2026-09-23 12:45:02 +07:00
|
|
|
private function validateTreatment(Request $request)
|
2026-02-08 15:16:35 +07:00
|
|
|
{
|
|
|
|
|
return $request->validate([
|
|
|
|
|
'cat_id' => 'required|exists:cats,id',
|
|
|
|
|
'tanggal' => 'required|date',
|
|
|
|
|
'keluhan' => 'required|string|max:255',
|
|
|
|
|
'diagnosa' => 'required|string',
|
|
|
|
|
'foto_bukti' => 'nullable|image|max:2048',
|
2026-09-23 12:45:02 +07:00
|
|
|
|
|
|
|
|
// Validasi untuk Single Medicine
|
|
|
|
|
'medicine_id' => 'required_without:items|nullable|exists:medicines,id',
|
|
|
|
|
'qty_used' => 'required_without:items|nullable|numeric|min:0',
|
|
|
|
|
|
|
|
|
|
// Validasi untuk Multi Medicine
|
|
|
|
|
// FIX: Wajibkan array items jika mode single (medicine_id) kosong
|
|
|
|
|
'items' => 'required_without:medicine_id|nullable|array',
|
|
|
|
|
'items.*.medicine_id' => 'required_with:items|exists:medicines,id',
|
|
|
|
|
'items.*.qty_used' => 'required_with:items|numeric|min:0.01',
|
|
|
|
|
|
|
|
|
|
], [
|
|
|
|
|
// Pesan Error Custom (Opsional, agar lebih mudah di-debug)
|
|
|
|
|
'items.required_without' => 'Anda harus memasukkan minimal 1 obat.',
|
|
|
|
|
'items.*.medicine_id.required_with' => 'Ada pilihan obat yang masih kosong di daftar.',
|
|
|
|
|
'items.*.qty_used.required_with' => 'Jumlah (Qty) obat harus diisi.',
|
|
|
|
|
|
2026-02-08 15:16:35 +07:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|