seedConfig(); $this->seedClassSection(101, '2'); $this->seedClassSection(102, '5'); $this->seedClassSection(103, '11'); $service = $this->makeService(); $result = $service->calculate([ [ 'student_id' => 1, 'firstname' => 'Anna', 'lastname' => 'Doe', 'class_section_id' => 101, 'enrollment_status' => 'enrolled', 'admission_status' => 'accepted', ], [ 'student_id' => 2, 'firstname' => 'Ben', 'lastname' => 'Doe', 'class_section_id' => 102, 'enrollment_status' => 'enrolled', 'admission_status' => 'accepted', ], [ 'student_id' => 3, 'firstname' => 'Cara', 'lastname' => 'Doe', 'class_section_id' => 103, 'enrollment_status' => 'enrolled', 'admission_status' => 'accepted', ], ]); $this->assertSame('2025-2026', $result['school_year']); $this->assertSame(3, $result['billable_count']); $this->assertSame(0, $result['non_billable_count']); $this->assertSame(730.0, $result['family_total']); $byStudent = []; foreach ($result['students'] as $row) { $byStudent[$row['student_id']] = $row; } $this->assertSame('first_regular', $byStudent[1]['fee_category']); $this->assertSame(350.0, $byStudent[1]['tuition_fee']); $this->assertSame('Anna Doe', $byStudent[1]['student_name']); $this->assertSame('additional_regular', $byStudent[2]['fee_category']); $this->assertSame(200.0, $byStudent[2]['tuition_fee']); $this->assertSame('youth', $byStudent[3]['fee_category']); $this->assertSame(180.0, $byStudent[3]['tuition_fee']); } public function test_calculate_excludes_non_billable_students_from_family_total(): void { $this->seedConfig(); $this->seedClassSection(201, '3'); $this->seedClassSection(202, '4'); $service = $this->makeService(); $result = $service->calculate([ [ 'student_id' => 10, 'class_section_id' => 201, 'enrollment_status' => 'enrolled', 'admission_status' => 'accepted', ], [ 'student_id' => 11, 'class_section_id' => 202, 'enrollment_status' => 'withdrawn', 'admission_status' => 'accepted', ], ]); $this->assertSame(1, $result['billable_count']); $this->assertSame(1, $result['non_billable_count']); $this->assertSame(350.0, $result['family_total']); $byStudent = []; foreach ($result['students'] as $row) { $byStudent[$row['student_id']] = $row; } $this->assertSame('not_billable', $byStudent[11]['fee_category']); $this->assertSame(0.0, $byStudent[11]['tuition_fee']); } public function test_calculate_applies_per_student_discount(): void { $this->seedConfig(); $this->seedClassSection(301, '2'); $service = $this->makeService(); $result = $service->calculate([ [ 'student_id' => 99, 'class_section_id' => 301, 'enrollment_status' => 'enrolled', 'admission_status' => 'accepted', 'discount_amount' => 100.0, ], ]); $this->assertSame(250.0, $result['family_total']); $this->assertSame(100.0, $result['students'][0]['discount_amount']); $this->assertSame(250.0, $result['students'][0]['final_tuition_amount']); } public function test_family_total_helper(): void { $this->seedConfig(); $this->seedClassSection(401, '3'); $service = $this->makeService(); $this->assertSame(350.0, $service->familyTotal([ [ 'student_id' => 1, 'class_section_id' => 401, 'enrollment_status' => 'enrolled', 'admission_status' => 'accepted', ], ])); } private function makeService(): TuitionCalculationService { $config = new FeeConfigService(); return new TuitionCalculationService( $config, new FeeStudentFeeService(new FeeGradeService(), $config) ); } private function seedConfig(): void { DB::table('configuration')->insert([ ['config_key' => 'school_year', 'config_value' => '2025-2026'], ['config_key' => 'first_student_fee', 'config_value' => '350'], ['config_key' => 'second_student_fee', 'config_value' => '200'], ['config_key' => 'youth_fee', 'config_value' => '180'], ]); } private function seedClassSection(int $sectionId, string $gradeName): void { DB::table('classSection')->insert([ 'class_id' => $sectionId, 'class_section_id' => $sectionId, 'class_section_name' => $gradeName, 'semester' => 'Fall', 'school_year' => '2025-2026', ]); } }