This commit is contained in:
@@ -9,6 +9,7 @@ use App\Models\ClassSectionModel;
|
||||
use App\Models\StudentClassModel;
|
||||
use App\Models\StudentSectionDistributionDraftModel;
|
||||
use App\Models\AuthorizedUserModel;
|
||||
use App\Models\ParentPolicyAcceptanceModel;
|
||||
use App\Models\EmergencyContactModel;
|
||||
use App\Models\ConfigurationModel;
|
||||
use App\Models\StudentMedicalConditionModel;
|
||||
@@ -54,6 +55,7 @@ class ParentController extends BaseController
|
||||
protected $medicalConditionModel;
|
||||
protected $allergyModel;
|
||||
protected $authorizedUsersModel;
|
||||
protected ParentPolicyAcceptanceModel $policyAcceptanceModel;
|
||||
protected $schoolStartDate;
|
||||
protected $ageDateRefernce;
|
||||
|
||||
@@ -75,6 +77,7 @@ class ParentController extends BaseController
|
||||
$this->medicalConditionModel = new StudentMedicalConditionModel();
|
||||
$this->allergyModel = new StudentAllergyModel();
|
||||
$this->authorizedUsersModel = new AuthorizedUserModel();
|
||||
$this->policyAcceptanceModel = new ParentPolicyAcceptanceModel();
|
||||
|
||||
$this->ageDateRefernce = $this->configModel->getConfig('date_age_reference');
|
||||
$this->lastDayOfRegistration = $this->configModel->getConfig('enrollment_deadline');
|
||||
@@ -270,7 +273,7 @@ class ParentController extends BaseController
|
||||
|
||||
// Verify user type is "parent" from the `users` table
|
||||
$userData = $this->db->table('users')
|
||||
->select('user_type')
|
||||
->select('user_type, accept_school_policy')
|
||||
->where('id', $parentId)
|
||||
->get()
|
||||
->getRowArray();
|
||||
@@ -290,6 +293,9 @@ class ParentController extends BaseController
|
||||
return redirect()->to('/no-kids')->with('error', 'No students found. Please register your child first.');
|
||||
}
|
||||
|
||||
$previousSchoolYear = $this->previousSchoolYearName($selectedYear);
|
||||
$fallMakeupExamOn = $this->fallMakeupExamDateForYear($selectedYear);
|
||||
|
||||
// Map enrollment statuses
|
||||
$statusMap = [
|
||||
'admission under review' => 'admission under review',
|
||||
@@ -356,16 +362,23 @@ class ParentController extends BaseController
|
||||
$student['enrollment_status'],
|
||||
['admission under review', 'payment pending', 'enrolled', 'withdraw under review', 'denied']
|
||||
);
|
||||
|
||||
$student['previous_year_decision'] = $previousSchoolYear !== null
|
||||
? $this->studentDecisionForYear((int) $studentId, $previousSchoolYear)
|
||||
: null;
|
||||
}
|
||||
|
||||
// Render view
|
||||
return view('/parent/enroll_classes', [
|
||||
'students' => $students,
|
||||
'selectedYear' => $selectedYear,
|
||||
'previousSchoolYear' => $previousSchoolYear,
|
||||
'fallMakeupExamOn' => $fallMakeupExamOn,
|
||||
'isEditable' => $isEditable,
|
||||
'withdrawalDeadline' => $this->withdrawalDeadline,
|
||||
'lastDayOfRegistration' => $this->lastDayOfRegistration,
|
||||
'schoolStartDate' => $this->schoolStartDate
|
||||
'schoolStartDate' => $this->schoolStartDate,
|
||||
'hasAcceptedSchoolPolicy' => $this->hasAcceptedPolicyForYear((int) $parentId, $selectedYear),
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
log_message('error', 'An error occurred in enrollClasses: ' . $e->getMessage());
|
||||
@@ -389,6 +402,25 @@ class ParentController extends BaseController
|
||||
$withdraw = $this->request->getPost('withdraw'); // Selected students for withdrawal
|
||||
$parentId = session()->get('user_id'); // Parent ID from session
|
||||
|
||||
if (!empty($enroll)) {
|
||||
$parent = $this->userModel->find((int) $parentId);
|
||||
$selectedYear = $this->currentSchoolYearName((string) ($this->schoolYear ?? ''));
|
||||
$hasAcceptedPolicy = $this->hasAcceptedPolicyForYear((int) $parentId, $selectedYear);
|
||||
$acceptedOnSubmit = (string) ($this->request->getPost('accept_school_policy') ?? '') === '1';
|
||||
|
||||
if (!$hasAcceptedPolicy && !$acceptedOnSubmit) {
|
||||
return redirect()->back()->withInput()->with('error', 'You must read and accept the school policy before enrolling students.');
|
||||
}
|
||||
|
||||
if (!$hasAcceptedPolicy && $acceptedOnSubmit) {
|
||||
$this->recordPolicyAcceptance((int) $parentId, $selectedYear, 'returning_parent_enrollment');
|
||||
$this->userModel->update((int) $parentId, [
|
||||
'accept_school_policy' => 1,
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->lastDayOfRegistration && local_date(utc_now(), 'Y-m-d') > date('Y-m-d', strtotime($this->lastDayOfRegistration))) {
|
||||
if (!empty($enroll)) {
|
||||
return redirect()->back()->with('error', 'Enrollment deadline has passed. New enrollments are not allowed.');
|
||||
@@ -404,6 +436,12 @@ class ParentController extends BaseController
|
||||
$studentData = [];
|
||||
|
||||
if (!empty($enroll)) {
|
||||
$selectedYear = $this->currentSchoolYearName((string) ($this->schoolYear ?? ''));
|
||||
$blockingDecisionMessages = $this->blockedEnrollmentDecisionMessages(array_map('intval', (array) $enroll), $selectedYear);
|
||||
if ($blockingDecisionMessages !== []) {
|
||||
return redirect()->back()->withInput()->with('error', implode(' ', $blockingDecisionMessages));
|
||||
}
|
||||
|
||||
foreach ($enroll as $studentId) {
|
||||
// Get student full name (supports both string return or array with firstname/lastname)
|
||||
$studentInfo = $this->studentModel->getFullNameById($studentId);
|
||||
@@ -571,6 +609,40 @@ class ParentController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
private function hasAcceptedPolicyForYear(int $parentId, string $schoolYear): bool
|
||||
{
|
||||
if ($parentId <= 0 || $schoolYear === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->policyAcceptanceModel->hasAccepted($parentId, $schoolYear);
|
||||
} catch (Throwable $e) {
|
||||
log_message('error', 'Failed to read parent policy acceptance: {message}', [
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function recordPolicyAcceptance(int $parentId, string $schoolYear, string $source): void
|
||||
{
|
||||
if ($parentId <= 0 || $schoolYear === '') {
|
||||
throw new \RuntimeException('Unable to record school policy acceptance.');
|
||||
}
|
||||
|
||||
if (! $this->policyAcceptanceModel->recordAcceptance(
|
||||
$parentId,
|
||||
$schoolYear,
|
||||
$source,
|
||||
$this->request->getIPAddress(),
|
||||
$this->request->getUserAgent()->getAgentString()
|
||||
)) {
|
||||
throw new \RuntimeException('Unable to record school policy acceptance.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a promotion_queue record exists for this student and the given school year,
|
||||
* create/update student_class row accordingly (base section until distribution),
|
||||
@@ -705,6 +777,84 @@ class ParentController extends BaseController
|
||||
return false;
|
||||
}
|
||||
|
||||
private function studentDecisionForYear(int $studentId, string $schoolYear): ?array
|
||||
{
|
||||
if ($studentId <= 0 || $schoolYear === '' || ! $this->db->tableExists('student_decisions')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->db->table('student_decisions')
|
||||
->select('decision, source, notes, class_section_name')
|
||||
->where('student_id', $studentId)
|
||||
->where('school_year', $schoolYear)
|
||||
->orderBy('updated_at', 'DESC')
|
||||
->orderBy('id', 'DESC')
|
||||
->limit(1)
|
||||
->get()
|
||||
->getRowArray() ?: null;
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'studentDecisionForYear failed: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function fallMakeupExamDateForYear(string $schoolYear): ?string
|
||||
{
|
||||
if ($schoolYear === '' || ! $this->db->tableExists('school_years') || ! $this->db->fieldExists('fall_makeup_exam_on', 'school_years')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$row = $this->db->table('school_years')
|
||||
->select('fall_makeup_exam_on')
|
||||
->where('name', $schoolYear)
|
||||
->limit(1)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
$date = trim((string) ($row['fall_makeup_exam_on'] ?? ''));
|
||||
return $date !== '' ? $date : null;
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'fallMakeupExamDateForYear failed: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function blockedEnrollmentDecisionMessages(array $studentIds, string $targetSchoolYear): array
|
||||
{
|
||||
$previousSchoolYear = $this->previousSchoolYearName($targetSchoolYear);
|
||||
if ($previousSchoolYear === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$messages = [];
|
||||
foreach (array_unique($studentIds) as $studentId) {
|
||||
$studentId = (int) $studentId;
|
||||
$decisionRow = $this->studentDecisionForYear($studentId, $previousSchoolYear);
|
||||
$decision = strtolower(trim((string) ($decisionRow['decision'] ?? '')));
|
||||
$source = strtolower(trim((string) ($decisionRow['source'] ?? '')));
|
||||
|
||||
if ($decision === 'pass' || $decision === 'repeat class') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($decision === '' && $source !== 'pending') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$studentInfo = $this->studentModel->getFullNameById($studentId);
|
||||
$studentName = is_array($studentInfo)
|
||||
? trim((string) ($studentInfo['firstname'] ?? '') . ' ' . (string) ($studentInfo['lastname'] ?? ''))
|
||||
: trim((string) $studentInfo);
|
||||
$studentName = $studentName !== '' ? $studentName : 'Student ID ' . $studentId;
|
||||
|
||||
$messages[] = $studentName . ': enrollment cannot be submitted until the prior-year decision is resolved with administration.';
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
private function previousSchoolYearName(string $schoolYear): ?string
|
||||
{
|
||||
$schoolYear = trim($schoolYear);
|
||||
@@ -1292,15 +1442,17 @@ class ParentController extends BaseController
|
||||
|
||||
$kids = $this->studentModel->where('parent_id', $parentId)->findAll();
|
||||
foreach ($kids as &$kid) {
|
||||
$studentId = (int) ($kid['id'] ?? 0);
|
||||
$kid['allergies'] = $this->allergyModel
|
||||
->where('student_id', $kid['id'])
|
||||
->where('student_id', $studentId)
|
||||
->findColumn('allergy') ?? [];
|
||||
|
||||
$kid['medical_conditions'] = $this->medicalConditionModel
|
||||
->where('student_id', $kid['id'])
|
||||
->where('student_id', $studentId)
|
||||
->findColumn('condition_name') ?? [];
|
||||
|
||||
$kid['enrollment'] = isset($enrollmentMap[$kid['id']]['id']) && !empty($enrollmentMap[$kid['id']]['id']) ? 1 : 0;
|
||||
$kid['enrollment'] = isset($enrollmentMap[$studentId]['id']) && !empty($enrollmentMap[$studentId]['id']) ? 1 : 0;
|
||||
$kid['can_delete'] = $this->canParentDeleteStudent($kid, $parentId);
|
||||
}
|
||||
|
||||
$emergencies = $this->emergencyContactModel->where('parent_id', $parentId)->findAll();
|
||||
@@ -1784,6 +1936,10 @@ $existing = $this->studentModel
|
||||
return redirect()->back()->with('error', 'Student not found or unauthorized.');
|
||||
}
|
||||
|
||||
if (! $this->canParentDeleteStudent($student, (int) $parentId)) {
|
||||
return redirect()->back()->with('error', 'This student has enrollment history and cannot be deleted. Please contact administration if a change is needed.');
|
||||
}
|
||||
|
||||
// Perform deletion
|
||||
if (!$this->studentModel->delete($id)) {
|
||||
return redirect()->back()->with('error', 'Failed to delete student.');
|
||||
@@ -1803,6 +1959,51 @@ $existing = $this->studentModel
|
||||
return redirect()->to('/parent/child_register')->with('success', 'Student deleted successfully.');
|
||||
}
|
||||
|
||||
private function canParentDeleteStudent(array $student, int $parentId): bool
|
||||
{
|
||||
$studentId = (int) ($student['id'] ?? 0);
|
||||
if ($studentId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((string) ($student['is_new'] ?? '1') === '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->studentHasEnrollmentHistory($studentId, $parentId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->studentHasClassAssignmentHistory($studentId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function studentHasEnrollmentHistory(int $studentId, int $parentId): bool
|
||||
{
|
||||
if (! $this->db->tableExists('enrollments')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->db->table('enrollments')
|
||||
->where('student_id', $studentId)
|
||||
->where('parent_id', $parentId)
|
||||
->countAllResults() > 0;
|
||||
}
|
||||
|
||||
private function studentHasClassAssignmentHistory(int $studentId): bool
|
||||
{
|
||||
if (! $this->db->tableExists('student_class')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->db->table('student_class')
|
||||
->where('student_id', $studentId)
|
||||
->countAllResults() > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function formatName(string $name): string
|
||||
|
||||
Reference in New Issue
Block a user