select('id', 'class_id', 'class_section_id') ->where('class_section_id', $rawSection) ->first(); if (! $sectionRow && ctype_digit($rawSection)) { $sectionRow = DB::table('classSection') ->select('id', 'class_id', 'class_section_id') ->where('id', (int) $rawSection) ->first(); } if (! $sectionRow) { throw new RuntimeException('Invalid class section.'); } $classSectionId = (int) $sectionRow->class_section_id; $classSectionPk = (int) ($sectionRow->id ?? 0); $alsoSectionId = ($classSectionPk > 0 && $classSectionPk !== $classSectionId) ? $classSectionPk : null; $classId = (int) $sectionRow->class_id; $attendanceData = $data['attendance'] ?? []; $teachersData = $data['teachers'] ?? []; if (empty($attendanceData) || ! is_array($attendanceData)) { throw new RuntimeException('No student attendance data provided.'); } if (empty($teachersData) || ! is_array($teachersData)) { throw new RuntimeException('No teacher attendance data provided.'); } $rawDate = $data['date'] ?? now()->toDateString(); $attendDate = Carbon::parse($rawDate)->toDateString(); $semester = (string) $currentSemester(); $schoolYear = (string) $currentSchoolYear(); $dayRow = AttendanceDay::query() ->where('class_section_id', $classSectionId) ->whereDate('date', $attendDate) ->where('semester', $semester) ->where('school_year', $schoolYear) ->first(); $currentUser = [ 'id' => $user->id, 'roles' => method_exists($user, 'roles') ? $user->roles->pluck('name')->all() : [], 'permissions' => method_exists($user, 'permissions') ? $user->permissions->pluck('name')->all() : [], ]; $dayPolicyRow = $dayRow?->toArray() ?? [ 'status' => 'draft', 'class_section_id' => $classSectionId, 'date' => $attendDate, 'semester' => $semester, 'school_year' => $schoolYear, ]; $isTeacher = $this->attendancePolicyService->isTeacher($currentUser['roles']); $dayStatus = strtolower((string) ($dayPolicyRow['status'] ?? 'draft')); if (! $this->attendancePolicyService->canEditDay($dayPolicyRow, $currentUser) && ! ($isTeacher && $dayStatus === 'submitted')) { throw new RuntimeException('Attendance is locked for your role.'); } $existingRows = AttendanceData::query() ->where('class_section_id', $classSectionId) ->whereDate('date', $attendDate) ->where('semester', $semester) ->where('school_year', $schoolYear) ->get(); $existingByStudent = []; foreach ($existingRows as $row) { $existingByStudent[(int) $row->student_id] = $row->toArray(); } $allowedStatuses = ['present', 'absent', 'late']; $assignedTeachers = $this->teacherClass->assignedForSectionTerm( $classSectionId, $semester, $schoolYear, $alsoSectionId, true, ); if ($assignedTeachers === []) { $assignedTeachers = $this->teacherClass->assignedForSectionTerm( $classSectionId, $semester, $schoolYear, $alsoSectionId, false, ); } foreach ($assignedTeachers as $teacher) { $teacherId = (int) $teacher['teacher_id']; $status = strtolower((string) ($teachersData[$teacherId]['status'] ?? '')); if (! in_array($status, $allowedStatuses, true)) { throw new RuntimeException('Please fill teacher attendance for all assigned teachers.'); } } DB::transaction(function () use ( $user, $assignedTeachers, $teachersData, $attendanceData, $existingByStudent, $classSectionId, $classId, $attendDate, $semester, $schoolYear, $isTeacher, $detectExternalSubmission ) { $dayRow = AttendanceDay::query()->firstOrCreate( [ 'class_section_id' => $classSectionId, 'date' => $attendDate, 'semester' => $semester, 'school_year' => $schoolYear, ], [ 'status' => 'draft', 'created_at' => now(), 'updated_at' => now(), ] ); foreach ($assignedTeachers as $teacher) { $teacherId = (int) $teacher['teacher_id']; $position = strtolower((string) ($teacher['position'] ?? 'main')); $position = $position === 'ta' ? 'ta' : 'main'; if ($isTeacher && DB::table('staff_attendance') ->where('user_id', $teacherId) ->whereDate('date', $attendDate) ->where('semester', $semester) ->where('school_year', $schoolYear) ->exists()) { continue; } $staffPayload = [ 'role_name' => 'Teacher', 'status' => strtolower((string) ($teachersData[$teacherId]['status'] ?? 'present')), 'reason' => ($teachersData[$teacherId]['reason'] ?? null) ?: null, 'updated_at' => now(), 'created_at' => now(), ]; if (Schema::hasColumn('staff_attendance', 'class_section_id')) { $staffPayload['class_section_id'] = $classSectionId; } if (Schema::hasColumn('staff_attendance', 'position')) { $staffPayload['position'] = $position; } DB::table('staff_attendance')->updateOrInsert( [ 'user_id' => $teacherId, 'date' => $attendDate, 'semester' => $semester, 'school_year' => $schoolYear, ], $staffPayload ); } foreach ($attendanceData as $row) { $studentId = (int) ($row['student_id'] ?? 0); $status = strtolower(trim((string) ($row['status'] ?? ''))); $reason = trim((string) ($row['reason'] ?? '')); $existing = $existingByStudent[$studentId] ?? null; if ($isTeacher && $existing) { continue; } $this->studentAttendanceWriterService->saveOrUpdateStudentAttendance([ 'class_section_id' => $classSectionId, 'student_id' => $studentId, 'school_id' => $row['school_id'] ?? null, 'status' => $status, 'reason' => $reason, 'is_reported' => 'no', 'semester' => $semester, 'school_year' => $schoolYear, 'date' => $attendDate, 'class_id' => $classId, 'user_id' => $user->id, ]); } $autoPublishAt = $this->attendanceAutoPublishService->secondSundayAfterEndOfDay($attendDate); $dayRow->update([ 'status' => 'submitted', 'submitted_by' => $user->id, 'submitted_at' => now(), 'auto_publish_at' => $autoPublishAt, 'updated_at' => now(), ]); }); return [ 'ok' => true, 'message' => 'Attendance submitted successfully.', ]; } }