Files
alrahma_sunday_school/app/Services/StudentScoreHistoryService.php
T
root ac19226bf0
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 49s
Tests / PHPUnit (push) Successful in 1m51s
add features to family card, scores, invoices and students details
2026-09-09 23:02:16 -04:00

236 lines
9.1 KiB
PHP

<?php
namespace App\Services;
use CodeIgniter\Database\BaseConnection;
final class StudentScoreHistoryService
{
public function __construct(private readonly BaseConnection $db)
{
}
/**
* Build one score-history row per student and school year.
*
* @return array<int, array<int, array<string, mixed>>>
*/
public function forStudents(array $studentIds): array
{
$studentIds = array_values(array_unique(array_filter(
array_map('intval', $studentIds),
static fn(int $id): bool => $id > 0
)));
if ($studentIds === []) {
return [];
}
$history = [];
$semesterRows = $this->latestSemesterRows($studentIds);
foreach ($semesterRows as $row) {
$studentId = (int) ($row['student_id'] ?? 0);
$schoolYear = trim((string) ($row['school_year'] ?? ''));
$semester = $this->normalizeSemester($row['semester'] ?? '');
if ($studentId <= 0 || $schoolYear === '' || !in_array($semester, ['fall', 'spring'], true)) {
continue;
}
$history[$studentId][$schoolYear] ??= $this->emptyYear($schoolYear);
if ($semester === 'fall') {
$history[$studentId][$schoolYear]['midterm_s1'] = $this->score($row['midterm_exam_score'] ?? null);
$history[$studentId][$schoolYear]['ptap_s1'] = $this->score($row['ptap_score'] ?? null);
$history[$studentId][$schoolYear]['attendance_s1'] = $this->score($row['attendance_score'] ?? null);
$history[$studentId][$schoolYear]['_semester_score_s1'] = $this->score($row['semester_score'] ?? null);
} else {
$finalScore = $this->score($row['final_exam_score'] ?? null);
if ($finalScore === null) {
// Some older Spring rows stored the final in this legacy column.
$finalScore = $this->score($row['midterm_exam_score'] ?? null);
}
$history[$studentId][$schoolYear]['final_s2'] = $finalScore;
$history[$studentId][$schoolYear]['ptap_s2'] = $this->score($row['ptap_score'] ?? null);
$history[$studentId][$schoolYear]['attendance_s2'] = $this->score($row['attendance_score'] ?? null);
$history[$studentId][$schoolYear]['_semester_score_s2'] = $this->score($row['semester_score'] ?? null);
}
}
foreach ($this->commentRows($studentIds) as $row) {
$studentId = (int) ($row['student_id'] ?? 0);
$schoolYear = trim((string) ($row['school_year'] ?? ''));
$semester = $this->normalizeSemester($row['semester'] ?? '');
$type = $this->normalizeCommentType($row['score_type'] ?? '');
if ($studentId <= 0 || $schoolYear === '') {
continue;
}
$history[$studentId][$schoolYear] ??= $this->emptyYear($schoolYear);
$field = match ($semester . ':' . $type) {
'fall:midterm' => 'midterm_comment_s1',
'fall:ptap' => 'ptap_comment_s1',
'fall:attendance' => 'attendance_comment_s1',
'spring:final' => 'final_comment_s2',
'spring:ptap' => 'ptap_comment_s2',
'spring:attendance' => 'attendance_comment_s2',
default => null,
};
if ($field === null || $history[$studentId][$schoolYear][$field] !== '') {
continue;
}
$comment = trim((string) ($row['comment'] ?? ''));
$review = trim((string) ($row['comment_review'] ?? ''));
$history[$studentId][$schoolYear][$field] = $type === 'attendance'
? ($comment !== '' ? $comment : $review)
: ($review !== '' ? $review : $comment);
}
foreach ($this->decisionRows($studentIds) as $row) {
$studentId = (int) ($row['student_id'] ?? 0);
$schoolYear = trim((string) ($row['school_year'] ?? ''));
if ($studentId <= 0 || $schoolYear === '') {
continue;
}
$history[$studentId][$schoolYear] ??= $this->emptyYear($schoolYear);
if ($history[$studentId][$schoolYear]['year_score'] === null) {
$history[$studentId][$schoolYear]['year_score'] = $this->score($row['year_score'] ?? null);
}
}
foreach ($history as &$studentHistory) {
foreach ($studentHistory as &$year) {
if ($year['year_score'] === null) {
$semesterScores = array_values(array_filter(
[$year['_semester_score_s1'], $year['_semester_score_s2']],
static fn($score): bool => $score !== null
));
if ($semesterScores !== []) {
$year['year_score'] = round(array_sum($semesterScores) / count($semesterScores), 1);
}
}
unset($year['_semester_score_s1'], $year['_semester_score_s2']);
}
unset($year);
krsort($studentHistory, SORT_STRING);
$studentHistory = array_values($studentHistory);
}
unset($studentHistory);
return $history;
}
private function latestSemesterRows(array $studentIds): array
{
if (!$this->db->tableExists('semester_scores')) {
return [];
}
$rows = $this->db->table('semester_scores')
->select('id, student_id, school_year, semester, midterm_exam_score, final_exam_score, ptap_score, attendance_score, semester_score, updated_at')
->whereIn('student_id', $studentIds)
->orderBy('school_year', 'DESC')
->orderBy('updated_at', 'DESC')
->orderBy('id', 'DESC')
->get()
->getResultArray();
$latest = [];
foreach ($rows as $row) {
$key = (int) ($row['student_id'] ?? 0)
. '|' . trim((string) ($row['school_year'] ?? ''))
. '|' . $this->normalizeSemester($row['semester'] ?? '');
$latest[$key] ??= $row;
}
return array_values($latest);
}
private function commentRows(array $studentIds): array
{
if (!$this->db->tableExists('score_comments')) {
return [];
}
$builder = $this->db->table('score_comments')
->select('id, student_id, school_year, semester, score_type, comment, comment_review')
->whereIn('student_id', $studentIds);
if ($this->db->fieldExists('updated_at', 'score_comments')) {
$builder->orderBy('updated_at', 'DESC');
} elseif ($this->db->fieldExists('created_at', 'score_comments')) {
$builder->orderBy('created_at', 'DESC');
}
return $builder->orderBy('id', 'DESC')->get()->getResultArray();
}
private function decisionRows(array $studentIds): array
{
if (!$this->db->tableExists('student_decisions')) {
return [];
}
return $this->db->table('student_decisions')
->select('id, student_id, school_year, year_score')
->whereIn('student_id', $studentIds)
->orderBy('school_year', 'DESC')
->orderBy('updated_at', 'DESC')
->orderBy('id', 'DESC')
->get()
->getResultArray();
}
private function emptyYear(string $schoolYear): array
{
return [
'school_year' => $schoolYear,
'midterm_s1' => null,
'midterm_comment_s1' => '',
'ptap_s1' => null,
'ptap_comment_s1' => '',
'attendance_s1' => null,
'attendance_comment_s1' => '',
'final_s2' => null,
'final_comment_s2' => '',
'ptap_s2' => null,
'ptap_comment_s2' => '',
'attendance_s2' => null,
'attendance_comment_s2' => '',
'year_score' => null,
'_semester_score_s1' => null,
'_semester_score_s2' => null,
];
}
private function normalizeSemester($value): string
{
$semester = strtolower(trim((string) $value));
return match ($semester) {
'fall', 'first', 'first semester', 'semester 1', '1' => 'fall',
'spring', 'second', 'second semester', 'semester 2', '2' => 'spring',
default => $semester,
};
}
private function normalizeCommentType($value): string
{
$type = strtolower(trim((string) $value));
$type = trim((string) preg_replace('/[^a-z0-9]+/', '_', $type), '_');
return match ($type) {
'midterm_comment', 'midterm_comments' => 'midterm',
'final_comment', 'final_comments' => 'final',
'ptap_comment', 'ptap_comments' => 'ptap',
'attendance_comment', 'attendance_comments', 'attendence', 'attendence_comment' => 'attendance',
default => $type,
};
}
private function score($value): ?float
{
return $value !== null && $value !== '' && is_numeric($value)
? round((float) $value, 2)
: null;
}
}