MIF_E31231003/app/Http/Controllers/Admin/JurusanAdminController.php

386 lines
13 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Jurusan;
use App\Models\Kriteria;
use App\Models\JurusanKriteria;
use App\Models\Penyakit;
use App\Services\ActivityLogger;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class JurusanAdminController extends Controller
{
public function index()
{
$jurusans = Jurusan::withCount(['artikelJurusan', 'prospekKerja'])
->orderBy('nama_jurusan')
->get();
return view('pages.admin.jurusan.index', compact('jurusans'));
}
public function create()
{
$kriterias = Kriteria::where('is_active', true)
->orderBy('kode_kriteria')
->get();
$bobotMap = [];
$aturanMap = [];
$penyakits = Penyakit::where('is_active', true)
->orderBy('nama_penyakit')
->get();
$penyakitTerlarangIds = [];
return view('pages.admin.jurusan.create', compact('kriterias', 'bobotMap', 'aturanMap', 'penyakits', 'penyakitTerlarangIds'));
}
public function store(Request $request)
{
$kriterias = Kriteria::where('is_active', true)
->orderBy('kode_kriteria')
->get();
$rules = [
'nama_jurusan' => 'required|string|max:100|unique:jurusan,nama_jurusan',
'is_active' => 'required|boolean',
'bobot' => 'required|array',
];
foreach ($kriterias as $kriteria) {
$rules["bobot.{$kriteria->id}"] = 'required|numeric|min:0|max:1';
if ($this->isKriteriaPenyakit($kriteria)) {
$rules["wajib_lolos.{$kriteria->id}"] = 'nullable|boolean';
} elseif ($this->isKriteriaWajibLolos($kriteria)) {
$maxNilai = 100;
if ($kriteria->kode_kriteria === 'C6') $maxNilai = 1;
$rules["wajib_lolos.{$kriteria->id}"] = 'nullable|boolean';
$rules["nilai_min.{$kriteria->id}"] = "nullable|numeric|min:0|max:{$maxNilai}";
$rules["nilai_max.{$kriteria->id}"] = "nullable|numeric|min:0|max:{$maxNilai}";
}
}
$rules['penyakit_larangan'] = 'nullable|array';
$rules['penyakit_larangan.*'] = 'exists:penyakit,id';
$request->validate($rules);
if ($errors = $this->validateAturanWajibLolos($request, $kriterias)) {
return back()->withErrors($errors)->withInput();
}
if ((int) $request->is_active === 1 && !$this->isBobotValid($request->bobot)) {
return back()
->withErrors([
'bobot' => 'Jurusan tidak dapat dibuat aktif karena bobot kriteria belum lengkap atau total bobot tidak sama dengan 1.00.'
])
->withInput();
}
DB::transaction(function () use ($request, $kriterias) {
$jurusan = Jurusan::create([
'nama_jurusan' => $request->nama_jurusan,
'is_active' => $request->is_active,
]);
$insertData = [];
foreach ($kriterias as $kriteria) {
$insertData[] = [
'jurusan_id' => $jurusan->id,
'kriteria_id' => $kriteria->id,
'bobot' => (float) ($request->bobot[$kriteria->id] ?? 0),
'wajib_lolos' => $this->isKriteriaWajibLolos($kriteria)
? $request->boolean("wajib_lolos.{$kriteria->id}")
: false,
'nilai_min' => $this->nilaiMinUntukKriteria($request, $kriteria),
'nilai_max' => $this->nilaiMaxUntukKriteria($request, $kriteria),
'created_at' => now(),
'updated_at' => now(),
];
}
if (!empty($insertData)) {
JurusanKriteria::upsert(
$insertData,
['jurusan_id', 'kriteria_id'],
['bobot', 'wajib_lolos', 'nilai_min', 'nilai_max', 'updated_at']
);
}
$jurusan->penyakit()->sync($request->input('penyakit_larangan', []));
});
ActivityLogger::log('Admin tambah jurusan: ' . $request->nama_jurusan);
return redirect()->route('admin.jurusan.index')
->with('success', "Jurusan {$request->nama_jurusan} berhasil ditambahkan!");
}
public function edit($id)
{
$jurusan = Jurusan::findOrFail($id);
$kriterias = Kriteria::where('is_active', true)
->orderBy('kode_kriteria')
->get();
$bobotMap = JurusanKriteria::where('jurusan_id', $jurusan->id)
->pluck('bobot', 'kriteria_id')
->toArray();
$aturanMap = JurusanKriteria::where('jurusan_id', $jurusan->id)
->get()
->keyBy('kriteria_id');
$penyakits = Penyakit::where('is_active', true)
->orderBy('nama_penyakit')
->get();
$penyakitTerlarangIds = $jurusan->penyakit()
->pluck('penyakit.id')
->toArray();
return view('pages.admin.jurusan.edit', compact('jurusan', 'kriterias', 'bobotMap', 'aturanMap', 'penyakits', 'penyakitTerlarangIds'));
}
public function update(Request $request, $id)
{
$jurusan = Jurusan::findOrFail($id);
$kriterias = Kriteria::where('is_active', true)
->orderBy('kode_kriteria')
->get();
$rules = [
'nama_jurusan' => 'required|string|max:100|unique:jurusan,nama_jurusan,' . $id,
'is_active' => 'required|boolean',
'bobot' => 'required|array',
];
foreach ($kriterias as $kriteria) {
$rules["bobot.{$kriteria->id}"] = 'required|numeric|min:0|max:1';
if ($this->isKriteriaPenyakit($kriteria)) {
$rules["wajib_lolos.{$kriteria->id}"] = 'nullable|boolean';
} elseif ($this->isKriteriaWajibLolos($kriteria)) {
$maxNilai = 100;
if ($kriteria->kode_kriteria === 'C6') $maxNilai = 1;
$rules["wajib_lolos.{$kriteria->id}"] = 'nullable|boolean';
$rules["nilai_min.{$kriteria->id}"] = "nullable|numeric|min:0|max:{$maxNilai}";
$rules["nilai_max.{$kriteria->id}"] = "nullable|numeric|min:0|max:{$maxNilai}";
}
}
$rules['penyakit_larangan'] = 'nullable|array';
$rules['penyakit_larangan.*'] = 'exists:penyakit,id';
$request->validate($rules);
if ($errors = $this->validateAturanWajibLolos($request, $kriterias)) {
return back()->withErrors($errors)->withInput();
}
if ((int) $request->is_active === 1 && !$this->isBobotValid($request->bobot)) {
return back()
->withErrors([
'bobot' => 'Jurusan tidak dapat dibuat aktif karena bobot kriteria belum lengkap atau total bobot tidak sama dengan 1.00.'
])
->withInput();
}
DB::transaction(function () use ($request, $jurusan, $kriterias) {
$jurusan->update([
'nama_jurusan' => $request->nama_jurusan,
'is_active' => $request->is_active,
]);
$updateData = [];
$existingMap = JurusanKriteria::where('jurusan_id', $jurusan->id)
->get()
->keyBy('kriteria_id');
foreach ($kriterias as $kriteria) {
$existing = $existingMap->get($kriteria->id);
$isAturanWajib = $this->isKriteriaWajibLolos($kriteria);
$updateData[] = [
'jurusan_id' => $jurusan->id,
'kriteria_id' => $kriteria->id,
'bobot' => (float) ($request->bobot[$kriteria->id] ?? 0),
'wajib_lolos' => $isAturanWajib
? $request->boolean("wajib_lolos.{$kriteria->id}")
: (bool) ($existing?->wajib_lolos ?? false),
'nilai_min' => $isAturanWajib
? $this->nilaiMinUntukKriteria($request, $kriteria)
: $existing?->nilai_min,
'nilai_max' => $isAturanWajib
? $this->nilaiMaxUntukKriteria($request, $kriteria)
: $existing?->nilai_max,
'created_at' => now(),
'updated_at' => now(),
];
}
if (!empty($updateData)) {
JurusanKriteria::upsert(
$updateData,
['jurusan_id', 'kriteria_id'],
['bobot', 'wajib_lolos', 'nilai_min', 'nilai_max', 'updated_at']
);
}
$jurusan->penyakit()->sync($request->input('penyakit_larangan', []));
});
ActivityLogger::log('Admin edit jurusan: ' . $jurusan->nama_jurusan);
return redirect()->route('admin.jurusan.index')
->with('success', "Jurusan {$jurusan->nama_jurusan} berhasil diperbarui!");
}
public function toggleStatus($id)
{
$jurusan = Jurusan::findOrFail($id);
// Jika jurusan sedang nonaktif dan akan diaktifkan,
// maka bobot harus valid terlebih dahulu.
if (!$jurusan->is_active) {
$bobot = JurusanKriteria::where('jurusan_id', $jurusan->id)
->pluck('bobot')
->toArray();
if (!$this->isBobotValid($bobot)) {
return redirect()->back()
->withErrors([
'error' => 'Jurusan tidak dapat diaktifkan karena bobot kriteria belum lengkap atau total bobot tidak sama dengan 1.00.'
]);
}
}
$jurusan->update([
'is_active' => !$jurusan->is_active
]);
$status = $jurusan->is_active ? 'diaktifkan' : 'dinonaktifkan';
ActivityLogger::log("Admin {$status} jurusan: {$jurusan->nama_jurusan}");
return redirect()->back()
->with('success', "Jurusan {$jurusan->nama_jurusan} berhasil {$status}.");
}
/**
* Validasi bobot:
* - semua bobot harus > 0
* - total bobot harus = 1.00
*/
private function isBobotValid(array $bobot): bool
{
if (empty($bobot)) {
return false;
}
foreach ($bobot as $nilai) {
if (!is_numeric($nilai) || (float) $nilai <= 0) {
return false;
}
}
$total = collect($bobot)->sum(fn($v) => (float) $v);
return abs($total - 1) <= 0.001;
}
private function isKriteriaWajibLolos(Kriteria $kriteria): bool
{
return in_array($kriteria->kode_kriteria, ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8'], true);
}
private function isKriteriaPenyakit(Kriteria $kriteria): bool
{
return $kriteria->kode_kriteria === 'C8';
}
private function nullableFloat($value): ?float
{
if ($value === null || $value === '') {
return null;
}
return (float) $value;
}
private function validateAturanWajibLolos(Request $request, $kriterias): array
{
$errors = [];
foreach ($kriterias as $kriteria) {
if (!$this->isKriteriaWajibLolos($kriteria)) {
continue;
}
$id = $kriteria->id;
$wajibLolos = $request->boolean("wajib_lolos.{$id}");
$nilaiMin = $this->nullableFloat($request->input("nilai_min.{$id}"));
$nilaiMax = $this->nullableFloat($request->input("nilai_max.{$id}"));
if ($this->isKriteriaPenyakit($kriteria)) {
if ($wajibLolos && empty($request->input('penyakit_larangan', []))) {
$errors['penyakit_larangan'] = 'Pilih minimal satu penyakit larangan untuk aturan wajib lolos C8.';
}
continue;
}
if ($kriteria->kode_kriteria === 'C6') {
continue;
}
if ($wajibLolos && $nilaiMin === null && $nilaiMax === null) {
$errors["nilai_min.{$id}"] = "Isi nilai minimum atau maksimum untuk aturan wajib lolos {$kriteria->kode_kriteria}.";
}
if ($nilaiMin !== null && $nilaiMax !== null && $nilaiMin > $nilaiMax) {
$errors["nilai_max.{$id}"] = "Nilai maksimum {$kriteria->kode_kriteria} harus lebih besar atau sama dengan nilai minimum.";
}
}
return $errors;
}
private function nilaiMinUntukKriteria(Request $request, Kriteria $kriteria): ?float
{
if ($this->isKriteriaPenyakit($kriteria)) {
return $request->boolean("wajib_lolos.{$kriteria->id}") ? 1 : null;
}
// Otomatisasi C6 (Buta Warna)
if ($kriteria->kode_kriteria === 'C6') {
return $request->boolean("wajib_lolos.{$kriteria->id}") ? 1 : null;
}
if ($this->isKriteriaWajibLolos($kriteria)) {
return $this->nullableFloat($request->input("nilai_min.{$kriteria->id}"));
}
return null;
}
private function nilaiMaxUntukKriteria(Request $request, Kriteria $kriteria): ?float
{
if ($this->isKriteriaPenyakit($kriteria)) {
return null;
}
if ($this->isKriteriaWajibLolos($kriteria)) {
return $this->nullableFloat($request->input("nilai_max.{$kriteria->id}"));
}
return null;
}
}