initializeErrorLog(); // Path ke file csv $filePath = realpath(__DIR__ . '/csv/penilaian/penilai.fix.latest_20251011.csv'); if (!$filePath) { Log::error('File csv tidak ditemukan: ' . __DIR__ . '/csv/penilaian/penilai.fix.latest_20251011.csv'); $this->command->error('File csv tidak ditemukan.'); return; } if (($handle = fopen($filePath, 'r')) === false) { Log::error('Gagal membuka file CSV: ' . $filePath); $this->command->error('Gagal membuka file CSV.'); return; } $header = fgetcsv($handle, 0, ','); $rows = []; $batchSize = 500; $userData = []; $branchCache = []; // <-- Gunakan sebagai cache $totalData = 0; while (($data = fgetcsv($handle, 0, ',')) !== false) { $totalData++; } rewind($handle); fgetcsv($handle, 0, ','); // Skip header $batchCount = 0; $currentRow = 0; $errorCount = 0; $errorDebitureIds = []; while (($data = fgetcsv($handle, 0, ',')) !== false) { if (count($data) != count($header)) { Log::warning('Baris CSV memiliki jumlah kolom yang tidak sesuai: ' . json_encode($data)); continue; } $rows[] = array_combine($header, $data); $currentRow++; if (count($rows) >= $batchSize) { $batchCount++; $this->command->info("Memproses batch ke-{$batchCount} ({$currentRow}/{$totalData})"); $this->processBatch($rows, $branchCache, $userData, $errorCount, $errorDebitureIds, $totalData, $batchCount, $currentRow); $rows = []; } } // info_harga_laporan_202505021522.csv // print_r($rows); if (!empty($rows)) { $batchCount++; $this->command->info("Memproses batch ke-{$batchCount} ({$currentRow}/{$totalData})"); $this->processBatch($rows, $branchCache, $userData, $errorCount, $errorDebitureIds, $totalData, $batchCount, $currentRow); } fclose($handle); $this->command->info("Data debiture berhasil dimigrasikan. Total data: {$totalData}, Total batch: {$batchCount}"); } private function processBatch(array $rows, array &$branchCache, array &$userData, int &$errorCount, array &$errorDebitureIds, int $totalData, int $batchCount, int $currentRow) { // Kelompokkan berdasarkan mig_nomor_lpj $groupedRows = $this->groupRowsByLpj($rows); foreach ($groupedRows as $nomorLpj => $groupRows) { try { // Ambil informasi permohonan dan dokument_id $nomorRegis = $this->getNomorRegistrasiPermohonan($nomorLpj, $branchCache); if (!$nomorRegis) { Log::warning("Nomor registrasi tidak ditemukan untuk nomor LPJ: {$nomorLpj}"); $errorCount++; $errorDebitureIds[] = $nomorLpj; //continue; } // Dapatkan type_penilai (misal: standar, sederhana) $firstRow = reset($groupRows); $typePenilai = $this->checkTypePenilai($firstRow['mig_kode_jenis_laporan'] ?? ''); if (!$typePenilai) { Log::warning("Tidak ada jenis laporan valid untuk nomor LPJ: {$nomorLpj}"); $errorCount++; $errorDebitureIds[] = $nomorLpj; //continue; } // Bangun JSON type_penilai $penilaiJson = $this->cekJenisPenilai($groupRows); // Simpan ke tabel Penilai // print_r(json_decode($penilaiJson, true)); // Mapping field JSON berdasarkan tipe penilaian $fillableFieldMap = [ 'memo' => 'memo', 'standar' => 'lpj', 'sederhana' => 'lpj', 'call_report' => 'call_report', 'rap' => 'rap', 'resume' => 'resume' ]; $fieldToUpdate = $fillableFieldMap[$typePenilai] ?? null; if (!$fieldToUpdate) { Log::warning("Field tidak dikenali untuk tipe: {$typePenilai}"); $errorCount++; $errorDebitureIds[] = $nomorLpj; //continue; } // NO: 001/241917/LPJ/PJ-2251/VII/24 // 001 => kode cabang // 241917 => nomor lpj // LPJ => jenis laporan // PJ-2251 => nomor registrasi ambil nilai akhirnnya 242251 // VII => bulan // 24 => tahun // Generate nomor_laporan $mig_permohonan = json_decode($nomorRegis['mig_permohonan'],true) ?? ''; $tanggal = $mig_permohonan['mig_mst_lpj_tgl_laporan'] ?? $mig_permohonan['mig_mst_lpj_tgl_oto'] ?? $firstRow['mig_created_at']; //$tanggal = $firstRow['mig_created_at']; $nomorD = $nomorRegis['nomor_registrasi']; $nomorRegistrasi = "PJ-{$nomorD}"; $nomorLaporan = $this->generateNomorLaporan($typePenilai, $nomorLpj, $nomorRegistrasi, $tanggal, $nomorRegis['id']); // Simpan atau update ke tabel Penilai $penilaiLP = Penilai::updateOrCreate( [ 'permohonan_id' => $nomorRegis['id'], 'dokument_id' => $nomorRegis['dokument_id'] ], [ 'type' => $typePenilai, $fieldToUpdate => $penilaiJson, 'type_penilai' => $typePenilai, 'created_at' => $this->parseTimestamp($tanggal), 'updated_at' => $this->parseTimestamp($tanggal), ] ); // Simpan ke tabel Laporan Laporan::updateOrCreate( [ 'permohonan_id' => $penilaiLP->permohonan_id, 'dokumen_jaminan_id' => $penilaiLP->dokument_id ], [ 'nomor_laporan' => $nomorLaporan, 'created_at' => $this->parseTimestamp($tanggal), 'updated_at' => $this->parseTimestamp($tanggal) ] ); $this->command->info("Berhasil simpan data penilai untuk nomor LPJ: {$nomorLpj}"); } catch (\Exception $e) { Log::error("Error pada nomor LPJ {$nomorLpj}: " . $e->getMessage()); $errorCount++; $errorDebitureIds[] = $nomorLpj; continue; } } } private function getNomorRegistrasiPermohonan($nomorLpj, array &$cache) { if (isset($cache[$nomorLpj])) { return $cache[$nomorLpj]; } $permohonan = Permohonan::where('nomor_lpj', $nomorLpj)->first(); if (!$permohonan) { $cache[$nomorLpj] = null; return null; } $dokumenJaminan = DokumenJaminan::where('permohonan_id', $permohonan->id)->first(); $result = [ 'id' => $permohonan->id, 'dokument_id' => $dokumenJaminan ? $dokumenJaminan->id : null, 'nomor_registrasi' => $permohonan->nomor_registrasi, 'mig_permohonan' => $permohonan->mig_permohonan ]; $cache[$nomorLpj] = $result; return $result; } private function groupRowsByLpj(array $rows): array { $grouped = []; foreach ($rows as $row) { $nomorLpj = $row['mig_nomor_lpj'] ?? null; if (!empty($nomorLpj)) { $grouped[$nomorLpj][] = $row; } } return $grouped; } private function checkTypePenilai($type) { $data = [ 'MAK' => 'memo', 'STD' => 'standar', 'SPL' => 'sederhana', 'RHP' => 'call-report', 'RAP' => 'rap', 'PRG' => 'resume', ]; return $data[$type]; } private function cekJenisPenilai(array $groupRows) { // Urutkan grup berdasarkan mig_urutan_seq usort($groupRows, function ($a, $b) { return ($a['mig_urutan_seq'] ?? 999) <=> ($b['mig_urutan_seq'] ?? 999); }); // Inisialisasi struktur JSON $penilaiJson = [ 'luas_bangunan' => null, 'luas_tanah' => null, 'nilai_tanah_1' => null, 'nilai_tanah_2' => null, 'sarana_pelengkap_penilai' => null, 'nilai_sarana_pelengkap_1' => null, 'nilai_sarana_pelengkap_2' => null, 'total_nilai_pasar_wajar' => null, 'likuidasi' => null, 'likuidasi_nilai_1' => null, 'likuidasi_nilai_2' => null, 'asuransi_luas_bangunan' => null, 'asuransi_nilai_1' => null, 'asuransi_nilai_2' => "0", 'npw_tambahan' => [] ]; // Ambil mainRow (urutan pertama) $mainRow = null; foreach ($groupRows as $row) { if (($row['mig_urutan_seq'] ?? '') == 1) { $mainRow = $row; break; } } // Jika tidak ada mainRow, ambil baris pertama sebagai fallback if (!$mainRow && !empty($groupRows[0])) { $mainRow = $groupRows[0]; } if ($mainRow) { $penilaiJson['total_nilai_pasar_wajar'] = $mainRow['mig_nilai_total_nilai_pasar'] ?? null; $penilaiJson['likuidasi'] = $mainRow['mig_nilai_likudasi'] ?? null; // Hitung likuidasi nilai 2 jika ada total dan persen likuidasi $totalPasarWajar = (int)str_replace('.', '', $mainRow['mig_nilai_total_nilai_pasar'] ?? 0); $persenLikuidasi = (int)($mainRow['mig_nilai_likudasi'] ?? 0); $penilaiJson['likuidasi_nilai_1'] = $mainRow['mig_nilai_total_nilai_pasar'] ?? null; $penilaiJson['likuidasi_nilai_2'] = number_format( $totalPasarWajar * ($persenLikuidasi / 100), 0, '', '' ); // Isi data utama hanya untuk urutan 1 $penilaiJson['luas_tanah'] = $mainRow['mig_nilai_satuan'] ?? null; $penilaiJson['nilai_tanah_1'] = $mainRow['mig_harga_satuan'] ?? null; $penilaiJson['nilai_tanah_2'] = number_format( (int)str_replace('.', '', $mainRow['mig_nilai_satuan'] ?? 0) * (int)str_replace('.', '', $mainRow['mig_harga_satuan'] ?? 0), 0, '', '' ); } // Proses tambahan (urutan > 1) foreach ($groupRows as $row) { // Hanya proses jika mig_urutan_seq ada $urutan = $row['mig_urutan_seq'] ?? ''; if (empty($urutan)) { continue; } // Tambahkan ke npw_tambahan $penilaiJson['npw_tambahan'][] = [ 'name' => $row['mig_keterangan'] ?? 'Luas Bangunan Tambahan', 'luas' => $row['mig_nilai_satuan'] ?? null, 'nilai_1' => $row['mig_harga_satuan'] ?? null, 'nilai_2' => number_format( (int)str_replace('.', '', $row['mig_nilai_satuan'] ?? 0) * (int)str_replace('.', '', $row['mig_harga_satuan'] ?? 0), 0, '', '' ) ]; } // Kosongkan npw_tambahan jika kosong if (empty($penilaiJson['npw_tambahan'])) { $penilaiJson['npw_tambahan'] = []; } return json_encode($penilaiJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } private function convertToRoman($month) { $roman = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII']; return $month >= 1 && $month <= 12 ? $roman[$month - 1] : ''; } private function generateNomorLaporan($typePenilai, $nomorLpj, $nomorRegistrasi, $tanggal) { // Mapping type_penilai ke singkatan laporan $laporanMap = [ 'memo' => 'MEMO', 'standar' => 'LPJ', 'sederhana' => 'LPJ', 'call_report' => 'CALL', 'rap' => 'RAP', 'resume' => 'RESUME' ]; // Dapatkan tahun dan bulan dari tanggal $tanggal_ = $this->parseTimestamp($tanggal); $date = \Carbon\Carbon::parse($tanggal_); $kodeCabang = '001'; // bisa diambil dari user atau parameter lain jika dinamis $tahun = substr($date->year, -2); // 2024 → 24 $bulan = $this->convertToRoman($date->month); // 7 → VII // Format akhir nomor registrasi (PJ-XXX) $nomorDebiturAkhir = substr($nomorRegistrasi, -4); // PJ-2251 → 2251 return sprintf( "%s/%s/%s/%s/%s/%s", $kodeCabang, $nomorLpj, $laporanMap[$typePenilai] ?? strtoupper($typePenilai), "PJ-" . $nomorDebiturAkhir, $bulan, $tahun ); } private function parseTimestamp(?string $timestamp): ?string { if (!$timestamp) { return null; } // Trim whitespace dan normalize $timestamp = trim($timestamp); // Log untuk debugging Log::info('Mencoba parsing timestamp: "' . $timestamp . '"'); // Parsing dengan DateTime native PHP untuk lebih robust try { // Pattern untuk format d/m/Y H:i:s if (preg_match('/^(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})$/', $timestamp, $matches)) { $day = (int) $matches[1]; $month = (int) $matches[2]; $year = (int) $matches[3]; $hour = (int) $matches[4]; $minute = (int) $matches[5]; $second = (int) $matches[6]; // Validasi nilai if ($day >= 1 && $day <= 31 && $month >= 1 && $month <= 12 && $year >= 1900 && $year <= 2100 && $hour >= 0 && $hour <= 23 && $minute >= 0 && $minute <= 59 && $second >= 0 && $second <= 59) { // Buat DateTime object langsung $dateTime = new \DateTime(); $dateTime->setDate($year, $month, $day); $dateTime->setTime($hour, $minute, $second); $result = $dateTime->format('Y-m-d H:i:s'); Log::info('Berhasil parsing dengan DateTime: ' . $timestamp . ' -> ' . $result); return $result; } } // Pattern untuk format d/m/Y tanpa waktu if (preg_match('/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/', $timestamp, $matches)) { $day = (int) $matches[1]; $month = (int) $matches[2]; $year = (int) $matches[3]; // Validasi nilai if ($day >= 1 && $day <= 31 && $month >= 1 && $month <= 12 && $year >= 1900 && $year <= 2100) { // Buat DateTime object langsung $dateTime = new \DateTime(); $dateTime->setDate($year, $month, $day); $dateTime->setTime(0, 0, 0); $result = $dateTime->format('Y-m-d H:i:s'); Log::info('Berhasil parsing tanpa waktu dengan DateTime: ' . $timestamp . ' -> ' . $result); return $result; } } } catch (\Exception $e) { Log::error('Gagal parsing dengan DateTime: ' . $timestamp . '. Error: ' . $e->getMessage()); } // Fallback ke format Carbon standar untuk format lainnya $formats = [ 'Y-m-d H:i:s', 'Y-m-d', 'd-m-Y H:i:s', 'd-m-Y', 'j-n-Y H:i:s', 'j-n-Y', ]; foreach ($formats as $format) { try { $carbon = \Carbon\Carbon::createFromFormat($format, $timestamp); if ($carbon && $carbon->format($format) === $timestamp) { // Jika format tidak mengandung waktu, set ke awal hari if (!str_contains($format, 'H:i:s')) { $carbon = $carbon->startOfDay(); } Log::info('Berhasil parsing dengan format ' . $format . ': ' . $timestamp . ' -> ' . $carbon->toDateTimeString()); return $carbon->toDateTimeString(); } } catch (\Exception $e) { // Lanjut ke format berikutnya continue; } } Log::error('Tidak dapat memparsing timestamp dengan format apapun: "' . $timestamp . '"'); return null; } private function initializeErrorLog() { $file = $this->errorLogFile; if (file_exists($file)) { unlink($file); // Hapus file lama } $handle = fopen($file, 'w'); fputcsv($handle, ['mig_kd_debitur_seq', 'Error']); fclose($handle); } private function logError(string $kode, string $message) { Log::error("Error migrasi dokumen jaminan [$kode]: $message"); $handle = fopen($this->errorLogFile, 'a'); fputcsv($handle, [$kode, $message]); fclose($handle); } }