fix reportcard and certificate
This commit is contained in:
@@ -845,6 +845,7 @@ class ReportCardsController extends PrintablesBaseController
|
||||
$studentName = trim(($data['student']['firstname'] ?? '') . ' ' . ($data['student']['lastname'] ?? ''));
|
||||
$gradeLabel = (string)($data['grade'] ?? ($data['class_section_name'] ?? 'N/A'));
|
||||
$today = (string)($data['report_date_display'] ?? date('m-d-Y'));
|
||||
$termRank = trim((string)($data['term_rank_display'] ?? ''));
|
||||
$firstSemScore = $data['first_semester_score'] ?? null;
|
||||
$secondSemScore = $data['second_semester_score'] ?? ($data['total_score'] ?? null);
|
||||
$finalAverage = $data['final_average'] ?? null;
|
||||
@@ -980,54 +981,91 @@ class ReportCardsController extends PrintablesBaseController
|
||||
$gradeY = $pdf->GetY();
|
||||
$gradePad = max(0, 2 - (4 * 0.3528)); // reduce indentation by 4pt
|
||||
|
||||
$drawGradeCell = static function (\FPDF $pdf, float $x, float $y, float $w, float $h, string $label, string $value, float $pad) {
|
||||
$pdf->Rect($x, $y, $w, $h);
|
||||
$pdf->SetXY($x + $pad, $y + 3);
|
||||
$pdf->SetFont('Helvetica', 'B', 11);
|
||||
$pdf->Write(5, $label . ' ');
|
||||
$labelWidth = $pdf->GetStringWidth($label . ' ');
|
||||
$pdf->SetFont('Helvetica', '', 12);
|
||||
$pdf->SetXY($x + 2 + $labelWidth, $y + 3);
|
||||
$pdf->Write(5, $value);
|
||||
};
|
||||
$drawGradeCell = static function (
|
||||
\FPDF $pdf,
|
||||
float $x,
|
||||
float $y,
|
||||
float $w,
|
||||
float $h,
|
||||
string $label,
|
||||
string $value,
|
||||
float $pad
|
||||
) {
|
||||
$pdf->Rect($x, $y, $w, $h);
|
||||
$pdf->SetXY($x + $pad, $y + 3);
|
||||
$pdf->SetFont('Helvetica', 'B', 11);
|
||||
$pdf->Write(5, $label . ' ');
|
||||
$labelWidth = $pdf->GetStringWidth($label . ' ');
|
||||
$pdf->SetFont('Helvetica', '', 12);
|
||||
$pdf->SetXY($x + 2 + $labelWidth, $y + 3);
|
||||
$pdf->Write(5, $value);
|
||||
};
|
||||
$drawRankCell = static function (
|
||||
\FPDF $pdf,
|
||||
float $x,
|
||||
float $y,
|
||||
float $w,
|
||||
float $h,
|
||||
string $rankValue,
|
||||
float $pad
|
||||
) {
|
||||
$rankValue = trim($rankValue) !== '' ? trim($rankValue) : 'N/A';
|
||||
|
||||
if ($semNum === 2) {
|
||||
$firstLabel = ' 1st Semester Grade:';
|
||||
$firstValue = $numFmt($firstSemScore) . '/100';
|
||||
$secondLabel = ' 2nd Semester Grade:';
|
||||
$secondValue = $numFmt($effectiveSecond) . '/100';
|
||||
$drawGradeCell($pdf, $gradeX, $gradeY, $gradeCellWidth, $gradeCellHeight, $firstLabel, $firstValue, $gradePad);
|
||||
$drawGradeCell($pdf, $gradeX + $gradeCellWidth, $gradeY, $gradeCellWidth, $gradeCellHeight, $secondLabel, $secondValue, $gradePad);
|
||||
} else {
|
||||
$gradeLabel = ' 1st Semester Grade:';
|
||||
$gradeValue = $numFmt($effectiveSecond) . '/100';
|
||||
$drawGradeCell($pdf, $gradeX, $gradeY, $gradeCellWidth, $gradeCellHeight, $gradeLabel, $gradeValue, $gradePad);
|
||||
}
|
||||
$pdf->SetY($gradeY + $gradeCellHeight);
|
||||
$pdf->Rect($x, $y, $w, $h);
|
||||
$pdf->SetXY($x + $pad, $y + 3);
|
||||
$pdf->SetFont('Helvetica', 'B', 11);
|
||||
$pdf->Write(5, ' Ranking: ');
|
||||
|
||||
$finalScoreRowHeight = 12;
|
||||
$finalScoreEndY = null;
|
||||
// Show Final Score only for second semester / Spring
|
||||
if ($semNum === 2) {
|
||||
$finalLabel = 'Final Score****:';
|
||||
$finalValue = (is_string($finalScoreVal) ? $finalScoreVal : $numFmt($finalScoreVal)) . '/100';
|
||||
$finalCellWidth = 65; // match Total Semester Days column width
|
||||
$finalCellHeight = $finalScoreRowHeight;
|
||||
$finalX = $pdf->GetX();
|
||||
$finalY = $pdf->GetY();
|
||||
$pdf->Rect($finalX, $finalY, $finalCellWidth, $finalCellHeight);
|
||||
$finalPad = max(0, 2 - (4 * 0.3528)); // reduce indentation by 4pt
|
||||
$pdf->SetXY($finalX + $finalPad, $finalY + 3);
|
||||
$pdf->SetFont('Helvetica', 'B', 11);
|
||||
$pdf->Write(5, ' ' . $finalLabel . ' ');
|
||||
$finalLabelWidth = $pdf->GetStringWidth(' ' . $finalLabel . ' ');
|
||||
$pdf->SetFont('Helvetica', '', 12);
|
||||
$pdf->SetXY($finalX + 2 + $finalLabelWidth, $finalY + 3);
|
||||
$pdf->Write(5, $finalValue);
|
||||
$pdf->Ln($finalCellHeight);
|
||||
$finalScoreEndY = $finalY + $finalCellHeight;
|
||||
}
|
||||
$scoresEndY = $pdf->GetY();
|
||||
$labelWidth = $pdf->GetStringWidth(' Ranking: ');
|
||||
$pdf->SetFont('Helvetica', '', 12);
|
||||
$pdf->SetXY($x + 2 + $labelWidth, $y + 3);
|
||||
$pdf->Write(5, $rankValue);
|
||||
};
|
||||
if ($semNum === 2) {
|
||||
$firstLabel = ' 1st Semester Grade:';
|
||||
$firstValue = $numFmt($firstSemScore) . '/100';
|
||||
|
||||
$secondLabel = ' 2nd Semester Grade:';
|
||||
$secondValue = $numFmt($effectiveSecond) . '/100';
|
||||
|
||||
$drawGradeCell($pdf, $gradeX, $gradeY, $gradeCellWidth, $gradeCellHeight, $firstLabel, $firstValue, $gradePad);
|
||||
$drawGradeCell($pdf, $gradeX + $gradeCellWidth, $gradeY, $gradeCellWidth, $gradeCellHeight, $secondLabel, $secondValue, $gradePad);
|
||||
} else {
|
||||
$gradeLabel = ' 1st Semester Grade:';
|
||||
$gradeValue = $numFmt($effectiveSecond) . '/100';
|
||||
|
||||
$drawGradeCell($pdf, $gradeX, $gradeY, $gradeCellWidth, $gradeCellHeight, $gradeLabel, $gradeValue, $gradePad);
|
||||
|
||||
// Fall: Ranking goes to the right of 1st Semester Grade.
|
||||
$drawRankCell($pdf, $gradeX + $gradeCellWidth, $gradeY, $gradeCellWidth, $gradeCellHeight, $termRank, $gradePad);
|
||||
}
|
||||
|
||||
$pdf->SetY($gradeY + $gradeCellHeight);
|
||||
|
||||
$finalScoreRowHeight = 12;
|
||||
$finalScoreEndY = null;
|
||||
|
||||
// Show Final Score only for second semester / Spring.
|
||||
if ($semNum === 2) {
|
||||
$finalLabel = 'Final Score****:';
|
||||
$finalValue = (is_string($finalScoreVal) ? $finalScoreVal : $numFmt($finalScoreVal)) . '/100';
|
||||
|
||||
$finalCellWidth = 65;
|
||||
$finalCellHeight = $finalScoreRowHeight;
|
||||
$finalX = $pdf->GetX();
|
||||
$finalY = $pdf->GetY();
|
||||
$finalPad = max(0, 2 - (4 * 0.3528));
|
||||
|
||||
$drawGradeCell($pdf, $finalX, $finalY, $finalCellWidth, $finalCellHeight, ' ' . $finalLabel, $finalValue, $finalPad);
|
||||
|
||||
// Spring: Ranking goes to the right of Final Score.
|
||||
$drawRankCell($pdf, $finalX + $finalCellWidth, $finalY, $finalCellWidth, $finalCellHeight, $termRank, $finalPad);
|
||||
|
||||
$pdf->SetY($finalY + $finalCellHeight);
|
||||
$finalScoreEndY = $finalY + $finalCellHeight;
|
||||
}
|
||||
|
||||
$scoresEndY = $pdf->GetY();
|
||||
|
||||
// Legend anchored near bottom with a 3mm buffer (dynamic placement)
|
||||
$examLegendLabel = ($semNum === 1) ? 'Midterm Exam' : 'Final Exam';
|
||||
@@ -1546,6 +1584,15 @@ class ReportCardsController extends PrintablesBaseController
|
||||
$firstSemesterScore = $secondSemesterScore;
|
||||
}
|
||||
|
||||
$termRanking = $this->calculateTermRanking(
|
||||
$studentId,
|
||||
$sectionCode,
|
||||
$sectionId,
|
||||
$refYear,
|
||||
$refSemester !== '' ? $refSemester : (string)($score['semester'] ?? ''),
|
||||
$secondSemesterScore
|
||||
);
|
||||
|
||||
// Total semester days from attendance records (max total_attendance within same term)
|
||||
$totalSemesterDays = null;
|
||||
|
||||
@@ -1672,12 +1719,138 @@ class ReportCardsController extends PrintablesBaseController
|
||||
'second_semester_score' => $secondSemesterScore,
|
||||
'first_semester_score' => $firstSemesterScore,
|
||||
'final_average' => $finalAverage,
|
||||
'term_rank' => $termRanking,
|
||||
'term_rank_display' => $termRanking['display'] ?? null,
|
||||
'comments' => $commentMap,
|
||||
'total_score' => $secondSemesterScore,
|
||||
'total_attendance_days' => $totalSemesterDays,
|
||||
];
|
||||
}
|
||||
|
||||
private function calculateTermRanking(
|
||||
int $studentId,
|
||||
int $sectionCode,
|
||||
?int $sectionId,
|
||||
string $schoolYear,
|
||||
?string $semester,
|
||||
?float $studentScore
|
||||
): ?array {
|
||||
if ($studentId <= 0 || $schoolYear === '' || $studentScore === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sectionIds = array_values(array_unique(array_filter([
|
||||
$sectionCode > 0 ? $sectionCode : null,
|
||||
$sectionId && $sectionId > 0 ? $sectionId : null,
|
||||
])));
|
||||
|
||||
if (empty($sectionIds)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('semester_scores ss')
|
||||
->select('ss.student_id, ss.semester_score, ss.updated_at, ss.id, s.firstname, s.lastname')
|
||||
->join('students s', 's.id = ss.student_id', 'inner')
|
||||
->where('s.is_active', 1)
|
||||
->where('ss.school_year', $schoolYear)
|
||||
->whereIn('ss.class_section_id', $sectionIds)
|
||||
->orderBy('ss.updated_at', 'DESC')
|
||||
->orderBy('ss.id', 'DESC');
|
||||
|
||||
if (trim((string)$semester) !== '') {
|
||||
$this->applySemesterFilter($builder, (string)$semester, 'ss.semester');
|
||||
}
|
||||
|
||||
$rows = $builder->get()->getResultArray();
|
||||
if (empty($rows)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$scoresByStudent = [];
|
||||
foreach ($rows as $row) {
|
||||
$sid = (int)($row['student_id'] ?? 0);
|
||||
if ($sid <= 0 || isset($scoresByStudent[$sid])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$scoreVal = $row['semester_score'] ?? null;
|
||||
if (!is_numeric($scoreVal)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$scoresByStudent[$sid] = [
|
||||
'student_id' => $sid,
|
||||
'score' => round((float)$scoreVal, 4),
|
||||
'firstname' => trim((string)($row['firstname'] ?? '')),
|
||||
'lastname' => trim((string)($row['lastname'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($scoresByStudent) || !isset($scoresByStudent[$studentId])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$rankable = array_values($scoresByStudent);
|
||||
usort($rankable, static function (array $a, array $b): int {
|
||||
$scoreCmp = $b['score'] <=> $a['score'];
|
||||
if ($scoreCmp !== 0) {
|
||||
return $scoreCmp;
|
||||
}
|
||||
|
||||
$lastCmp = strcasecmp($a['lastname'], $b['lastname']);
|
||||
if ($lastCmp !== 0) {
|
||||
return $lastCmp;
|
||||
}
|
||||
|
||||
$firstCmp = strcasecmp($a['firstname'], $b['firstname']);
|
||||
if ($firstCmp !== 0) {
|
||||
return $firstCmp;
|
||||
}
|
||||
|
||||
return $a['student_id'] <=> $b['student_id'];
|
||||
});
|
||||
|
||||
$position = null;
|
||||
$previousScore = null;
|
||||
foreach ($rankable as $index => $row) {
|
||||
if ($previousScore === null || abs($row['score'] - $previousScore) > 0.0001) {
|
||||
$position = $index + 1;
|
||||
$previousScore = $row['score'];
|
||||
}
|
||||
|
||||
if ((int)$row['student_id'] === $studentId) {
|
||||
$total = count($rankable);
|
||||
return [
|
||||
'position' => $position,
|
||||
'total' => $total,
|
||||
'display' => $this->formatOrdinal($position) . ' of ' . $total,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function formatOrdinal(?int $value): string
|
||||
{
|
||||
$n = (int)$value;
|
||||
if ($n <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$mod100 = $n % 100;
|
||||
if ($mod100 >= 11 && $mod100 <= 13) {
|
||||
return $n . 'th';
|
||||
}
|
||||
|
||||
return match ($n % 10) {
|
||||
1 => $n . 'st',
|
||||
2 => $n . 'nd',
|
||||
3 => $n . 'rd',
|
||||
default => $n . 'th',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve teacher & TA names for a class section (by PK or code),
|
||||
* relaxing semester/year if needed. Returns ['teacher_name' => string, 'ta_names' => string[]].
|
||||
|
||||
Reference in New Issue
Block a user