fix parent pages and teachers and admin
API CI/CD / Validate (composer + pint) (push) Successful in 3m8s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Test (PHPUnit) (push) Failing after 6m5s
API CI/CD / Security audit (push) Failing after 52s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
API CI/CD / Validate (composer + pint) (push) Successful in 3m8s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Test (PHPUnit) (push) Failing after 6m5s
API CI/CD / Security audit (push) Failing after 52s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
This commit is contained in:
@@ -152,28 +152,49 @@ class SchoolYearControllerTest extends TestCase
|
||||
$this->assertSame([502], $row['credit_invoice_ids']);
|
||||
}
|
||||
|
||||
public function test_credit_only_parent_is_reported_as_credit_not_unpaid_carryover(): void
|
||||
public function test_parent_balance_report_uses_only_positive_invoice_balances(): void
|
||||
{
|
||||
$this->seedClosureData();
|
||||
$this->seedParent(12, 'Credit', 'Only', 'credit@example.com');
|
||||
$this->seedParent(13, 'Discount', 'Paid', 'discount-paid@example.com');
|
||||
|
||||
DB::table('invoices')->insert([
|
||||
'id' => 503,
|
||||
'parent_id' => 12,
|
||||
'invoice_number' => 'INV-503-CREDIT',
|
||||
'total_amount' => 0,
|
||||
'balance' => -50,
|
||||
'paid_amount' => 50,
|
||||
'has_discount' => 0,
|
||||
'issue_date' => '2026-05-11',
|
||||
'due_date' => '2026-06-10',
|
||||
'status' => 'credit',
|
||||
'description' => 'Credit only',
|
||||
'school_year' => '2025-2026',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'updated_by' => 1,
|
||||
'semester' => 'Fall',
|
||||
[
|
||||
'id' => 503,
|
||||
'parent_id' => 12,
|
||||
'invoice_number' => 'INV-503-CREDIT',
|
||||
'total_amount' => 0,
|
||||
'balance' => -50,
|
||||
'paid_amount' => 50,
|
||||
'has_discount' => 0,
|
||||
'issue_date' => '2026-05-11',
|
||||
'due_date' => '2026-06-10',
|
||||
'status' => 'credit',
|
||||
'description' => 'Credit only',
|
||||
'school_year' => '2025-2026',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'updated_by' => 1,
|
||||
'semester' => 'Fall',
|
||||
],
|
||||
[
|
||||
'id' => 505,
|
||||
'parent_id' => 13,
|
||||
'invoice_number' => 'INV-505-DISCOUNT-PAID',
|
||||
'total_amount' => 500,
|
||||
'balance' => 0,
|
||||
'paid_amount' => 500,
|
||||
'has_discount' => 1,
|
||||
'issue_date' => '2026-05-11',
|
||||
'due_date' => '2026-06-10',
|
||||
'status' => 'paid',
|
||||
'description' => 'Discount paid invoice',
|
||||
'school_year' => '2025-2026',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'updated_by' => 1,
|
||||
'semester' => 'Fall',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->actingAs(User::query()->findOrFail(1), 'api');
|
||||
@@ -182,18 +203,53 @@ class SchoolYearControllerTest extends TestCase
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.summary.parents_with_positive_balance', 1)
|
||||
->assertJsonPath('data.summary.parents_with_credit_balance', 1)
|
||||
->assertJsonPath('data.summary.parents_with_credit_balance', 0)
|
||||
->assertJsonPath('data.summary.total_positive_unpaid_balance', 200)
|
||||
->assertJsonPath('data.summary.total_credit_balance', 50);
|
||||
->assertJsonPath('data.summary.total_credit_balance', 0)
|
||||
->assertJsonPath('data.summary.parent_count', 1)
|
||||
->assertJsonPath('data.summary.total_transferred', 200);
|
||||
|
||||
$creditRow = collect($response->json('data.rows'))
|
||||
->first(fn (array $row): bool => (int) $row['parent_id'] === 12);
|
||||
$rows = collect($response->json('data.rows'));
|
||||
|
||||
$this->assertNotNull($creditRow);
|
||||
$this->assertSame(0.0, (float) $creditRow['positive_unpaid_balance']);
|
||||
$this->assertSame(50.0, (float) $creditRow['credit_balance']);
|
||||
$this->assertSame(0.0, (float) $creditRow['amount_to_transfer']);
|
||||
$this->assertSame([503], $creditRow['credit_invoice_ids']);
|
||||
$this->assertTrue($rows->contains(fn (array $row): bool => (int) $row['parent_id'] === 10));
|
||||
$this->assertSame(200.0, (float) $rows->first(fn (array $row): bool => (int) $row['parent_id'] === 10)['old_unpaid_amount']);
|
||||
$this->assertFalse($rows->contains(fn (array $row): bool => (int) $row['parent_id'] === 12));
|
||||
$this->assertFalse($rows->contains(fn (array $row): bool => (int) $row['parent_id'] === 13));
|
||||
}
|
||||
|
||||
public function test_closing_report_unpaid_balance_totals_are_recomputed_from_positive_invoice_balances(): void
|
||||
{
|
||||
$this->seedClosureData();
|
||||
|
||||
DB::table('school_years')->where('id', 1)->update([
|
||||
'status' => 'closed',
|
||||
'closed_at' => now(),
|
||||
'closed_by' => 1,
|
||||
]);
|
||||
|
||||
DB::table('audit_logs')->insert([
|
||||
'user_id' => 1,
|
||||
'action' => 'school_year_closed',
|
||||
'table_name' => 'school_years',
|
||||
'record_id' => 1,
|
||||
'old_value' => null,
|
||||
'new_value' => null,
|
||||
'metadata' => json_encode([
|
||||
'new_school_year' => '2026-2027',
|
||||
'balance_counts' => [
|
||||
'transferred_parents' => 48,
|
||||
'transferred_amount' => 9999,
|
||||
],
|
||||
]),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$this->actingAs(User::query()->findOrFail(1), 'api');
|
||||
|
||||
$this->getJson('/api/v1/school-years/reports/closing?school_year=2025-2026')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.totals.parents_with_unpaid_balances', 1)
|
||||
->assertJsonPath('data.totals.total_unpaid_balance_transferred', 200);
|
||||
}
|
||||
|
||||
public function test_pending_or_draft_invoice_blocks_close_but_is_not_transferred(): void
|
||||
@@ -567,6 +623,139 @@ class SchoolYearControllerTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_passing_age_17_student_graduates_and_is_not_enrolled_next_year(): void
|
||||
{
|
||||
$this->seedClosureData();
|
||||
|
||||
DB::table('students')->where('id', 100)->update(['age' => 17]);
|
||||
|
||||
$this->actingAs(User::query()->findOrFail(1), 'api');
|
||||
|
||||
$preview = $this->postJson('/api/v1/school-years/1/preview-close', [
|
||||
'new_school_year' => [
|
||||
'name' => '2026-2027',
|
||||
'start_date' => '2026-09-01',
|
||||
'end_date' => '2027-06-30',
|
||||
],
|
||||
'transfer_unpaid_balances' => true,
|
||||
]);
|
||||
|
||||
$preview->assertOk()
|
||||
->assertJsonPath('data.promotion_preview.summary.promote', 0)
|
||||
->assertJsonPath('data.promotion_preview.summary.graduate', 1)
|
||||
->assertJsonPath('data.promotion_preview.rows.0.promotion_action', 'graduate');
|
||||
|
||||
$this->postJson('/api/v1/school-years/1/close', [
|
||||
'new_school_year' => [
|
||||
'name' => '2026-2027',
|
||||
'start_date' => '2026-09-01',
|
||||
'end_date' => '2027-06-30',
|
||||
],
|
||||
'transfer_unpaid_balances' => true,
|
||||
'confirmation' => 'CLOSE 2025-2026',
|
||||
])->assertOk();
|
||||
|
||||
$this->assertDatabaseMissing('enrollments', [
|
||||
'student_id' => 100,
|
||||
'school_year' => '2026-2027',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseMissing('student_class', [
|
||||
'student_id' => 100,
|
||||
'school_year' => '2026-2027',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_kindergarten_student_age_six_next_year_promotes_to_grade_one_not_arabic(): void
|
||||
{
|
||||
$this->seedClosureData();
|
||||
|
||||
DB::table('classes')->insert([
|
||||
['id' => 1, 'class_name' => '1', 'schedule' => 'Sunday 9:00', 'school_year' => '2026-2027', 'semester' => 'Fall', 'capacity' => 30],
|
||||
['id' => 13, 'class_name' => 'KG', 'schedule' => 'Sunday 9:00', 'school_year' => '2025-2026', 'semester' => 'Fall', 'capacity' => 30],
|
||||
['id' => 14, 'class_name' => 'Arabic', 'schedule' => 'Sunday 11:00', 'school_year' => '2026-2027', 'semester' => 'Fall', 'capacity' => 30],
|
||||
]);
|
||||
|
||||
DB::table('classSection')->insert([
|
||||
[
|
||||
'class_section_id' => 1,
|
||||
'class_section_name' => 'KG',
|
||||
'class_id' => 13,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
],
|
||||
[
|
||||
'class_section_id' => 10,
|
||||
'class_section_name' => '1A',
|
||||
'class_id' => 1,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2026-2027',
|
||||
],
|
||||
[
|
||||
'class_section_id' => 140,
|
||||
'class_section_name' => 'Arabic A',
|
||||
'class_id' => 14,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2026-2027',
|
||||
],
|
||||
]);
|
||||
|
||||
DB::table('students')->where('id', 100)->update(['age' => 5]);
|
||||
DB::table('student_class')->where('student_id', 100)->where('school_year', '2025-2026')->update([
|
||||
'class_section_id' => 1,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
DB::table('enrollments')->where('student_id', 100)->where('school_year', '2025-2026')->update([
|
||||
'class_section_id' => 1,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->actingAs(User::query()->findOrFail(1), 'api');
|
||||
|
||||
$preview = $this->postJson('/api/v1/school-years/1/preview-close', [
|
||||
'new_school_year' => [
|
||||
'name' => '2026-2027',
|
||||
'start_date' => '2026-09-01',
|
||||
'end_date' => '2027-06-30',
|
||||
],
|
||||
'transfer_unpaid_balances' => true,
|
||||
]);
|
||||
|
||||
$preview->assertOk()
|
||||
->assertJsonPath('data.promotion_preview.rows.0.promotion_action', 'promote')
|
||||
->assertJsonPath('data.promotion_preview.rows.0.current_grade', 'KG')
|
||||
->assertJsonPath('data.promotion_preview.rows.0.target_grade', '1')
|
||||
->assertJsonPath('data.promotion_preview.rows.0.target_class_section_id', 10);
|
||||
|
||||
$this->postJson('/api/v1/school-years/1/close', [
|
||||
'new_school_year' => [
|
||||
'name' => '2026-2027',
|
||||
'start_date' => '2026-09-01',
|
||||
'end_date' => '2027-06-30',
|
||||
],
|
||||
'transfer_unpaid_balances' => true,
|
||||
'confirmation' => 'CLOSE 2025-2026',
|
||||
])->assertOk();
|
||||
|
||||
$this->assertDatabaseHas('enrollments', [
|
||||
'student_id' => 100,
|
||||
'school_year' => '2026-2027',
|
||||
'class_section_id' => 10,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('student_class', [
|
||||
'student_id' => 100,
|
||||
'school_year' => '2026-2027',
|
||||
'class_section_id' => 10,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseMissing('enrollments', [
|
||||
'student_id' => 100,
|
||||
'school_year' => '2026-2027',
|
||||
'class_section_id' => 140,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_closed_school_year_blocks_legacy_invoice_generation(): void
|
||||
{
|
||||
$this->seedClosureData();
|
||||
|
||||
Reference in New Issue
Block a user