studentModel->query()->find($studentId); if (!$student) { return [ 'success' => false, 'message' => 'Student not found', 'status' => 404, ]; } $student = $student->toArray(); $student['name'] = trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')); $codeParam = strtoupper((string) ($codeParam ?? '')); $incidentDate = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) $incidentDate) ? (string) $incidentDate : ''; $statusFilter = ['absent', 'late']; if (str_starts_with($codeParam, 'ABS')) { $statusFilter = ['absent']; } elseif (str_starts_with($codeParam, 'LATE')) { $statusFilter = ['late']; } elseif (str_starts_with($codeParam, 'MIX')) { $statusFilter = ['absent', 'late']; } $schoolYear = $schoolYearFilter ?? ($incidentDate !== '' ? $this->violationRuleEngine->schoolYearForDate($incidentDate, $defaultSchoolYear) : (string) $defaultSchoolYear); $semester = $semesterFilter; if ($semester === null && $codeParam === '' && $incidentDate === '') { $semester = (string) $defaultSemester; } [$syStart, $syEnd] = $this->violationRuleEngine->deriveSchoolYearBounds($schoolYear ?: date('Y')); $today = Carbon::today(); $start = Carbon::parse($syStart)->startOfDay(); $rangeEnd = Carbon::parse(min($syEnd, $today->format('Y-m-d')))->startOfDay(); $windowWeeks = $this->violationRuleEngine->windowWeeksForViolationCode($codeParam); $endYmd = $incidentDate !== '' ? $incidentDate : date('Y-m-d'); $startYmd = $this->violationRuleEngine->activeWeekWindowStartYmd($endYmd, $windowWeeks, $schoolYear, $semester); $studentIdValues = [(int) $studentId]; $studentCodeRaw = trim((string) ($student['student_code'] ?? $student['school_id'] ?? '')); $studentCodeValues = []; if ($studentCodeRaw !== '') { if (is_numeric($studentCodeRaw)) { $studentCodeNum = (int) $studentCodeRaw; if ($studentCodeNum > 0 && $studentCodeNum !== (int) $studentId) { $studentIdValues[] = $studentCodeNum; } } else { $studentCodeValues[] = $studentCodeRaw; } } $studentIdValues = array_values(array_unique(array_filter($studentIdValues, fn ($v) => $v > 0))); $studentCodeValues = array_values(array_unique(array_filter($studentCodeValues, fn ($v) => $v !== ''))); $qb = $this->attendanceDataModel->query() ->select([ 'id', 'student_id', 'class_id', 'class_section_id', 'date', 'status', 'reason', 'school_year', 'semester', 'is_reported', 'is_notified', ]) ->where(function ($q) use ($studentIdValues, $studentCodeValues) { $q->whereIn('student_id', $studentIdValues); if (!empty($studentCodeValues)) { $q->orWhereIn('student_id', $studentCodeValues); } }) ->where('date', '>=', max($start->format('Y-m-d'), $startYmd)) ->where('date', '<', $this->violationRuleEngine->dayBounds($endYmd)[1]); if ($statusFilter === ['absent']) { $qb->whereRaw("(LOWER(TRIM(status)) LIKE 'abs%' OR LOWER(TRIM(status)) = 'a')"); } elseif ($statusFilter === ['late']) { $qb->whereRaw("(LOWER(TRIM(status)) LIKE 'late%' OR LOWER(TRIM(status)) = 'l')"); } else { $qb->whereRaw("(LOWER(TRIM(status)) LIKE 'abs%' OR LOWER(TRIM(status)) LIKE 'late%' OR LOWER(TRIM(status)) IN ('a','l'))"); } if ($pendingOnly) { $this->violationRuleEngine->applyUnreportedAttendanceFilter($qb, $this->attendanceDataModel->getTable()); if (!$includeNotified) { $qb->whereRaw("(LOWER(TRIM(COALESCE(is_notified, ''))) NOT IN ('yes','1'))"); } } if ($schoolYearFilter !== null) { $qb->where('school_year', $schoolYearFilter); } if ($semesterFilter !== null) { $qb->where('semester', $semesterFilter); } $attendanceRows = $qb->orderByDesc('date')->get()->toArray(); $data = [ 'student' => $student, 'attendance' => $attendanceRows, 'violation_code' => $codeParam !== '' ? $codeParam : null, 'violation_date' => $incidentDate, 'violation_notified' => null, 'show_violation_summary' => true, 'school_year' => $schoolYear, 'semester' => $semester, ]; if ($data['violation_date'] === '' && !empty($data['attendance'][0]['date'])) { $data['violation_date'] = substr((string) $data['attendance'][0]['date'], 0, 10); } if (!empty($data['attendance'][0]['is_notified'])) { $data['violation_notified'] = $data['attendance'][0]['is_notified']; } if ($data['violation_date'] !== '') { foreach ($attendanceRows as $row) { $ymd = substr((string) ($row['date'] ?? ''), 0, 10); if ($ymd === $data['violation_date']) { $data['show_violation_summary'] = false; break; } } } return [ 'success' => true, 'data' => $data, ]; } public function getNotifiedViolations( ?string $schoolYearParam = null, ?string $semesterParam = null, ?string $defaultSchoolYear = null, ?string $defaultSemester = null ): array { $schoolYear = filled($schoolYearParam) ? (string) $schoolYearParam : (string) $defaultSchoolYear; $semester = filled($semesterParam) ? (string) $semesterParam : (string) $defaultSemester; $trackingData = $this->attendanceTrackingModel->query() ->where('school_year', $schoolYear) ->where('semester', $semester) ->where(function ($q) { $q->where('is_notified', 1) ->orWhere('is_notified', '1') ->orWhereRaw("LOWER(TRIM(COALESCE(is_notified, ''))) = 'yes'"); }) ->orderByDesc('date') ->get() ->toArray(); if (empty($trackingData)) { $latest = $this->attendanceTrackingModel->query() ->select(['school_year', 'semester']) ->where(function ($q) { $q->where('is_notified', 1) ->orWhere('is_notified', '1') ->orWhereRaw("LOWER(TRIM(COALESCE(is_notified, ''))) = 'yes'"); }) ->orderByDesc('date') ->orderByDesc('id') ->first(); if ($latest) { $schoolYear = (string) ($latest->school_year ?? $schoolYear); $semester = (string) ($latest->semester ?? $semester); $trackingData = $this->attendanceTrackingModel->query() ->where('school_year', $schoolYear) ->where('semester', $semester) ->where(function ($q) { $q->where('is_notified', 1) ->orWhere('is_notified', '1') ->orWhereRaw("LOWER(TRIM(COALESCE(is_notified, ''))) = 'yes'"); }) ->orderByDesc('date') ->get() ->toArray(); } } if (empty($trackingData)) { return [ 'students' => [], 'school_year' => $schoolYear, 'semester' => $semester, 'show_actions' => false, ]; } $studentIds = array_unique(array_map(fn ($r) => (int) ($r['student_id'] ?? 0), $trackingData)); $students = $this->studentModel->query() ->whereIn('id', $studentIds) ->where('is_active', 1) ->get() ->toArray(); $activeIdSet = array_flip(array_map( static fn ($row) => (int) ($row['id'] ?? 0), $students )); $trackingData = array_values(array_filter( $trackingData, static fn ($row) => isset($activeIdSet[(int) ($row['student_id'] ?? 0)]) )); $studentIds = array_keys($activeIdSet); if (empty($trackingData)) { return [ 'students' => [], 'school_year' => $schoolYear, 'semester' => $semester, 'show_actions' => false, ]; } $studentMap = []; foreach ($students as $s) { $studentMap[$s['id']] = $s; } $classByStudent = []; try { $rows = DB::table('student_class as sc') ->select([ 'sc.student_id', 'sc.class_section_id', 'cs.class_section_name', 'cs.class_id', 'c.class_name', ]) ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id') ->leftJoin('classes as c', 'c.id', '=', 'cs.class_id') ->whereIn('sc.student_id', $studentIds) ->where('sc.school_year', $schoolYear) ->orderByDesc('sc.id') ->get(); foreach ($rows as $row) { $sid = (int) ($row->student_id ?? 0); if ($sid > 0 && !isset($classByStudent[$sid])) { $classByStudent[$sid] = [ 'class_section_id' => $row->class_section_id ?? null, 'class_section_name' => (string) ($row->class_section_name ?? ''), 'class_id' => $row->class_id ?? null, 'class_name' => (string) ($row->class_name ?? ''), ]; } } } catch (Throwable) { } $mergedData = []; foreach ($trackingData as $row) { $sid = (int) ($row['student_id'] ?? 0); if (!isset($studentMap[$sid])) { continue; } $merged = array_merge($studentMap[$sid], $row); if (isset($classByStudent[$sid])) { $merged['class_section_name'] = (string) ($classByStudent[$sid]['class_section_name'] ?? ''); $merged['class_name'] = (string) ($classByStudent[$sid]['class_name'] ?? ''); } elseif (!empty($merged['registration_grade'])) { $merged['grade'] = (string) $merged['registration_grade']; } try { $p = $this->parentLookupService->getPrimaryParentForStudent($sid); if ($p) { $merged['parent_name'] = $p['parent_name'] ?? ''; $merged['parent_email'] = $p['email'] ?? ''; $merged['parent_phone'] = $p['phone'] ?? ''; } } catch (Throwable) { } $mergedData[] = $merged; } return [ 'students' => $mergedData, 'school_year' => $schoolYear, 'semester' => $semester, ]; } }