add features to family card, scores, invoices and students details
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user