db = \Config\Database::connect(); $this->configModel = new ConfigurationModel(); $this->ackModel = new ReportCardAcknowledgementModel(); $this->studentModel = new StudentModel(); } public function index() { $parentId = $this->resolvePrimaryParentId(); if (! $parentId) { return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.'); } $schoolYearContext = $this->resolveSchoolYearContext(); $schoolYear = trim($schoolYearContext->yearName()); $semesterOptions = $this->semesterOptions($schoolYear, ''); $builder = $this->db->table('students s') ->select('s.id, s.firstname, s.lastname, cs.class_section_name') ->join('student_class sc', 'sc.student_id = s.id', 'left') ->join('classSection cs', 'cs.class_section_id = sc.class_section_id', 'left') ->where('s.parent_id', $parentId) ->orderBy('s.firstname', 'ASC') ->orderBy('s.lastname', 'ASC'); if ($schoolYear !== '') { $builder->where('sc.school_year', $schoolYear); } $students = $this->uniqueStudentRows($builder->get()->getResultArray()); $studentIds = array_values(array_filter(array_map(static fn ($s) => (int) ($s['id'] ?? 0), $students))); $ackMap = []; $reportAvailableMap = $this->reportAvailabilityMap($studentIds, $schoolYear); if (! empty($studentIds)) { $ackQuery = $this->ackModel ->where('parent_id', $parentId) ->where('school_year', $schoolYear) ->whereIn('student_id', $studentIds); $rows = $ackQuery->findAll(); foreach ($rows as $row) { $semester = $this->selectedSemester($row['semester'] ?? ''); $ackMap[$this->semesterStudentKey((int) $row['student_id'], $semester)] = $row; } } $reportRows = []; foreach ($students as $student) { foreach ($semesterOptions as $semester) { $reportRows[] = [ 'student' => $student, 'semester' => $semester, 'key' => $this->semesterStudentKey((int) ($student['id'] ?? 0), $semester), ]; } } return view('parent/report_cards', [ 'students' => $students, 'reportRows' => $reportRows, 'ackMap' => $ackMap, 'reportAvailableMap' => $reportAvailableMap, 'schoolYear' => $schoolYear, 'semesterOptions' => $semesterOptions, 'isEditable' => ! $schoolYearContext->isReadonly(), ]); } public function view($studentId) { $parentId = $this->resolvePrimaryParentId(); if (! $parentId) { return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.'); } $student = $this->studentModel->find((int) $studentId); if (! $student || (int) ($student['parent_id'] ?? 0) !== $parentId) { throw new PageNotFoundException('Student not found.'); } $schoolYearContext = $this->resolveSchoolYearContext(); $schoolYear = trim($schoolYearContext->yearName()); $semester = $this->selectedSemester($this->request->getGet('semester')); if (! $this->reportExists((int) $studentId, $schoolYear, $semester)) { return redirect()->to(site_url('parent/report-cards')) ->with('error', 'No report card exists for the selected school year and semester.'); } if (! $schoolYearContext->isReadonly()) { $this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [ 'viewed_at' => date('Y-m-d H:i:s'), ]); } $url = site_url('report-card/student/' . (int) $studentId); $query = []; if ($schoolYear !== '') { $query['school_year'] = $schoolYear; } if ($semester !== '') { $query['semester'] = $semester; } if ($query) { $url .= '?' . http_build_query($query); } return redirect()->to($url); } public function sign($studentId) { $parentId = $this->resolvePrimaryParentId(); if (! $parentId) { return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.'); } $schoolYearContext = $this->resolveSchoolYearContext(); try { $this->assertSchoolYearWritable($schoolYearContext); } catch (SchoolYearWriteConflictException $e) { return redirect()->back()->withInput()->with('error', $e->getMessage()); } $student = $this->studentModel->find((int) $studentId); if (! $student || (int) ($student['parent_id'] ?? 0) !== $parentId) { throw new PageNotFoundException('Student not found.'); } $name = trim((string) $this->request->getPost('signed_name')); if ($name === '') { return redirect()->back()->with('error', 'Please type your full name to sign.'); } $schoolYear = trim($schoolYearContext->yearName()); $semester = $this->selectedSemester($this->request->getPost('semester')); if (! $this->reportExists((int) $studentId, $schoolYear, $semester)) { return redirect()->to(site_url('parent/report-cards')) ->with('error', 'No report card exists for the selected school year and semester.'); } $now = date('Y-m-d H:i:s'); $this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [ 'viewed_at' => $now, 'signed_at' => $now, 'signed_name' => $name, 'signer_ip' => $this->request->getIPAddress(), ]); return redirect()->to(site_url('parent/report-cards'))->with('success', 'Report card acknowledged.'); } protected function selectedSemester($semester): string { $selected = trim((string) ($semester ?? '')); if ($selected === '') { $selected = trim((string) (getSemester() ?? '')); } $normalized = strtolower($selected); if ($normalized === 'fall' || $normalized === 'first' || str_contains($normalized, 'fall') || str_contains($normalized, '1')) { return 'Fall'; } if ($normalized === 'spring' || $normalized === 'second' || str_contains($normalized, 'spring') || str_contains($normalized, '2')) { return 'Spring'; } return $selected !== '' ? $selected : 'Fall'; } protected function uniqueStudentRows(array $students): array { $unique = []; foreach ($students as $student) { $studentId = (int) ($student['id'] ?? 0); if ($studentId <= 0) { continue; } if (! isset($unique[$studentId])) { $unique[$studentId] = $student; continue; } if ( empty($unique[$studentId]['class_section_name']) && ! empty($student['class_section_name']) ) { $unique[$studentId]['class_section_name'] = $student['class_section_name']; } } return array_values($unique); } protected function semesterOptions(string $schoolYear, string $selectedSemester): array { $options = ['Fall', 'Spring']; if ($schoolYear !== '') { $rows = $this->db->table('semester_scores') ->select('DISTINCT semester', false) ->where('school_year', $schoolYear) ->where('semester IS NOT NULL', null, false) ->where('semester !=', '') ->orderBy('semester', 'ASC') ->get() ->getResultArray(); foreach ($rows as $row) { $semester = $this->selectedSemester($row['semester'] ?? ''); if ($semester !== '' && ! in_array($semester, $options, true)) { $options[] = $semester; } } } if ($selectedSemester !== '' && ! in_array($selectedSemester, $options, true)) { $options[] = $selectedSemester; } return array_values(array_unique($options)); } protected function resolvePrimaryParentId(): ?int { $parentId = (int) (session()->get('user_id') ?? 0); $userType = (string) ($_SESSION['user_type'] ?? ''); if ($userType === 'primary') { return $parentId ?: null; } if ($userType === 'secondary') { $row = $this->db->table('parents') ->select('parent_id') ->where('secondparent_user_id', $parentId) ->get() ->getRowArray(); return $row ? (int) ($row['parent_id'] ?? 0) : null; } if ($userType === 'tertiary') { $row = $this->db->table('authorized_users') ->select('user_id as parent_id') ->where('authorized_user_id', $parentId) ->get() ->getRowArray(); return $row ? (int) ($row['parent_id'] ?? 0) : null; } return $parentId ?: null; } protected function reportAvailabilityMap(array $studentIds, string $schoolYear): array { $studentIds = array_values(array_filter(array_map('intval', $studentIds), static fn ($id) => $id > 0)); if (empty($studentIds) || $schoolYear === '') { return []; } $builder = $this->db->table('semester_scores') ->select('student_id, semester') ->whereIn('student_id', $studentIds) ->where('school_year', $schoolYear); $rows = $builder ->groupBy('student_id, semester') ->get() ->getResultArray(); $map = []; foreach ($rows as $row) { $sid = (int) ($row['student_id'] ?? 0); if ($sid > 0) { $semester = $this->selectedSemester($row['semester'] ?? ''); $map[$this->semesterStudentKey($sid, $semester)] = true; } } return $map; } protected function semesterStudentKey(int $studentId, string $semester): string { return $studentId . '|' . $this->selectedSemester($semester); } protected function reportExists(int $studentId, string $schoolYear, string $semester): bool { if ($studentId <= 0 || $schoolYear === '') { return false; } $builder = $this->db->table('semester_scores') ->select('id') ->where('student_id', $studentId) ->where('school_year', $schoolYear) ->limit(1); $semesterVariants = $this->semesterVariants($semester); if (! empty($semesterVariants)) { $builder->whereIn('semester', $semesterVariants); } return (bool) $builder->get()->getRowArray(); } protected function semesterVariants(string $semester): array { $raw = trim($semester); if ($raw === '') { return []; } $normalized = strtolower($raw); if ($normalized === 'fall' || $normalized === 'first' || str_contains($normalized, 'fall') || str_contains($normalized, '1')) { $values = ['Fall', 'fall', 'First', 'first', 'Semester 1', 'semester 1', 'Semester1', 'semester1', 'Sem 1', 'sem 1', '1', '01', 'S1', 's1']; } elseif ($normalized === 'spring' || $normalized === 'second' || str_contains($normalized, 'spring') || str_contains($normalized, '2')) { $values = ['Spring', 'spring', 'Second', 'second', 'Semester 2', 'semester 2', 'Semester2', 'semester2', 'Sem 2', 'sem 2', '2', '02', 'S2', 's2']; } else { $values = [$raw]; } $values[] = $raw; return array_values(array_unique($values)); } protected function touchAcknowledgement( int $parentId, int $studentId, string $schoolYear, string $semester, array $values ): void { $criteria = [ 'parent_id' => $parentId, 'student_id' => $studentId, 'school_year' => $schoolYear, 'semester' => $semester, ]; $existing = $this->ackModel->where($criteria)->first(); if ($existing) { $this->ackModel->update((int) $existing['id'], $values); return; } $this->ackModel->insert(array_merge($criteria, $values)); } }