'Kehadiran', 'slug' => 'kehadiran', 'color' => '#EF4444', 'is_active' => true, ]); ViolationType::create([ 'category_id' => $category->id, 'name' => 'Membolos/tidak masuk sekolah tanpa keterangan', 'slug' => 'alpha', 'points' => 20, // poin per 10 JP (2 poin/JP) 'description' => 'Tidak hadir tanpa keterangan', 'is_active' => true, 'is_system' => true, ]); } protected function makeStudent(int $sourceId, string $name): Student { return Student::create([ 'source_id' => (string) $sourceId, 'nisn' => '00' . $sourceId, 'student_number' => 'TEST/' . $sourceId, 'full_name' => $name, 'class_name' => 'XII TKJ 3', 'class_level' => 'XII', 'department_name' => 'TKJ', 'status' => 'active', 'is_active' => true, ]); } protected function sync(array $attendances): \Illuminate\Testing\TestResponse { return $this->post('/api/v1/attendance/sync', [ 'token' => 'test-token', 'attendances' => $attendances, ]); } public function test_push_alpha_membuat_violation_hanya_untuk_siswa_alpha(): void { $s1 = $this->makeStudent(1001, 'SISWA ALPHA'); $s2 = $this->makeStudent(1002, 'SISWA HADIR'); $this->sync([ ['source_student_id' => 1001, 'date' => '2026-08-26', 'lesson_hour' => 1, 'status' => 'alpha'], ['source_student_id' => 1001, 'date' => '2026-08-26', 'lesson_hour' => 2, 'status' => 'alpha'], ['source_student_id' => 1002, 'date' => '2026-08-26', 'lesson_hour' => 1, 'status' => 'hadir'], ])->assertOk(); // Siswa alpha → violation 2 JP = 4 poin (2 poin/JP) $this->assertDatabaseHas('violations', [ 'student_id' => $s1->id, 'violation_date' => '2026-08-26', 'points' => 4, ]); // Siswa hadir → TIDAK boleh kena violation $this->assertDatabaseMissing('violations', [ 'student_id' => $s2->id, 'violation_date' => '2026-08-26', ]); } public function test_push_koreksi_tanpa_alpha_menghapus_violation_lama(): void { $s1 = $this->makeStudent(1001, 'SISWA KOREKSI'); $alphaType = ViolationType::where('slug', 'alpha')->first(); // Simulasikan violation system-generated lama (salah: siswa sebenarnya sakit) Violation::create([ 'student_id' => $s1->id, 'student_class' => 'XII TKJ 3', 'violation_type_id' => $alphaType->id, 'recorded_by' => null, 'description' => 'Alpha - Tidak hadir tanpa keterangan (4 jam pelajaran)', 'points' => 8, 'violation_date' => '2026-08-26', 'notes' => 'Dibuat otomatis dari sinkron E-Jurnal.', ]); // Push koreksi: siswa kini SAKIT (tidak ada alpha sama sekali dalam push) $this->sync([ ['source_student_id' => 1001, 'date' => '2026-08-26', 'lesson_hour' => 1, 'status' => 'sakit'], ['source_student_id' => 1001, 'date' => '2026-08-26', 'lesson_hour' => 2, 'status' => 'sakit'], ])->assertOk(); // Violation lama harus TERHAPUS (bug lama: tidak terhapus karena $alphaDate null) $this->assertNull( Violation::where('student_id', $s1->id)->where('violation_date', '2026-08-26')->first(), 'Violation alpha lama harus terhapus setelah push koreksi tanpa alpha.' ); } public function test_push_multi_tanggal_tidak_membuat_violation_salah_tanggal(): void { $s1 = $this->makeStudent(1001, 'SISWA CAMPURAN'); // Push berisi 2 tanggal: 26-08 hadir, 27-08 alpha. // Bug lama: $alphaDate global = 27-08 → siswa dihitung alpha di 27-08 untuk semua siswa. // Harusnya violation HANYA untuk 27-08. $this->sync([ ['source_student_id' => 1001, 'date' => '2026-08-26', 'lesson_hour' => 1, 'status' => 'hadir'], ['source_student_id' => 1001, 'date' => '2026-08-27', 'lesson_hour' => 1, 'status' => 'alpha'], ['source_student_id' => 1001, 'date' => '2026-08-27', 'lesson_hour' => 2, 'status' => 'alpha'], ])->assertOk(); $this->assertDatabaseHas('violations', [ 'student_id' => $s1->id, 'violation_date' => '2026-08-27', 'points' => 4, ]); $this->assertDatabaseMissing('violations', [ 'student_id' => $s1->id, 'violation_date' => '2026-08-26', ]); } public function test_push_siswa_yang_bukan_alpha_tidak_kena_violation_saat_push_berisi_alpha_siswa_lain(): void { $s1 = $this->makeStudent(1001, 'SISWA ALPHA'); $s2 = $this->makeStudent(1002, 'SISWA HADIR'); // Bug lama: totalAlpha dihitung utk SEMUA siswa dgn tanggal global → s2 ikut kena. $this->sync([ ['source_student_id' => 1001, 'date' => '2026-08-26', 'lesson_hour' => 1, 'status' => 'alpha'], ['source_student_id' => 1002, 'date' => '2026-08-26', 'lesson_hour' => 1, 'status' => 'hadir'], ['source_student_id' => 1002, 'date' => '2026-08-26', 'lesson_hour' => 2, 'status' => 'hadir'], ])->assertOk(); $this->assertDatabaseHas('violations', [ 'student_id' => $s1->id, 'violation_date' => '2026-08-26', ]); $this->assertDatabaseMissing('violations', [ 'student_id' => $s2->id, 'violation_date' => '2026-08-26', ]); } }