calculateTotalTuitionFee($students), 2); } public function calculateRefund(array $students, int $parentId): float { $totalCents = 0; $seenEnrollmentIds = []; foreach ($students as $student) { if ((int) ($student['parent_id'] ?? $parentId) !== $parentId) { continue; } $status = strtolower(trim((string) ($student['enrollment_status'] ?? ''))); if (! in_array($status, ['withdrawn', 'refund pending', 'withdraw under review'], true)) { continue; } $enrollmentId = (int) ($student['enrollment_id'] ?? $student['id'] ?? 0); if ($enrollmentId <= 0 || isset($seenEnrollmentIds[$enrollmentId])) { continue; } $seenEnrollmentIds[$enrollmentId] = true; $calculation = $this->latestWithdrawalCalculation($enrollmentId); if ($calculation === null || ($calculation['status'] ?? '') === 'superseded') { $calculation = $this->previewWithdrawalCalculation($enrollmentId); } $totalCents += max(0, (int) ($calculation['new_refund_request_cents'] ?? 0)); } return round($totalCents / 100, 2); } protected function latestWithdrawalCalculation(int $enrollmentId): ?array { return service('withdrawalFinancial')->latestForEnrollment($enrollmentId); } protected function previewWithdrawalCalculation(int $enrollmentId): array { return service('withdrawalFinancial')->preview($enrollmentId, (int) (session()->get('user_id') ?? 0) ?: null); } /** * Refunds reverse the family tuition stack. * * Example: three students are charged [first, additional, additional]. * If one student withdraws, refund the last/additional fee first, not the * withdrawn student's sorted family position. * * @return array */ private function reverseTuitionRefundFeeStack( int $originalStudentCount, int $remainingStudentCount, float $firstStudentFee, float $additionalStudentFee ): array { $withdrawnCount = max(0, $originalStudentCount - $remainingStudentCount); if ($withdrawnCount === 0) { return []; } $fees = []; for ($position = $originalStudentCount; $position > $remainingStudentCount; $position--) { $fees[] = $position === 1 ? $firstStudentFee : $additionalStudentFee; } return $fees; } 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') ?? 380); $secondStudentFee = (float) ($configModel->getConfig('second_student_fee') ?? 280); // ✅ Pre-fetch and assign grade/class section names before sorting foreach ($students as &$student) { $gradeName = $classSectionModel->getClassSectionNameBySectionId((int) ($student['class_section_id'] ?? 0)); $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']); }); $studentCount = 0; $totalFee = 0; // ✅ Calculate fee foreach ($students as $student) { $totalFee += ($studentCount === 0) ? $firstStudentFee : $secondStudentFee; $studentCount++; } return $totalFee; } }