feat: add graduation archive and public document services
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\AcademicYear;
|
||||
use App\Models\Department;
|
||||
use App\Models\SchoolClass;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentAcademicHistory;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class StudentPromotionTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_index_shows_class_summary_instead_of_loading_all_students_when_class_is_not_selected(): void
|
||||
{
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
[$fromYear, $toYear, $department, $fromClass] = $this->createPromotionContext();
|
||||
|
||||
Student::create([
|
||||
'student_number' => 'S-100',
|
||||
'full_name' => 'Ahmad Ringkasan',
|
||||
'gender' => 'L',
|
||||
'academic_year_id' => $fromYear->id,
|
||||
'department_id' => $department->id,
|
||||
'class_id' => $fromClass->id,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($admin)->get(route('student-promotions.index', [
|
||||
'from_academic_year_id' => $fromYear->id,
|
||||
'to_academic_year_id' => $toYear->id,
|
||||
]));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertSee('Pilih <span class="font-semibold">kelas asal</span> untuk memproses siswa per rombel.', false);
|
||||
$response->assertSee($fromClass->name);
|
||||
$response->assertSee('Proses kelas ini');
|
||||
$response->assertDontSee('Ahmad Ringkasan');
|
||||
}
|
||||
|
||||
public function test_admin_can_promote_student_and_create_new_academic_history(): void
|
||||
{
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
[$fromYear, $toYear, $department, $fromClass, $targetClass] = $this->createPromotionContext();
|
||||
|
||||
$student = Student::create([
|
||||
'student_number' => 'S-001',
|
||||
'full_name' => 'Budi Santoso',
|
||||
'gender' => 'L',
|
||||
'academic_year_id' => $fromYear->id,
|
||||
'department_id' => $department->id,
|
||||
'class_id' => $fromClass->id,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
StudentAcademicHistory::create([
|
||||
'student_id' => $student->id,
|
||||
'academic_year_id' => $fromYear->id,
|
||||
'department_id' => $department->id,
|
||||
'class_id' => $fromClass->id,
|
||||
'status' => 'active',
|
||||
'start_date' => '2025-07-01',
|
||||
'is_current' => true,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($admin)->post(route('student-promotions.store'), [
|
||||
'from_academic_year_id' => $fromYear->id,
|
||||
'to_academic_year_id' => $toYear->id,
|
||||
'class_id' => $fromClass->id,
|
||||
'promotions' => [
|
||||
[
|
||||
'student_id' => $student->id,
|
||||
'target_class_id' => $targetClass->id,
|
||||
'action' => 'promote',
|
||||
'notes' => 'Naik kelas otomatis hasil akhir tahun.',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertRedirect(route('student-promotions.index', [
|
||||
'from_academic_year_id' => $fromYear->id,
|
||||
'to_academic_year_id' => $toYear->id,
|
||||
'class_id' => $fromClass->id,
|
||||
]));
|
||||
|
||||
$student->refresh();
|
||||
|
||||
$this->assertSame($toYear->id, $student->academic_year_id);
|
||||
$this->assertSame($targetClass->id, $student->class_id);
|
||||
$this->assertSame('active', $student->status);
|
||||
|
||||
$this->assertDatabaseHas('student_academic_histories', [
|
||||
'student_id' => $student->id,
|
||||
'academic_year_id' => $fromYear->id,
|
||||
'class_id' => $fromClass->id,
|
||||
'status' => 'promoted',
|
||||
'is_current' => 0,
|
||||
'notes' => 'Naik kelas otomatis hasil akhir tahun.',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('student_academic_histories', [
|
||||
'student_id' => $student->id,
|
||||
'academic_year_id' => $toYear->id,
|
||||
'class_id' => $targetClass->id,
|
||||
'status' => 'active',
|
||||
'is_current' => 1,
|
||||
'notes' => 'Naik kelas otomatis hasil akhir tahun.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_promotion_is_rejected_when_target_class_exceeds_capacity(): void
|
||||
{
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
[$fromYear, $toYear, $department, $fromClass, $targetClass] = $this->createPromotionContext([
|
||||
'target_capacity' => 1,
|
||||
]);
|
||||
|
||||
Student::create([
|
||||
'student_number' => 'S-EXIST',
|
||||
'full_name' => 'Siswa Existing',
|
||||
'gender' => 'L',
|
||||
'academic_year_id' => $toYear->id,
|
||||
'department_id' => $department->id,
|
||||
'class_id' => $targetClass->id,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
$student = Student::create([
|
||||
'student_number' => 'S-002',
|
||||
'full_name' => 'Citra Lestari',
|
||||
'gender' => 'P',
|
||||
'academic_year_id' => $fromYear->id,
|
||||
'department_id' => $department->id,
|
||||
'class_id' => $fromClass->id,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
StudentAcademicHistory::create([
|
||||
'student_id' => $student->id,
|
||||
'academic_year_id' => $fromYear->id,
|
||||
'department_id' => $department->id,
|
||||
'class_id' => $fromClass->id,
|
||||
'status' => 'active',
|
||||
'start_date' => '2025-07-01',
|
||||
'is_current' => true,
|
||||
]);
|
||||
|
||||
$response = $this->from(route('student-promotions.index', [
|
||||
'from_academic_year_id' => $fromYear->id,
|
||||
'to_academic_year_id' => $toYear->id,
|
||||
'class_id' => $fromClass->id,
|
||||
]))->actingAs($admin)->post(route('student-promotions.store'), [
|
||||
'from_academic_year_id' => $fromYear->id,
|
||||
'to_academic_year_id' => $toYear->id,
|
||||
'class_id' => $fromClass->id,
|
||||
'promotions' => [
|
||||
[
|
||||
'student_id' => $student->id,
|
||||
'target_class_id' => $targetClass->id,
|
||||
'action' => 'promote',
|
||||
'notes' => 'Coba promosi ke kelas penuh.',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertRedirect(route('student-promotions.index', [
|
||||
'from_academic_year_id' => $fromYear->id,
|
||||
'to_academic_year_id' => $toYear->id,
|
||||
'class_id' => $fromClass->id,
|
||||
]));
|
||||
$response->assertSessionHas('errors');
|
||||
$this->assertStringContainsString(
|
||||
"Kelas {$targetClass->name} melebihi kapasitas. Sisa kuota 0, tetapi alokasi promosi/repeat yang dipilih 1.",
|
||||
session('errors')->first()
|
||||
);
|
||||
|
||||
$student->refresh();
|
||||
|
||||
$this->assertSame($fromYear->id, $student->academic_year_id);
|
||||
$this->assertSame($fromClass->id, $student->class_id);
|
||||
|
||||
$this->assertDatabaseMissing('student_academic_histories', [
|
||||
'student_id' => $student->id,
|
||||
'academic_year_id' => $toYear->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_promotion_is_rejected_when_student_already_has_target_year_history(): void
|
||||
{
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
[$fromYear, $toYear, $department, $fromClass, $targetClass] = $this->createPromotionContext();
|
||||
|
||||
$student = Student::create([
|
||||
'student_number' => 'S-003',
|
||||
'full_name' => 'Dewi Anjani',
|
||||
'gender' => 'P',
|
||||
'academic_year_id' => $fromYear->id,
|
||||
'department_id' => $department->id,
|
||||
'class_id' => $fromClass->id,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
StudentAcademicHistory::create([
|
||||
'student_id' => $student->id,
|
||||
'academic_year_id' => $fromYear->id,
|
||||
'department_id' => $department->id,
|
||||
'class_id' => $fromClass->id,
|
||||
'status' => 'active',
|
||||
'start_date' => '2025-07-01',
|
||||
'is_current' => true,
|
||||
]);
|
||||
|
||||
StudentAcademicHistory::create([
|
||||
'student_id' => $student->id,
|
||||
'academic_year_id' => $toYear->id,
|
||||
'department_id' => $department->id,
|
||||
'class_id' => $targetClass->id,
|
||||
'status' => 'active',
|
||||
'start_date' => '2026-07-01',
|
||||
'is_current' => false,
|
||||
]);
|
||||
|
||||
$response = $this->from(route('student-promotions.index', [
|
||||
'from_academic_year_id' => $fromYear->id,
|
||||
'to_academic_year_id' => $toYear->id,
|
||||
'class_id' => $fromClass->id,
|
||||
]))->actingAs($admin)->post(route('student-promotions.store'), [
|
||||
'from_academic_year_id' => $fromYear->id,
|
||||
'to_academic_year_id' => $toYear->id,
|
||||
'class_id' => $fromClass->id,
|
||||
'promotions' => [
|
||||
[
|
||||
'student_id' => $student->id,
|
||||
'target_class_id' => $targetClass->id,
|
||||
'action' => 'promote',
|
||||
'notes' => 'Tidak boleh dobel histori.',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertRedirect(route('student-promotions.index', [
|
||||
'from_academic_year_id' => $fromYear->id,
|
||||
'to_academic_year_id' => $toYear->id,
|
||||
'class_id' => $fromClass->id,
|
||||
]));
|
||||
$response->assertSessionHasErrors([
|
||||
'promotions.0.student_id' => 'Siswa Dewi Anjani sudah memiliki histori akademik pada tahun ajaran tujuan.',
|
||||
]);
|
||||
|
||||
$student->refresh();
|
||||
|
||||
$this->assertSame($fromYear->id, $student->academic_year_id);
|
||||
$this->assertSame($fromClass->id, $student->class_id);
|
||||
$this->assertDatabaseCount('student_academic_histories', 2);
|
||||
}
|
||||
|
||||
protected function createPromotionContext(array $overrides = []): array
|
||||
{
|
||||
$fromYear = AcademicYear::create([
|
||||
'name' => '2025/2026',
|
||||
'start_year' => 2025,
|
||||
'end_year' => 2026,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$toYear = AcademicYear::create([
|
||||
'name' => '2026/2027',
|
||||
'start_year' => 2026,
|
||||
'end_year' => 2027,
|
||||
'is_active' => false,
|
||||
]);
|
||||
|
||||
$department = Department::create([
|
||||
'code' => 'RPL',
|
||||
'name' => 'Rekayasa Perangkat Lunak',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$fromClass = SchoolClass::create([
|
||||
'academic_year_id' => $fromYear->id,
|
||||
'department_id' => $department->id,
|
||||
'name' => 'X RPL 1',
|
||||
'level' => 'X',
|
||||
'capacity' => 36,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$targetClass = SchoolClass::create([
|
||||
'academic_year_id' => $toYear->id,
|
||||
'department_id' => $department->id,
|
||||
'name' => 'XI RPL 1',
|
||||
'level' => 'XI',
|
||||
'capacity' => $overrides['target_capacity'] ?? 36,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
return [$fromYear, $toYear, $department, $fromClass, $targetClass];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user