teacherClass->assignedBySectionForTerm($semester, $schoolYear); $sectionCodes = array_keys($bySection); $labels = []; if (! empty($sectionCodes)) { $rows = $this->classSection ->query() ->select(['class_section_id', 'class_section_name']) ->whereIn('class_section_id', $sectionCodes) ->orderBy('class_section_id') ->get() ->toArray(); foreach ($rows as $row) { $code = (int) $row['class_section_id']; $name = trim((string) ($row['class_section_name'] ?? '')); $labels[$code] = $name !== '' ? $name : 'Section #'.$code; } foreach ($sectionCodes as $code) { $labels[$code] ??= 'Section #'.$code; } } $grid = []; $locked = false; if ($sectionCode > 0) { $assigned = $this->teacherClass->assignedForSectionTerm($sectionCode, $semester, $schoolYear); $teacherIds = array_map(fn ($r) => (int) $r['teacher_id'], $assigned); $statusMap = []; if (! empty($teacherIds)) { $rows = DB::table('staff_attendance as sa') ->select('sa.user_id', 'sa.status', 'sa.reason') ->where('sa.semester', $semester) ->where('sa.school_year', $schoolYear) ->whereDate('sa.date', $date) ->whereIn('sa.user_id', $teacherIds) ->get(); foreach ($rows as $row) { $statusMap[(int) $row->user_id] = [ 'status' => $row->status, 'reason' => $row->reason, ]; } } foreach ($assigned as $teacher) { $teacherId = (int) $teacher['teacher_id']; $position = strtolower((string) ($teacher['position'] ?? 'main')); $position = in_array($position, ['main', 'ta'], true) ? $position : 'main'; $name = trim(($teacher['firstname'] ?? '').' '.($teacher['lastname'] ?? '')); $cell = $statusMap[$teacherId] ?? ['status' => null, 'reason' => null]; $grid[] = [ 'teacher_id' => $teacherId, 'name' => $name !== '' ? $name : 'User#'.$teacherId, 'position' => $position, 'status' => $cell['status'], 'reason' => $cell['reason'], ]; } try { $locked = method_exists($this->attendanceDay, 'isFinalized') ? $this->attendanceDay->isFinalized($sectionCode, $date, $semester, $schoolYear) : false; } catch (\Throwable) { $locked = false; } } return [ 'semester' => $semester, 'school_year' => $schoolYear, 'date' => $date, 'class_section_id' => $sectionCode, 'sections' => $labels, 'grid' => $grid, 'locked' => $locked, ]; } public function teacherAttendanceFormData(?int $userId, int $requestedSectionId = 0): array { if (! $userId) { throw new RuntimeException('User not logged in.'); } $teacher = $this->user->find($userId); $teacherName = $teacher ? trim(($teacher->firstname ?? '').' '.($teacher->lastname ?? '')) : 'Unknown Teacher'; $semester = $this->attendanceService->currentSemester(); $schoolYear = $this->attendanceService->currentSchoolYear(); $assignments = $this->teacherClass->getClassAssignmentsByUserId($userId, $schoolYear, $semester); if (empty($assignments)) { throw new RuntimeException('You do not have an assigned class yet.'); } $activeSection = null; foreach ($assignments as $assignment) { $cid = (int) ($assignment['class_section_id'] ?? 0); $pk = (int) ($assignment['class_section_pk'] ?? 0); if ($requestedSectionId > 0 && ($requestedSectionId === $cid || $requestedSectionId === $pk)) { $activeSection = $assignment; break; } } $activeSection ??= $assignments[0]; $classSectionCode = (int) ($activeSection['class_section_id'] ?? 0); $classSectionPk = (int) ($activeSection['class_section_pk'] ?? 0); $classSectionId = $classSectionCode > 0 ? $classSectionCode : $classSectionPk; if ($classSectionId <= 0) { throw new RuntimeException('Unable to determine your class section.'); } $today = Carbon::today(); $anchorSunday = $today->dayOfWeek === Carbon::SUNDAY ? $today->copy() : $today->copy()->next(Carbon::SUNDAY); $sundayDates = [ $anchorSunday->copy()->subWeeks(2)->toDateString(), $anchorSunday->copy()->subWeek()->toDateString(), $anchorSunday->toDateString(), ]; $currentSunday = $sundayDates[2]; $students = $this->student->getByClassAndYear($classSectionId, $schoolYear, $semester); if (empty($students) && $classSectionPk > 0 && $classSectionPk !== $classSectionId) { $students = $this->student->getByClassAndYear($classSectionPk, $schoolYear, $semester); } $attendanceRows = []; $lockedByStudent = []; $attendanceTable = $this->attendanceData->getTable(); $dateInSundaysSql = 'DATE(`'.$attendanceTable.'`.`date`) IN ('. implode(',', array_fill(0, count($sundayDates), '?')).')'; foreach ($students as $student) { $student = is_array($student) ? $student : (array) $student; unset($student['created_by'], $student['updated_by'], $student['deleted_at']); $studentId = (int) $student['id']; $rows = AttendanceData::query() ->select(['date', 'status', 'is_reported', 'reason', 'modified_by']) ->where('student_id', $studentId) ->where('school_year', $schoolYear) ->where('semester', $semester) ->where(function ($q) use ($classSectionId, $classSectionPk) { $q->where('class_section_id', $classSectionId); if ($classSectionPk > 0 && $classSectionPk !== $classSectionId) { $q->orWhere('class_section_id', $classSectionPk); } }) ->whereRaw($dateInSundaysSql, $sundayDates) ->get() ->map(fn ($row) => (array) $row) ->all(); $rows = $this->attendanceService->normalizeAttendanceEntries($rows); $recentRows = AttendanceData::query() ->select(['date', 'status']) ->where('student_id', $studentId) ->where('school_year', $schoolYear) ->where('semester', $semester) ->where(function ($q) use ($classSectionId, $classSectionPk) { $q->where('class_section_id', $classSectionId); if ($classSectionPk > 0 && $classSectionPk !== $classSectionId) { $q->orWhere('class_section_id', $classSectionPk); } }) ->orderByDesc('date') ->limit(3) ->get() ->toArray(); $recentHistory = []; foreach ($recentRows as $row) { $recentHistory[] = [ 'date' => substr((string) ($row['date'] ?? ''), 0, 10), 'status' => strtolower((string) ($row['status'] ?? '')), ]; } $snapshot = []; $rowsByDate = []; foreach ($rows as $entry) { $d = $this->calendarDateKey($entry['date'] ?? null); if ($d === '') { continue; } $snapshot[$d] = $entry['status'] ?? null; $rowsByDate[$d] = $entry; } $statusHistory = []; foreach ($sundayDates as $date) { $statusHistory[$date] = array_key_exists($date, $snapshot) ? $snapshot[$date] : ($date === $currentSunday ? null : 'N/A'); } // Recent list uses the same section filters but no Sunday window; if the windowed query // missed rows (SQL/driver quirks), still show statuses that match the visible columns. foreach ($recentRows as $row) { $d = $this->calendarDateKey($row['date'] ?? null); if ($d === '' || ! in_array($d, $sundayDates, true)) { continue; } $rowStatus = $row['status'] ?? null; if ($rowStatus === null || $rowStatus === '') { continue; } $current = $statusHistory[$d] ?? null; if ($current === 'N/A' || $current === null) { $statusHistory[$d] = $rowStatus; } } $todayRow = $rowsByDate[$currentSunday] ?? null; $lockInfo = $this->attendanceService->detectExternalSubmission($todayRow); $todayReason = $todayRow['reason'] ?? null; $attendanceRows[] = [ 'student' => $student, 'sunday_statuses' => $statusHistory, 'today' => $statusHistory[$currentSunday] ?? null, 'today_reason' => $todayReason, 'locked' => $lockInfo['locked'], 'locked_source' => $lockInfo['source'], 'recent_three' => $recentHistory, ]; $lockedByStudent[$studentId] = [ 'locked' => $lockInfo['locked'], 'source' => $lockInfo['source'], 'reason' => $todayReason, 'status' => $statusHistory[$currentSunday] ?? null, ]; } $candidates = array_values(array_unique(array_filter( [$classSectionCode, $classSectionPk], static fn (int $v): bool => $v > 0, ))); $alsoSectionId = null; foreach ($candidates as $cid) { if ($cid !== $classSectionId) { $alsoSectionId = $cid; break; } } $assigned = $this->teacherClass->assignedForSectionTerm( $classSectionId, $semester, $schoolYear, $alsoSectionId, true, ); if ($assigned === []) { $assigned = $this->teacherClass->assignedForSectionTerm( $classSectionId, $semester, $schoolYear, $alsoSectionId, false, ); } if ($assigned === []) { $assigned = $this->teacherRosterFromStaffAttendance( $sundayDates, $semester, $schoolYear, $classSectionId, $classSectionPk, ); } if ($assigned === [] && $userId > 0) { $self = $this->user->query()->select(['id', 'firstname', 'lastname'])->find($userId); if ($self) { $assigned = [[ 'class_section_id' => $classSectionId, 'teacher_id' => (int) $self->id, 'position' => 'main', 'firstname' => $self->firstname, 'lastname' => $self->lastname, ]]; } } $teacherRows = []; if (! empty($assigned)) { $teacherIds = array_map(static fn ($r) => (int) $r['teacher_id'], $assigned); $statusMap = []; if (! empty($teacherIds)) { $staffDateInSql = 'DATE(`sa`.`date`) IN ('. implode(',', array_fill(0, count($sundayDates), '?')).')'; $staffQ = DB::table('staff_attendance as sa') ->select('sa.user_id', 'sa.date', 'sa.status', 'sa.reason') ->where('sa.semester', $semester) ->where('sa.school_year', $schoolYear) ->whereIn('sa.user_id', $teacherIds) ->whereRaw($staffDateInSql, $sundayDates); if (Schema::hasColumn('staff_attendance', 'class_section_id')) { $staffQ->where(function ($w) use ($classSectionId, $classSectionPk) { $w->whereNull('sa.class_section_id') ->orWhere('sa.class_section_id', 0) ->orWhere('sa.class_section_id', $classSectionId); if ($classSectionPk > 0 && $classSectionPk !== $classSectionId) { $w->orWhere('sa.class_section_id', $classSectionPk); } }); } $rows = $staffQ->get(); foreach ($rows as $row) { $dk = $this->calendarDateKey($row->date ?? null); if ($dk === '') { continue; } $statusMap[(int) $row->user_id][$dk] = [ 'status' => $row->status, 'reason' => $row->reason, ]; } } foreach ($assigned as $teacher) { $teacherId = (int) $teacher['teacher_id']; $position = strtolower((string) ($teacher['position'] ?? 'main')); $position = in_array($position, ['main', 'ta'], true) ? $position : 'main'; $name = trim(($teacher['firstname'] ?? '').' '.($teacher['lastname'] ?? '')); $history = []; foreach ($sundayDates as $date) { $history[$date] = isset($statusMap[$teacherId][$date]['status']) ? $statusMap[$teacherId][$date]['status'] : ($date === $currentSunday ? null : 'N/A'); } $todayReason = null; if (isset($statusMap[$teacherId][$currentSunday]) && is_array($statusMap[$teacherId][$currentSunday])) { $todayReason = $statusMap[$teacherId][$currentSunday]['reason'] ?? null; } $teacherRows[] = [ 'teacher_id' => $teacherId, 'position' => $position, 'name' => $name !== '' ? $name : 'User#'.$teacherId, 'sunday_statuses' => $history, 'today' => $history[$currentSunday] ?? null, 'today_reason' => $todayReason, ]; } } $noSchoolDays = []; $calendarTable = $this->calendar->getTable(); $calDateInSql = 'DATE(`'.$calendarTable.'`.`date`) IN ('. implode(',', array_fill(0, count($sundayDates), '?')).')'; $calendarRows = $this->calendar->query() ->where('no_school', 1) ->whereRaw($calDateInSql, $sundayDates) ->where('semester', $semester) ->where('school_year', $schoolYear) ->get() ->toArray(); foreach ($calendarRows as $row) { $d = $this->calendarDateKey($row['date'] ?? null); if ($d === '') { continue; } $noSchoolDays[$d] = [ 'title' => $row['title'] ?? 'No School', 'description' => $row['description'] ?? '', ]; } return [ 'teacher_name' => $teacherName, 'students' => $attendanceRows, 'teachers' => $teacherRows, 'class_section_id' => $classSectionId, 'sunday_dates' => $sundayDates, 'current_sunday' => $currentSunday, 'enable_attendance' => (int) $this->configuration->getConfig('enable_attendance'), 'can_edit' => ((int) $this->configuration->getConfig('enable_attendance') === 1), 'class_id' => (int) $this->classSection->getClassId($classSectionId), 'no_school_days' => $noSchoolDays, 'today' => $currentSunday, 'locked_by_student' => $lockedByStudent, ]; } public function buildDailyAttendanceData(string $termSemester, string $termYear): array { $effectiveTermYear = $termYear; $loadClassSections = function (string $schoolYear): array { if ($schoolYear === '') { return []; } return $this->classSection ->query() ->select('classSection.id', 'classSection.class_id', 'classSection.class_section_id', 'classSection.class_section_name') ->join('student_class as sc', 'sc.class_section_id', '=', 'classSection.class_section_id') ->where('sc.school_year', $schoolYear) ->groupBy('classSection.id', 'classSection.class_id', 'classSection.class_section_id', 'classSection.class_section_name') ->get() ->map(fn ($row) => $row->toArray()) ->all(); }; $classSections = $loadClassSections($effectiveTermYear); if ($classSections === []) { $fallbackYear = (string) DB::table('student_class') ->whereNotNull('school_year') ->where('school_year', '<>', '') ->orderByDesc('school_year') ->value('school_year'); if ($fallbackYear !== '' && $fallbackYear !== $effectiveTermYear) { $effectiveTermYear = $fallbackYear; $classSections = $loadClassSections($effectiveTermYear); } } $attendanceData = []; $attendanceRecord = []; $studentsBySection = []; $grades = []; $datesBySection = []; $studentSchoolMap = []; $sectionCounts = $this->studentClass->getStudentCountsBySection($effectiveTermYear); foreach ($classSections as $classSection) { $secPk = (int) ($classSection['id'] ?? 0); $secCodeRaw = (string) ($classSection['class_section_id'] ?? ''); $secCode = $secCodeRaw !== '' ? $secCodeRaw : (string) $secPk; $classId = (int) ($classSection['class_id'] ?? 0); $studentsBySection[$secCode] = []; $datesBySection[$secCode] = []; $attendanceData[$secCode] = []; $attendanceRecord[$secCode] = []; $countForCode = (int) ($sectionCounts[$secCode] ?? 0); $countForPk = (int) ($sectionCounts[$secPk] ?? 0); $sectionHasStudents = ($countForCode + $countForPk) > 0; $students = $this->studentClass->getClassStudents($secCode, $effectiveTermYear); if (! $students && $secPk && (string) $secPk !== (string) $secCode) { $students = $this->studentClass->getClassStudents($secPk, $effectiveTermYear); } if (! $sectionHasStudents || ! $students) { continue; } // Bulk-load student profiles for the whole section in one query $studentsArray = is_array($students) ? $students : $students->toArray(); $studentIds = array_map(fn ($sc) => (int) ($sc['student_id'] ?? $sc->student_id ?? 0), $studentsArray); $studentProfiles = $this->student ->query() ->select(['id', 'firstname', 'lastname', 'school_id']) ->whereIn('id', $studentIds) ->where('is_active', 1) ->get() ->keyBy('id') ->map(fn ($row) => $row->toArray()) ->all(); $activeIds = array_keys($studentProfiles); if (empty($activeIds)) { unset($studentsBySection[$secCode], $datesBySection[$secCode], $attendanceData[$secCode], $attendanceRecord[$secCode]); continue; } // Bulk-load all attendance entries for the section in one query $allEntries = AttendanceData::query() ->whereIn('student_id', $activeIds) ->where(function ($q) use ($secCode, $secPk) { $q->where('class_section_id', $secCode); if ($secPk && (string) $secPk !== (string) $secCode) { $q->orWhere('class_section_id', $secPk); } }) ->when($termSemester !== '', fn ($q) => $q->where('semester', $termSemester)) ->when($effectiveTermYear !== '', fn ($q) => $q->where('school_year', $effectiveTermYear)) ->orderBy('date') ->get() ->map(fn ($row) => $row->toArray()) ->groupBy('student_id') ->map(fn ($group) => $group->values()->all()) ->all(); // Bulk-load all attendance records (summaries) for the section in one query $allRecords = AttendanceRecord::query() ->whereIn('student_id', $activeIds) ->where(function ($q) use ($secCode, $secPk) { $q->where('class_section_id', $secCode); if ($secPk && (string) $secPk !== (string) $secCode) { $q->orWhere('class_section_id', $secPk); } }) ->when($termSemester !== '', fn ($q) => $q->where('semester', $termSemester)) ->when($effectiveTermYear !== '', fn ($q) => $q->where('school_year', $effectiveTermYear)) ->get() ->keyBy('student_id') ->map(fn ($row) => $row->toArray()) ->all(); $hasRoster = false; foreach ($studentIds as $studentId) { $student = $studentProfiles[$studentId] ?? null; if (! $student) { continue; } $studentsBySection[$secCode][] = $student; $studentSchoolMap[$studentId] = (string) ($student['school_id'] ?? ''); $hasRoster = true; $entries = $this->attendanceService->normalizeAttendanceEntries( array_map(fn ($r) => (array) $r, $allEntries[$studentId] ?? []) ); $attendanceData[$secCode][$studentId] = $entries; foreach ($entries as $entry) { $d = (string) ($entry['date'] ?? ''); if ($d !== '') { $datesBySection[$secCode][$d] = true; } } $attendanceRecord[$secCode][$studentId] = $allRecords[$studentId] ?? [ 'total_presence' => 0, 'total_late' => 0, 'total_absence' => 0, 'total_attendance' => 0, ]; } if (! $hasRoster) { unset($studentsBySection[$secCode], $datesBySection[$secCode], $attendanceData[$secCode], $attendanceRecord[$secCode]); continue; } $grades[$classId][] = $classSection; } foreach ($attendanceData as $secId => &$studentEntries) { foreach ($studentEntries as &$entries) { usort($entries, static fn ($a, $b) => strcmp((string) ($a['date'] ?? ''), (string) ($b['date'] ?? ''))); } } unset($studentEntries, $entries); foreach ($datesBySection as $sec => $set) { $arr = array_keys($set); sort($arr, SORT_STRING); $datesBySection[$sec] = $arr; } ksort($grades, SORT_NUMERIC); [$rangeStart, $rangeEnd] = $this->semesterRangeService->getSchoolYearRange($effectiveTermYear); $semesterNorm = $this->semesterRangeService->normalizeSemester($termSemester); if ($semesterNorm !== '') { $semRange = $this->semesterRangeService->getSemesterRange($effectiveTermYear, $semesterNorm); if ($semRange) { [$rangeStart, $rangeEnd] = $semRange; } } $dateList = $this->semesterRangeService->buildSundayList($rangeStart, $rangeEnd); $noSchoolDays = []; $events = $this->calendar->query() ->where('no_school', 1) ->where('date', '>=', $rangeStart) ->where('date', '<=', $rangeEnd) ->get() ->toArray(); foreach ($events as $event) { $d = substr((string) ($event['date'] ?? ''), 0, 10); if ($d !== '') { $noSchoolDays[$d] = trim((string) ($event['title'] ?? 'No School')) ?: 'No School'; } } $anchorSunday = now()->dayOfWeek === Carbon::SUNDAY ? now()->toDateString() : now()->next(Carbon::SUNDAY)->toDateString(); $passedDatesSet = []; foreach ($dateList as $d) { if ($d <= $anchorSunday && empty($noSchoolDays[$d])) { $passedDatesSet[$d] = true; } } $totalPassedDays = count($passedDatesSet); if ($totalPassedDays > 0 && $effectiveTermYear !== '' && $termSemester !== '') { foreach ($attendanceData as $secCode => $studentEntries) { foreach ($studentEntries as $studentId => $entries) { $statusByDate = []; foreach ($entries as $entry) { $d = substr((string) ($entry['date'] ?? ''), 0, 10); if ($d === '' || empty($passedDatesSet[$d])) { continue; } $st = strtolower(trim((string) ($entry['status'] ?? ''))); if (! in_array($st, ['present', 'absent', 'late'], true)) { continue; } $statusByDate[$d] = $st; } $p = 0; $l = 0; $a = 0; foreach ($statusByDate as $st) { if ($st === 'present') { $p++; } elseif ($st === 'late') { $l++; } elseif ($st === 'absent') { $a++; } } $sum = $p + $l + $a; $record = $attendanceRecord[$secCode][$studentId] ?? []; $storedSum = (int) ($record['total_presence'] ?? 0) + (int) ($record['total_late'] ?? 0) + (int) ($record['total_absence'] ?? 0); if ($storedSum !== $totalPassedDays && $storedSum !== $sum) { DB::table('attendance_record') ->updateOrInsert( [ 'student_id' => $studentId, 'semester' => $termSemester, 'school_year' => $effectiveTermYear, ], [ 'class_section_id' => $secCode, 'school_id' => $studentSchoolMap[$studentId] ?? '', 'total_presence' => $p, 'total_late' => $l, 'total_absence' => $a, 'total_attendance' => $sum, 'modified_by' => auth()->id(), 'updated_at' => now(), 'created_at' => now(), ] ); $attendanceRecord[$secCode][$studentId]['total_presence'] = $p; $attendanceRecord[$secCode][$studentId]['total_late'] = $l; $attendanceRecord[$secCode][$studentId]['total_absence'] = $a; $attendanceRecord[$secCode][$studentId]['total_attendance'] = $sum; } } } } $adminName = ''; if (auth()->id()) { $u = $this->user->query()->select('firstname', 'lastname')->find(auth()->id()); if ($u) { $adminName = trim(($u->firstname ?? '').' '.($u->lastname ?? '')); } } return [ 'attendanceData' => $attendanceData, 'attendanceRecord' => $attendanceRecord, 'studentsBySection' => $studentsBySection, 'grades' => $grades, 'datesBySection' => $datesBySection, 'dateList' => $dateList, 'noSchoolDays' => $noSchoolDays, 'totalPassedDays' => $totalPassedDays, 'semester' => $termSemester, 'schoolYear' => $effectiveTermYear, 'currentAdminName' => $adminName, ]; } /** * Distinct user_ids with staff rows on the given Sundays, scoped by teacher_class section assignment * (works when staff_attendance has no class_section_id column). * * @return list */ private function distinctStaffAttendeeIdsViaTeacherClassJoin( array $sundayDates, string $semester, string $schoolYear, int $classSectionId, int $classSectionPk, ): array { $sectionIds = array_values(array_unique(array_filter( [$classSectionId, $classSectionPk], static fn (int $v): bool => $v > 0, ))); if ($sundayDates === [] || $sectionIds === []) { return []; } $staffDateInSql = 'DATE(`sa`.`date`) IN ('. implode(',', array_fill(0, count($sundayDates), '?')).')'; $run = static function ( bool $requireTcSemester, bool $requireSaSemester, ) use ($sundayDates, $schoolYear, $semester, $staffDateInSql, $sectionIds): array { $q = DB::table('staff_attendance as sa') ->join('teacher_class as tc', function ($j) use ($sectionIds, $schoolYear, $requireTcSemester, $semester) { $j->on('tc.teacher_id', '=', 'sa.user_id') ->whereIn('tc.class_section_id', $sectionIds) ->where('tc.school_year', '=', $schoolYear); if ($requireTcSemester && trim($semester) !== '') { $j->whereRaw('TRIM(tc.semester) = ?', [trim($semester)]); } }) ->where('sa.school_year', $schoolYear) ->whereRaw($staffDateInSql, $sundayDates); if ($requireSaSemester && trim($semester) !== '') { $q->whereRaw('TRIM(sa.semester) = ?', [trim($semester)]); } return $q->distinct() ->pluck('sa.user_id') ->map(static fn ($v): int => (int) $v) ->filter(static fn (int $v): bool => $v > 0) ->values() ->all(); }; $ids = $run(true, true); if ($ids === []) { $ids = $run(false, true); } if ($ids === []) { $ids = $run(false, false); } return $ids; } /** * When teacher_class has no rows, build a roster from staff_attendance for this section/window. * * @return array */ private function teacherRosterFromStaffAttendance( array $sundayDates, string $semester, string $schoolYear, int $classSectionId, int $classSectionPk, ): array { if ($sundayDates === []) { return []; } $staffDateInSql = 'DATE(`sa`.`date`) IN ('. implode(',', array_fill(0, count($sundayDates), '?')).')'; $ids = []; if (Schema::hasColumn('staff_attendance', 'class_section_id')) { $applySectionScope = function ($query) use ($classSectionId, $classSectionPk): void { $query->where(function ($w) use ($classSectionId, $classSectionPk) { $w->whereNull('sa.class_section_id') ->orWhere('sa.class_section_id', 0) ->orWhere('sa.class_section_id', $classSectionId); if ($classSectionPk > 0 && $classSectionPk !== $classSectionId) { $w->orWhere('sa.class_section_id', $classSectionPk); } }); }; $base = function (bool $useSemester) use ($sundayDates, $schoolYear, $semester, $staffDateInSql, $applySectionScope) { $q = DB::table('staff_attendance as sa') ->where('sa.school_year', $schoolYear) ->whereRaw($staffDateInSql, $sundayDates); if ($useSemester && trim($semester) !== '') { $q->whereRaw('TRIM(sa.semester) = ?', [trim($semester)]); } $applySectionScope($q); return $q->distinct()->pluck('sa.user_id')->map(static fn ($v) => (int) $v)->filter(static fn (int $v) => $v > 0)->values()->all(); }; $ids = $base(true); if ($ids === []) { $ids = $base(false); } } if ($ids === []) { $ids = $this->distinctStaffAttendeeIdsViaTeacherClassJoin( $sundayDates, $semester, $schoolYear, $classSectionId, $classSectionPk, ); } if ($ids === []) { return []; } $users = $this->user->query() ->select(['id', 'firstname', 'lastname']) ->whereIn('id', $ids) ->orderBy('firstname') ->orderBy('lastname') ->get(); $out = []; foreach ($users as $u) { $out[] = [ 'class_section_id' => $classSectionId, 'teacher_id' => (int) $u->id, 'position' => 'main', 'firstname' => $u->firstname, 'lastname' => $u->lastname, ]; } return $out; } /** * Normalize DB datetimes / date strings to Y-m-d so they match {@see $sundayDates} keys. */ private function calendarDateKey(mixed $value): string { $s = trim((string) ($value ?? '')); if ($s === '') { return ''; } if (preg_match('/^(\d{4}-\d{2}-\d{2})/', $s, $m)) { return $m[1]; } try { return Carbon::parse($s)->toDateString(); } catch (\Throwable) { return ''; } } }