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, ]; if (!$this->attendancePolicyService->canEditDay($dayPolicyRow, $currentUser)) { 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.'); } } $isTeacher = $this->attendancePolicyService->isTeacher($currentUser['roles']); 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'; DB::table('staff_attendance')->updateOrInsert( [ 'user_id' => $teacherId, 'date' => $attendDate, 'semester' => $semester, 'school_year' => $schoolYear, ], [ 'role_name' => 'Teacher', 'class_section_id' => $classSectionId, 'position' => $position, 'status' => strtolower((string)($teachersData[$teacherId]['status'] ?? 'present')), 'reason' => ($teachersData[$teacherId]['reason'] ?? null) ?: null, 'updated_at' => now(), 'created_at' => now(), ] ); } 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) { $lockInfo = $detectExternalSubmission($existing); if (($lockInfo['locked'] ?? false) === true) { 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.', ]; } }