370 lines
13 KiB
PHP
370 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Exceptions\SchoolYear\SchoolYearWriteConflictException;
|
|
use App\Models\ConfigurationModel;
|
|
use App\Models\ReportCardAcknowledgementModel;
|
|
use App\Models\StudentModel;
|
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
|
use CodeIgniter\Database\BaseConnection;
|
|
|
|
class ParentReportCardController extends BaseController
|
|
{
|
|
protected ConfigurationModel $configModel;
|
|
protected ReportCardAcknowledgementModel $ackModel;
|
|
protected StudentModel $studentModel;
|
|
protected BaseConnection $db;
|
|
|
|
public function __construct()
|
|
{
|
|
helper(['url', 'form']);
|
|
$this->db = \Config\Database::connect();
|
|
$this->configModel = new ConfigurationModel();
|
|
$this->ackModel = new ReportCardAcknowledgementModel();
|
|
$this->studentModel = new StudentModel();
|
|
}
|
|
|
|
public function index()
|
|
{
|
|
$parentId = $this->resolvePrimaryParentId();
|
|
if (! $parentId) {
|
|
return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.');
|
|
}
|
|
|
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
|
$schoolYear = trim($schoolYearContext->yearName());
|
|
$semesterOptions = $this->semesterOptions($schoolYear, '');
|
|
|
|
$builder = $this->db->table('students s')
|
|
->select('s.id, s.firstname, s.lastname, cs.class_section_name')
|
|
->join('student_class sc', 'sc.student_id = s.id', 'left')
|
|
->join('classSection cs', 'cs.class_section_id = sc.class_section_id', 'left')
|
|
->where('s.parent_id', $parentId)
|
|
->orderBy('s.firstname', 'ASC')
|
|
->orderBy('s.lastname', 'ASC');
|
|
|
|
if ($schoolYear !== '') {
|
|
$builder->where('sc.school_year', $schoolYear);
|
|
}
|
|
|
|
$students = $this->uniqueStudentRows($builder->get()->getResultArray());
|
|
$studentIds = array_values(array_filter(array_map(static fn ($s) => (int) ($s['id'] ?? 0), $students)));
|
|
|
|
$ackMap = [];
|
|
$reportAvailableMap = $this->reportAvailabilityMap($studentIds, $schoolYear);
|
|
if (! empty($studentIds)) {
|
|
$ackQuery = $this->ackModel
|
|
->where('parent_id', $parentId)
|
|
->where('school_year', $schoolYear)
|
|
->whereIn('student_id', $studentIds);
|
|
|
|
$rows = $ackQuery->findAll();
|
|
foreach ($rows as $row) {
|
|
$semester = $this->selectedSemester($row['semester'] ?? '');
|
|
$ackMap[$this->semesterStudentKey((int) $row['student_id'], $semester)] = $row;
|
|
}
|
|
}
|
|
|
|
$reportRows = [];
|
|
foreach ($students as $student) {
|
|
foreach ($semesterOptions as $semester) {
|
|
$reportRows[] = [
|
|
'student' => $student,
|
|
'semester' => $semester,
|
|
'key' => $this->semesterStudentKey((int) ($student['id'] ?? 0), $semester),
|
|
];
|
|
}
|
|
}
|
|
|
|
return view('parent/report_cards', [
|
|
'students' => $students,
|
|
'reportRows' => $reportRows,
|
|
'ackMap' => $ackMap,
|
|
'reportAvailableMap' => $reportAvailableMap,
|
|
'schoolYear' => $schoolYear,
|
|
'semesterOptions' => $semesterOptions,
|
|
'isEditable' => ! $schoolYearContext->isReadonly(),
|
|
]);
|
|
}
|
|
|
|
public function view($studentId)
|
|
{
|
|
$parentId = $this->resolvePrimaryParentId();
|
|
if (! $parentId) {
|
|
return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.');
|
|
}
|
|
|
|
$student = $this->studentModel->find((int) $studentId);
|
|
if (! $student || (int) ($student['parent_id'] ?? 0) !== $parentId) {
|
|
throw new PageNotFoundException('Student not found.');
|
|
}
|
|
|
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
|
$schoolYear = trim($schoolYearContext->yearName());
|
|
$semester = $this->selectedSemester($this->request->getGet('semester'));
|
|
|
|
if (! $this->reportExists((int) $studentId, $schoolYear, $semester)) {
|
|
return redirect()->to(site_url('parent/report-cards'))
|
|
->with('error', 'No report card exists for the selected school year and semester.');
|
|
}
|
|
|
|
if (! $schoolYearContext->isReadonly()) {
|
|
$this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [
|
|
'viewed_at' => date('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
|
|
$url = site_url('report-card/student/' . (int) $studentId);
|
|
$query = [];
|
|
if ($schoolYear !== '') {
|
|
$query['school_year'] = $schoolYear;
|
|
}
|
|
if ($semester !== '') {
|
|
$query['semester'] = $semester;
|
|
}
|
|
if ($query) {
|
|
$url .= '?' . http_build_query($query);
|
|
}
|
|
|
|
return redirect()->to($url);
|
|
}
|
|
|
|
public function sign($studentId)
|
|
{
|
|
$parentId = $this->resolvePrimaryParentId();
|
|
if (! $parentId) {
|
|
return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.');
|
|
}
|
|
|
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
|
try {
|
|
$this->assertSchoolYearWritable($schoolYearContext);
|
|
} catch (SchoolYearWriteConflictException $e) {
|
|
return redirect()->back()->withInput()->with('error', $e->getMessage());
|
|
}
|
|
|
|
$student = $this->studentModel->find((int) $studentId);
|
|
if (! $student || (int) ($student['parent_id'] ?? 0) !== $parentId) {
|
|
throw new PageNotFoundException('Student not found.');
|
|
}
|
|
|
|
$name = trim((string) $this->request->getPost('signed_name'));
|
|
if ($name === '') {
|
|
return redirect()->back()->with('error', 'Please type your full name to sign.');
|
|
}
|
|
|
|
$schoolYear = trim($schoolYearContext->yearName());
|
|
$semester = $this->selectedSemester($this->request->getPost('semester'));
|
|
|
|
if (! $this->reportExists((int) $studentId, $schoolYear, $semester)) {
|
|
return redirect()->to(site_url('parent/report-cards'))
|
|
->with('error', 'No report card exists for the selected school year and semester.');
|
|
}
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [
|
|
'viewed_at' => $now,
|
|
'signed_at' => $now,
|
|
'signed_name' => $name,
|
|
'signer_ip' => $this->request->getIPAddress(),
|
|
]);
|
|
|
|
return redirect()->to(site_url('parent/report-cards'))->with('success', 'Report card acknowledged.');
|
|
}
|
|
|
|
protected function selectedSemester($semester): string
|
|
{
|
|
$selected = trim((string) ($semester ?? ''));
|
|
if ($selected === '') {
|
|
$selected = trim((string) (getSemester() ?? ''));
|
|
}
|
|
|
|
$normalized = strtolower($selected);
|
|
if ($normalized === 'fall' || $normalized === 'first' || str_contains($normalized, 'fall') || str_contains($normalized, '1')) {
|
|
return 'Fall';
|
|
}
|
|
if ($normalized === 'spring' || $normalized === 'second' || str_contains($normalized, 'spring') || str_contains($normalized, '2')) {
|
|
return 'Spring';
|
|
}
|
|
|
|
return $selected !== '' ? $selected : 'Fall';
|
|
}
|
|
|
|
protected function uniqueStudentRows(array $students): array
|
|
{
|
|
$unique = [];
|
|
foreach ($students as $student) {
|
|
$studentId = (int) ($student['id'] ?? 0);
|
|
if ($studentId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
if (! isset($unique[$studentId])) {
|
|
$unique[$studentId] = $student;
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
empty($unique[$studentId]['class_section_name'])
|
|
&& ! empty($student['class_section_name'])
|
|
) {
|
|
$unique[$studentId]['class_section_name'] = $student['class_section_name'];
|
|
}
|
|
}
|
|
|
|
return array_values($unique);
|
|
}
|
|
|
|
protected function semesterOptions(string $schoolYear, string $selectedSemester): array
|
|
{
|
|
$options = ['Fall', 'Spring'];
|
|
|
|
if ($schoolYear !== '') {
|
|
$rows = $this->db->table('semester_scores')
|
|
->select('DISTINCT semester', false)
|
|
->where('school_year', $schoolYear)
|
|
->where('semester IS NOT NULL', null, false)
|
|
->where('semester !=', '')
|
|
->orderBy('semester', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
foreach ($rows as $row) {
|
|
$semester = $this->selectedSemester($row['semester'] ?? '');
|
|
if ($semester !== '' && ! in_array($semester, $options, true)) {
|
|
$options[] = $semester;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($selectedSemester !== '' && ! in_array($selectedSemester, $options, true)) {
|
|
$options[] = $selectedSemester;
|
|
}
|
|
|
|
return array_values(array_unique($options));
|
|
}
|
|
|
|
protected function resolvePrimaryParentId(): ?int
|
|
{
|
|
$parentId = (int) (session()->get('user_id') ?? 0);
|
|
$userType = (string) ($_SESSION['user_type'] ?? '');
|
|
if ($userType === 'primary') {
|
|
return $parentId ?: null;
|
|
}
|
|
if ($userType === 'secondary') {
|
|
$row = $this->db->table('parents')
|
|
->select('parent_id')
|
|
->where('secondparent_user_id', $parentId)
|
|
->get()
|
|
->getRowArray();
|
|
return $row ? (int) ($row['parent_id'] ?? 0) : null;
|
|
}
|
|
if ($userType === 'tertiary') {
|
|
$row = $this->db->table('authorized_users')
|
|
->select('user_id as parent_id')
|
|
->where('authorized_user_id', $parentId)
|
|
->get()
|
|
->getRowArray();
|
|
return $row ? (int) ($row['parent_id'] ?? 0) : null;
|
|
}
|
|
return $parentId ?: null;
|
|
}
|
|
|
|
protected function reportAvailabilityMap(array $studentIds, string $schoolYear): array
|
|
{
|
|
$studentIds = array_values(array_filter(array_map('intval', $studentIds), static fn ($id) => $id > 0));
|
|
if (empty($studentIds) || $schoolYear === '') {
|
|
return [];
|
|
}
|
|
|
|
$builder = $this->db->table('semester_scores')
|
|
->select('student_id, semester')
|
|
->whereIn('student_id', $studentIds)
|
|
->where('school_year', $schoolYear);
|
|
|
|
$rows = $builder
|
|
->groupBy('student_id, semester')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$map = [];
|
|
foreach ($rows as $row) {
|
|
$sid = (int) ($row['student_id'] ?? 0);
|
|
if ($sid > 0) {
|
|
$semester = $this->selectedSemester($row['semester'] ?? '');
|
|
$map[$this->semesterStudentKey($sid, $semester)] = true;
|
|
}
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
protected function semesterStudentKey(int $studentId, string $semester): string
|
|
{
|
|
return $studentId . '|' . $this->selectedSemester($semester);
|
|
}
|
|
|
|
protected function reportExists(int $studentId, string $schoolYear, string $semester): bool
|
|
{
|
|
if ($studentId <= 0 || $schoolYear === '') {
|
|
return false;
|
|
}
|
|
|
|
$builder = $this->db->table('semester_scores')
|
|
->select('id')
|
|
->where('student_id', $studentId)
|
|
->where('school_year', $schoolYear)
|
|
->limit(1);
|
|
|
|
$semesterVariants = $this->semesterVariants($semester);
|
|
if (! empty($semesterVariants)) {
|
|
$builder->whereIn('semester', $semesterVariants);
|
|
}
|
|
|
|
return (bool) $builder->get()->getRowArray();
|
|
}
|
|
|
|
protected function semesterVariants(string $semester): array
|
|
{
|
|
$raw = trim($semester);
|
|
if ($raw === '') {
|
|
return [];
|
|
}
|
|
|
|
$normalized = strtolower($raw);
|
|
if ($normalized === 'fall' || $normalized === 'first' || str_contains($normalized, 'fall') || str_contains($normalized, '1')) {
|
|
$values = ['Fall', 'fall', 'First', 'first', 'Semester 1', 'semester 1', 'Semester1', 'semester1', 'Sem 1', 'sem 1', '1', '01', 'S1', 's1'];
|
|
} elseif ($normalized === 'spring' || $normalized === 'second' || str_contains($normalized, 'spring') || str_contains($normalized, '2')) {
|
|
$values = ['Spring', 'spring', 'Second', 'second', 'Semester 2', 'semester 2', 'Semester2', 'semester2', 'Sem 2', 'sem 2', '2', '02', 'S2', 's2'];
|
|
} else {
|
|
$values = [$raw];
|
|
}
|
|
|
|
$values[] = $raw;
|
|
|
|
return array_values(array_unique($values));
|
|
}
|
|
|
|
protected function touchAcknowledgement(
|
|
int $parentId,
|
|
int $studentId,
|
|
string $schoolYear,
|
|
string $semester,
|
|
array $values
|
|
): void {
|
|
$criteria = [
|
|
'parent_id' => $parentId,
|
|
'student_id' => $studentId,
|
|
'school_year' => $schoolYear,
|
|
'semester' => $semester,
|
|
];
|
|
$existing = $this->ackModel->where($criteria)->first();
|
|
if ($existing) {
|
|
$this->ackModel->update((int) $existing['id'], $values);
|
|
return;
|
|
}
|
|
$this->ackModel->insert(array_merge($criteria, $values));
|
|
}
|
|
}
|