Kelas model, ...] */ protected array $kelasMapping = []; /** * Execute the console command. */ public function handle(): int { $isDryRun = $this->option('dry-run'); $this->newLine(); $this->info('╔══════════════════════════════════════════════════════╗'); $this->info('║ MIGRASI SANTRI KE SISTEM KELAS BARU ║'); $this->info('║ ' . ($isDryRun ? '🔍 MODE: DRY-RUN (Preview Only)' : '🚀 MODE: EXECUTE (Real Migration)') . ' ║'); $this->info('╚══════════════════════════════════════════════════════╝'); $this->newLine(); // ──────────────────── // STEP 1: Validasi kelompok kelas // ──────────────────── $this->info('📋 Step 1: Validasi kelompok kelas...'); if (!$this->validateAndBuildMapping()) { $this->error('❌ Validasi gagal! Pastikan data kelompok_kelas dan kelas sudah tersedia.'); return Command::FAILURE; } $this->info(' ✅ Mapping kelas berhasil di-resolve:'); foreach ($this->kelasMapping as $oldKelas => $kelasModel) { $this->line(" {$oldKelas} → {$kelasModel->kode_kelas} ({$kelasModel->nama_kelas})"); } $this->newLine(); // ──────────────────── // STEP 2: Ambil semua santri // ──────────────────── $this->info('📋 Step 2: Mengambil data santri...'); $santris = Santri::select('id', 'id_santri', 'nama_lengkap', 'kelas')->get(); $this->totalSantri = $santris->count(); if ($this->totalSantri === 0) { $this->warn('⚠️ Tidak ada data santri ditemukan.'); return Command::SUCCESS; } $this->info(" 📊 Total santri ditemukan: {$this->totalSantri}"); $this->newLine(); // ──────────────────── // STEP 3: Migrate // ──────────────────── $tahunAjaran = SantriKelas::getCurrentAcademicYear(); $this->info("📋 Step 3: Memulai migrasi (Tahun Ajaran: {$tahunAjaran})..."); $this->newLine(); if (!$isDryRun) { // Wrap dalam transaction untuk safety DB::beginTransaction(); } try { $this->output->progressStart($this->totalSantri); foreach ($santris as $santri) { $this->processSantri($santri, $tahunAjaran, $isDryRun); $this->output->progressAdvance(); } $this->output->progressFinish(); $this->newLine(); if (!$isDryRun) { DB::commit(); $this->info('✅ Transaction committed.'); } } catch (\Exception $e) { if (!$isDryRun) { DB::rollBack(); $this->error('❌ Transaction rolled back!'); } $this->error("Fatal error: {$e->getMessage()}"); Log::error('MigrateSantriToNewKelas fatal error', [ 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); return Command::FAILURE; } // ──────────────────── // STEP 4: Summary Report // ──────────────────── $this->printSummary($isDryRun, $tahunAjaran); // ──────────────────── // STEP 5: Post-migration validation // ──────────────────── if (!$isDryRun) { $this->validatePostMigration(); } return Command::SUCCESS; } /** * Validasi kelompok kelas dan build mapping dinamis. */ protected function validateAndBuildMapping(): bool { $mappings = [ 'PB' => '%PB%', 'Lambatan' => '%Lambatan%', 'Cepatan' => '%Cepatan%', ]; foreach ($mappings as $oldKelas => $likePattern) { $kelas = Kelas::whereHas('kelompok', function ($q) use ($likePattern) { $q->where('nama_kelompok', 'like', $likePattern); }) ->where('is_active', true) ->orderBy('urutan') ->first(); if (!$kelas) { $this->error(" ❌ Tidak ditemukan kelas aktif untuk kelompok '{$oldKelas}' (pattern: {$likePattern})"); return false; } $this->kelasMapping[$oldKelas] = $kelas; } return true; } /** * Process satu santri. */ protected function processSantri(Santri $santri, string $tahunAjaran, bool $isDryRun): void { try { $kelasLama = $santri->kelas; // Skip jika kelas NULL atau tidak dikenali if (empty($kelasLama) || !isset($this->kelasMapping[$kelasLama])) { $reason = empty($kelasLama) ? 'Kelas NULL' : "Kelas '{$kelasLama}' tidak dikenali"; $this->skipped[] = [ 'id_santri' => $santri->id_santri, 'nama' => $santri->nama_lengkap, 'reason' => $reason, ]; $this->skipCount++; return; } $kelasBaru = $this->kelasMapping[$kelasLama]; if ($isDryRun) { // Dry-run: hanya tampilkan $this->line(" ✓ {$santri->id_santri} ({$santri->nama_lengkap}): {$kelasLama} → {$kelasBaru->kode_kelas} ({$kelasBaru->nama_kelas})"); $this->successCount++; return; } // Real execute: Insert/update ke santri_kelas SantriKelas::updateOrCreate( [ 'id_santri' => $santri->id_santri, 'tahun_ajaran' => $tahunAjaran, 'is_primary' => true, ], [ 'id_kelas' => $kelasBaru->id, ] ); $this->successCount++; } catch (\Exception $e) { $this->errors[] = [ 'id_santri' => $santri->id_santri, 'nama' => $santri->nama_lengkap, 'error' => $e->getMessage(), ]; $this->errorCount++; Log::warning('MigrateSantriToNewKelas: Error processing santri', [ 'id_santri' => $santri->id_santri, 'error' => $e->getMessage(), ]); } } /** * Print summary report. */ protected function printSummary(bool $isDryRun, string $tahunAjaran): void { $this->newLine(); $this->info('╔══════════════════════════════════════════════════════╗'); $this->info('║ 📊 SUMMARY REPORT ║'); $this->info('╚══════════════════════════════════════════════════════╝'); $this->newLine(); $this->line(" Mode : DRY-RUN (Preview)' : 'green>EXECUTED (Real)') . ""); $this->line(" Tahun Ajaran : {$tahunAjaran}"); $this->newLine(); $this->line(" Total santri : {$this->totalSantri}"); $this->line(" ✅ Berhasil : {$this->successCount}"); $this->line(" ⚠️ Skipped : {$this->skipCount}"); $this->line(" ❌ Error : {$this->errorCount}"); // List skipped if (count($this->skipped) > 0) { $this->newLine(); $this->warn(' ⚠️ Santri yang di-skip:'); foreach ($this->skipped as $item) { $this->line(" - {$item['id_santri']} ({$item['nama']}): {$item['reason']}"); } } // List errors if (count($this->errors) > 0) { $this->newLine(); $this->error(' ❌ Santri yang error:'); foreach ($this->errors as $item) { $this->line(" - {$item['id_santri']} ({$item['nama']}): {$item['error']}"); } } $this->newLine(); if ($isDryRun) { $this->info('💡 Ini hanya preview. Jalankan tanpa --dry-run untuk eksekusi migrasi.'); } else { $this->info('✅ Migrasi selesai! Data santri_kelas telah diperbarui.'); } $this->newLine(); } /** * Validasi setelah migrasi. */ protected function validatePostMigration(): void { $this->info('📋 Post-migration validation...'); // Count santri yang punya kelas (kolom lama) tapi belum ada di santri_kelas $santriDenganKelas = Santri::whereNotNull('kelas') ->where('kelas', '!=', '') ->count(); $santriDiSantriKelas = SantriKelas::where('is_primary', true)->count(); $this->line(" Santri dengan kelas (kolom lama) : {$santriDenganKelas}"); $this->line(" Santri di santri_kelas (primary) : {$santriDiSantriKelas}"); if ($santriDiSantriKelas >= $santriDenganKelas) { $this->info(' ✅ Validasi OK! Semua santri sudah ter-migrate.'); } else { $diff = $santriDenganKelas - $santriDiSantriKelas; $this->warn(" ⚠️ Ada {$diff} santri yang belum ter-migrate. Periksa log di atas."); } $this->newLine(); } }