fix financial issues
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\Libraries;
|
||||
|
||||
use App\Libraries\FinancialAttachmentService;
|
||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
|
||||
class FinancialAttachmentServiceTest extends CIUnitTestCase
|
||||
{
|
||||
private FinancialAttachmentService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->service = new FinancialAttachmentService();
|
||||
}
|
||||
|
||||
public function testSaveUploadedFileReturnsNullForInvalidInput(): void
|
||||
{
|
||||
$this->assertNull($this->service->saveUploadedFile(null, 'payments'));
|
||||
|
||||
$file = $this->createMock(UploadedFile::class);
|
||||
$file->method('isValid')->willReturn(false);
|
||||
|
||||
$this->assertNull($this->service->saveUploadedFile($file, 'payments'));
|
||||
}
|
||||
|
||||
public function testSaveUploadedFileRejectsUnsupportedMimeType(): void
|
||||
{
|
||||
$file = $this->createMock(UploadedFile::class);
|
||||
$file->method('isValid')->willReturn(true);
|
||||
$file->method('hasMoved')->willReturn(false);
|
||||
$file->method('getSize')->willReturn(1000);
|
||||
$file->method('getMimeType')->willReturn('text/plain');
|
||||
$file->method('getClientExtension')->willReturn('txt');
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('Unsupported file type.');
|
||||
|
||||
$this->service->saveUploadedFile($file, 'payments');
|
||||
}
|
||||
|
||||
public function testSaveUploadedFileRejectsOversizedFiles(): void
|
||||
{
|
||||
$file = $this->createMock(UploadedFile::class);
|
||||
$file->method('isValid')->willReturn(true);
|
||||
$file->method('hasMoved')->willReturn(false);
|
||||
$file->method('getSize')->willReturn(5242881);
|
||||
$file->method('getMimeType')->willReturn('application/pdf');
|
||||
$file->method('getClientExtension')->willReturn('pdf');
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('File too large.');
|
||||
|
||||
$this->service->saveUploadedFile($file, 'payments');
|
||||
}
|
||||
|
||||
public function testSaveUploadedFileMovesValidFilesAndReturnsRandomName(): void
|
||||
{
|
||||
$file = $this->createMock(UploadedFile::class);
|
||||
$file->method('isValid')->willReturn(true);
|
||||
$file->method('hasMoved')->willReturn(false);
|
||||
$file->method('getSize')->willReturn(1024);
|
||||
$file->method('getMimeType')->willReturn('application/pdf');
|
||||
$file->method('getClientExtension')->willReturn('pdf');
|
||||
$file->method('getRandomName')->willReturn('proof.pdf');
|
||||
|
||||
$file->expects($this->once())
|
||||
->method('move')
|
||||
->with($this->stringContains('writable/uploads/payments'), 'proof.pdf');
|
||||
|
||||
$this->assertSame('proof.pdf', $this->service->saveUploadedFile($file, 'payments'));
|
||||
}
|
||||
|
||||
public function testResolvePathUsesBasenameAndReturnsNullForMissingFiles(): void
|
||||
{
|
||||
$dir = $this->service->ensureSubdir('receipts');
|
||||
$path = $dir . DIRECTORY_SEPARATOR . 'receipt.pdf';
|
||||
file_put_contents($path, 'pdf');
|
||||
|
||||
$this->assertSame($path, $this->service->resolvePath('receipts', '../receipt.pdf'));
|
||||
$this->assertNull($this->service->resolvePath('receipts', 'missing.pdf'));
|
||||
}
|
||||
|
||||
public function testDetectMimeReturnsKnownMimeForExistingFile(): void
|
||||
{
|
||||
$dir = $this->service->ensureSubdir('mime-tests');
|
||||
$path = $dir . DIRECTORY_SEPARATOR . 'image.png';
|
||||
file_put_contents(
|
||||
$path,
|
||||
base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aap8AAAAASUVORK5CYII=')
|
||||
);
|
||||
|
||||
$mime = $this->service->detectMime($path);
|
||||
|
||||
$this->assertStringContainsString('image/', $mime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\Libraries;
|
||||
|
||||
use App\Libraries\Tuition\NewTuitionCalculatorService;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
|
||||
class NewTuitionCalculatorServiceTest extends CIUnitTestCase
|
||||
{
|
||||
public function testFamilyDiscountsApplyByStudentPosition(): void
|
||||
{
|
||||
$service = new NewTuitionCalculatorService();
|
||||
$result = $service->calculateFamilyTuition([
|
||||
['student_id' => 4, 'student_name' => 'Fourth', 'grade_level' => '4'],
|
||||
['student_id' => 1, 'student_name' => 'First', 'grade_level' => '1'],
|
||||
['student_id' => 3, 'student_name' => 'Third', 'grade_level' => '3'],
|
||||
['student_id' => 2, 'student_name' => 'Second', 'grade_level' => '2'],
|
||||
], [
|
||||
'grade_fee' => 9,
|
||||
'new_tuition_full_amount' => '350.00',
|
||||
'new_tuition_second_student_discount' => '50.00',
|
||||
'new_tuition_third_student_discount' => '50.00',
|
||||
'new_tuition_fourth_plus_discount' => '100.00',
|
||||
]);
|
||||
|
||||
$this->assertSame('1200.00', $result['total']);
|
||||
$this->assertSame('350.00', $result['details'][0]['amount']);
|
||||
$this->assertSame('300.00', $result['details'][1]['amount']);
|
||||
$this->assertSame('300.00', $result['details'][2]['amount']);
|
||||
$this->assertSame('250.00', $result['details'][3]['amount']);
|
||||
}
|
||||
|
||||
public function testDiscountCannotDriveAmountBelowZero(): void
|
||||
{
|
||||
$service = new NewTuitionCalculatorService();
|
||||
$result = $service->calculateFamilyTuition([
|
||||
['student_id' => 1, 'student_name' => 'One', 'grade_level' => '1'],
|
||||
['student_id' => 2, 'student_name' => 'Two', 'grade_level' => '2'],
|
||||
['student_id' => 3, 'student_name' => 'Three', 'grade_level' => '3'],
|
||||
['student_id' => 4, 'student_name' => 'Four', 'grade_level' => '4'],
|
||||
['student_id' => 5, 'student_name' => 'Five', 'grade_level' => '5'],
|
||||
], [
|
||||
'grade_fee' => 9,
|
||||
'new_tuition_full_amount' => '75.00',
|
||||
'new_tuition_second_student_discount' => '50.00',
|
||||
'new_tuition_third_student_discount' => '50.00',
|
||||
'new_tuition_fourth_plus_discount' => '100.00',
|
||||
]);
|
||||
|
||||
$this->assertSame('125.00', $result['total']);
|
||||
$this->assertSame('0.00', $result['details'][3]['amount']);
|
||||
$this->assertSame('0.00', $result['details'][4]['amount']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\Libraries;
|
||||
|
||||
use App\Libraries\Tuition\OldTuitionCalculatorService;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
|
||||
class OldTuitionCalculatorServiceTest extends CIUnitTestCase
|
||||
{
|
||||
public function testRegularAndYouthStudentsUseLegacyRules(): void
|
||||
{
|
||||
$service = new OldTuitionCalculatorService();
|
||||
$result = $service->calculateFamilyTuition([
|
||||
['student_id' => 3, 'student_name' => 'Older Youth', 'grade_level' => 'Youth 1'],
|
||||
['student_id' => 1, 'student_name' => 'First Regular', 'grade_level' => '3'],
|
||||
['student_id' => 2, 'student_name' => 'Second Regular', 'grade_level' => '5'],
|
||||
], [
|
||||
'grade_fee' => 9,
|
||||
'first_student_fee' => '350.00',
|
||||
'second_student_fee' => '200.00',
|
||||
'youth_fee' => '180.00',
|
||||
]);
|
||||
|
||||
$this->assertSame('730.00', $result['total']);
|
||||
$this->assertSame('old_first_student_fee', $result['details'][0]['rule']);
|
||||
$this->assertSame('old_second_student_fee', $result['details'][1]['rule']);
|
||||
$this->assertSame('old_youth_fee', $result['details'][2]['rule']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\Libraries;
|
||||
|
||||
use App\Libraries\Tuition\TuitionForecastService;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
|
||||
class TuitionForecastServiceTest extends CIUnitTestCase
|
||||
{
|
||||
public function testForecastSubtractsAlreadyCollectedAndKeepsEventOnlyStudentsNonBillable(): void
|
||||
{
|
||||
$service = new class () extends TuitionForecastService {
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function getDefaultSchoolYear(): string
|
||||
{
|
||||
return '2025-2026';
|
||||
}
|
||||
|
||||
protected function getDefaultSemester(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function loadFamilies(string $schoolYear, string $semester): array
|
||||
{
|
||||
return [
|
||||
['parent_id' => 10, 'parent_name' => 'Yusuf, Layla'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function loadFamilyStudents(int $parentId, string $schoolYear, string $semester, array $options): array
|
||||
{
|
||||
return [
|
||||
'students' => [
|
||||
['student_id' => 1, 'student_name' => 'Mariam', 'grade_level' => '1'],
|
||||
['student_id' => 2, 'student_name' => 'Yahya', 'grade_level' => '2'],
|
||||
],
|
||||
'all_students' => [
|
||||
['student_id' => 1, 'student_name' => 'Mariam', 'grade_level' => '1', 'billable' => true, 'excluded_reason' => null],
|
||||
['student_id' => 2, 'student_name' => 'Yahya', 'grade_level' => '2', 'billable' => true, 'excluded_reason' => null],
|
||||
['student_id' => 3, 'student_name' => 'Event Only', 'grade_level' => '3', 'billable' => false, 'excluded_reason' => 'event_only'],
|
||||
],
|
||||
'warnings' => ['1 event-only student(s) excluded from tuition.'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getTuitionConfig(): array
|
||||
{
|
||||
return [
|
||||
'grade_fee' => 9,
|
||||
'first_student_fee' => '350.00',
|
||||
'second_student_fee' => '200.00',
|
||||
'youth_fee' => '180.00',
|
||||
'new_tuition_full_amount' => $this->unitPriceOverride ?? '350.00',
|
||||
'new_tuition_second_student_discount' => '50.00',
|
||||
'new_tuition_third_student_discount' => '50.00',
|
||||
'new_tuition_fourth_plus_discount' => '100.00',
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
$result = $service->calculate('2025-2026', 'Fall', 'compare', [
|
||||
'include_payment_pending' => true,
|
||||
'include_withdrawn_mode' => 'refund_deadline',
|
||||
'unit_price' => '400.00',
|
||||
]);
|
||||
|
||||
$this->assertSame(1, $result['summary']['family_count']);
|
||||
$this->assertSame(3, $result['summary']['student_count']);
|
||||
$this->assertSame(2, $result['summary']['billable_student_count']);
|
||||
$this->assertSame('550.00', $result['summary']['old_projected_tuition']);
|
||||
$this->assertSame('750.00', $result['summary']['new_projected_tuition']);
|
||||
$this->assertSame('550.00', $result['summary']['old_projected_income']);
|
||||
$this->assertSame('750.00', $result['summary']['new_projected_income']);
|
||||
$this->assertSame('400.00', $result['summary']['unit_price']);
|
||||
$this->assertSame('event_only', $result['families'][0]['student_details'][2]['excluded_reason']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user