fix certificates
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Controllers\BaseController;
|
||||
use App\Models\ClassSectionModel;
|
||||
use App\Models\ConfigurationModel;
|
||||
use App\Models\CertificateRecordModel;
|
||||
use App\Models\StudentDecisionModel;
|
||||
|
||||
class CertificateController extends BaseController
|
||||
{
|
||||
@@ -27,40 +28,120 @@ class CertificateController extends BaseController
|
||||
public function index()
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$classSectionId = $this->request->getGet('class_section_id');
|
||||
$selectedCsid = $this->request->getGet('class_section_id');
|
||||
$schoolYear = $this->request->getGet('school_year') ?? $this->schoolYear;
|
||||
|
||||
$classSections = $db->table('classSection cs')
|
||||
->select('cs.class_section_id, cs.class_section_name')
|
||||
->join('student_class sc', 'sc.class_section_id = cs.class_section_id')
|
||||
// ── All enrolled students across every class section ───────────────────
|
||||
$allEnrolled = $db->table('student_class sc')
|
||||
->select('s.id AS student_id, s.firstname, s.lastname, sc.class_section_id, cs.class_section_name')
|
||||
->join('students s', 's.id = sc.student_id')
|
||||
->join('classSection cs', 'cs.class_section_id = sc.class_section_id')
|
||||
->where('s.is_active', 1)
|
||||
->where('sc.school_year', $schoolYear)
|
||||
->groupBy('cs.class_section_id, cs.class_section_name')
|
||||
->having('COUNT(s.id) >', 1)
|
||||
->orderBy('cs.class_section_name', 'ASC')
|
||||
->orderBy('s.firstname', 'ASC')
|
||||
->orderBy('s.lastname', 'ASC')
|
||||
->get()->getResultArray();
|
||||
|
||||
$students = [];
|
||||
if ($classSectionId) {
|
||||
$students = $db->table('student_class sc')
|
||||
->select('s.id, s.firstname, s.lastname, cs.class_section_name AS grade')
|
||||
->join('students s', 's.id = sc.student_id')
|
||||
->join('classSection cs', 'cs.class_section_id = sc.class_section_id')
|
||||
->where('sc.class_section_id', (int) $classSectionId)
|
||||
->where('s.is_active', 1)
|
||||
->where('sc.school_year', $schoolYear)
|
||||
->orderBy('s.lastname', 'ASC')
|
||||
->orderBy('s.firstname', 'ASC')
|
||||
->get()->getResultArray();
|
||||
$allIds = array_unique(array_column($allEnrolled, 'student_id'));
|
||||
|
||||
// ── Semester scores ────────────────────────────────────────────────────
|
||||
$allScoreMap = [];
|
||||
if (!empty($allIds)) {
|
||||
foreach ($db->table('semester_scores')
|
||||
->select('student_id, semester, semester_score')
|
||||
->whereIn('student_id', $allIds)
|
||||
->where('school_year', $schoolYear)
|
||||
->where('semester_score IS NOT NULL', null, false)
|
||||
->get()->getResultArray() as $sr) {
|
||||
$allScoreMap[(int)$sr['student_id']][ucfirst(strtolower($sr['semester']))] =
|
||||
is_numeric($sr['semester_score']) ? (float)$sr['semester_score'] : null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Below-60 manual decisions ──────────────────────────────────────────
|
||||
$allBelowMap = [];
|
||||
if (!empty($allIds)) {
|
||||
foreach ($db->table('below_sixty_decisions')
|
||||
->whereIn('student_id', $allIds)
|
||||
->where('school_year', $schoolYear)
|
||||
->get()->getResultArray() as $b) {
|
||||
$allBelowMap[(int)$b['student_id']][ucfirst(strtolower($b['semester']))] = (string)$b['decision'];
|
||||
}
|
||||
}
|
||||
|
||||
// ── Certificate records (most recent per student) ──────────────────────
|
||||
$certsByStudent = [];
|
||||
if (!empty($allIds)) {
|
||||
foreach ($db->table('certificate_records')
|
||||
->select('student_id, certificate_number, issued_at')
|
||||
->whereIn('student_id', $allIds)
|
||||
->where('school_year', $schoolYear)
|
||||
->orderBy('issued_at', 'DESC')
|
||||
->get()->getResultArray() as $c) {
|
||||
$sid = (int)$c['student_id'];
|
||||
if (!isset($certsByStudent[$sid])) {
|
||||
$certsByStudent[$sid] = $c['certificate_number'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Build per-student decisions + per-class buckets ────────────────────
|
||||
$decisionsByStudent = [];
|
||||
$studentsByClass = []; // [csid => [student rows...]]
|
||||
$statsPerClass = []; // [csid => {name, total, pass, cert}]
|
||||
|
||||
foreach ($allEnrolled as $row) {
|
||||
$sid = (int)$row['student_id'];
|
||||
$csid = (int)$row['class_section_id'];
|
||||
|
||||
// Decisions per semester
|
||||
foreach ($allScoreMap[$sid] ?? [] as $sem => $score) {
|
||||
if ($score === null) continue;
|
||||
if ($score >= 60) {
|
||||
$dec = 'Pass'; $src = 'auto';
|
||||
} elseif (!empty($allBelowMap[$sid][$sem])) {
|
||||
$dec = $allBelowMap[$sid][$sem]; $src = 'manual';
|
||||
} else {
|
||||
$dec = ''; $src = 'pending';
|
||||
}
|
||||
$decisionsByStudent[$sid][$sem] = ['decision' => $dec, 'source' => $src];
|
||||
}
|
||||
|
||||
// Per-class stats
|
||||
if (!isset($statsPerClass[$csid])) {
|
||||
$statsPerClass[$csid] = ['name' => $row['class_section_name'], 'total' => 0, 'pass' => 0, 'cert' => 0];
|
||||
}
|
||||
$statsPerClass[$csid]['total']++;
|
||||
|
||||
$sems = $allScoreMap[$sid] ?? [];
|
||||
$isPass = !empty($sems);
|
||||
foreach ($sems as $sem => $score) {
|
||||
if ($score === null) { $isPass = false; break; }
|
||||
if ($score >= 60) continue;
|
||||
$md = $allBelowMap[$sid][$sem] ?? '';
|
||||
if ($md === '' || $md !== 'Pass') { $isPass = false; break; }
|
||||
}
|
||||
if ($isPass) $statsPerClass[$csid]['pass']++;
|
||||
if (isset($certsByStudent[$sid])) $statsPerClass[$csid]['cert']++;
|
||||
|
||||
// Group students by class
|
||||
$studentsByClass[$csid][] = $row;
|
||||
}
|
||||
|
||||
// ── Determine default active tab ───────────────────────────────────────
|
||||
$firstCsid = !empty($allEnrolled) ? (int)$allEnrolled[0]['class_section_id'] : null;
|
||||
if ($selectedCsid === null && $firstCsid !== null) {
|
||||
$selectedCsid = (string)$firstCsid;
|
||||
}
|
||||
|
||||
return view('admin/certificates/index', [
|
||||
'classSections' => $classSections,
|
||||
'students' => $students,
|
||||
'selectedClassId' => $classSectionId,
|
||||
'schoolYear' => $schoolYear,
|
||||
'certDate' => date('m/d/Y'),
|
||||
'studentsByClass' => $studentsByClass,
|
||||
'selectedClassId' => $selectedCsid,
|
||||
'schoolYear' => $schoolYear,
|
||||
'certDate' => date('m/d/Y'),
|
||||
'decisionsByStudent' => $decisionsByStudent,
|
||||
'certsByStudent' => $certsByStudent,
|
||||
'statsPerClass' => $statsPerClass,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -102,6 +183,51 @@ class CertificateController extends BaseController
|
||||
return view('certificates/verify', ['record' => $record ?: null]);
|
||||
}
|
||||
|
||||
// ─── Reprint an existing certificate ──────────────────────────────────────
|
||||
|
||||
public function reprint(string $certNumber)
|
||||
{
|
||||
$record = \Config\Database::connect()
|
||||
->table('certificate_records')
|
||||
->where('certificate_number', strtoupper($certNumber))
|
||||
->get()->getRowArray();
|
||||
|
||||
if (!$record) {
|
||||
return redirect()->to('administrator/certificates/log')
|
||||
->with('error', 'Certificate not found: ' . $certNumber);
|
||||
}
|
||||
|
||||
$certDateFormatted = '';
|
||||
if (!empty($record['cert_date'])) {
|
||||
$ts = strtotime((string)$record['cert_date']);
|
||||
if ($ts) {
|
||||
$certDateFormatted = date('m/d/Y', $ts);
|
||||
}
|
||||
}
|
||||
|
||||
$student = [
|
||||
'firstname' => (string)($record['student_name'] ?? ''),
|
||||
'lastname' => '',
|
||||
'grade' => (string)($record['grade'] ?? ''),
|
||||
'cert_number' => (string)($record['certificate_number'] ?? ''),
|
||||
];
|
||||
|
||||
// student_name is stored as "Firstname Lastname" — split for the PDF builder
|
||||
$parts = explode(' ', trim($student['firstname']), 2);
|
||||
if (count($parts) === 2) {
|
||||
$student['firstname'] = $parts[0];
|
||||
$student['lastname'] = $parts[1];
|
||||
}
|
||||
|
||||
$pdfData = $this->buildPdf([$student], $certDateFormatted ?: date('m/d/Y'));
|
||||
$filename = 'Certificate_' . strtoupper($certNumber) . '.pdf';
|
||||
|
||||
return $this->response
|
||||
->setHeader('Content-Type', 'application/pdf')
|
||||
->setHeader('Content-Disposition', 'inline; filename="' . $filename . '"')
|
||||
->setBody($pdfData);
|
||||
}
|
||||
|
||||
// ─── PDF generation ────────────────────────────────────────────────────────
|
||||
|
||||
public function generate()
|
||||
@@ -188,20 +314,38 @@ class CertificateController extends BaseController
|
||||
$issuedAt = date('Y-m-d H:i:s');
|
||||
$certDateDb = $this->parseCertDate($certDate);
|
||||
|
||||
// Load any existing certificates for these students this school year
|
||||
$db = \Config\Database::connect();
|
||||
$existingCerts = $db->table('certificate_records')
|
||||
->select('student_id, certificate_number')
|
||||
->whereIn('student_id', array_column($students, 'id'))
|
||||
->where('school_year', $schoolYear)
|
||||
->get()->getResultArray();
|
||||
$existingCertMap = [];
|
||||
foreach ($existingCerts as $ec) {
|
||||
$existingCertMap[(int)$ec['student_id']] = $ec['certificate_number'];
|
||||
}
|
||||
|
||||
foreach ($students as &$student) {
|
||||
$certNumber = $this->certRecordModel->nextNumber($schoolYear);
|
||||
$this->certRecordModel->insert([
|
||||
'certificate_number' => $certNumber,
|
||||
'student_id' => $student['id'],
|
||||
'student_name' => $student['firstname'] . ' ' . $student['lastname'],
|
||||
'grade' => $this->formatGrade($student['grade'] ?? ''),
|
||||
'cert_date' => $certDateDb,
|
||||
'school_year' => $schoolYear,
|
||||
'class_section_id' => $classSectionId ?: null,
|
||||
'issued_by' => $issuedBy,
|
||||
'issued_at' => $issuedAt,
|
||||
]);
|
||||
$student['cert_number'] = $certNumber;
|
||||
$sid = (int)$student['id'];
|
||||
if (isset($existingCertMap[$sid])) {
|
||||
// Reuse existing certificate number — do not create a new record
|
||||
$student['cert_number'] = $existingCertMap[$sid];
|
||||
} else {
|
||||
$certNumber = $this->certRecordModel->nextNumber($schoolYear);
|
||||
$this->certRecordModel->insert([
|
||||
'certificate_number' => $certNumber,
|
||||
'student_id' => $sid,
|
||||
'student_name' => $student['firstname'] . ' ' . $student['lastname'],
|
||||
'grade' => $this->formatGrade($student['grade'] ?? ''),
|
||||
'cert_date' => $certDateDb,
|
||||
'school_year' => $schoolYear,
|
||||
'class_section_id' => $classSectionId ?: null,
|
||||
'issued_by' => $issuedBy,
|
||||
'issued_at' => $issuedAt,
|
||||
]);
|
||||
$student['cert_number'] = $certNumber;
|
||||
}
|
||||
}
|
||||
unset($student);
|
||||
|
||||
@@ -234,15 +378,20 @@ class CertificateController extends BaseController
|
||||
$clean = trim($raw);
|
||||
$lower = strtolower($clean);
|
||||
|
||||
if (preg_match('/^\d+$/', $clean) && (int)$clean >= 1 && (int)$clean <= 9) {
|
||||
return 'Grade ' . $clean;
|
||||
// Strip section suffix: "Grade 1-A" → "Grade 1", "Grade 2-B" → "Grade 2"
|
||||
if (preg_match('/^grade\s*(\d+)/i', $clean, $m)) {
|
||||
return 'Grade ' . (int)$m[1];
|
||||
}
|
||||
if ($lower === 'youth') {
|
||||
return 'Youth';
|
||||
}
|
||||
if ($lower === 'kg') {
|
||||
if ($lower === 'kg' || $lower === 'kindergarten') {
|
||||
return 'Kindergarten';
|
||||
}
|
||||
// Raw number or number-section (e.g. "1", "1-A", "2-B") → keep number only
|
||||
if (preg_match('/^(\d+)([- ][A-Za-z0-9]+)?$/', $clean, $m)) {
|
||||
return 'Grade ' . (int)$m[1];
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user