2026-02-08 15:16:35 +07:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
|
|
|
|
use App\Models\Supply;
|
|
|
|
|
use App\Models\Category;
|
|
|
|
|
use App\Traits\LogsActivity;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\View\View;
|
|
|
|
|
use Illuminate\Http\RedirectResponse;
|
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
2026-09-23 12:45:02 +07:00
|
|
|
use Illuminate\Support\Facades\Gate;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
2026-02-08 15:16:35 +07:00
|
|
|
|
|
|
|
|
class SupplyController extends Controller
|
|
|
|
|
{
|
|
|
|
|
use LogsActivity;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tampilkan daftar perlengkapan.
|
|
|
|
|
*/
|
2026-09-23 12:45:02 +07:00
|
|
|
public function index(Request $request): View
|
|
|
|
|
{
|
|
|
|
|
// 1. Proteksi Akses (Sesuai Matriks Role Management)
|
|
|
|
|
Gate::authorize('check-access', ['supplies', 'read']);
|
|
|
|
|
|
|
|
|
|
$now = now();
|
|
|
|
|
$query = Supply::with('category');
|
|
|
|
|
|
|
|
|
|
// 2. FILTER KHUSUS (SINKRONISASI DENGAN DASHBOARD)
|
|
|
|
|
if ($request->filled('filter')) {
|
|
|
|
|
$filter = $request->get('filter');
|
|
|
|
|
|
|
|
|
|
if ($filter === 'expired') {
|
|
|
|
|
// Menampilkan perlengkapan yang sudah kadaluwarsa
|
|
|
|
|
$query->whereNotNull('expired_at')->where('expired_at', '<', $now);
|
|
|
|
|
} elseif ($filter === 'expiring_soon') {
|
|
|
|
|
// Menampilkan yang kadaluwarsa dalam 7 hari kedepan
|
|
|
|
|
$query->whereNotNull('expired_at')
|
|
|
|
|
->whereBetween('expired_at', [$now, $now->copy()->addDays(7)]);
|
|
|
|
|
} elseif ($filter === 'low_stock') {
|
|
|
|
|
// Menampilkan stok kritis
|
|
|
|
|
$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
|
|
|
// 3. SEARCH GLOBAL (Mencari di Nama, Kategori, dan Unit)
|
|
|
|
|
if ($request->filled('search')) {
|
|
|
|
|
$search = $request->search;
|
|
|
|
|
$query->where(function($q) use ($search) {
|
|
|
|
|
$q->where('nama_perlengkapan', 'like', "%{$search}%")
|
|
|
|
|
->orWhere('kategori', 'like', "%{$search}%")
|
|
|
|
|
->orWhere('unit', 'like', "%{$search}%")
|
|
|
|
|
->orWhereHas('category', function($sub) use ($search) {
|
|
|
|
|
$sub->where('name', 'like', "%{$search}%");
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// 4. LOGIKA SORTING DINAMIS (Menambahkan expired_at dalam daftar sort)
|
|
|
|
|
$allowedSorts = ['nama_perlengkapan', 'qty', 'kategori', 'expired_at', 'created_at'];
|
|
|
|
|
$sort = in_array($request->get('sort'), $allowedSorts) ? $request->get('sort') : 'nama_perlengkapan';
|
|
|
|
|
$direction = strtolower($request->get('direction')) === 'desc' ? 'desc' : 'asc';
|
|
|
|
|
|
|
|
|
|
// Eksekusi Query dengan Pagination
|
|
|
|
|
$supplies = $query->orderBy($sort, $direction)
|
|
|
|
|
->paginate(15)
|
|
|
|
|
->withQueryString();
|
|
|
|
|
|
|
|
|
|
// 5. STATISTIK RINGKAS (Komprehensif)
|
|
|
|
|
$totalStock = Supply::sum('qty');
|
|
|
|
|
$lowStockCount = Supply::where('qty', '<=', 5)->count();
|
|
|
|
|
|
|
|
|
|
// Hitung item yang expired (Wajib ada kolom expired_at di database)
|
|
|
|
|
$expiredCount = Supply::whereNotNull('expired_at')
|
|
|
|
|
->where('expired_at', '<', $now)
|
|
|
|
|
->count();
|
|
|
|
|
|
|
|
|
|
$totalItems = Supply::count();
|
|
|
|
|
|
|
|
|
|
return view('pages.inventory.supplies.index', compact(
|
|
|
|
|
'supplies',
|
|
|
|
|
'totalStock',
|
|
|
|
|
'lowStockCount',
|
|
|
|
|
'expiredCount', // Variabel statistik baru
|
|
|
|
|
'totalItems'
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 15:16:35 +07:00
|
|
|
/**
|
|
|
|
|
* Tampilkan form tambah.
|
|
|
|
|
*/
|
|
|
|
|
public function create(): View
|
|
|
|
|
{
|
|
|
|
|
// Category ditentukan otomatis oleh Model Supply
|
|
|
|
|
return view('pages.inventory.supplies.create');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Simpan data perlengkapan baru.
|
|
|
|
|
*/
|
|
|
|
|
public function store(Request $request): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'nama_perlengkapan' => 'required|string|max:255',
|
|
|
|
|
'qty' => 'required|numeric|min:0',
|
|
|
|
|
'unit' => 'required|string|max:50',
|
|
|
|
|
'category_id' => 'nullable|exists:categories,id',
|
|
|
|
|
'foto_produk' => 'nullable|image|max:1024',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if ($request->hasFile('foto_produk')) {
|
|
|
|
|
$validated['foto_produk'] = $request->file('foto_produk')->store('supplies', 'public');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$supply = Supply::create($validated);
|
|
|
|
|
|
|
|
|
|
$this->logAction('CREATED_SUPPLY', $supply, "Menambah perlengkapan: {$supply->nama_perlengkapan}");
|
|
|
|
|
|
|
|
|
|
// FIX: Menggunakan .index agar tidak RouteNotFound
|
|
|
|
|
return redirect()->route('inventory.supplies.index')->with('success', 'Perlengkapan berhasil ditambahkan.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tampilkan form edit.
|
|
|
|
|
*/
|
|
|
|
|
public function edit(Supply $supply): View
|
|
|
|
|
{
|
|
|
|
|
$categories = Category::all();
|
|
|
|
|
return view('pages.inventory.supplies.edit', compact('supply', 'categories'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update data perlengkapan.
|
|
|
|
|
*/
|
|
|
|
|
public function update(Request $request, Supply $supply): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'nama_perlengkapan' => 'required|string|max:255',
|
|
|
|
|
'qty' => 'required|numeric|min:0',
|
|
|
|
|
'unit' => 'required|string|max:50',
|
|
|
|
|
'category_id' => 'nullable|exists:categories,id',
|
|
|
|
|
'foto_produk' => 'nullable|image|max:1024',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if ($request->hasFile('foto_produk')) {
|
|
|
|
|
if ($supply->foto_produk) {
|
|
|
|
|
Storage::disk('public')->delete($supply->foto_produk);
|
|
|
|
|
}
|
|
|
|
|
$validated['foto_produk'] = $request->file('foto_produk')->store('supplies', 'public');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$supply->update($validated);
|
|
|
|
|
|
|
|
|
|
$this->logAction('UPDATED_SUPPLY', $supply, "Memperbarui data perlengkapan: {$supply->nama_perlengkapan}");
|
|
|
|
|
|
|
|
|
|
// FIX: Menggunakan .index
|
|
|
|
|
return redirect()->route('inventory.supplies.index')->with('success', 'Data perlengkapan berhasil diperbarui.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Hapus data perlengkapan.
|
|
|
|
|
*/
|
2026-09-23 12:45:02 +07:00
|
|
|
public function destroy(Supply $supply): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
// 1. OTORISASI DINAMIS (Inilah perbaikan untuk akses Supervisor)
|
|
|
|
|
// Menggunakan Gate 'check-access' agar sinkron dengan Role Management Anda
|
|
|
|
|
Gate::authorize('check-access', ['supplies', 'delete']);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
DB::beginTransaction();
|
2026-02-08 15:16:35 +07:00
|
|
|
|
|
|
|
|
$supplyName = $supply->nama_perlengkapan;
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// 2. PEMBERSIHAN FILE (STORAGE)
|
|
|
|
|
if ($supply->foto_produk && Storage::disk('public')->exists($supply->foto_produk)) {
|
2026-02-08 15:16:35 +07:00
|
|
|
Storage::disk('public')->delete($supply->foto_produk);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// 3. AUDIT TRAIL
|
|
|
|
|
// Dicatat sebelum delete agar konteks data $supply masih tersedia secara lengkap
|
|
|
|
|
if (method_exists($this, 'logAction')) {
|
|
|
|
|
$this->logAction('DELETE', 'Inventory', "Menghapus perlengkapan: {$supplyName}");
|
|
|
|
|
}
|
2026-02-08 15:16:35 +07:00
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
// 4. EKSEKUSI HAPUS
|
2026-02-08 15:16:35 +07:00
|
|
|
$supply->delete();
|
|
|
|
|
|
2026-09-23 12:45:02 +07:00
|
|
|
DB::commit();
|
|
|
|
|
|
|
|
|
|
return redirect()->route('inventory.supplies.index')
|
|
|
|
|
->with('success', "Perlengkapan '{$supplyName}' berhasil dihapus dari sistem.");
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
DB::rollBack();
|
|
|
|
|
|
|
|
|
|
// Reporting error jika terjadi kegagalan sistem
|
|
|
|
|
return back()->with('error', 'Gagal menghapus data. Silakan coba lagi atau hubungi IT.');
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|
2026-09-23 12:45:02 +07:00
|
|
|
}
|
2026-02-08 15:16:35 +07:00
|
|
|
}
|