configuration->getConfig('semester') ?? 'Fall'); } public function currentSchoolYear(): string { return (string) ($this->configuration->getConfig('school_year') ?? now()->year.'-'.(now()->year + 1)); } public function updateAttendanceManagement(Authenticatable $user, array $data): array { $classSectionId = (int) $data['class_section_id']; $studentId = (int) $data['student_id']; $status = strtolower((string) $data['status']); $date = (string) $data['date']; $reason = $data['reason'] ?? null; $classId = $data['class_id'] ?? null; $semester = (string) ($data['semester'] ?? $this->currentSemester()); $schoolYear = (string) ($data['school_year'] ?? $this->currentSchoolYear()); $dayRow = AttendanceDay::query() ->where('class_section_id', $classSectionId) ->whereDate('date', $date) ->where('semester', $semester) ->where('school_year', $schoolYear) ->first(); $dayPolicyRow = $dayRow?->toArray() ?? [ 'status' => 'draft', 'class_section_id' => $classSectionId, 'date' => $date, 'semester' => $semester, 'school_year' => $schoolYear, ]; $currentUser = [ 'id' => $user->id, 'roles' => method_exists($user, 'roles') ? $user->roles->pluck('name')->all() : [], 'permissions' => method_exists($user, 'permissions') ? $user->permissions->pluck('name')->all() : [], ]; if (! $this->attendancePolicyService->canEditDay($dayPolicyRow, $currentUser)) { throw new RuntimeException('Attendance is locked for your role.'); } $reportedRaw = strtolower((string) ($data['is_reported'] ?? '')); $isReported = $status === 'present' ? 'no' : (in_array($reportedRaw, ['1', 'yes', 'y', 'true', 'on'], true) ? 'yes' : 'no'); DB::transaction(function () use ( $user, $classSectionId, $studentId, $status, $date, $reason, $classId, $semester, $schoolYear, $isReported ) { $this->studentAttendanceWriterService->saveOrUpdateStudentAttendance([ 'class_section_id' => $classSectionId, 'student_id' => $studentId, 'school_id' => null, 'status' => $status, 'reason' => $reason, 'is_reported' => $isReported, 'semester' => $semester, 'school_year' => $schoolYear, 'date' => $date, 'class_id' => (int) $classId, 'user_id' => $user->id, ]); AttendanceDay::query()->firstOrCreate( [ 'class_section_id' => $classSectionId, 'date' => $date, 'semester' => $semester, 'school_year' => $schoolYear, ], [ 'status' => 'draft', 'created_at' => now(), 'updated_at' => now(), ] ); }); return [ 'ok' => true, 'message' => 'Attendance updated successfully.', ]; } public function submitTeacherAttendance(Authenticatable $user, array $data): array { return $this->teacherAttendanceSubmissionService->submit( $user, $data, fn (?array $row) => $this->detectExternalSubmission($row), fn () => $this->currentSemester(), fn () => $this->currentSchoolYear(), ); } public function adminAddEntry(Authenticatable $user, array $data): array { $classSectionId = (int) $data['class_section_id']; $studentId = (int) $data['student_id']; $status = strtolower((string) $data['status']); $date = (string) ($data['date'] ?? now()->toDateString()); $reason = $data['reason'] ?? null; $isReported = in_array(strtolower((string) ($data['is_reported'] ?? 'no')), ['yes', 'no'], true) ? strtolower((string) ($data['is_reported'] ?? 'no')) : 'no'; $section = $this->classSection ->query() ->select('class_id', 'id', 'class_section_id') ->where('id', $classSectionId) ->orWhere('class_section_id', $classSectionId) ->first(); $resolvedClassId = (int) ($section->class_id ?? 0); if ($resolvedClassId <= 0) { throw new RuntimeException('Cannot resolve class_id for this section.'); } DB::transaction(function () use ( $user, $classSectionId, $studentId, $status, $date, $reason, $resolvedClassId, $isReported ) { AttendanceDay::query()->firstOrCreate( [ 'class_section_id' => $classSectionId, 'date' => $date, 'semester' => $this->currentSemester(), 'school_year' => $this->currentSchoolYear(), ], [ 'status' => 'draft', 'created_at' => now(), 'updated_at' => now(), ] ); $this->studentAttendanceWriterService->saveOrUpdateStudentAttendance([ 'class_section_id' => $classSectionId, 'student_id' => $studentId, 'school_id' => $data['school_id'] ?? null, 'status' => $status, 'reason' => $reason, 'is_reported' => $isReported, 'semester' => $this->currentSemester(), 'school_year' => $this->currentSchoolYear(), 'date' => $date, 'class_id' => $resolvedClassId, 'user_id' => $user->id, ]); }); return [ 'ok' => true, 'message' => 'Entry saved successfully.', ]; } public function normalizeAttendanceEntries(array $entries): array { foreach ($entries as &$row) { $row['status'] = $this->normalizeStatus($row['status'] ?? ''); $reportedRaw = $row['is_reported'] ?? ($row['reported'] ?? ''); $row['is_reported'] = $this->normalizeReportedFlag($reportedRaw); } return $entries; } public function normalizeStatus(?string $status): string { $status = strtolower(trim((string) $status)); return in_array($status, ['present', 'absent', 'late'], true) ? $status : $status; } public function normalizeReportedFlag(mixed $flag): string { $v = strtolower(trim((string) $flag)); return in_array($v, ['yes', '1', 'true', 'y', 'on'], true) ? 'yes' : 'no'; } public function detectExternalSubmission(?array $row): array { if (empty($row) || ! is_array($row)) { return ['locked' => false, 'source' => null]; } $reportedRaw = strtolower((string) ($row['is_reported'] ?? '')); $reason = strtolower((string) ($row['reason'] ?? '')); $isParent = in_array($reportedRaw, ['yes', '1', 'true', 'y'], true) || str_contains($reason, 'parent-reported') || str_contains($reason, 'parent reported'); if ($isParent) { return ['locked' => true, 'source' => 'parent']; } $modifierId = (int) ($row['modified_by'] ?? 0); if ($modifierId > 0 && $this->isAdminLikeUserId($modifierId)) { return ['locked' => true, 'source' => 'admin']; } return ['locked' => false, 'source' => null]; } public function isAdminLikeUserId(int $userId): bool { if ($userId <= 0) { return false; } $roles = $this->userRole->getRolesByUserId($userId); if (empty($roles)) { return false; } $excluded = ['parent', 'guest', 'teacher', 'teacher_assistant', 'assistant_teacher', 'ta']; foreach ($roles as $role) { $name = strtolower(str_replace([' ', '-'], '_', (string) ($role['role_name'] ?? ''))); if ($name !== '' && ! in_array($name, $excluded, true)) { return true; } } return false; } }