Files
alrahma_sunday_school/app/Services/FeeCalculationService.php
T
root d906a915d6
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 47s
Tests / PHPUnit (push) Failing after 1m20s
add refund logic and fix books inventory logic
2026-08-22 13:44:25 -04:00

145 lines
4.7 KiB
PHP

<?php
namespace App\Services;
use App\Models\ConfigurationModel;
use App\Models\ClassSectionModel;
class FeeCalculationService
{
public function calculateEnrollmentTuition(array $students): float
{
return round($this->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<int,float>
*/
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;
}
}