$schoolYear, 'semester_param' => $semester, 'class_students' => 0, 'student_ids' => 0, 'attendance_rows' => 0, 'filtered_rows' => 0, 'violations' => 0, 'active_weeks' => 0, 'abs_last5' => 0, 'late_last5' => 0, ]; $resolved = $this->studentResolverService->resolveForSchoolYear($schoolYear, $semester); $schoolYear = (string) ($resolved['school_year'] ?? $schoolYear); $semester = $resolved['semester'] ?? $semester; $studentIds = $resolved['student_ids'] ?? []; $studentCodes = $resolved['student_codes'] ?? []; $students = $resolved['students'] ?? []; $studentCodeToId = $resolved['student_code_to_id'] ?? []; $existingIds = $resolved['existing_ids'] ?? []; $resolverDebug = $resolved['debug'] ?? []; $debugInfo['class_students'] = $resolverDebug['class_students'] ?? 0; $debugInfo['student_ids'] = $resolverDebug['student_ids'] ?? 0; if (empty($studentIds) && empty($studentCodes)) { return [ 'students' => [], 'school_year' => $schoolYear, 'semester' => $semester, 'error' => 'No student identifiers found for the current school year.', 'debug' => $debugInfo, ]; } if (empty($students)) { return [ 'students' => [], 'school_year' => $schoolYear, 'semester' => $semester, 'error' => 'No students found matching attendance records.', 'debug' => $debugInfo, ]; } $today = Carbon::today(); [$syStart, $syEnd] = $this->violationRuleEngine->deriveSchoolYearBounds($schoolYear); $start = Carbon::parse($syStart)->startOfDay(); $rangeEnd = Carbon::parse(min($syEnd, $today->format('Y-m-d')))->startOfDay(); $endEx = $rangeEnd->copy()->addDay(); $qb = $this->attendanceDataModel->query() ->select([ 'id', 'student_id', 'class_id', 'class_section_id', 'date', 'status', 'reason', 'school_year', 'semester', 'is_reported', 'is_notified', ]) ->when(! empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear)) ->when(! empty($semester), fn ($q) => $q->where('semester', $semester)) ->where(function ($q) use ($studentIds, $studentCodes) { if (! empty($studentIds)) { $q->whereIn('student_id', $studentIds); } if (! empty($studentCodes)) { if (! empty($studentIds)) { $q->orWhereIn('student_id', $studentCodes); } else { $q->whereIn('student_id', $studentCodes); } } }); $this->violationRuleEngine->applyUnreportedAttendanceFilter($qb, $this->attendanceDataModel->getTable()); $qb->whereRaw("(LOWER(TRIM(COALESCE(is_notified, ''))) NOT IN ('yes','1'))") ->whereRaw("LOWER(TRIM(status)) IN ('absent','late')") ->where('date', '>=', $start->format('Y-m-d')) ->where('date', '<', $endEx->format('Y-m-d')); $termRows = $qb->orderByDesc('date')->get()->toArray(); if (empty($termRows)) { $latestAttendanceTerm = DB::table($this->attendanceDataModel->getTable()) ->select('school_year', 'semester', 'date') ->when(! empty($studentIds), fn ($q) => $q->whereIn('student_id', $studentIds)) ->orderByDesc('date') ->first(); if ($latestAttendanceTerm) { $schoolYear = ! empty($latestAttendanceTerm->school_year) ? (string) $latestAttendanceTerm->school_year : $schoolYear; $semester = ! empty($latestAttendanceTerm->semester) ? (string) $latestAttendanceTerm->semester : $semester; [$syStart, $syEnd] = $this->violationRuleEngine->deriveSchoolYearBounds($schoolYear ?: $defaultSchoolYear); $start = Carbon::parse($syStart)->startOfDay(); $rangeEnd = Carbon::parse(min($syEnd, $today->format('Y-m-d')))->startOfDay(); $endEx = $rangeEnd->copy()->addDay(); $qb = $this->attendanceDataModel->query() ->select([ 'id', 'student_id', 'class_id', 'class_section_id', 'date', 'status', 'reason', 'school_year', 'semester', 'is_reported', 'is_notified', ]) ->when(! empty($schoolYear), fn ($q) => $q->where('school_year', $schoolYear)) ->when(! empty($semester), fn ($q) => $q->where('semester', $semester)) ->where(function ($q) use ($studentIds, $studentCodes) { if (! empty($studentIds)) { $q->whereIn('student_id', $studentIds); } if (! empty($studentCodes)) { if (! empty($studentIds)) { $q->orWhereIn('student_id', $studentCodes); } else { $q->whereIn('student_id', $studentCodes); } } }); $this->violationRuleEngine->applyUnreportedAttendanceFilter($qb, $this->attendanceDataModel->getTable()); $qb->whereRaw("(LOWER(TRIM(COALESCE(is_notified, ''))) NOT IN ('yes','1'))") ->whereRaw("LOWER(TRIM(status)) IN ('absent','late')") ->where('date', '>=', $start->format('Y-m-d')) ->where('date', '<', $endEx->format('Y-m-d')); $termRows = $qb->orderByDesc('date')->get()->toArray(); } if (empty($termRows)) { return [ 'students' => [], 'school_year' => $schoolYear, 'semester' => $semester, 'debug' => $debugInfo, ]; } } $attendanceRows = []; foreach ($termRows as $row) { $dStr = $row['date'] ?? null; if (! $dStr) { continue; } try { $d = Carbon::parse($dStr); } catch (Throwable) { continue; } if ($d->lt($start) || ! $d->lt($endEx)) { continue; } $sid = $this->studentResolverService->ensureAttendanceStudentExists( $students, $studentCodeToId, $existingIds, $row['student_id'] ?? null ); if ($sid === null || $sid <= 0) { continue; } $row['student_id'] = $sid; $row['status'] = strtolower(trim((string) ($row['status'] ?? ''))); $row['date'] = $d->format('Y-m-d'); $attendanceRows[] = $row; } if (empty($attendanceRows)) { return [ 'students' => [], 'school_year' => $schoolYear, 'semester' => $semester, 'debug' => $debugInfo, ]; } $debugInfo['attendance_rows'] = count($termRows); $debugInfo['filtered_rows'] = count($attendanceRows); $violations = $this->violationRuleEngine->computeViolations( $students, $attendanceRows, $schoolYear, null, $attendanceRows ); $ruleDebug = $this->violationRuleEngine->getDebugCompute(); $debugInfo['violations'] = count($violations); $debugInfo['active_weeks'] = $ruleDebug['active_weeks'] ?? 0; $debugInfo['by_student'] = $ruleDebug['by_student'] ?? 0; $debugInfo['abs_last5'] = $ruleDebug['abs_last5'] ?? 0; $debugInfo['late_last5'] = $ruleDebug['late_last5'] ?? 0; return [ 'students' => $violations, 'school_year' => $schoolYear, 'semester' => $semester, 'debug' => $debugInfo, ]; } }