2026-02-08 15:16:35 +07:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
|
|
|
|
use App\Models\Medicine;
|
|
|
|
|
use App\Traits\LogsActivity;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\View\View;
|
|
|
|
|
use Illuminate\Http\RedirectResponse;
|
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
|
use Maatwebsite\Excel\Facades\Excel;
|
|
|
|
|
use App\Imports\MedicineImport;
|
|
|
|
|
use App\Exports\MedicineTemplateExport;
|
2026-09-23 12:45:02 +07:00
|
|
|
use Illuminate\Support\Facades\Gate;
|
2026-02-08 15:16:35 +07:00
|
|
|
|
|
|
|
|
class MedicineController extends Controller
|
|
|
|
|
{
|
|
|
|
|
use LogsActivity;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tampilkan daftar obat-obatan (Index).
|
|
|
|
|
*/
|
|
|
|
|
public function index(Request $request): View
|
2026-09-23 12:45:02 +07:00
|
|
|
{
|
|
|
|
|
// Proteksi Akses
|
|
|
|
|
Gate::authorize('check-access', ['medicines', 'read']);
|
|
|
|
|
|
|
|
|
|
$now = now();
|
|
|
|
|
$query = Medicine::query();
|
2026-02-08 15:16:35 +07:00
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// 1. FILTER KHUSUS (DARI DASHBOARD)
|
|
|
|
|
if ($request->filled('filter')) {
|
|
|
|
|
$filter = $request->get('filter');
|
|
|
|
|
|
|
|
|
|
if ($filter === 'expired') {
|
|
|
|
|
// Menampilkan yang sudah lewat tanggal kadaluwarsa
|
|
|
|
|
$query->whereNotNull('expired_at')->where('expired_at', '<', $now);
|
|
|
|
|
} elseif ($filter === 'expiring_soon') {
|
|
|
|
|
// Menampilkan yang akan kadaluwarsa dalam 7 hari kedepan
|
|
|
|
|
$query->whereNotNull('expired_at')
|
|
|
|
|
->whereBetween('expired_at', [$now, $now->copy()->addDays(7)]);
|
|
|
|
|
} elseif ($filter === 'low_stock') {
|
|
|
|
|
// Menampilkan stok kritis (<= 5)
|
|
|
|
|
$query->where('qty', '<=', 5);
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|
2026-09-23 12:45:02 +07:00
|
|
|
}
|
2026-02-08 15:16:35 +07:00
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// 2. SEARCH GLOBAL
|
|
|
|
|
if ($request->filled('search')) {
|
|
|
|
|
$search = $request->search;
|
|
|
|
|
$query->where(function($q) use ($search) {
|
|
|
|
|
$q->where('nama_obat', 'like', "%{$search}%")
|
|
|
|
|
->orWhere('kategori', 'like', "%{$search}%")
|
|
|
|
|
->orWhere('unit', 'like', "%{$search}%");
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-02-08 15:16:35 +07:00
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// 3. FILTER KATEGORI (Opsional jika ada dropdown di UI)
|
|
|
|
|
if ($request->filled('category')) {
|
|
|
|
|
$query->where('kategori', $request->category);
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// 4. SORTING DINAMIS
|
|
|
|
|
// Gunakan 'expired_at' sebagai default sorting jika dari Dashboard 'expired'
|
|
|
|
|
$allowedSorts = ['nama_obat', 'kategori', 'qty', 'expired_at', 'created_at'];
|
|
|
|
|
$sort = in_array($request->get('sort'), $allowedSorts) ? $request->get('sort') : 'nama_obat';
|
|
|
|
|
$direction = strtolower($request->get('direction')) === 'desc' ? 'desc' : 'asc';
|
|
|
|
|
|
|
|
|
|
$medicines = $query->orderBy($sort, $direction)
|
|
|
|
|
->paginate(15)
|
|
|
|
|
->withQueryString();
|
|
|
|
|
|
|
|
|
|
// 5. STATISTIK BOX (Selalu hitung total keseluruhan, bukan hasil filter)
|
|
|
|
|
$totalStock = Medicine::sum('qty');
|
|
|
|
|
$lowStockCount = Medicine::where('qty', '<=', 5)->count();
|
|
|
|
|
|
|
|
|
|
// Menghitung barang yang sudah expired (merah)
|
|
|
|
|
$expiredCount = Medicine::whereNotNull('expired_at')
|
|
|
|
|
->where('expired_at', '<', $now)
|
|
|
|
|
->count();
|
|
|
|
|
|
|
|
|
|
$categories = Medicine::distinct()->whereNotNull('kategori')->pluck('kategori');
|
|
|
|
|
|
|
|
|
|
return view('pages.inventory.medicines.index', compact(
|
|
|
|
|
'medicines',
|
|
|
|
|
'totalStock',
|
|
|
|
|
'lowStockCount',
|
|
|
|
|
'expiredCount', // Tambahan statistik baru
|
|
|
|
|
'categories'
|
|
|
|
|
));
|
|
|
|
|
}
|
2026-02-08 15:16:35 +07:00
|
|
|
/**
|
|
|
|
|
* Tampilkan form tambah obat.
|
|
|
|
|
*/
|
|
|
|
|
public function create(): View
|
|
|
|
|
{
|
|
|
|
|
return view('pages.inventory.medicines.create');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Simpan data obat baru.
|
|
|
|
|
*/
|
|
|
|
|
public function store(Request $request): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'nama_obat' => 'required|string|max:255',
|
|
|
|
|
'qty' => 'required|numeric|min:0',
|
|
|
|
|
'unit' => 'required|string|max:50',
|
|
|
|
|
'expired_date' => 'nullable|date|after_or_equal:today',
|
|
|
|
|
'foto_produk' => 'nullable|image|max:1024',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Catatan: Logika pembuatan slug sudah dipindah ke Model Medicine (event saving)
|
|
|
|
|
// agar Controller lebih ringkas.
|
|
|
|
|
|
|
|
|
|
if ($request->hasFile('foto_produk')) {
|
|
|
|
|
$validated['foto_produk'] = $request->file('foto_produk')->store('medicines', 'public');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$medicine = Medicine::create($validated);
|
|
|
|
|
|
|
|
|
|
$this->logAction('CREATED_MEDICINE', $medicine, "Menambah obat baru: {$medicine->nama_obat}");
|
|
|
|
|
|
|
|
|
|
// FIX: Tambahkan .index agar rute terdeteksi
|
|
|
|
|
return redirect()->route('inventory.medicines.index')->with('success', 'Obat berhasil ditambahkan ke inventori.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tampilkan form edit obat.
|
|
|
|
|
*/
|
|
|
|
|
public function edit(Medicine $medicine): View
|
|
|
|
|
{
|
|
|
|
|
return view('pages.inventory.medicines.edit', compact('medicine'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update data obat.
|
|
|
|
|
*/
|
|
|
|
|
public function update(Request $request, Medicine $medicine): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'nama_obat' => 'required|string|max:255',
|
|
|
|
|
'qty' => 'required|numeric|min:0',
|
|
|
|
|
'unit' => 'required|string|max:50',
|
|
|
|
|
'expired_date' => 'nullable|date|after_or_equal:today',
|
|
|
|
|
'foto_produk' => 'nullable|image|max:1024',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if ($request->hasFile('foto_produk')) {
|
|
|
|
|
if ($medicine->foto_produk) {
|
|
|
|
|
Storage::disk('public')->delete($medicine->foto_produk);
|
|
|
|
|
}
|
|
|
|
|
$validated['foto_produk'] = $request->file('foto_produk')->store('medicines', 'public');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$medicine->update($validated);
|
|
|
|
|
|
|
|
|
|
$this->logAction('UPDATED_MEDICINE', $medicine, "Memperbarui data obat: {$medicine->nama_obat}");
|
|
|
|
|
|
|
|
|
|
// FIX: Tambahkan .index
|
|
|
|
|
return redirect()->route('inventory.medicines.index')->with('success', 'Data obat berhasil diperbarui.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Hapus obat.
|
|
|
|
|
*/
|
|
|
|
|
public function destroy(Medicine $medicine): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
if (auth()->user()->role !== 'admin') {
|
|
|
|
|
abort(403, 'Hanya Admin yang diizinkan menghapus data.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Proteksi Integritas rekam medis
|
|
|
|
|
if ($medicine->treatments()->exists()) {
|
|
|
|
|
return back()->with('error', "Gagal: Obat '{$medicine->nama_obat}' sudah tercatat di rekam medis kucing.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($medicine->foto_produk) {
|
|
|
|
|
Storage::disk('public')->delete($medicine->foto_produk);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$medicineName = $medicine->nama_obat;
|
|
|
|
|
|
|
|
|
|
// PENTING: Log dilakukan SEBELUM delete
|
|
|
|
|
$this->logAction('DELETED_MEDICINE', $medicine, "Menghapus data obat: {$medicineName}");
|
|
|
|
|
|
|
|
|
|
$medicine->delete();
|
|
|
|
|
|
|
|
|
|
// FIX: Tambahkan .index
|
|
|
|
|
return redirect()->route('inventory.medicines.index')->with('success', 'Data obat berhasil dihapus.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fitur Impor Excel
|
|
|
|
|
*/
|
|
|
|
|
public function batchUpload(Request $request): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$request->validate(['file_excel' => 'required|mimes:xlsx,xls,csv|max:2048']);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
Excel::import(new MedicineImport, $request->file('file_excel'));
|
|
|
|
|
$this->logAction('BATCH_UPLOAD', null, "Melakukan impor massal data obat");
|
|
|
|
|
|
|
|
|
|
// FIX: Tambahkan .index
|
|
|
|
|
return redirect()->route('inventory.medicines.index')->with('success', 'Data obat berhasil diimpor.');
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
return back()->with('error', 'Gagal impor: ' . $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Download Template Excel
|
|
|
|
|
*/
|
|
|
|
|
public function downloadTemplate()
|
|
|
|
|
{
|
|
|
|
|
return Excel::download(new MedicineTemplateExport, 'template_obat_earrin_cats.xlsx');
|
|
|
|
|
}
|
|
|
|
|
}
|