getConfig('school_year'); $refundDeadline = date('Y-m-d', strtotime($configModel->getConfig('refund_deadline'))); $weekOfStudy = (float) ($configModel->getConfig('weeks_study') ?? 8); $schoolEndDate = date('Y-m-d', strtotime($configModel->getConfig('last_school_day'))); $totalPaid = $paymentModel->getTotalPaidByParentId($parentId, $schoolYear); if ($totalPaid <= 0) { log_message('info', "No payments made. Refund = 0."); return 0; } // Classify and enrich student data $registeredStudents = []; $withdrawnStudents = []; foreach ($students as &$student) { $gradeName = $classSectionModel->getClassSectionNameBySectionId($student['class_section_id']); $student['grade'] = strtoupper(trim($gradeName)); if (in_array($student['enrollment_status'], ['withdrawn', 'refund pending', 'withdraw under review'])) { $withdrawnStudents[] = $student; } elseif ( in_array($student['enrollment_status'], ['enrolled', 'payment pending']) && $student['admission_status'] === 'accepted' ) { $registeredStudents[] = $student; } } unset($student); if (empty($withdrawnStudents)) { log_message('info', "No withdrawn students found. Refund = 0."); return 0; } // Combine all students for proper fee tiering $allStudents = array_merge($registeredStudents, $withdrawnStudents); // Sort all students by grade for correct tiering usort($allStudents, function ($a, $b) { return $this->compareGrades($a['grade'], $b['grade']); }); // Retrieve fee configs $firstStudentFee = (float) ($configModel->getConfig('first_student_fee') ?? 350); $secondStudentFee = (float) ($configModel->getConfig('second_student_fee') ?? 200); $youthFee = (float) ($configModel->getConfig('youth_fee') ?? 200); // Assign tuition_fee to all students (before filtering refunds) $regularCount = 0; foreach ($allStudents as &$student) { $gradeLevel = $this->getGradeLevel($student['grade']); if ($gradeLevel > 9) { $studentFee = $youthFee; } else { $studentFee = ($regularCount === 0) ? $firstStudentFee : $secondStudentFee; $regularCount++; } } unset($student); // Calculate refund for withdrawn students $refundAmount = 0; foreach ($withdrawnStudents as $student) { if (empty($student['withdrawal_date'])) { log_message('warning', "Missing withdraw date for student ID: {$student['student_id']}"); continue; } $withdrawDate = date('Y-m-d', strtotime($student['withdrawal_date'])); if (strtotime($withdrawDate) > strtotime($refundDeadline)) { log_message('info', "Withdraw date {$withdrawDate} is after refund deadline {$refundDeadline}. No refund for this student."); continue; } $withdrawDateObj = new \DateTime($withdrawDate); $schoolEndDateObj = new \DateTime($schoolEndDate); $daysRemaining = $withdrawDateObj->diff($schoolEndDateObj)->days; $weeksRemaining = min($weekOfStudy, max(0, ceil($daysRemaining / 7))); //$studentFee = $student['tuition_fee']; $proportionalRefund = ($studentFee / $weekOfStudy) * $weeksRemaining; $refundAmount += $proportionalRefund; log_message('info', "Student ID {$student['student_id']} refund portion: {$proportionalRefund} of {$studentFee} for {$weeksRemaining} weeks."); } if ($refundAmount > $totalPaid) { log_message('info', "Refund capped at total paid amount: {$totalPaid}"); return $totalPaid; } log_message('info', "Final calculated refund: {$refundAmount}"); return $refundAmount; } private function compareGrades($gradeA, $gradeB) { $valA = $this->getGradeLevel($gradeA); $valB = $this->getGradeLevel($gradeB); if ($valA !== $valB) return $valA <=> $valB; // Same level, compare suffix preg_match('/\d+([A-Z]*)$/i', strtoupper($gradeA), $suffixA); preg_match('/\d+([A-Z]*)$/i', strtoupper($gradeB), $suffixB); return strcmp($suffixA[1] ?? '', $suffixB[1] ?? ''); } private function getGradeLevel($grade) { if (strtoupper($grade) === 'K') return 0; if (strtoupper($grade) === 'Y') return 99; if (preg_match('/^(\d+)([A-Z]*)$/i', $grade, $matches)) { return (int) $matches[1]; } return 999; // fallback for unknown/malformed grades } private function calculateTotalTuitionFee(array $students): float { $configModel = new ConfigurationModel(); $classSectionModel = new \App\Models\ClassSectionModel(); $firstStudentFee = (float) ($configModel->getConfig('first_student_fee') ?? 350); $secondStudentFee = (float) ($configModel->getConfig('second_student_fee') ?? 200); $youthFee = (float) ($configModel->getConfig('youth_fee') ?? 200); // ✅ Pre-fetch and assign grade/class section names before sorting foreach ($students as &$student) { $gradeName = $classSectionModel->getClassSectionNameBySectionId($student['class_section_id']); $student['grade'] = strtoupper(trim($gradeName)); } unset($student); // break reference // ✅ Sort students by grade usort($students, function ($a, $b) { return $this->compareGrades($a['grade'], $b['grade']); }); $regularCount = 0; $totalFee = 0; // ✅ Calculate fee foreach ($students as $student) { $gradeLevel = $this->getGradeLevel($student['grade']); if ($gradeLevel > 9) { $totalFee += $youthFee; } else { $totalFee += ($regularCount === 0) ? $firstStudentFee : $secondStudentFee; $regularCount++; } } return $totalFee; } }