computeStickerCounts($schoolYear, null, $semester); } // Compute per-student sticker counts for one class section. protected function computeStickerCountsForClass(\CodeIgniter\Database\BaseConnection $db, string $schoolYear, int $classSectionId, ?string $semester = null): array { return $this->computeStickerCounts($schoolYear, $classSectionId, $semester); } protected function computeStickerCounts( string $schoolYear, ?int $classSectionId, ?string $semester ): array { $specialMap = ['KG' => 1, '1-A' => 3, '1-B' => 4, '2-A' => 5, '2-B' => 5, '9' => 2]; $isUpperBlock = static fn(string $g) => (bool) preg_match('/^(3|4|5|6|7|8)(-[AB])?$/', trim($g)); $isGrade5 = static fn(string $g) => (bool) preg_match('/^5(\-|$)/', trim($g)); $b = $this->db->table('student_class sc') ->select('sc.student_id, s.firstname, s.lastname, s.is_new, cs.class_section_name AS grade_label') ->join('students s', 's.id = sc.student_id', 'inner') ->join('classSection cs', 'cs.class_section_id = sc.class_section_id', 'inner') ->join('classes c', 'c.id = cs.class_id', 'inner') ->where('sc.school_year', $schoolYear); if (!empty($classSectionId)) { $b->where('sc.class_section_id', $classSectionId); } $rows = $b->get()->getResultArray(); // Exclude Youth and KG (case-insensitive; also ignore variants like youth-*, KG-*) $rows = array_values(array_filter($rows, static function ($r) { $g = trim((string) $r['grade_label']); return $g !== '' && !preg_match('/^youth(?:\b|-)/i', $g) && !preg_match('/^kg(?:\b|-)/i', $g); })); $students = []; $total = 0; foreach ($rows as $r) { $g = trim((string) $r['grade_label']); $isNew = ((int) $r['is_new']) === 1; // Primary-only rules if (array_key_exists($g, $specialMap)) { $p = $specialMap[$g]; } elseif ($isUpperBlock($g)) { $p = $isNew ? 4 : ($isGrade5($g) ? 3 : 2); } else { $p = 4; } $total += $p; $students[] = [ 'student_id' => (int) $r['student_id'], 'firstname' => trim($r['firstname']), 'lastname' => trim($r['lastname']), 'grade_label' => $g, 'primary_count' => $p, ]; } return [ 'students' => $students, 'totals' => [ 'stickers' => $total, 'students' => count($students), ], ]; } /** * Compute sticker counts per student (no file I/O). * * @param BaseConnection $db * @param string $schoolYear e.g. "2025-2026" * @param string|null $semester e.g. "Fall" (optional filter) * @return array{ * students: array, * totals: array{primary:int, secondary:int, students:int} * } */ private function getStickerCountsPerStudent(string $schoolYear, ?string $semester = null): array { // --- Sticker rules helpers ---------------------------------------------- $specialMap = [ 'KG' => 1, '1-A' => 3, '1-B' => 4, '2-A' => 5, '2-B' => 5, '9' => 2, ]; $isUpperBlock = static function (string $g): bool { // 3-A, 3-B, 4-A, 4-B, 5-A, 5-B, 6, 7, 8 (also allow plain 3/4/5/6/7/8) return (bool) preg_match('/^(3|4|5|6|7|8)(-[AB])?$/', trim($g)); }; $isGrade5 = static function (string $g): bool { // "5" OR "5-A"/"5-B" return (bool) preg_match('/^5(\-|$)/', trim($g)); }; // --- Pull roster: student_class -> classSection -> classes -> students --- $builder = $this->db->table('student_class sc') ->select([ 'sc.student_id', 's.firstname', 's.lastname', 's.is_new', 'cs.class_section_name AS grade_label', ]) ->join('students s', 's.id = sc.student_id') ->join('classSection cs', 'cs.class_section_id = sc.class_section_id') ->join('classes c', 'c.id = cs.class_id') ->where('sc.school_year', $schoolYear); $rows = $builder->get()->getResultArray(); // Filter out Youth (any case; includes youth-*), and empty labels $rows = array_values(array_filter($rows, static function ($r) { $g = trim((string) $r['grade_label']); return $g !== '' && strcasecmp($g, 'youth') !== 0 && strncasecmp($g, 'youth-', 6) !== 0; })); $out = []; $totalPrimary = 0; $totalSecondary = 0; foreach ($rows as $r) { $first = trim((string) $r['firstname']); $last = trim((string) $r['lastname']); $grade = trim((string) $r['grade_label']); $isNew = ((int) $r['is_new']) === 1; $primary = 0; $secondary = 0; // 1) Special fixed grades if (array_key_exists($grade, $specialMap)) { $primary = (int) $specialMap[$grade]; $secondary = 0; } // 2) Upper block 3..8 elseif ($isUpperBlock($grade)) { if ($isNew) { $primary = 4; $secondary = 0; } else { $primary = $isGrade5($grade) ? 3 : 2; $secondary = max(0, 4 - $primary); } } // 3) Fallback for other non-Youth labels (e.g., "1", "2", "10", "11") else { $primary = 4; $secondary = 0; } $totalPrimary += $primary; $totalSecondary += $secondary; $out[] = [ 'student_id' => (int) $r['student_id'], 'firstname' => $first, 'lastname' => $last, 'grade_label' => $grade, 'primary_count' => $primary, 'secondary_count' => $secondary, ]; } return [ 'students' => $out, 'totals' => [ 'primary' => $totalPrimary, 'secondary' => $totalSecondary, 'students' => count($out), ], ]; } protected function getStickerCountsPerStudentForClass( string $schoolYear, int $classSectionId, ?string $semester = null ): array { // Reuse exact helpers/rules from getStickerCountsPerStudent() $specialMap = [ '1-A' => 3, '1-B' => 4, '2-A' => 5, '2-B' => 5, '9' => 2, ]; $isUpperBlock = static fn($g) => (bool) preg_match('/^(3|4|5|6|7|8)(-[AB])?$/', trim($g)); $isGrade5 = static fn($g) => (bool) preg_match('/^5(\-|$)/', trim($g)); $b = $this->db->table('student_class sc') ->select('sc.student_id, s.firstname, s.lastname, s.is_new, cs.class_section_name AS grade_label') ->join('students s', 's.id = sc.student_id') ->join('classSection cs', 'cs.class_section_id = sc.class_section_id') ->join('classes c', 'c.id = cs.class_id') ->where('sc.school_year', $schoolYear) ->where('sc.class_section_id', $classSectionId); $rows = $b->get()->getResultArray(); $rows = array_values(array_filter($rows, static function ($r) { $g = trim((string) $r['grade_label']); return $g !== '' && strcasecmp($g, 'youth') !== 0 && strncasecmp($g, 'youth-', 6) !== 0; })); $out = []; $tp = 0; $ts = 0; foreach ($rows as $r) { $g = trim((string) $r['grade_label']); $isNew = ((int) $r['is_new']) === 1; $p = 0; $s = 0; if (isset($specialMap[$g])) { $p = $specialMap[$g]; } elseif ($isUpperBlock($g)) { if ($isNew) { $p = 4; } else { $p = $isGrade5($g) ? 3 : 2; $s = max(0, 4 - $p); } } else { $p = 4; } $tp += $p; $ts += $s; $out[] = [ 'student_id' => (int) $r['student_id'], 'firstname' => trim($r['firstname']), 'lastname' => trim($r['lastname']), 'grade_label' => $g, 'primary_count' => $p, 'secondary_count' => $s, ]; } return ['students' => $out, 'totals' => ['primary' => $tp, 'secondary' => $ts, 'students' => count($out)]]; } public function previewStickerCounts() { // Optional: allow class_id filter; if present, preview only that class $classId = (int) ($this->request->getGet('class_id') ?? 0); if ($classId > 0) { // same join as getStickerCountsPerStudent but add class filter $result = $this->getStickerCountsPerStudentForClass($this->schoolYear, $classId, $this->semester); } else { // whole school year $result = $this->getStickerCountsPerStudent($this->schoolYear, $this->semester); } return $this->response->setJSON([ 'status' => 'ok', 'data' => $result ]); } public function studentsByClass(int $classSectionId) { $rows = $this->studentModel ->select('students.id, students.firstname, students.lastname, students.gender, cs.class_section_name AS registration_grade') ->join('student_class sc', 'sc.student_id = students.id', 'inner') ->join('classSection cs', 'cs.class_section_id = sc.class_section_id', 'inner') ->where('sc.school_year', $this->schoolYear) ->where('sc.class_section_id', $classSectionId) ->groupBy('students.id, students.firstname, students.lastname, students.gender, cs.class_section_name') ->orderBy('students.firstname', 'ASC') ->orderBy('students.lastname', 'ASC') ->findAll(1000); return $this->response->setJSON($rows); } }