fix enrollment logic, add financial aid, fix class distribution
Tests / PHPUnit (push) Failing after 1m6s
Tests / PHPUnit (push) Failing after 1m6s
This commit is contained in:
@@ -3,9 +3,9 @@
|
||||
namespace Tests\App\Libraries;
|
||||
|
||||
use App\Libraries\Tuition\NewTuitionCalculatorService;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class NewTuitionCalculatorServiceTest extends CIUnitTestCase
|
||||
class NewTuitionCalculatorServiceTest extends TestCase
|
||||
{
|
||||
public function testFamilyDiscountsApplyByStudentPosition(): void
|
||||
{
|
||||
@@ -17,17 +17,17 @@ class NewTuitionCalculatorServiceTest extends CIUnitTestCase
|
||||
['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',
|
||||
'new_tuition_full_amount' => '380.00',
|
||||
'new_tuition_second_student_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']);
|
||||
$this->assertSame('1220.00', $result['total']);
|
||||
$this->assertSame('380.00', $result['details'][0]['amount']);
|
||||
$this->assertSame('280.00', $result['details'][1]['amount']);
|
||||
$this->assertSame('280.00', $result['details'][2]['amount']);
|
||||
$this->assertSame('280.00', $result['details'][3]['amount']);
|
||||
$this->assertSame('new_first_student_full_amount', $result['details'][0]['rule']);
|
||||
$this->assertSame('new_additional_student_discount', $result['details'][1]['rule']);
|
||||
}
|
||||
|
||||
public function testDiscountCannotDriveAmountBelowZero(): void
|
||||
@@ -37,22 +37,19 @@ class NewTuitionCalculatorServiceTest extends CIUnitTestCase
|
||||
['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',
|
||||
'new_tuition_second_student_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']);
|
||||
$this->assertSame('75.00', $result['total']);
|
||||
$this->assertSame('75.00', $result['details'][0]['amount']);
|
||||
$this->assertSame('0.00', $result['details'][1]['amount']);
|
||||
$this->assertSame('0.00', $result['details'][2]['amount']);
|
||||
}
|
||||
|
||||
public function testYouthStudentsUseSeparateUnitPriceWithoutAffectingRegularDiscountOrder(): void
|
||||
public function testYouthStudentsUseTheSameFamilyTuitionRule(): void
|
||||
{
|
||||
$service = new NewTuitionCalculatorService();
|
||||
$result = $service->calculateFamilyTuition([
|
||||
@@ -61,19 +58,31 @@ class NewTuitionCalculatorServiceTest extends CIUnitTestCase
|
||||
['student_id' => 2, 'student_name' => 'Regular Two', 'grade_level' => '5'],
|
||||
], [
|
||||
'grade_fee' => 9,
|
||||
'new_tuition_full_amount' => '370.00',
|
||||
'new_tuition_youth_amount' => '200.00',
|
||||
'new_tuition_second_student_discount' => '150.00',
|
||||
'new_tuition_third_student_discount' => '150.00',
|
||||
'new_tuition_fourth_plus_discount' => '150.00',
|
||||
'new_tuition_full_amount' => '380.00',
|
||||
'new_tuition_second_student_discount' => '100.00',
|
||||
]);
|
||||
|
||||
$this->assertSame('790.00', $result['total']);
|
||||
$this->assertSame('new_first_student_full_amount', $result['details'][0]['rule']);
|
||||
$this->assertSame('370.00', $result['details'][0]['amount']);
|
||||
$this->assertSame('new_second_student_discount', $result['details'][1]['rule']);
|
||||
$this->assertSame('220.00', $result['details'][1]['amount']);
|
||||
$this->assertSame('new_youth_unit_price', $result['details'][2]['rule']);
|
||||
$this->assertSame('200.00', $result['details'][2]['amount']);
|
||||
$this->assertSame('940.00', $result['total']);
|
||||
$this->assertSame('380.00', $result['details'][0]['amount']);
|
||||
$this->assertSame('280.00', $result['details'][1]['amount']);
|
||||
$this->assertSame('280.00', $result['details'][2]['amount']);
|
||||
$this->assertSame('new_additional_student_discount', $result['details'][2]['rule']);
|
||||
}
|
||||
|
||||
public function testGradeStudentAndYouthStudentAreTwoFamilyStudents(): void
|
||||
{
|
||||
$service = new NewTuitionCalculatorService();
|
||||
$result = $service->calculateFamilyTuition([
|
||||
['student_id' => 1, 'student_name' => 'Grade Student', 'grade_level' => '4'],
|
||||
['student_id' => 2, 'student_name' => 'Youth Student', 'grade_level' => 'Youth 1'],
|
||||
], [
|
||||
'grade_fee' => 9,
|
||||
'new_tuition_full_amount' => '380.00',
|
||||
'new_tuition_second_student_discount' => '100.00',
|
||||
]);
|
||||
|
||||
$this->assertSame('660.00', $result['total']);
|
||||
$this->assertSame('380.00', $result['details'][0]['amount']);
|
||||
$this->assertSame('280.00', $result['details'][1]['amount']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
namespace Tests\App\Libraries;
|
||||
|
||||
use App\Libraries\Tuition\OldTuitionCalculatorService;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class OldTuitionCalculatorServiceTest extends CIUnitTestCase
|
||||
class OldTuitionCalculatorServiceTest extends TestCase
|
||||
{
|
||||
public function testRegularAndYouthStudentsUseLegacyRules(): void
|
||||
public function testAllStudentsUseTheSameFirstAndAdditionalFeeRule(): void
|
||||
{
|
||||
$service = new OldTuitionCalculatorService();
|
||||
$result = $service->calculateFamilyTuition([
|
||||
@@ -16,14 +16,16 @@ class OldTuitionCalculatorServiceTest extends CIUnitTestCase
|
||||
['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',
|
||||
'first_student_fee' => '380.00',
|
||||
'second_student_fee' => '280.00',
|
||||
]);
|
||||
|
||||
$this->assertSame('730.00', $result['total']);
|
||||
$this->assertSame('940.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']);
|
||||
$this->assertSame('380.00', $result['details'][0]['amount']);
|
||||
$this->assertSame('old_additional_student_fee', $result['details'][1]['rule']);
|
||||
$this->assertSame('280.00', $result['details'][1]['amount']);
|
||||
$this->assertSame('old_additional_student_fee', $result['details'][2]['rule']);
|
||||
$this->assertSame('280.00', $result['details'][2]['amount']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +56,9 @@ class TuitionForecastServiceTest extends CIUnitTestCase
|
||||
'grade_fee' => 9,
|
||||
'first_student_fee' => '350.00',
|
||||
'second_student_fee' => '200.00',
|
||||
'youth_fee' => '200.00',
|
||||
'youth_fee' => '350.00',
|
||||
'new_tuition_full_amount' => $this->unitPriceOverride ?? '350.00',
|
||||
'new_tuition_youth_amount' => $this->youthUnitPriceOverride ?? '200.00',
|
||||
'new_tuition_second_student_discount' => '50.00',
|
||||
'new_tuition_third_student_discount' => '50.00',
|
||||
'new_tuition_fourth_plus_discount' => '100.00',
|
||||
'new_tuition_second_student_discount' => '100.00',
|
||||
];
|
||||
}
|
||||
};
|
||||
@@ -70,18 +67,17 @@ class TuitionForecastServiceTest extends CIUnitTestCase
|
||||
'include_payment_pending' => true,
|
||||
'include_withdrawn_mode' => 'refund_deadline',
|
||||
'unit_price' => '400.00',
|
||||
'youth_unit_price' => '200.00',
|
||||
]);
|
||||
|
||||
$this->assertSame(1, $result['summary']['family_count']);
|
||||
$this->assertSame(4, $result['summary']['student_count']);
|
||||
$this->assertSame(3, $result['summary']['billable_student_count']);
|
||||
$this->assertSame('750.00', $result['summary']['old_projected_tuition']);
|
||||
$this->assertSame('950.00', $result['summary']['new_projected_tuition']);
|
||||
$this->assertSame('1000.00', $result['summary']['new_projected_tuition']);
|
||||
$this->assertSame('750.00', $result['summary']['old_projected_income']);
|
||||
$this->assertSame('950.00', $result['summary']['new_projected_income']);
|
||||
$this->assertSame('1000.00', $result['summary']['new_projected_income']);
|
||||
$this->assertSame('400.00', $result['summary']['unit_price']);
|
||||
$this->assertSame('200.00', $result['summary']['youth_unit_price']);
|
||||
$this->assertSame('400.00', $result['summary']['youth_unit_price']);
|
||||
$this->assertSame('event_only', $result['families'][0]['student_details'][3]['excluded_reason']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ final class EnrollmentTransitionServiceTest extends TestCase
|
||||
private function builderReturning(?array $row): BaseBuilder
|
||||
{
|
||||
$builder = $this->createMock(BaseBuilder::class);
|
||||
$builder->method('select')->willReturnSelf();
|
||||
$builder->method('orderBy')->willReturnSelf();
|
||||
$builder->method('limit')->willReturnSelf();
|
||||
$builder->method('get')->willReturn(new class($row) {
|
||||
@@ -91,6 +92,306 @@ final class EnrollmentTransitionServiceTest extends TestCase
|
||||
return $builder;
|
||||
}
|
||||
|
||||
public function testNormalizeLastNameIgnoresPunctuationAndCase(): void
|
||||
{
|
||||
$service = new EnrollmentTransitionService($this->createMock(BaseConnection::class));
|
||||
|
||||
$this->assertSame('omalley', $this->invoke($service, 'normalizeLastName', ["O'Malley"]));
|
||||
$this->assertSame('omalley', $this->invoke($service, 'normalizeLastName', ['O-Malley']));
|
||||
}
|
||||
|
||||
public function testSingleLinkedStudentPassesLastNameRule(): void
|
||||
{
|
||||
$service = $this->serviceWithStudentRows([
|
||||
['id' => 1, 'firstname' => 'Amina', 'lastname' => 'Hassan', 'parent_id' => 9],
|
||||
]);
|
||||
$evaluation = [];
|
||||
|
||||
$this->invoke($service, 'applyHouseholdLastNameRule', [&$evaluation, 9]);
|
||||
|
||||
$this->assertTrue($evaluation['family_name_check_ok']);
|
||||
$this->assertArrayNotHasKey('blocking_rule_codes', $evaluation);
|
||||
}
|
||||
|
||||
public function testMismatchedSiblingLastNamesBlockEveryStudent(): void
|
||||
{
|
||||
$service = $this->serviceWithStudentRows([
|
||||
['id' => 1, 'firstname' => 'Amina', 'lastname' => 'Hassan', 'parent_id' => 9],
|
||||
['id' => 2, 'firstname' => 'Omar', 'lastname' => 'Ali', 'parent_id' => 9],
|
||||
]);
|
||||
$evaluation = [];
|
||||
|
||||
$this->invoke($service, 'applyHouseholdLastNameRule', [&$evaluation, 9]);
|
||||
|
||||
$this->assertFalse($evaluation['family_name_check_ok']);
|
||||
$this->assertContains('SIBLING_LAST_NAME_MISMATCH', $evaluation['blocking_rule_codes']);
|
||||
}
|
||||
|
||||
public function testMissingSiblingLastNameBlocksFamily(): void
|
||||
{
|
||||
$service = $this->serviceWithStudentRows([
|
||||
['id' => 1, 'firstname' => 'Amina', 'lastname' => 'Hassan', 'parent_id' => 9],
|
||||
['id' => 2, 'firstname' => 'Omar', 'lastname' => ' ', 'parent_id' => 9],
|
||||
]);
|
||||
$evaluation = [];
|
||||
|
||||
$this->invoke($service, 'applyHouseholdLastNameRule', [&$evaluation, 9]);
|
||||
|
||||
$this->assertContains('SIBLING_LAST_NAME_MISMATCH', $evaluation['blocking_rule_codes']);
|
||||
}
|
||||
|
||||
public function testPreviousYearBalanceBlocksEnrollment(): void
|
||||
{
|
||||
$service = $this->serviceWithFinance(125.50, 'submission_blocked_until_payment');
|
||||
$evaluation = [];
|
||||
|
||||
$this->invoke($service, 'applyFinancialRule', [&$evaluation, 9, '2025-2026', '2026-2027']);
|
||||
|
||||
$this->assertContains('OUTSTANDING_BALANCE_BLOCKED', $evaluation['blocking_rule_codes']);
|
||||
$this->assertSame(125.50, $evaluation['financial_summary']['carry_over_balance']);
|
||||
}
|
||||
|
||||
public function testPreviousYearBalanceWithAdminApprovalPolicyUsesFinanceReviewCode(): void
|
||||
{
|
||||
$service = $this->serviceWithFinance(40.00, 'admin_approval_required');
|
||||
$evaluation = [];
|
||||
|
||||
$this->invoke($service, 'applyFinancialRule', [&$evaluation, 9, '2025-2026', '2026-2027']);
|
||||
|
||||
$this->assertContains('FINANCE_APPROVAL_REQUIRED', $evaluation['blocking_rule_codes']);
|
||||
}
|
||||
|
||||
public function testZeroCarryOverBalanceDoesNotBlock(): void
|
||||
{
|
||||
$service = $this->serviceWithFinance(0.00, 'submission_blocked_until_payment');
|
||||
$evaluation = [];
|
||||
|
||||
$this->invoke($service, 'applyFinancialRule', [&$evaluation, 9, '2025-2026', '2026-2027']);
|
||||
|
||||
$this->assertArrayNotHasKey('blocking_rule_codes', $evaluation);
|
||||
}
|
||||
|
||||
public function testActiveEnrollmentStatusesBlockDuplicates(): void
|
||||
{
|
||||
$service = new EnrollmentTransitionService($this->createMock(BaseConnection::class));
|
||||
|
||||
$this->assertTrue($this->invoke($service, 'activeEnrollmentBlocksDuplicate', [[
|
||||
'is_withdrawn' => 0,
|
||||
'enrollment_status' => 'payment pending',
|
||||
'admission_status' => 'pending',
|
||||
]]));
|
||||
$this->assertFalse($this->invoke($service, 'activeEnrollmentBlocksDuplicate', [[
|
||||
'is_withdrawn' => 1,
|
||||
'enrollment_status' => 'withdrawn',
|
||||
'admission_status' => 'accepted',
|
||||
]]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $rows
|
||||
*/
|
||||
private function serviceWithStudentRows(array $rows): EnrollmentTransitionService
|
||||
{
|
||||
$builder = $this->createMock(BaseBuilder::class);
|
||||
$builder->method('select')->willReturnSelf();
|
||||
$builder->method('where')->willReturnSelf();
|
||||
$builder->method('orderBy')->willReturnSelf();
|
||||
$builder->method('get')->willReturn(new class($rows) {
|
||||
public function __construct(private readonly array $rows)
|
||||
{
|
||||
}
|
||||
|
||||
public function getResultArray(): array
|
||||
{
|
||||
return $this->rows;
|
||||
}
|
||||
});
|
||||
|
||||
$db = $this->createMock(BaseConnection::class);
|
||||
$db->method('tableExists')->with('students')->willReturn(true);
|
||||
$db->method('table')->with('students')->willReturn($builder);
|
||||
|
||||
return new EnrollmentTransitionService($db);
|
||||
}
|
||||
|
||||
private function serviceWithFinance(float $balance, string $behavior): EnrollmentTransitionService
|
||||
{
|
||||
$yearBuilder = $this->builderReturning([
|
||||
'name' => '2026-2027',
|
||||
'carry_over_balance_behavior' => $behavior,
|
||||
'registration_fee' => 0,
|
||||
'tuition_due_at_registration' => 0,
|
||||
'mandatory_fees' => 0,
|
||||
]);
|
||||
$yearBuilder->method('where')->willReturnSelf();
|
||||
$invoiceBuilder = $this->builderReturning(['balance' => $balance]);
|
||||
$invoiceBuilder->method('where')->willReturnSelf();
|
||||
|
||||
$db = $this->createMock(BaseConnection::class);
|
||||
$db->method('tableExists')->willReturn(true);
|
||||
$db->method('table')->willReturnCallback(static function (string $table) use ($yearBuilder, $invoiceBuilder) {
|
||||
return $table === 'school_years' ? $yearBuilder : $invoiceBuilder;
|
||||
});
|
||||
|
||||
return new EnrollmentTransitionService($db);
|
||||
}
|
||||
|
||||
public function testPreviousLastNameExceptionCarriesForwardWhenHouseholdUnchangedAndBalancePaid(): void
|
||||
{
|
||||
$service = $this->serviceWithLastNameCarryForward(
|
||||
[
|
||||
['id' => 1, 'firstname' => 'Amina', 'lastname' => 'Hassan', 'parent_id' => 9],
|
||||
['id' => 2, 'firstname' => 'Omar', 'lastname' => 'Ali', 'parent_id' => 9],
|
||||
],
|
||||
[[
|
||||
'id' => 44,
|
||||
'parent_id' => 9,
|
||||
'student_id' => 1,
|
||||
'school_year' => '2025-2026',
|
||||
'status' => 'used',
|
||||
'reason_code' => 'SIBLING_LAST_NAME_REVIEWED',
|
||||
'bypassed_rule_codes_json' => json_encode(['SIBLING_LAST_NAME_MISMATCH']),
|
||||
'family_student_ids_json' => json_encode([1, 2]),
|
||||
]]
|
||||
);
|
||||
$evaluation = [
|
||||
'blocking_rule_codes' => ['SIBLING_LAST_NAME_MISMATCH'],
|
||||
'blockers' => ['Linked siblings have different last names. Please contact administration to review the family record.'],
|
||||
'warnings' => [],
|
||||
];
|
||||
|
||||
$this->invoke($service, 'applyCarriedForwardLastNameException', [&$evaluation, 9, '2025-2026', '2026-2027']);
|
||||
|
||||
$this->assertNotContains('SIBLING_LAST_NAME_MISMATCH', $evaluation['blocking_rule_codes']);
|
||||
$this->assertContains('LAST_NAME_EXCEPTION_CARRIED_FORWARD', $evaluation['warning_rule_codes']);
|
||||
$this->assertTrue($evaluation['last_name_exception_carry_forward']['eligible']);
|
||||
}
|
||||
|
||||
public function testEmptyBypassCodesDoNotGrantException(): void
|
||||
{
|
||||
$builder = $this->createMock(BaseBuilder::class);
|
||||
$builder->method('where')->willReturnSelf();
|
||||
$builder->method('groupStart')->willReturnSelf();
|
||||
$builder->method('orWhere')->willReturnSelf();
|
||||
$builder->method('groupEnd')->willReturnSelf();
|
||||
$builder->method('orderBy')->willReturnSelf();
|
||||
$builder->method('limit')->willReturnSelf();
|
||||
$builder->method('get')->willReturn(new class {
|
||||
public function getRowArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => 7,
|
||||
'reason_code' => 'FINANCIAL_REVIEW_REQUIRED',
|
||||
'bypassed_rule_codes_json' => '[]',
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
$db = $this->createMock(BaseConnection::class);
|
||||
$db->method('tableExists')->with('enrollment_exceptions')->willReturn(true);
|
||||
$db->method('table')->with('enrollment_exceptions')->willReturn($builder);
|
||||
|
||||
$service = new EnrollmentTransitionService($db);
|
||||
$evaluation = [
|
||||
'blocking_rule_codes' => ['OUTSTANDING_BALANCE_BLOCKED'],
|
||||
'review_rule_codes' => [],
|
||||
];
|
||||
|
||||
$this->invoke($service, 'applyScopedException', [&$evaluation, 9, 1, '2025-2026', '2026-2027']);
|
||||
|
||||
$this->assertArrayNotHasKey('admin_exception', $evaluation);
|
||||
$this->assertArrayNotHasKey('parent_enrollment_allowed', $evaluation);
|
||||
}
|
||||
|
||||
public function testPreviousLastNameExceptionDoesNotCarryForwardWhenNewStudentIsAdded(): void
|
||||
{
|
||||
$service = $this->serviceWithLastNameCarryForward(
|
||||
[
|
||||
['id' => 1, 'firstname' => 'Amina', 'lastname' => 'Hassan', 'parent_id' => 9],
|
||||
['id' => 2, 'firstname' => 'Omar', 'lastname' => 'Ali', 'parent_id' => 9],
|
||||
['id' => 3, 'firstname' => 'Layla', 'lastname' => 'Hassan', 'parent_id' => 9],
|
||||
],
|
||||
[[
|
||||
'id' => 44,
|
||||
'parent_id' => 9,
|
||||
'student_id' => 1,
|
||||
'school_year' => '2025-2026',
|
||||
'status' => 'used',
|
||||
'reason_code' => 'SIBLING_LAST_NAME_REVIEWED',
|
||||
'bypassed_rule_codes_json' => json_encode(['SIBLING_LAST_NAME_MISMATCH']),
|
||||
'family_student_ids_json' => json_encode([1, 2]),
|
||||
]]
|
||||
);
|
||||
$evaluation = [
|
||||
'blocking_rule_codes' => ['SIBLING_LAST_NAME_MISMATCH'],
|
||||
'blockers' => ['Linked siblings have different last names. Please contact administration to review the family record.'],
|
||||
];
|
||||
|
||||
$this->invoke($service, 'applyCarriedForwardLastNameException', [&$evaluation, 9, '2025-2026', '2026-2027']);
|
||||
|
||||
$this->assertContains('SIBLING_LAST_NAME_MISMATCH', $evaluation['blocking_rule_codes']);
|
||||
$this->assertFalse($evaluation['last_name_exception_carry_forward']['eligible']);
|
||||
$this->assertSame('NEW_STUDENT_ADDED', $evaluation['last_name_exception_carry_forward']['reason']);
|
||||
}
|
||||
|
||||
public function testPreviousLastNameExceptionDoesNotCarryForwardWhenBalanceIsUnpaid(): void
|
||||
{
|
||||
$service = new EnrollmentTransitionService($this->createMock(BaseConnection::class));
|
||||
$evaluation = [
|
||||
'blocking_rule_codes' => ['SIBLING_LAST_NAME_MISMATCH', 'OUTSTANDING_BALANCE_BLOCKED'],
|
||||
];
|
||||
|
||||
$this->invoke($service, 'applyCarriedForwardLastNameException', [&$evaluation, 9, '2025-2026', '2026-2027']);
|
||||
|
||||
$this->assertContains('SIBLING_LAST_NAME_MISMATCH', $evaluation['blocking_rule_codes']);
|
||||
$this->assertArrayNotHasKey('last_name_exception_carry_forward', $evaluation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $students
|
||||
* @param list<array<string, mixed>> $exceptions
|
||||
*/
|
||||
private function serviceWithLastNameCarryForward(array $students, array $exceptions): EnrollmentTransitionService
|
||||
{
|
||||
$studentBuilder = $this->createMock(BaseBuilder::class);
|
||||
$studentBuilder->method('select')->willReturnSelf();
|
||||
$studentBuilder->method('where')->willReturnSelf();
|
||||
$studentBuilder->method('orderBy')->willReturnSelf();
|
||||
$studentBuilder->method('get')->willReturn(new class($students) {
|
||||
public function __construct(private readonly array $rows)
|
||||
{
|
||||
}
|
||||
|
||||
public function getResultArray(): array
|
||||
{
|
||||
return $this->rows;
|
||||
}
|
||||
});
|
||||
|
||||
$exceptionBuilder = $this->createMock(BaseBuilder::class);
|
||||
$exceptionBuilder->method('where')->willReturnSelf();
|
||||
$exceptionBuilder->method('whereIn')->willReturnSelf();
|
||||
$exceptionBuilder->method('orderBy')->willReturnSelf();
|
||||
$exceptionBuilder->method('get')->willReturn(new class($exceptions) {
|
||||
public function __construct(private readonly array $rows)
|
||||
{
|
||||
}
|
||||
|
||||
public function getResultArray(): array
|
||||
{
|
||||
return $this->rows;
|
||||
}
|
||||
});
|
||||
|
||||
$db = $this->createMock(BaseConnection::class);
|
||||
$db->method('tableExists')->willReturn(true);
|
||||
$db->method('table')->willReturnCallback(static function (string $table) use ($studentBuilder, $exceptionBuilder) {
|
||||
return $table === 'students' ? $studentBuilder : $exceptionBuilder;
|
||||
});
|
||||
|
||||
return new EnrollmentTransitionService($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<mixed> $args
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\Services;
|
||||
|
||||
use App\Services\SchoolYearManagementService;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use ReflectionClass;
|
||||
|
||||
final class SchoolYearManagementServiceCalendarTest extends CIUnitTestCase
|
||||
{
|
||||
public function testCalendarDefaultsForNextSchoolYear(): void
|
||||
{
|
||||
$calendar = $this->calendarPayload('2026-2027');
|
||||
|
||||
$this->assertSame('2026-08-01', $calendar['starts_on']);
|
||||
$this->assertSame('2027-07-31', $calendar['ends_on']);
|
||||
$this->assertSame('2026-08-01', $calendar['registration_starts_on']);
|
||||
$this->assertSame('2026-10-05', $calendar['registration_ends_on']);
|
||||
$this->assertSame('2026-09-20', $calendar['first_day_of_school']);
|
||||
$this->assertSame('2026-09-20', $calendar['1st_day_of_school']);
|
||||
$this->assertSame('2026-09-13', $calendar['fall_makeup_exam_on']);
|
||||
$this->assertSame('2026-09-06', $calendar['orientation_day']);
|
||||
$this->assertSame('2027-05-23', $calendar['final_exam_day']);
|
||||
$this->assertSame('2027-06-06', $calendar['last_day_of_school']);
|
||||
$this->assertSame('2027-06-06', $calendar['last_school_day']);
|
||||
$this->assertSame('2027-01-17', $calendar['midterm_exam_day']);
|
||||
$this->assertSame('2027-01-24', $calendar['spring_semester_start']);
|
||||
$this->assertSame('2027-03-01', $calendar['installment_date']);
|
||||
}
|
||||
|
||||
public function testNextToLastSeptemberSundayWhenMonthEndsOnSunday(): void
|
||||
{
|
||||
$calendar = $this->calendarPayload('2029-2030');
|
||||
|
||||
$this->assertSame('2029-09-23', $calendar['first_day_of_school']);
|
||||
}
|
||||
|
||||
private function calendarPayload(string $schoolYear): array
|
||||
{
|
||||
$service = (new ReflectionClass(SchoolYearManagementService::class))->newInstanceWithoutConstructor();
|
||||
$method = (new ReflectionClass(SchoolYearManagementService::class))->getMethod('calendarPayloadForSchoolYear');
|
||||
$method->setAccessible(true);
|
||||
|
||||
return $method->invoke($service, $schoolYear);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user