filled('search')) { $search = $request->search; $query->where(function ($q) use ($search) { $q->where('nama_kelas', 'like', "%{$search}%") ->orWhere('kode_kelas', 'like', "%{$search}%"); }); } if ($request->filled('kelompok')) { $query->where('id_kelompok', $request->kelompok); } if ($request->filled('status')) { $query->where('is_active', $request->status === 'active'); } $kelas = $query->orderBy('id_kelompok', 'asc') ->orderBy('urutan', 'asc') ->paginate(15) ->appends(request()->query()); $kelompokKelas = KelompokKelas::active()->ordered()->get(); return view('admin.kelas.index', compact('kelas', 'kelompokKelas')); } public function create() { $nextKodeKelas = Cache::remember('next_kelas_kode', 60, function () { $lastKelas = Kelas::orderBy('id', 'desc')->first(); $nextNum = $lastKelas ? intval(substr($lastKelas->kode_kelas, 3)) + 1 : 1; return 'KLS' . str_pad($nextNum, 3, '0', STR_PAD_LEFT); }); $kelompokKelas = KelompokKelas::active()->ordered()->get(); return view('admin.kelas.create', compact('nextKodeKelas', 'kelompokKelas')); } public function store(Request $request) { $validated = $request->validate([ 'nama_kelas' => 'required|string|max:100|unique:kelas,nama_kelas', 'id_kelompok' => 'required|string|exists:kelompok_kelas,id_kelompok', 'urutan' => 'required|integer|min:0', 'is_active' => 'boolean', ]); $validated['is_active'] = $request->has('is_active'); Kelas::create($validated); Cache::forget('next_kelas_kode'); return redirect()->route('admin.kelas.index') ->with('success', 'Kelas berhasil ditambahkan.'); } public function show(Kelas $kela) { $kela->load(['kelompok', 'santriKelas.santri']); $santriCount = $kela->santriKelas() ->whereHas('santri', fn($q) => $q->where('status', 'Aktif')) ->count(); return view('admin.kelas.show', compact('kela', 'santriCount')); } public function edit(Kelas $kela) { $kelompokKelas = KelompokKelas::active()->ordered()->get(); return view('admin.kelas.edit', compact('kela', 'kelompokKelas')); } public function update(Request $request, Kelas $kela) { $validated = $request->validate([ 'nama_kelas' => 'required|string|max:100|unique:kelas,nama_kelas,' . $kela->id, 'id_kelompok' => 'required|string|exists:kelompok_kelas,id_kelompok', 'urutan' => 'required|integer|min:0', 'is_active' => 'boolean', ]); $validated['is_active'] = $request->has('is_active'); $kela->update($validated); return redirect()->route('admin.kelas.index') ->with('success', 'Kelas berhasil diperbarui.'); } public function destroy(Kelas $kela) { $santriCount = $kela->santriKelas()->count(); $kegiatanCount = $kela->kegiatans()->count(); if ($santriCount > 0) { return redirect()->route('admin.kelas.index') ->with('error', "Kelas tidak dapat dihapus karena masih digunakan oleh {$santriCount} santri."); } if ($kegiatanCount > 0) { return redirect()->route('admin.kelas.index') ->with('error', "Kelas tidak dapat dihapus karena masih memiliki {$kegiatanCount} kegiatan."); } $kela->delete(); return redirect()->route('admin.kelas.index') ->with('success', 'Kelas berhasil dihapus.'); } // ========================================== // SECTION 2: CRUD KELOMPOK KELAS // ========================================== public function kelompokIndex(Request $request) { $query = KelompokKelas::withCount('kelas'); if ($request->filled('search')) { $query->where('nama_kelompok', 'like', '%' . $request->search . '%'); } if ($request->filled('status')) { $query->where('is_active', $request->status === 'active'); } $kelompokKelas = $query->orderBy('urutan', 'asc') ->paginate(15) ->appends(request()->query()); return view('admin.kelas.kelompok.index', compact('kelompokKelas')); } public function kelompokCreate() { $nextIdKelompok = Cache::remember('next_kelompok_id', 60, function () { $last = KelompokKelas::orderBy('id', 'desc')->first(); $nextNum = $last ? intval(substr($last->id_kelompok, 3)) + 1 : 1; return 'KEL' . str_pad($nextNum, 3, '0', STR_PAD_LEFT); }); return view('admin.kelas.kelompok.create', compact('nextIdKelompok')); } public function kelompokStore(Request $request) { $validated = $request->validate([ 'nama_kelompok' => 'required|string|max:100|unique:kelompok_kelas,nama_kelompok', 'deskripsi' => 'nullable|string|max:500', 'urutan' => 'required|integer|min:0', 'is_active' => 'boolean', ]); $validated['is_active'] = $request->has('is_active'); KelompokKelas::create($validated); Cache::forget('next_kelompok_id'); return redirect()->route('admin.kelas.kelompok.index') ->with('success', 'Kelompok kelas berhasil ditambahkan.'); } public function kelompokEdit($id) { $kelompok = KelompokKelas::findOrFail($id); $kelompok->loadCount('kelas'); return view('admin.kelas.kelompok.edit', compact('kelompok')); } public function kelompokUpdate(Request $request, $id) { $kelompok = KelompokKelas::findOrFail($id); $validated = $request->validate([ 'nama_kelompok' => 'required|string|max:100|unique:kelompok_kelas,nama_kelompok,' . $kelompok->id, 'deskripsi' => 'nullable|string|max:500', 'urutan' => 'required|integer|min:0', 'is_active' => 'boolean', ]); $validated['is_active'] = $request->has('is_active'); $kelompok->update($validated); return redirect()->route('admin.kelas.kelompok.index') ->with('success', 'Kelompok kelas berhasil diperbarui.'); } public function kelompokDestroy($id) { $kelompok = KelompokKelas::findOrFail($id); $kelasCount = $kelompok->kelas()->count(); if ($kelasCount > 0) { return redirect()->route('admin.kelas.kelompok.index') ->with('error', "Kelompok tidak dapat dihapus karena masih memiliki {$kelasCount} kelas."); } $kelompok->delete(); return redirect()->route('admin.kelas.kelompok.index') ->with('success', 'Kelompok kelas berhasil dihapus.'); } // ========================================== // SECTION 3: KENAIKAN KELAS MASSAL // ========================================== public function kenaikanIndex(Request $request) { $tahunAjaranAktif = $this->getActiveTahunAjaran(); $tahunAjaranBaru = $this->getNextAcademicYear($tahunAjaranAktif); $totalSantriAktif = Santri::where('status', 'Aktif')->count(); $kelompokKelas = KelompokKelas::with([ 'kelas' => fn($q) => $q->where('is_active', true)->orderBy('urutan'), ])->active()->ordered()->get(); $selectedKelompok = $request->get('kelompok'); if (!$selectedKelompok && $kelompokKelas->isNotEmpty()) { $selectedKelompok = $kelompokKelas->first()->id_kelompok; } $kelasList = Kelas::with('kelompok') ->where('is_active', true) ->when($selectedKelompok, fn($q) => $q->where('id_kelompok', $selectedKelompok)) ->withCount([ 'santriKelas as santri_aktif_count' => fn($q) => $q->whereHas('santri', fn($s) => $s->where('status', 'Aktif')), ]) ->orderBy('urutan', 'asc') ->get(); $allKelasList = Kelas::with('kelompok') ->where('is_active', true) ->orderBy('id_kelompok', 'asc') ->orderBy('urutan', 'asc') ->get(); return view('admin.kelas.kenaikan.index', compact( 'tahunAjaranAktif', 'tahunAjaranBaru', 'totalSantriAktif', 'kelompokKelas', 'kelasList', 'allKelasList', 'selectedKelompok' )); } public function kenaikanPreview($id) { $kelas = Kelas::with('kelompok')->findOrFail($id); $tahunAjaranAktif = $this->getActiveTahunAjaran(); $tahunAjaranBaru = $this->getNextAcademicYear($tahunAjaranAktif); $santriList = Santri::whereHas('kelasSantri', fn($q) => $q->where('id_kelas', $id)) ->where('status', 'Aktif') ->orderBy('nama_lengkap') ->get(); $kelasOptions = KelompokKelas::with([ 'kelas' => fn($q) => $q->where('is_active', true)->orderBy('urutan'), ])->active()->ordered()->get(); return view('admin.kelas.kenaikan.preview', compact( 'kelas', 'santriList', 'tahunAjaranAktif', 'tahunAjaranBaru', 'kelasOptions' )); } public function kenaikanProcess(Request $request) { $request->validate([ 'id_kelas_asal' => 'required|exists:kelas,id', 'id_kelas_tujuan' => 'required|exists:kelas,id|different:id_kelas_asal', ], [ 'id_kelas_tujuan.different' => 'Kelas tujuan tidak boleh sama dengan kelas asal.', ]); $kelasAsal = Kelas::findOrFail($request->id_kelas_asal); $kelasTujuan = Kelas::findOrFail($request->id_kelas_tujuan); // Ambil semua id kelas yang satu kelompok dengan kelas asal $kelasSeKelompok = Kelas::where('id_kelompok', $kelasAsal->id_kelompok) ->pluck('id'); $santriIds = Santri::whereHas('kelasSantri', fn($q) => $q->where('id_kelas', $request->id_kelas_asal)) ->where('status', 'Aktif') ->pluck('id_santri'); if ($santriIds->isEmpty()) { return redirect()->route('admin.kelas.kenaikan.index') ->with('error', 'Tidak ada santri aktif di kelas ' . $kelasAsal->nama_kelas . '.'); } $processed = 0; DB::beginTransaction(); try { foreach ($santriIds as $idSantri) { // Ambil record kelas asal $recordAsal = SantriKelas::where('id_santri', $idSantri) ->where('id_kelas', $kelasAsal->id) ->orderBy('tahun_ajaran', 'desc') ->first(); if (!$recordAsal) continue; // Hapus record lain milik santri ini yang satu kelompok dengan kelas asal // (kecuali record asal itu sendiri, karena akan kita update) SantriKelas::where('id_santri', $idSantri) ->whereIn('id_kelas', $kelasSeKelompok) ->where('id', '!=', $recordAsal->id) ->delete(); // Pindahkan record kelas asal ke kelas tujuan $recordAsal->update(['id_kelas' => $kelasTujuan->id]); $processed++; } DB::commit(); return redirect()->route('admin.kelas.kenaikan.index') ->with('success', "Berhasil menaikkan {$processed} santri dari {$kelasAsal->nama_kelas} ke {$kelasTujuan->nama_kelas}."); } catch (\Exception $e) { DB::rollBack(); return redirect()->route('admin.kelas.kenaikan.index') ->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); } } public function kenaikanProcessSelected(Request $request) { $request->validate([ 'id_kelas_asal' => 'required|exists:kelas,id', 'id_kelas_tujuan' => 'required|exists:kelas,id|different:id_kelas_asal', 'santri_ids' => 'required|array|min:1', 'santri_ids.*' => 'exists:santris,id_santri', ], [ 'santri_ids.required' => 'Pilih minimal 1 santri untuk dinaikkan kelasnya.', 'santri_ids.min' => 'Pilih minimal 1 santri untuk dinaikkan kelasnya.', 'id_kelas_tujuan.different' => 'Kelas tujuan tidak boleh sama dengan kelas asal.', ]); $kelasAsal = Kelas::findOrFail($request->id_kelas_asal); $kelasTujuan = Kelas::findOrFail($request->id_kelas_tujuan); // Ambil semua id kelas yang satu kelompok dengan kelas asal $kelasSeKelompok = Kelas::where('id_kelompok', $kelasAsal->id_kelompok) ->pluck('id'); $processed = 0; DB::beginTransaction(); try { foreach ($request->santri_ids as $idSantri) { // Ambil record kelas asal $recordAsal = SantriKelas::where('id_santri', $idSantri) ->where('id_kelas', $kelasAsal->id) ->orderBy('tahun_ajaran', 'desc') ->first(); if (!$recordAsal) continue; // Hapus record lain milik santri ini yang satu kelompok dengan kelas asal // (kecuali record asal itu sendiri, karena akan kita update) SantriKelas::where('id_santri', $idSantri) ->whereIn('id_kelas', $kelasSeKelompok) ->where('id', '!=', $recordAsal->id) ->delete(); // Pindahkan record kelas asal ke kelas tujuan $recordAsal->update(['id_kelas' => $kelasTujuan->id]); $processed++; } DB::commit(); return redirect()->route('admin.kelas.kenaikan.index') ->with('success', "Berhasil menaikkan {$processed} santri dari {$kelasAsal->nama_kelas} ke {$kelasTujuan->nama_kelas}."); } catch (\Exception $e) { DB::rollBack(); return redirect()->route('admin.kelas.kenaikan.preview', $request->id_kelas_asal) ->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); } } /** * Helper: tahun ajaran aktif berdasarkan data yang ada di santri_kelas. * Menggunakan tahun ajaran terbaru yang punya record, fallback ke kalkulasi. */ private function getActiveTahunAjaran(): string { return SantriKelas::max('tahun_ajaran') ?? SantriKelas::getCurrentAcademicYear(); } /** * Helper: hitung tahun ajaran berikutnya * Contoh: "2024/2025" -> "2025/2026" */ private function getNextAcademicYear(string $currentYear): string { $parts = explode('/', $currentYear); return ((int) $parts[0] + 1) . '/' . ((int) $parts[1] + 1); } }