386 lines
13 KiB
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;
|
|
}
|
|
}
|