violationService = $violationService; } public function index(Request $request): View { return view('attendances.index'); } public function create(Request $request): View { $date = $request->input('date', now()->toDateString()); $className = $request->input('class_name'); $students = collect(); if ($className) { $students = Student::where('is_active', true) ->where('class_name', $className) ->orderBy('full_name') ->get(); } // Load existing attendances for this class + date $existing = collect(); if ($students->isNotEmpty()) { $existing = Attendance::where('date', $date) ->whereIn('student_id', $students->pluck('id')) ->get() ->keyBy(fn($a) => $a->student_id . '-' . $a->lesson_hour); } $classNames = Student::where('is_active', true) ->whereNotNull('class_name') ->distinct() ->orderBy('class_name') ->pluck('class_name'); $lessonHours = range(1, 10); return view('attendances.create', compact( 'students', 'date', 'className', 'classNames', 'lessonHours', 'existing' )); } public function store(Request $request): RedirectResponse { $request->validate([ 'date' => ['required', 'date'], 'class_name' => ['nullable', 'string'], 'attendances' => ['required', 'array'], 'auto_violation' => ['nullable', 'boolean'], ]); $date = $request->input('date'); $class_name = $request->input('class_name'); $saved = 0; $autoViolations = 0; $alphaIds = []; $terlambatIds = []; foreach ($request->input('attendances') as $studentId => $studentData) { foreach ($studentData as $hour => $data) { if (!isset($data['student_id'], $data['lesson_hour'], $data['status'])) { continue; } $data['lesson_hour'] = (int) $data['lesson_hour']; Attendance::updateOrCreate( [ 'student_id' => $data['student_id'], 'date' => $date, 'lesson_hour' => $data['lesson_hour'], ], [ 'status' => $data['status'], 'recorded_by' => $request->user()->id, ] ); $saved++; if ($data['status'] === 'alpha') { $alphaIds[$data['student_id']] = ($alphaIds[$data['student_id']] ?? 0) + 1; } elseif ($data['status'] === 'terlambat') { $terlambatIds[$data['student_id']] = ($terlambatIds[$data['student_id']] ?? 0) + 1; } } } // Auto-generate violation untuk alpha & terlambat if ($request->boolean('auto_violation') && (!empty($alphaIds) || !empty($terlambatIds))) { $alphaType = ViolationType::where('slug', 'alpha')->first(); $terlambatType = ViolationType::where('slug', 'terlambat')->first(); foreach ($alphaIds as $studentId => $count) { if ($alphaType) { $this->violationService->recordViolation([ 'student_id' => $studentId, 'violation_type_id' => $alphaType->id, 'violation_date' => $date, 'points' => max(1, (int) round(($alphaType->points / 10) * $count)), 'description' => "Alpha - Tidak hadir tanpa keterangan ({$count} jam pelajaran)", 'notes' => 'Dibuat otomatis dari presensi.', 'evidences' => [], ], $request->user()->id); $autoViolations++; } } foreach ($terlambatIds as $studentId => $count) { if ($terlambatType) { $this->violationService->recordViolation([ 'student_id' => $studentId, 'violation_type_id' => $terlambatType->id, 'violation_date' => $date, 'points' => $terlambatType->points, 'description' => "Terlambat ({$count} jam pelajaran)", 'notes' => 'Dibuat otomatis dari presensi.', 'evidences' => [], ], $request->user()->id); $autoViolations++; } } if (!$alphaType) { $msgExtra = 'Jenis pelanggaran "Alpha" tidak ditemukan. Buat jenis pelanggaran dengan slug "alpha".'; } if (!$terlambatType) { $msgExtra = (empty($msgExtra) ? '' : $msgExtra . ' ') . 'Jenis pelanggaran "Terlambat" tidak ditemukan. Buat dengan slug "terlambat".'; } } $studentCount = count($request->input('attendances')); $lessonHourCount = $saved / max($studentCount, 1); $msg = "Presensi berhasil disimpan — {$studentCount} siswa × " . round($lessonHourCount) . " jam pelajaran."; if ($autoViolations > 0) { $msg .= " {$autoViolations} pelanggaran dibuat otomatis (alpha/terlambat)."; } elseif (!empty($msgExtra)) { $msg .= " Peringatan: {$msgExtra}"; } return redirect()->route('attendances.create', [ 'date' => $date, 'class_name' => $class_name, ])->with('success', $msg); } public function recap(Request $request): View { $month = $request->input('month', now()->month); $year = $request->input('year', now()->year); $className = $request->input('class_name'); $query = Student::where('is_active', true); if ($className) { $query->where('class_name', $className); } $students = $query->orderBy('class_name')->orderBy('full_name')->get(); $recap = []; foreach ($students as $student) { $attendances = Attendance::where('student_id', $student->id) ->whereMonth('date', $month) ->whereYear('date', $year) ->get() ->groupBy('status') ->map(fn($g) => $g->count()); $recap[] = (object) [ 'student' => $student, 'hadir' => $attendances->get('hadir', 0), 'sakit' => $attendances->get('sakit', 0), 'izin' => $attendances->get('izin', 0), 'alpha' => $attendances->get('alpha', 0), 'terlambat' => $attendances->get('terlambat', 0), 'total' => $attendances->sum(), ]; } $classNames = Student::where('is_active', true) ->whereNotNull('class_name') ->distinct() ->orderBy('class_name') ->pluck('class_name'); return view('attendances.recap', compact('recap', 'month', 'year', 'className', 'classNames')); } }