filled('search')) { $search = $request->search; $query->where(function($q) use ($search) { $q->where('nama_kelas', 'like', "%{$search}%") ->orWhere('kode_kelas', 'like', "%{$search}%"); }); } // Filter by kelompok kelas if ($request->filled('kelompok')) { $query->where('id_kelompok', $request->kelompok); } // Filter by status if ($request->filled('status')) { $isActive = $request->status === 'active'; $query->where('is_active', $isActive); } // Order by kelompok then urutan $kelas = $query->orderBy('id_kelompok', 'asc') ->orderBy('urutan', 'asc') ->paginate(15) ->appends(request()->query()); // Get kelompok kelas for filter dropdown $kelompokKelas = KelompokKelas::active()->ordered()->get(); return view('admin.kelas.index', compact('kelas', 'kelompokKelas')); } /** * Show the form for creating a new kelas. */ public function create() { // Get next kode_kelas $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); }); // Get kelompok kelas for dropdown $kelompokKelas = KelompokKelas::active()->ordered()->get(); return view('admin.kelas.create', compact('nextKodeKelas', 'kelompokKelas')); } /** * Store a newly created kelas in storage. */ 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', ], [ 'nama_kelas.required' => 'Nama kelas wajib diisi.', 'nama_kelas.unique' => 'Nama kelas sudah digunakan.', 'id_kelompok.required' => 'Kelompok kelas wajib dipilih.', 'id_kelompok.exists' => 'Kelompok kelas tidak valid.', 'urutan.required' => 'Urutan wajib diisi.', 'urutan.integer' => 'Urutan harus berupa angka.', 'urutan.min' => 'Urutan minimal 0.', ]); // Set is_active default to true if not provided $validated['is_active'] = $request->has('is_active') ? true : false; // Create kelas (kode_kelas will be auto-generated in model) Kelas::create($validated); // Clear cache Cache::forget('next_kelas_kode'); return redirect()->route('admin.kelas.index') ->with('success', 'Kelas berhasil ditambahkan.'); } /** * Display the specified kelas. */ public function show(Kelas $kela) { // Load relationships $kela->load(['kelompok', 'santriKelas.santri']); // Get santri count in this kelas for current academic year $tahunAjaranAktif = SantriKelas::getCurrentAcademicYear(); $santriCount = $kela->santriKelas() ->where('tahun_ajaran', $tahunAjaranAktif) ->whereHas('santri', function($q) { $q->where('status', 'Aktif'); }) ->count(); return view('admin.kelas.show', compact('kela', 'santriCount', 'tahunAjaranAktif')); } /** * Show the form for editing the specified kelas. */ public function edit(Kelas $kela) { // Get kelompok kelas for dropdown $kelompokKelas = KelompokKelas::active()->ordered()->get(); return view('admin.kelas.edit', compact('kela', 'kelompokKelas')); } /** * Update the specified kelas in storage. */ 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', ], [ 'nama_kelas.required' => 'Nama kelas wajib diisi.', 'nama_kelas.unique' => 'Nama kelas sudah digunakan.', 'id_kelompok.required' => 'Kelompok kelas wajib dipilih.', 'id_kelompok.exists' => 'Kelompok kelas tidak valid.', 'urutan.required' => 'Urutan wajib diisi.', 'urutan.integer' => 'Urutan harus berupa angka.', 'urutan.min' => 'Urutan minimal 0.', ]); // Set is_active $validated['is_active'] = $request->has('is_active') ? true : false; // Update kelas $kela->update($validated); return redirect()->route('admin.kelas.index') ->with('success', 'Kelas berhasil diperbarui.'); } /** * Remove the specified kelas from storage. */ public function destroy(Kelas $kela) { // Check if kelas is still being used $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."); } // Delete kelas $kela->delete(); return redirect()->route('admin.kelas.index') ->with('success', 'Kelas berhasil dihapus.'); } // ========================================== // SECTION 2: CRUD KELOMPOK KELAS // ========================================== /** * Display a listing of kelompok kelas. */ public function kelompokIndex(Request $request) { $query = KelompokKelas::withCount('kelas'); // Search by nama kelompok if ($request->filled('search')) { $search = $request->search; $query->where('nama_kelompok', 'like', "%{$search}%"); } // Filter by status if ($request->filled('status')) { $isActive = $request->status === 'active'; $query->where('is_active', $isActive); } // Order by urutan $kelompokKelas = $query->orderBy('urutan', 'asc') ->paginate(15) ->appends(request()->query()); return view('admin.kelas.kelompok.index', compact('kelompokKelas')); } /** * Show the form for creating a new kelompok kelas. */ public function kelompokCreate() { // Get next id_kelompok $nextIdKelompok = Cache::remember('next_kelompok_id', 60, function () { $lastKelompok = KelompokKelas::orderBy('id', 'desc')->first(); $nextNum = $lastKelompok ? intval(substr($lastKelompok->id_kelompok, 3)) + 1 : 1; return 'KEL' . str_pad($nextNum, 3, '0', STR_PAD_LEFT); }); return view('admin.kelas.kelompok.create', compact('nextIdKelompok')); } /** * Store a newly created kelompok kelas in storage. */ 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', ], [ 'nama_kelompok.required' => 'Nama kelompok wajib diisi.', 'nama_kelompok.unique' => 'Nama kelompok sudah digunakan.', 'urutan.required' => 'Urutan wajib diisi.', 'urutan.integer' => 'Urutan harus berupa angka.', 'urutan.min' => 'Urutan minimal 0.', 'deskripsi.max' => 'Deskripsi maksimal 500 karakter.', ]); // Set is_active default to true if not provided $validated['is_active'] = $request->has('is_active') ? true : false; // Create kelompok (id_kelompok will be auto-generated in model) KelompokKelas::create($validated); // Clear cache Cache::forget('next_kelompok_id'); return redirect()->route('admin.kelas.kelompok.index') ->with('success', 'Kelompok kelas berhasil ditambahkan.'); } /** * Show the form for editing the specified kelompok kelas. */ public function kelompokEdit($id) { $kelompok = KelompokKelas::findOrFail($id); $kelompok->loadCount('kelas'); return view('admin.kelas.kelompok.edit', compact('kelompok')); } /** * Update the specified kelompok kelas in storage. */ 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', ], [ 'nama_kelompok.required' => 'Nama kelompok wajib diisi.', 'nama_kelompok.unique' => 'Nama kelompok sudah digunakan.', 'urutan.required' => 'Urutan wajib diisi.', 'urutan.integer' => 'Urutan harus berupa angka.', 'urutan.min' => 'Urutan minimal 0.', 'deskripsi.max' => 'Deskripsi maksimal 500 karakter.', ]); // Set is_active $validated['is_active'] = $request->has('is_active') ? true : false; // Update kelompok $kelompok->update($validated); return redirect()->route('admin.kelas.kelompok.index') ->with('success', 'Kelompok kelas berhasil diperbarui.'); } /** * Remove the specified kelompok kelas from storage. */ public function kelompokDestroy($id) { $kelompok = KelompokKelas::findOrFail($id); // Check if kelompok still has kelas $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."); } // Delete kelompok $kelompok->delete(); return redirect()->route('admin.kelas.kelompok.index') ->with('success', 'Kelompok kelas berhasil dihapus.'); } // ========================================== // SECTION 3: KENAIKAN KELAS MASSAL // ========================================== /** * Display kenaikan kelas index page */ public function kenaikanIndex(Request $request) { $tahunAjaranAktif = SantriKelas::getCurrentAcademicYear(); $tahunAjaranBaru = $this->getNextAcademicYear($tahunAjaranAktif); // Get total santri aktif $totalSantriAktif = Santri::where('status', 'Aktif')->count(); // Get all kelompok kelas for dropdown $kelompokKelas = KelompokKelas::with(['kelas' => function($q) { $q->where('is_active', true)->orderBy('urutan'); }]) ->active() ->ordered() ->get(); // Determine selected kelompok (default: first kelompok) $selectedKelompok = $request->get('kelompok'); if (!$selectedKelompok && $kelompokKelas->isNotEmpty()) { $selectedKelompok = $kelompokKelas->first()->id_kelompok; } // Get kelas list for selected kelompok only $kelasList = Kelas::with('kelompok') ->where('is_active', true) ->when($selectedKelompok, function($q) use ($selectedKelompok) { $q->where('id_kelompok', $selectedKelompok); }) ->withCount(['santriKelas as santri_aktif_count' => function($q) use ($tahunAjaranAktif) { $q->where('tahun_ajaran', $tahunAjaranAktif) ->whereHas('santri', function($q2) { $q2->where('status', 'Aktif'); }); }]) ->orderBy('urutan', 'asc') ->get(); // Get all kelas for dropdown selection (bisa naik ke kelas manapun) $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' )); } /** * Preview santri in a class before kenaikan */ public function kenaikanPreview($id) { $kelas = Kelas::with('kelompok')->findOrFail($id); $tahunAjaranAktif = SantriKelas::getCurrentAcademicYear(); $tahunAjaranBaru = $this->getNextAcademicYear($tahunAjaranAktif); // Get santri in this class (tahun ajaran aktif, status aktif) $santriList = Santri::whereHas('kelasSantri', function($q) use ($id, $tahunAjaranAktif) { $q->where('id_kelas', $id) ->where('tahun_ajaran', $tahunAjaranAktif); }) ->where('status', 'Aktif') ->orderBy('nama_lengkap') ->get(); // Get all kelompok with kelas for dropdown $kelasOptions = KelompokKelas::with(['kelas' => function($q) { $q->where('is_active', true)->orderBy('urutan'); }]) ->active() ->ordered() ->get(); return view('admin.kelas.kenaikan.preview', compact( 'kelas', 'santriList', 'tahunAjaranAktif', 'tahunAjaranBaru', 'kelasOptions' )); } /** * Process kenaikan kelas for all santri in a class */ public function kenaikanProcess(Request $request) { $request->validate([ 'id_kelas_asal' => 'required|exists:kelas,id', 'id_kelas_tujuan' => 'required|exists:kelas,id', ]); $kelasAsal = Kelas::findOrFail($request->id_kelas_asal); $kelasTujuan = Kelas::findOrFail($request->id_kelas_tujuan); $tahunAjaranAktif = SantriKelas::getCurrentAcademicYear(); // Get all santri aktif in kelas asal $santriIds = Santri::whereHas('kelasSantri', function($q) use ($request, $tahunAjaranAktif) { $q->where('id_kelas', $request->id_kelas_asal) ->where('tahun_ajaran', $tahunAjaranAktif); }) ->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) { // Cari record santri_kelas yg ada di kelas asal $record = SantriKelas::where('id_santri', $idSantri) ->where('id_kelas', $kelasAsal->id) ->where('tahun_ajaran', $tahunAjaranAktif) ->first(); if ($record) { // Update record: ganti kelas saja, tahun_ajaran & is_primary TETAP $record->update([ 'id_kelas' => $kelasTujuan->id, ]); $processed++; } } DB::commit(); return redirect()->route('admin.kelas.kenaikan.index') ->with('success', "Berhasil menaikkan {$processed} santri dari kelas {$kelasAsal->nama_kelas} ke {$kelasTujuan->nama_kelas}."); } catch (\Exception $e) { DB::rollBack(); return redirect()->route('admin.kelas.kenaikan.index') ->with('error', 'Terjadi kesalahan saat memproses kenaikan kelas: ' . $e->getMessage()); } } /** * Process kenaikan kelas for selected santri only */ public function kenaikanProcessSelected(Request $request) { $request->validate([ 'id_kelas_asal' => 'required|exists:kelas,id', 'id_kelas_tujuan' => 'required|exists:kelas,id', '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.', ]); $kelasAsal = Kelas::findOrFail($request->id_kelas_asal); $kelasTujuan = Kelas::findOrFail($request->id_kelas_tujuan); $tahunAjaranAktif = SantriKelas::getCurrentAcademicYear(); $processed = 0; DB::beginTransaction(); try { foreach ($request->santri_ids as $idSantri) { // Cari record santri_kelas yg ada di kelas asal $record = SantriKelas::where('id_santri', $idSantri) ->where('id_kelas', $kelasAsal->id) ->where('tahun_ajaran', $tahunAjaranAktif) ->first(); if ($record) { // Update record: ganti kelas saja, tahun_ajaran & is_primary TETAP $record->update([ 'id_kelas' => $kelasTujuan->id, ]); $processed++; } } DB::commit(); return redirect()->route('admin.kelas.kenaikan.index') ->with('success', "Berhasil menaikkan {$processed} santri dari kelas {$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 saat memproses kenaikan kelas: ' . $e->getMessage()); } } /** * Helper: Get next academic year * Input: 2024/2025 * Output: 2025/2026 */ private function getNextAcademicYear($currentYear) { $parts = explode('/', $currentYear); $startYear = (int) $parts[0] + 1; $endYear = (int) $parts[1] + 1; return $startYear . '/' . $endYear; } }