fix enrollment logic, add financial aid, fix class distribution
Tests / PHPUnit (push) Failing after 1m6s

This commit is contained in:
root
2026-08-15 15:07:16 -04:00
parent c12bb59372
commit 4603d9ced2
95 changed files with 6892 additions and 1295 deletions
+34 -52
View File
@@ -22,6 +22,7 @@ use App\Libraries\FinancialStatus;
use App\Libraries\IssueInvoiceCommand;
use App\Libraries\InvoiceIssuanceService;
use App\Libraries\InvoiceLedgerService;
use App\Libraries\Tuition\GradeLevelParser;
use DateTime;
use DateTimeZone;
@@ -44,7 +45,6 @@ class InvoiceController extends ResourceController
protected $studentClassModel;
protected $firstStudentFee;
protected $secondStudentFee;
protected $youthFee;
protected $refundDeadline;
protected $invoiceEventModel;
protected $paymentModel;
@@ -83,9 +83,8 @@ class InvoiceController extends ResourceController
$this->semester = $this->configModel->getConfig('semester');
$this->dueDate = $this->configModel->getConfig('first_day_of_school')
?: $this->configModel->getConfig('due_date');
$this->firstStudentFee = (float) ($this->configModel->getConfig('first_student_fee') ?? 350);
$this->secondStudentFee = (float) ($this->configModel->getConfig('second_student_fee') ?? 200);
$this->youthFee = (float) ($this->configModel->getConfig('youth_fee') ?? 200);
$this->firstStudentFee = (float) ($this->configModel->getConfig('first_student_fee') ?? 380);
$this->secondStudentFee = (float) ($this->configModel->getConfig('second_student_fee') ?? 280);
$this->refundDeadline = date('Y-m-d', strtotime($this->configModel->getConfig('refund_deadline')));
}
@@ -676,36 +675,16 @@ class InvoiceController extends ResourceController
}
unset($student); // break reference
// 2) Partition into regular (<= grade 9) vs youth (> grade 9)
$regularCount = 0;
$youthCount = 0;
// 2) First student pays the base fee; every additional student pays base minus $100.
usort($students, fn (array $left, array $right): int => GradeLevelParser::parse($left['grade'] ?? null) <=> GradeLevelParser::parse($right['grade'] ?? null));
foreach ($students as $student) {
$levelInfo = $this->getGradeLevel($student['grade']);
$level = (int) ($levelInfo['level'] ?? 999);
// Youth if level > $this->gradeFee (e.g., gradeFee = 9)
if ($level > $this->gradeFee) {
$youthCount++;
} else {
$regularCount++;
}
}
// 3) Calculate totals per your rules
$studentCount = 0;
$total = 0.0;
// Youth: flat youth fee per student
$total += $youthCount * $this->youthFee;
// Regulars: first student full price, others discounted — but only if 2+ regulars
if ($regularCount >= 2) {
$total += $this->firstStudentFee; // one full
$total += ($regularCount - 1) * $this->secondStudentFee; // rest discounted
} elseif ($regularCount === 1) {
$total += $this->firstStudentFee; // single regular: no discount even if there are youths
foreach ($students as $student) {
$total += ($studentCount === 0) ? $this->firstStudentFee : $this->secondStudentFee;
$studentCount++;
}
// if 0 regulars, nothing to add here
return $total;
}
@@ -860,41 +839,27 @@ class InvoiceController extends ResourceController
$refundAllowed = $currentDate <= $deadline;
$studentCharges = [];
$regularCount = 0;
$studentCount = 0;
/**
* Computes the fee for a student given numeric level and original grade name.
* - KG/K/Kindergarten are always treated as "regular".
* - Otherwise, "regular" means gradeLevel <= $this->gradeFee threshold.
* First student pays the base fee; every additional student pays base minus $100.
*/
$computeCharge = function (int $gradeLevel, string $gradeName) use (&$regularCount) {
$threshold = (int) $this->gradeFee;
// Force Kindergarten to be regular regardless of numeric mapping
$isRegular = $this->isKindergarten($gradeName) || ($gradeLevel > 0 && $gradeLevel <= $threshold);
if ($isRegular) {
$fee = ($regularCount === 0) ? $this->firstStudentFee : $this->secondStudentFee;
$regularCount++;
return $fee;
}
return $this->youthFee;
$computeCharge = function (array $student) use (&$studentCount) {
$fee = ($studentCount === 0) ? $this->firstStudentFee : $this->secondStudentFee;
$studentCount++;
return $fee;
};
// Registered kids -> pay unit fee
foreach ($registeredKids as $student) {
$gradeName = (string)$student['grade'];
$gradeLevel = $this->gradeLevelInt($gradeName);
$unitFee = $computeCharge($gradeLevel, $gradeName);
$unitFee = $computeCharge($student);
$studentCharges[$student['student_id']] = ['unit_fee' => $unitFee, 'refund' => 0];
}
// Refund NOT allowed -> withdrawn students still owe unit fee
if (!$refundAllowed) {
foreach ($withdrawnKids as $student) {
$gradeName = (string)$student['grade'];
$gradeLevel = $this->gradeLevelInt($gradeName);
$unitFee = $computeCharge($gradeLevel, $gradeName);
$unitFee = $computeCharge($student);
$studentCharges[$student['student_id']] = ['unit_fee' => $unitFee, 'refund' => 0];
}
}
@@ -1625,6 +1590,23 @@ private function getGradeLevel($grade): array
// View: Get invoices by parent ID (for web views)
public function getByParent($parentId)
{
$parentId = (int) $parentId;
$userId = (int) (session()->get('user_id') ?? 0);
$roles = array_map(
static fn ($role): string => strtolower(trim((string) $role)),
array_filter(array_merge((array) session()->get('roles'), [session()->get('role')]))
);
$isStaff = (bool) array_intersect($roles, [
'administrator',
'administrative staff',
'principal',
'admin',
]);
if ($userId <= 0 || (! $isStaff && $userId !== $parentId)) {
return redirect()->to('/access_denied');
}
$invoices = $this->invoiceModel->getInvoicesByUserId($parentId, $this->schoolYear);
return view('invoice_list', ['invoices' => $invoices]);
}