Files
database-kesiswaan/app/Http/Controllers/SchoolClassController.php
T

266 lines
10 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\AcademicYear;
use App\Models\Department;
use App\Models\SchoolClass;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use RealRashid\SweetAlert\Facades\Alert;
class SchoolClassController extends Controller
{
public function index(Request $request): View
{
$search = trim((string) $request->get('search'));
$classes = SchoolClass::with(['academicYear', 'department'])
->withCount(['students', 'academicHistories'])
->when($search !== '', function ($query) use ($search) {
$query->where('name', 'like', "%{$search}%")
->orWhere('level', 'like', "%{$search}%")
->orWhere('homeroom_teacher', 'like', "%{$search}%");
})
->latest()
->paginate(10)
->withQueryString();
return view('classes.index', [
'classes' => $classes,
'search' => $search,
'academicYears' => AcademicYear::orderByDesc('start_year')->orderByDesc('end_year')->get(),
]);
}
public function create(): View
{
return view('classes.create', [
'academicYears' => AcademicYear::where('is_active', true)->orderBy('name')->pluck('name', 'id'),
'departments' => Department::where('is_active', true)->orderBy('name')->pluck('name', 'id'),
]);
}
public function store(Request $request): RedirectResponse
{
$data = $request->validate([
'academic_year_id' => ['required', Rule::exists('academic_years', 'id')->where(fn ($query) => $query->where('is_active', true))],
'department_id' => ['required', Rule::exists('departments', 'id')->where(fn ($query) => $query->where('is_active', true))],
'name' => ['required', 'string', 'max:100', Rule::unique('classes', 'name')->where(fn ($query) => $query->where('academic_year_id', $request->academic_year_id))],
'level' => ['required', 'string', 'max:10'],
'homeroom_teacher' => ['nullable', 'string', 'max:100'],
'capacity' => ['nullable', 'integer', 'min:1'],
'is_active' => ['nullable', 'boolean'],
]);
$data['is_active'] = $request->boolean('is_active');
SchoolClass::create($data);
return redirect()->route('classes.index')->with('success', 'Kelas berhasil ditambahkan.');
}
public function generate(Request $request): RedirectResponse
{
$data = $request->validate([
'source_academic_year_id' => ['required', 'exists:academic_years,id', 'different:target_academic_year_id'],
'target_academic_year_id' => ['required', 'exists:academic_years,id'],
'generation_mode' => ['required', Rule::in(['same_level', 'promote_level'])],
]);
$sourceClasses = SchoolClass::query()
->where('academic_year_id', $data['source_academic_year_id'])
->orderBy('department_id')
->orderBy('level')
->orderBy('name')
->get();
if ($sourceClasses->isEmpty()) {
Alert::error('Gagal', 'Tidak ada kelas pada tahun ajaran sumber yang bisa digenerate.');
return redirect()->route('classes.index')->withErrors([
'generate' => 'Tidak ada kelas pada tahun ajaran sumber yang bisa digenerate.',
]);
}
$created = 0;
$skipped = 0;
DB::transaction(function () use ($sourceClasses, $data, &$created, &$skipped) {
foreach ($sourceClasses as $sourceClass) {
$targetLevel = $data['generation_mode'] === 'promote_level'
? $this->nextLevel($sourceClass->level)
: $sourceClass->level;
if ($targetLevel === null) {
$skipped++;
continue;
}
$targetName = $this->transformClassName($sourceClass->name, $sourceClass->level, $targetLevel);
$exists = SchoolClass::query()
->where('academic_year_id', $data['target_academic_year_id'])
->where('name', $targetName)
->exists();
if ($exists) {
$skipped++;
continue;
}
SchoolClass::create([
'academic_year_id' => $data['target_academic_year_id'],
'department_id' => $sourceClass->department_id,
'name' => $targetName,
'level' => $targetLevel,
'homeroom_teacher' => $sourceClass->homeroom_teacher,
'capacity' => $sourceClass->capacity,
'is_active' => true,
]);
$created++;
}
});
Alert::success('Berhasil', "Generate kelas selesai. {$created} kelas dibuat, {$skipped} dilewati.");
return redirect()->route('classes.index')->with('success', "Generate kelas selesai. {$created} kelas dibuat, {$skipped} dilewati.");
}
public function show(SchoolClass $class): RedirectResponse
{
return redirect()->route('classes.edit', $class);
}
public function edit(SchoolClass $class): View
{
$class->loadMissing(['academicYear', 'department']);
$academicYears = AcademicYear::query()
->where(function ($query) use ($class) {
$query->where('is_active', true)
->orWhere('id', $class->academic_year_id);
})
->orderBy('name')
->pluck('name', 'id');
$departments = Department::query()
->where(function ($query) use ($class) {
$query->where('is_active', true)
->orWhere('id', $class->department_id);
})
->orderBy('name')
->pluck('name', 'id');
return view('classes.edit', [
'class' => $class,
'academicYears' => $academicYears,
'departments' => $departments,
]);
}
public function update(Request $request, SchoolClass $class): RedirectResponse
{
$data = $request->validate([
'academic_year_id' => ['required', Rule::exists('academic_years', 'id')->where(fn ($query) => $query->where('is_active', true)->orWhere('id', $class->academic_year_id))],
'department_id' => ['required', Rule::exists('departments', 'id')->where(fn ($query) => $query->where('is_active', true)->orWhere('id', $class->department_id))],
'name' => ['required', 'string', 'max:100', Rule::unique('classes', 'name')->ignore($class->id)->where(fn ($query) => $query->where('academic_year_id', $request->academic_year_id))],
'level' => ['required', 'string', 'max:10'],
'homeroom_teacher' => ['nullable', 'string', 'max:100'],
'capacity' => ['nullable', 'integer', 'min:1'],
'is_active' => ['nullable', 'boolean'],
]);
$data['is_active'] = $request->boolean('is_active');
$class->update($data);
return redirect()->route('classes.index')->with('success', 'Kelas berhasil diperbarui.');
}
public function destroy(SchoolClass $class): RedirectResponse
{
$class->loadCount(['students', 'academicHistories']);
if ($class->students_count > 0) {
Alert::error('Tidak bisa dihapus', 'Kelas ini masih digunakan oleh data siswa aktif.');
return redirect()->route('classes.index')->withErrors([
'delete' => 'Kelas tidak bisa dihapus karena masih digunakan oleh data siswa aktif.',
]);
}
if ($class->academic_histories_count > 0) {
Alert::error('Tidak bisa dihapus', 'Kelas ini masih tercatat pada histori akademik siswa.');
return redirect()->route('classes.index')->withErrors([
'delete' => 'Kelas tidak bisa dihapus karena masih tercatat pada histori akademik siswa. Hapus atau ubah histori terkait terlebih dahulu.',
]);
}
Alert::success('Berhasil', 'Kelas berhasil dihapus.');
$class->delete();
return redirect()->route('classes.index')->with('success', 'Kelas berhasil dihapus.');
}
public function bulkDestroy(Request $request): RedirectResponse
{
$validated = $request->validate([
'class_ids' => ['required', 'array', 'min:1'],
'class_ids.*' => ['integer', 'exists:classes,id'],
], [
'class_ids.required' => 'Pilih minimal satu kelas untuk dihapus.',
]);
$classes = SchoolClass::withCount(['students', 'academicHistories'])
->whereIn('id', $validated['class_ids'])
->get();
$blocked = $classes->filter(fn (SchoolClass $class) => $class->students_count > 0 || $class->academic_histories_count > 0);
if ($blocked->isNotEmpty()) {
Alert::error('Tidak bisa dihapus', 'Sebagian kelas masih dipakai oleh siswa aktif atau histori akademik.');
return redirect()->route('classes.index')->withErrors([
'delete' => 'Beberapa kelas tidak bisa dihapus karena masih dipakai oleh data siswa atau histori akademik.',
]);
}
$deletedCount = 0;
foreach ($classes as $class) {
$class->delete();
$deletedCount++;
}
Alert::success('Berhasil', $deletedCount . ' kelas berhasil dihapus.');
return redirect()->route('classes.index')->with('success', $deletedCount . ' kelas berhasil dihapus.');
}
protected function nextLevel(?string $level): ?string
{
return match (strtoupper((string) $level)) {
'X' => 'XI',
'XI' => 'XII',
'XII' => null,
default => $level,
};
}
protected function transformClassName(string $name, ?string $fromLevel, ?string $toLevel): string
{
if (! $fromLevel || ! $toLevel || strtoupper($fromLevel) === strtoupper($toLevel)) {
return $name;
}
return preg_replace('/^' . preg_quote($fromLevel, '/') . '\b/i', $toLevel, $name) ?: $name;
}
}