add tests batch 20

This commit is contained in:
root
2026-06-09 01:03:53 -04:00
parent 95efb9652e
commit 6be4875c5e
1502 changed files with 13797 additions and 11313 deletions
@@ -19,7 +19,6 @@ class InvoiceConfigService
public function getDueDate(): ?string
{
$value = Configuration::getConfig('due_date');
return $value ? (string) $value : null;
}
@@ -46,7 +45,7 @@ class InvoiceConfigService
public function getRefundDeadline(): ?string
{
$value = Configuration::getConfig('refund_deadline');
if (! $value) {
if (!$value) {
return null;
}
@@ -18,7 +18,8 @@ class InvoiceGenerationService
public function __construct(
private InvoiceConfigService $config,
private InvoiceTuitionService $tuitionService
) {}
) {
}
public function generateInvoice(int $parentId, ?string $schoolYear = null): array
{
@@ -63,13 +64,11 @@ class InvoiceGenerationService
$registeredKids = array_values(array_filter($registeredKids, function ($student) use ($schoolYear) {
$sid = (int) ($student['student_id'] ?? 0);
return $sid > 0 && StudentClass::hasNonEventAssignment($sid, $schoolYear);
}));
$withdrawnKids = array_values(array_filter($withdrawnKids, function ($student) use ($schoolYear) {
$sid = (int) ($student['student_id'] ?? 0);
return $sid > 0 && StudentClass::hasNonEventAssignment($sid, $schoolYear);
}));
@@ -84,7 +83,7 @@ class InvoiceGenerationService
$this->recalculateAndUpdateDiscount($parentId, $schoolYear, $tuitionFee, $enrollments);
$refundPaid = (new Refund)->getTotalApprovedRefundByParentIdAndSchoolYear($parentId, $schoolYear);
$refundPaid = (new Refund())->getTotalApprovedRefundByParentIdAndSchoolYear($parentId, $schoolYear);
$totalPaid = Payment::getTotalPaidByParentId($parentId, $schoolYear);
$totalAmount = max(0.0, (float) $tuitionFee) + (float) $eventChargeTotal;
@@ -94,9 +93,9 @@ class InvoiceGenerationService
$updatedIds = [];
$insertId = null;
if (! empty($existingInvoices)) {
if (!empty($existingInvoices)) {
foreach ($existingInvoices as $invoice) {
if (! isset($invoice['id'])) {
if (!isset($invoice['id'])) {
continue;
}
@@ -128,14 +127,14 @@ class InvoiceGenerationService
} else {
$schoolId = User::getSchoolIdByUserId($parentId);
$invoiceNumber = $schoolId
? ('INV-'.$schoolId.'-'.uniqid())
? ('INV-' . $schoolId . '-' . uniqid())
: uniqid('INV-');
$issueUtc = (new DateTime('now', new DateTimeZone('UTC')))->format('Y-m-d H:i:s');
$dueUtc = null;
$dueDate = $this->config->getDueDate();
if (! empty($dueDate)) {
$dueLocal = new DateTime($dueDate.' 19:59:59', new DateTimeZone($this->config->getTimezone()));
if (!empty($dueDate)) {
$dueLocal = new DateTime($dueDate . ' 19:59:59', new DateTimeZone($this->config->getTimezone()));
$dueLocal->setTimezone(new DateTimeZone('UTC'));
$dueUtc = $dueLocal->format('Y-m-d H:i:s');
}
@@ -181,7 +180,7 @@ class InvoiceGenerationService
}
foreach ($invoices as $invoice) {
if (! isset($invoice['id'])) {
if (!isset($invoice['id'])) {
continue;
}
@@ -194,7 +193,7 @@ class InvoiceGenerationService
->where('i.school_year', $schoolYear)
->first();
if (! $discountUsage) {
if (!$discountUsage) {
continue;
}
@@ -4,12 +4,13 @@ namespace App\Services\Invoices;
class InvoiceGradeService
{
public function __construct(private int $gradeFee) {}
public function __construct(private int $gradeFee)
{
}
public function isKindergarten(string $grade): bool
{
$g = strtolower(trim($grade));
return in_array($g, ['KG', 'kg', 'k', 'kindergarten', 'k-g', 'k.g', 'k g'], true);
}
@@ -28,7 +29,6 @@ class InvoiceGradeService
public function gradeLevelInt(string $grade): int
{
$gl = $this->getGradeLevel($grade);
return (int) ($gl['level'] ?? 0);
}
@@ -39,11 +39,10 @@ class InvoiceGradeService
if ($num === 13) {
return ['level' => 1, 'suffix' => '', 'classId' => 13];
}
return ['level' => $num, 'suffix' => '', 'classId' => null];
}
if (! is_string($grade)) {
if (!is_string($grade)) {
return ['level' => 999, 'suffix' => '', 'classId' => null];
}
@@ -64,7 +63,6 @@ class InvoiceGradeService
if (preg_match('/^Y(?:OUTH)?(?:\s*(\d+))?$/', $g, $m)) {
$n = isset($m[1]) && $m[1] !== '' ? max(1, (int) $m[1]) : 1;
return [
'level' => $this->gradeFee + $n,
'suffix' => '',
@@ -5,12 +5,15 @@ namespace App\Services\Invoices;
use App\Models\Enrollment;
use App\Models\Invoice;
use App\Models\Student;
use App\Models\StudentClass;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class InvoiceManagementService
{
public function __construct(private InvoiceConfigService $config) {}
public function __construct(private InvoiceConfigService $config)
{
}
public function getManagementData(?string $schoolYear): array
{
@@ -44,7 +47,7 @@ class InvoiceManagementService
->all();
$parentData = [
'parent_name' => trim(($parent['firstname'] ?? '').' '.($parent['lastname'] ?? '')),
'parent_name' => trim(($parent['firstname'] ?? '') . ' ' . ($parent['lastname'] ?? '')),
'parent_id' => (int) $parent['id'],
'enrolledKids' => [],
'withdrawnKids' => [],
@@ -57,7 +60,7 @@ class InvoiceManagementService
$invoices = Invoice::getInvoicesByParentId((int) $parent['id'], $schoolYear);
foreach ($invoices as $invoice) {
if (! $invoice) {
if (!$invoice) {
continue;
}
@@ -65,13 +68,13 @@ class InvoiceManagementService
$parentData['last_updated'] = $invoice['updated_at'] ?? null;
$parentData['invoice_id'] = $invoice['id'] ?? null;
if (! empty($invoice['issue_date'])) {
if (!empty($invoice['issue_date'])) {
$tzName = $this->config->getTimezone();
$parentData['invoice_date'] = (new \DateTimeImmutable($invoice['issue_date'], new \DateTimeZone('UTC')))
->setTimezone(new \DateTimeZone($tzName))
->format('Y-m-d H:i:s');
} else {
$parentData['invoice_date'] = ! empty($invoice['updated_at'])
$parentData['invoice_date'] = !empty($invoice['updated_at'])
? date('Y-m-d H:i:s', strtotime($invoice['updated_at']))
: null;
}
@@ -112,7 +115,7 @@ class InvoiceManagementService
foreach ($enrollments as $enrollment) {
$kid = [
'name' => trim(($student['firstname'] ?? '').' '.($student['lastname'] ?? '')),
'name' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')),
'grade' => $grade,
'tuition_fee' => (float) ($enrollment['tuition_fee'] ?? 0),
];
@@ -133,7 +136,7 @@ class InvoiceManagementService
}
}
if (! empty($parentData['enrolledKids']) || ! empty($parentData['withdrawnKids'])) {
if (!empty($parentData['enrolledKids']) || !empty($parentData['withdrawnKids'])) {
$invoiceData[] = $parentData;
}
}
@@ -8,7 +8,9 @@ use Illuminate\Support\Facades\DB;
class InvoicePaymentService
{
public function __construct(private InvoiceConfigService $config) {}
public function __construct(private InvoiceConfigService $config)
{
}
public function getParentInvoiceSummary(int $parentId, ?string $schoolYear): array
{
@@ -24,9 +26,9 @@ class InvoicePaymentService
->all();
$selectedYear = $schoolYear ?: null;
if (! $selectedYear) {
if (!$selectedYear) {
$hasCurrentYear = in_array($currentSchoolYear, array_column($schoolYears, 'school_year'), true);
$selectedYear = $hasCurrentYear ? $currentSchoolYear : (! empty($schoolYears) ? $schoolYears[0]['school_year'] : null);
$selectedYear = $hasCurrentYear ? $currentSchoolYear : (!empty($schoolYears) ? $schoolYears[0]['school_year'] : null);
}
$invoices = [];
@@ -36,7 +38,7 @@ class InvoicePaymentService
$invoiceIds = array_column($invoices, 'id');
$refunds = [];
if (! empty($invoiceIds)) {
if (!empty($invoiceIds)) {
$refundResults = DB::table('refunds')
->selectRaw('invoice_id, COALESCE(SUM(refund_paid_amount),0) as refund_paid_amount')
->whereIn('invoice_id', $invoiceIds)
@@ -52,7 +54,7 @@ class InvoicePaymentService
}
$lastPayments = [];
if (! empty($invoiceIds)) {
if (!empty($invoiceIds)) {
$paymentResults = Payment::getPaymentsByInvoice($invoiceIds);
foreach ($paymentResults as $payment) {
$lastPayments[$payment['invoice_id']] = [
+26 -29
View File
@@ -9,6 +9,7 @@ use App\Models\Enrollment;
use App\Models\EventCharges;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Refund;
use App\Models\Student;
use App\Models\StudentClass;
use App\Models\User;
@@ -20,7 +21,8 @@ class InvoicePdfService
public function __construct(
private InvoiceConfigService $config,
private InvoiceGradeService $grades
) {}
) {
}
public function buildPdf(int $invoiceId): string
{
@@ -40,7 +42,7 @@ class InvoicePdfService
private function prepareInvoiceData(int $invoiceId): array
{
$invoice = Invoice::query()->find($invoiceId);
if (! $invoice) {
if (!$invoice) {
return ['error' => 'No invoice was generated. Please contact the school administration.'];
}
@@ -67,7 +69,7 @@ class InvoicePdfService
$payments = $paymentsQuery->get()->map(fn ($r) => $r->toArray())->all();
$parent = User::query()->find($parentId);
if (! $parent) {
if (!$parent) {
return ['error' => 'Parent associated with the invoice was not found.'];
}
@@ -87,7 +89,7 @@ class InvoicePdfService
foreach ($enrollments as $enrollment) {
$student = Student::query()->find($enrollment['student_id']);
if (! $student) {
if (!$student) {
continue;
}
@@ -110,13 +112,11 @@ class InvoicePdfService
$registeredKids = array_values(array_filter($registeredKids, function ($student) use ($schoolYear) {
$sid = (int) ($student['student_id'] ?? 0);
return $sid > 0 && StudentClass::hasNonEventAssignment($sid, $schoolYear);
}));
$withdrawnKids = array_values(array_filter($withdrawnKids, function ($student) use ($schoolYear) {
$sid = (int) ($student['student_id'] ?? 0);
return $sid > 0 && StudentClass::hasNonEventAssignment($sid, $schoolYear);
}));
@@ -148,10 +148,8 @@ class InvoicePdfService
if ($isRegular) {
$fee = $regularCount === 0 ? $firstFee : $secondFee;
$regularCount++;
return $fee;
}
return $youthFee;
};
@@ -162,7 +160,7 @@ class InvoicePdfService
$studentCharges[$student['student_id']] = ['unit_fee' => $unitFee, 'refund' => 0];
}
if (! $refundAllowed) {
if (!$refundAllowed) {
foreach ($withdrawnKids as $student) {
$gradeName = (string) ($student['grade'] ?? '');
$gradeLevel = $this->grades->gradeLevelInt($gradeName);
@@ -175,7 +173,7 @@ class InvoicePdfService
$studentIds = array_values(array_unique(array_map(static fn ($r) => (int) ($r['student_id'] ?? 0), array_merge($registeredKids, $withdrawnKids))));
$schoolIdMap = [];
if (! empty($studentIds)) {
if (!empty($studentIds)) {
$rows = Student::query()->whereIn('id', $studentIds)->get(['id', 'school_id'])->all();
foreach ($rows as $row) {
$schoolIdMap[(int) $row->id] = $row->school_id ?? 'N/A';
@@ -224,9 +222,9 @@ class InvoicePdfService
$signed = abs($signed);
}
$lineDate = ! empty($ac['created_at'])
$lineDate = !empty($ac['created_at'])
? date('Y-m-d', strtotime($ac['created_at']))
: (! empty($invoice->created_at) ? local_date($invoice->created_at, 'Y-m-d') : local_date(utc_now(), 'Y-m-d'));
: (!empty($invoice->created_at) ? local_date($invoice->created_at, 'Y-m-d') : local_date(utc_now(), 'Y-m-d'));
$desc = $ac['description'] ?? '';
if ($desc === '') {
@@ -272,7 +270,7 @@ class InvoicePdfService
$additionalChargesTotal = (float) ($data['additionalChargesTotal'] ?? 0);
$refundsPaidTotal = (float) ($data['refundsPaidTotal'] ?? 0);
$pdf = new \FPDF;
$pdf = new \FPDF();
$pdf->AddPage();
$pdf->SetFont('Arial', 'B', 16);
$pdf->Cell(0, 10, 'Invoice', 0, 1, 'C');
@@ -281,7 +279,7 @@ class InvoicePdfService
$pdf->SetFont('Arial', 'B', 12);
$pdf->Cell(40, 6, 'Parent Name:', 0, 0, 'L');
$pdf->SetFont('Arial', '', 12);
$pdf->Cell(0, 6, ($parent['firstname'] ?? '').' '.($parent['lastname'] ?? ''), 0, 1, 'L');
$pdf->Cell(0, 6, ($parent['firstname'] ?? '') . ' ' . ($parent['lastname'] ?? ''), 0, 1, 'L');
$pdf->SetFont('Arial', 'B', 12);
$pdf->Cell(40, 6, 'Invoice Number:', 0, 0, 'L');
@@ -308,7 +306,7 @@ class InvoicePdfService
$tz = new \DateTimeZone($this->config->getTimezone());
$toLocal = function (?string $raw) use ($tz): \DateTimeImmutable {
if (! $raw) {
if (!$raw) {
return new \DateTimeImmutable('now', $tz);
}
try {
@@ -321,24 +319,24 @@ class InvoicePdfService
foreach ($registeredKids as $student) {
$id = $student['student_id'];
$unit = (float) ($studentCharges[$id]['unit_fee'] ?? 0.0);
$name = $student['student_firstname'].' '.$student['student_lastname'];
$name = $student['student_firstname'] . ' ' . $student['student_lastname'];
$gradeName = ClassSection::getClassSectionNameByClassId($student['grade']) ?? $student['grade'];
$push($toLocal($invoice['created_at'] ?? null), 'Registration of "'.$name.'" in '.$gradeName, $unit, 'registration');
$push($toLocal($invoice['created_at'] ?? null), 'Registration of "' . $name . '" in ' . $gradeName, $unit, 'registration');
}
foreach ($events as $event) {
$studentName = 'N/A';
if (! empty($event['student_id'])) {
if (!empty($event['student_id'])) {
foreach ($students as $st) {
if (($st['student_id'] ?? null) == $event['student_id']) {
$studentName = $st['student_firstname'].' '.$st['student_lastname'];
$studentName = $st['student_firstname'] . ' ' . $st['student_lastname'];
break;
}
}
}
$amount = (float) ($event['charged'] ?? 0.0);
$eventName = $event['event_name'] ?? 'Event';
$push($toLocal($event['created_at'] ?? null), 'Event '.$eventName.' for "'.$studentName.'"', $amount, 'event');
$push($toLocal($event['created_at'] ?? null), 'Event ' . $eventName . ' for "' . $studentName . '"', $amount, 'event');
}
foreach ($additionalChargeLines as $line) {
@@ -349,7 +347,7 @@ class InvoicePdfService
foreach ($payments as $payment) {
$amount = (float) ($payment['paid_amount'] ?? 0.0);
$totalPaid += $amount;
$push($toLocal($payment['payment_date'] ?? null), 'Payment ('.($payment['payment_method'] ?? 'Payment').')', -$amount, 'payment');
$push($toLocal($payment['payment_date'] ?? null), 'Payment (' . ($payment['payment_method'] ?? 'Payment') . ')', -$amount, 'payment');
}
$totalDiscount = 0.0;
@@ -379,7 +377,6 @@ class InvoicePdfService
return $pa <=> $pb;
}
$cmp = $a['dt'] <=> $b['dt'];
return $cmp !== 0 ? $cmp : ($a['seq'] <=> $b['seq']);
});
@@ -387,7 +384,7 @@ class InvoicePdfService
foreach ($transactions as $t) {
$pdf->Cell(30, 7, $t['dt']->format('m-d-Y'), 1);
$pdf->Cell(130, 7, $t['description'], 1);
$pdf->Cell(30, 7, ($t['amount'] < 0 ? '-$' : '$').number_format(abs($t['amount']), 2), 1, 1, 'R');
$pdf->Cell(30, 7, ($t['amount'] < 0 ? '-$' : '$') . number_format(abs($t['amount']), 2), 1, 1, 'R');
}
$tuitionSubtotal = 0.0;
@@ -409,38 +406,38 @@ class InvoicePdfService
$pdf->SetFont('Arial', 'B', 12);
$pdf->Cell(160, 7, 'Total Charges:', 0, 0, 'R');
$pdf->SetFont('Arial', '', 12);
$pdf->Cell(30, 7, '$'.number_format($totalAmount, 2), 0, 1, 'R');
$pdf->Cell(30, 7, '$' . number_format($totalAmount, 2), 0, 1, 'R');
$pdf->SetFont('Arial', 'B', 12);
$pdf->Cell(160, 7, 'Total Paid:', 0, 0, 'R');
$pdf->SetFont('Arial', '', 12);
$pdf->Cell(30, 7, '$'.number_format($totalPaid, 2), 0, 1, 'R');
$pdf->Cell(30, 7, '$' . number_format($totalPaid, 2), 0, 1, 'R');
if ($totalDiscount > 0) {
$pdf->SetFont('Arial', 'B', 12);
$pdf->Cell(160, 7, 'Total Discount:', 0, 0, 'R');
$pdf->SetFont('Arial', '', 12);
$pdf->Cell(30, 7, '$'.number_format($totalDiscount, 2), 0, 1, 'R');
$pdf->Cell(30, 7, '$' . number_format($totalDiscount, 2), 0, 1, 'R');
}
if ($refundsPaidTotal > 0) {
$pdf->SetFont('Arial', 'B', 12);
$pdf->Cell(160, 7, 'Refunds Paid:', 0, 0, 'R');
$pdf->SetFont('Arial', '', 12);
$pdf->Cell(30, 7, '$'.number_format($refundsPaidTotal, 2), 0, 1, 'R');
$pdf->Cell(30, 7, '$' . number_format($refundsPaidTotal, 2), 0, 1, 'R');
}
$pdf->SetFont('Arial', 'B', 12);
$pdf->Cell(160, 7, 'Balance Due:', 0, 0, 'R');
$pdf->SetFont('Arial', '', 12);
$pdf->Cell(30, 7, '$'.number_format($balance, 2), 0, 1, 'R');
$pdf->Cell(30, 7, '$' . number_format($balance, 2), 0, 1, 'R');
return $pdf->Output('S');
}
private function renderErrorPdf(string $message): string
{
$pdf = new \FPDF;
$pdf = new \FPDF();
$pdf->AddPage();
$pdf->SetFont('Arial', 'B', 16);
$pdf->Cell(0, 10, 'Error', 0, 1, 'C');
+2 -1
View File
@@ -13,7 +13,8 @@ class InvoiceService
private InvoiceGenerationService $generation,
private InvoiceTuitionService $tuition,
private InvoiceGradeService $grades
) {}
) {
}
public function getDefaultSchoolYear(): string
{
@@ -13,7 +13,8 @@ class InvoiceTuitionService
private float $secondStudentFee,
private float $youthFee,
private string $timezone
) {}
) {
}
public function calculateTuitionFee(array $registeredKids, array $withdrawnKids, ?string $refundDeadline): float
{
@@ -60,7 +61,7 @@ class InvoiceTuitionService
private function isRefundAllowed(?string $refundDeadline): bool
{
if (! $refundDeadline) {
if (!$refundDeadline) {
return true;
}
@@ -68,7 +69,6 @@ class InvoiceTuitionService
$tz = new \DateTimeZone($this->timezone);
$today = new \DateTimeImmutable('today', $tz);
$deadline = new \DateTimeImmutable($refundDeadline, $tz);
return $today <= $deadline;
} catch (\Throwable $e) {
return true;