184 lines
6.7 KiB
PHP
184 lines
6.7 KiB
PHP
<?php
|
|
namespace App\Services;
|
|
|
|
use App\Models\ConfigurationModel;
|
|
use App\Models\PaymentModel;
|
|
use App\Models\InvoiceModel;
|
|
use App\Models\ClassSectionModel;
|
|
|
|
class FeeCalculationService
|
|
{
|
|
public function calculateRefund(array $students, int $parentId): float
|
|
{
|
|
$configModel = new ConfigurationModel();
|
|
$paymentModel = new PaymentModel();
|
|
$invoiceModel = new InvoiceModel();
|
|
$classSectionModel = new ClassSectionModel();
|
|
|
|
$schoolYear = $configModel->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') ?? 180);
|
|
|
|
// 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') ?? 180);
|
|
|
|
// ✅ 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;
|
|
}
|
|
|
|
}
|