invoices, static function (array $invoice) use ($parentId, $schoolYear, $semester): bool { if ((int)($invoice['parent_id'] ?? 0) !== $parentId || (string)($invoice['school_year'] ?? '') !== $schoolYear) { return false; } return $semester === null || $semester === '' || (string)($invoice['semester'] ?? '') === $semester; })); } protected function ledgerForInvoice(int $invoiceId): array { return $this->ledgers[$invoiceId] ?? []; } protected function eligibilityForSource( int $parentId, ?int $invoiceId, string $sourceType, int $sourceId ): RefundEligibilityResult { return $this->eligibilities[$sourceId] ?? new RefundEligibilityResult(0, 0, 0, 0, []); } } class ParentLedgerServiceTest extends CIUnitTestCase { public function testParentProjectionAggregatesLedgerAndEligibilityFields(): void { $service = new ParentLedgerServiceHarness( [ ['id' => 10, 'parent_id' => 5, 'school_year' => '2026-2027', 'semester' => 'Fall', 'invoice_number' => 'INV-10'], ['id' => 11, 'parent_id' => 5, 'school_year' => '2026-2027', 'semester' => 'Fall', 'invoice_number' => 'INV-11'], ['id' => 12, 'parent_id' => 5, 'school_year' => '2026-2027', 'semester' => 'Spring', 'invoice_number' => 'INV-12'], ], [ 10 => [ 'totalAmountCents' => 10000, 'discountCents' => 1000, 'paidCents' => 11000, 'completedRefundCents' => 500, 'balanceDueCents' => 0, 'customerCreditCents' => 1500, 'status' => 'paid', ], 11 => [ 'totalAmountCents' => 8000, 'discountCents' => 0, 'paidCents' => 2000, 'completedRefundCents' => 0, 'balanceDueCents' => 6000, 'customerCreditCents' => 0, 'status' => 'partially_paid', ], ], [ 10 => new RefundEligibilityResult(1500, 0, 300, 1200, ['HAS_APPROVED_RESERVATIONS']), 11 => new RefundEligibilityResult(0, 0, 0, 0, ['NO_AVAILABLE_CREDIT']), ] ); $projection = $service->getParentProjection(5, '2026-2027', 'Fall'); $this->assertSame(18000, $projection['grossChargesCents']); $this->assertSame(1000, $projection['discountCents']); $this->assertSame(17000, $projection['netInvoiceChargesCents']); $this->assertSame(13000, $projection['validPaymentCents']); $this->assertSame(500, $projection['completedRefundCents']); $this->assertSame(6000, $projection['balanceDueCents']); $this->assertSame(1500, $projection['customerCreditCents']); $this->assertSame(300, $projection['approvedRefundReservationCents']); $this->assertSame(1200, $projection['availableRefundableCreditCents']); $this->assertSame('170.00', $projection['net_invoice_charges']); $this->assertSame('12.00', $projection['available_refundable_credit']); $this->assertCount(2, $projection['invoices']); } }